From 9dfccd92a750ef43da1630ffa5ce1dc988a857af Mon Sep 17 00:00:00 2001 From: Frazer McLean Date: Wed, 18 Sep 2019 19:02:48 +0200 Subject: [PATCH 001/419] Fix comment to match repository license --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 71cd4b18..c9f079bc 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ are you've used several just to read this text. Hyperlink is a featureful, pure-Python implementation of the URL, with -an emphasis on correctness. BSD licensed. +an emphasis on correctness. MIT licensed. See the docs at http://hyperlink.readthedocs.io. """ From 413d742ef06b1152676a4534a3756c6b9fc51ab8 Mon Sep 17 00:00:00 2001 From: Frazer McLean Date: Wed, 18 Sep 2019 19:03:12 +0200 Subject: [PATCH 002/419] Add PyPI trove classifier for license --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c9f079bc..f24e345a 100644 --- a/setup.py +++ b/setup.py @@ -40,7 +40,8 @@ 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: Implementation :: PyPy', ] + 'Programming Language :: Python :: Implementation :: PyPy', + 'License :: OSI Approved :: MIT License', ] ) """ From 7c0f773aca788204050f57bd529b53584162c2cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Fri, 18 Oct 2019 12:51:18 -0700 Subject: [PATCH 003/419] Add Python 3.7 to build matrix --- .travis.yml | 2 ++ tox.ini | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 1aff2d28..a758387d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,8 @@ matrix: env: TOXENV=py35 - python: "3.6" env: TOXENV=py36 + - python: "3.7" + env: TOXENV=py37 - python: "pypy" env: TOXENV=pypy - python: "2.7" diff --git a/tox.ini b/tox.ini index ef2ec9c5..4700d21a 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py26,py27,py34,py35,py36,pypy,coverage-report,packaging +envlist = py26,py27,py34,py35,py36,py37,pypy,coverage-report,packaging [testenv] changedir = .tox From 68f96bb356364f770724fc6405c293cf672ca05d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Fri, 18 Oct 2019 13:21:17 -0700 Subject: [PATCH 004/419] Update all dependencies. Add pypy3 to the matrix. Note current versions of pytest do not support Python 2.6, 2.7 or 3.4. That means we need to use pytest 4.6 for those, which means tox needs to be able to select which to use, which means we need to get rid of the static requirements-test.txt, which is probably a good idea anyway. --- .travis.yml | 9 ++++++--- requirements-test.txt | 5 ----- tox.ini | 36 ++++++++++++++++++++++++++++++++---- 3 files changed, 38 insertions(+), 12 deletions(-) delete mode 100644 requirements-test.txt diff --git a/.travis.yml b/.travis.yml index a758387d..47e3181c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,14 +18,17 @@ matrix: env: TOXENV=py36 - python: "3.7" env: TOXENV=py37 - - python: "pypy" - env: TOXENV=pypy + - python: "pypy2" + env: TOXENV=pypy2 + - python: "pypy3" + env: TOXENV=pypy3 - python: "2.7" env: TOXENV=packaging install: - - "pip install -r requirements-test.txt" + - pip install tox + script: - tox diff --git a/requirements-test.txt b/requirements-test.txt deleted file mode 100644 index 0e9a261b..00000000 --- a/requirements-test.txt +++ /dev/null @@ -1,5 +0,0 @@ -coverage==4.4.1 -idna==2.5 -pytest==2.9.2 -pytest-cov==2.3.0 -tox==2.6.0 diff --git a/tox.ini b/tox.ini index 4700d21a..2a801ba3 100644 --- a/tox.ini +++ b/tox.ini @@ -1,26 +1,54 @@ [tox] -envlist = py26,py27,py34,py35,py36,py37,pypy,coverage-report,packaging + +envlist = py{26,27,34,35,36,37,py2,py3},coverage-report,packaging + [testenv] + +basepython = + py26: python2.6 + py27: python2.7 + py34: python3.4 + py35: python3.5 + py36: python3.6 + py37: python3.7 + py38: python3.8 + pypy2: pypy + pypy3: pypy3 + changedir = .tox -deps = -rrequirements-test.txt + +deps = + coverage==4.5.4 + idna==2.8 + {py26,py27,py34}: pytest==4.6.6 + {py35,py36,py37,py38}: pytest==5.2.1 + pytest-cov==2.8.1 + commands = coverage run --parallel --rcfile {toxinidir}/.tox-coveragerc -m pytest --doctest-modules {envsitepackagesdir}/hyperlink {posargs} + # Uses default basepython otherwise reporting doesn't work on Travis where # Python 3.6 is only available in 3.6 jobs. [testenv:coverage-report] + changedir = .tox + deps = coverage + commands = coverage combine --rcfile {toxinidir}/.tox-coveragerc coverage report --rcfile {toxinidir}/.tox-coveragerc coverage html --rcfile {toxinidir}/.tox-coveragerc -d {toxinidir}/htmlcov [testenv:packaging] + changedir = {toxinidir} + deps = - check-manifest==0.35 - readme_renderer==17.2 + check-manifest==0.40 + readme_renderer==24.0 + commands = check-manifest python setup.py check --metadata --restructuredtext --strict From e71078f921744c7e0963e1b0f3ef6005ec3e2647 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Fri, 18 Oct 2019 15:14:23 -0700 Subject: [PATCH 005/419] OK fine pypy2 is pypy --- .travis.yml | 4 ++-- tox.ini | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 47e3181c..16931e30 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,8 +18,8 @@ matrix: env: TOXENV=py36 - python: "3.7" env: TOXENV=py37 - - python: "pypy2" - env: TOXENV=pypy2 + - python: "pypy" + env: TOXENV=pypy - python: "pypy3" env: TOXENV=pypy3 - python: "2.7" diff --git a/tox.ini b/tox.ini index 2a801ba3..338a06ce 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] -envlist = py{26,27,34,35,36,37,py2,py3},coverage-report,packaging +envlist = py{26,27,34,35,36,37,py,py3},coverage-report,packaging [testenv] @@ -13,7 +13,7 @@ basepython = py36: python3.6 py37: python3.7 py38: python3.8 - pypy2: pypy + pypy: pypy pypy3: pypy3 changedir = .tox From 2d5d4fc0496a0bd099cdcf0e8f3418a62cd328e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Fri, 18 Oct 2019 19:28:59 -0700 Subject: [PATCH 006/419] Borrow tox config from klein, etc. --- tox.ini | 74 +++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 56 insertions(+), 18 deletions(-) diff --git a/tox.ini b/tox.ini index 338a06ce..1f5b497e 100644 --- a/tox.ini +++ b/tox.ini @@ -1,8 +1,12 @@ [tox] -envlist = py{26,27,34,35,36,37,py,py3},coverage-report,packaging +envlist = test-py{26,27,34,35,36,37,py,py3},packaging +## +# Build (default environment) +## + [testenv] basepython = @@ -16,34 +20,68 @@ basepython = pypy: pypy pypy3: pypy3 -changedir = .tox - deps = - coverage==4.5.4 - idna==2.8 - {py26,py27,py34}: pytest==4.6.6 - {py35,py36,py37,py38}: pytest==5.2.1 - pytest-cov==2.8.1 + test: coverage==4.5.4 + test: idna==2.8 + test: {py26,py27,py34}: pytest==4.6.6 + test: {py35,py36,py37,py38}: pytest==5.2.1 + test: pytest-cov==2.8.1 + +passenv = + # See https://github.com/codecov/codecov-python/blob/master/README.md#using-tox + codecov: TOXENV + codecov: CI + codecov: TRAVIS TRAVIS_* + codecov: CODECOV_TOKEN + codecov: GITHUB_* + +setenv = + test COVERAGE_FILE={toxworkdir}/coverage/coverage.{envname} + codecov: COVERAGE_FILE={toxworkdir}/coverage/coverage + + test: COVERAGE_HTML={envlogdir}/coverage_report_html + codecov: COVERAGE_XML={envlogdir}/coverage_report.xml + +commands = + test: coverage run --parallel --rcfile {toxinidir}/.tox-coveragerc -m pytest --doctest-modules {envsitepackagesdir}/hyperlink {posargs} + + # Run coverage reports, ignore exit status + test: - coverage --rcfile {toxinidir}/.tox-coveragerc html -d "{env:COVERAGE_HTML}" + test: - coverage --rcfile {toxinidir}/.tox-coveragerc report --skip-covered + test: python -c 'print("Coverage reports are at: {env:COVERAGE_HTML}/index.html")' + -commands = coverage run --parallel --rcfile {toxinidir}/.tox-coveragerc -m pytest --doctest-modules {envsitepackagesdir}/hyperlink {posargs} +## +# Publish to Codecov +## +[testenv:codecov] -# Uses default basepython otherwise reporting doesn't work on Travis where -# Python 3.6 is only available in 3.6 jobs. -[testenv:coverage-report] +description = upload coverage to Codecov -changedir = .tox +basepython = python + +skip_install = True + +deps = + coverage==4.5.4 + codecov==2.0.15 + +commands = + "{toxinidir}/bin/environment" -deps = coverage + coverage --rcfile {toxinidir}/.tox-coveragerc combine --append + coverage --rcfile {toxinidir}/.tox-coveragerc xml -o "{env:COVERAGE_XML}" + codecov --file="{env:COVERAGE_XML}" --env GITHUB_REF GITHUB_COMMIT GITHUB_USER GITHUB_WORKFLOW -commands = coverage combine --rcfile {toxinidir}/.tox-coveragerc - coverage report --rcfile {toxinidir}/.tox-coveragerc - coverage html --rcfile {toxinidir}/.tox-coveragerc -d {toxinidir}/htmlcov +## +# Packaging +## [testenv:packaging] -changedir = {toxinidir} +basepython = python deps = check-manifest==0.40 From 2ffdb09b4e1649736f63010c492d1b3d20ded29e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Fri, 18 Oct 2019 19:39:12 -0700 Subject: [PATCH 007/419] Don't forget travis --- .travis.yml | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/.travis.yml b/.travis.yml index 16931e30..da0dd113 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,21 +9,21 @@ language: python matrix: include: - python: "2.7" - env: TOXENV=py27 + env: TOXENV=py27,codecov-py27 - python: "3.4" - env: TOXENV=py34 + env: TOXENV=py34,codecov-py34 - python: "3.5" - env: TOXENV=py35 + env: TOXENV=py35,codecov-py35 - python: "3.6" - env: TOXENV=py36 + env: TOXENV=py36,codecov-py36 - python: "3.7" - env: TOXENV=py37 + env: TOXENV=py37,codecov-py37 - python: "pypy" - env: TOXENV=pypy + env: TOXENV=pypy,codecov-pypy - python: "pypy3" - env: TOXENV=pypy3 + env: TOXENV=pypy3,codecov-pypy3 - python: "2.7" - env: TOXENV=packaging + env: TOXENV=packaging-py27 install: @@ -32,13 +32,3 @@ install: script: - tox - - -before_install: - - pip install codecov coverage - - -after_success: - - tox -e coverage-report - - COVERAGE_FILE=.tox/.coverage coverage xml - - codecov -f coverage.xml From f68d2a341f1d743d1671daace429b19f87386045 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Fri, 18 Oct 2019 20:32:02 -0700 Subject: [PATCH 008/419] =?UTF-8?q?=E2=80=A6and=20AppyVeyor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- appveyor.yml | 10 +++------- tox.ini | 5 ++++- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 5f146255..b0530fe0 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,10 +7,10 @@ environment: matrix: - PYTHON: "C:\\Python27-x64" - TOX_ENV: "py27" + TOX_ENV: "py27,codecov-py27" - PYTHON: "C:\\Python36-x64" - TOX_ENV: "py36" + TOX_ENV: "py36,codecov-py36" init: @@ -36,12 +36,8 @@ after_test: on_success: # Report coverage results to codecov.io # and export tox environment variables - - "%PYTHON%/Scripts/tox -e coverage-report" - - "%PYTHON%/Scripts/pip install codecov coverage" - - set COVERAGE_FILE=.tox/.coverage - - "%PYTHON%/Scripts/coverage xml" - set OS=WINDOWS - - "%PYTHON%/Scripts/codecov -f coverage.xml -e TOX_ENV OS" + - "%PYTHON%/Scripts/tox -e codecov" artifacts: - path: dist\* diff --git a/tox.ini b/tox.ini index 1f5b497e..1261e194 100644 --- a/tox.ini +++ b/tox.ini @@ -28,6 +28,9 @@ deps = test: pytest-cov==2.8.1 passenv = + # Use in AppVeyor config + codecov: OS + # See https://github.com/codecov/codecov-python/blob/master/README.md#using-tox codecov: TOXENV codecov: CI @@ -72,7 +75,7 @@ commands = coverage --rcfile {toxinidir}/.tox-coveragerc combine --append coverage --rcfile {toxinidir}/.tox-coveragerc xml -o "{env:COVERAGE_XML}" - codecov --file="{env:COVERAGE_XML}" --env GITHUB_REF GITHUB_COMMIT GITHUB_USER GITHUB_WORKFLOW + codecov --file="{env:COVERAGE_XML}" --env GITHUB_REF GITHUB_COMMIT GITHUB_USER GITHUB_WORKFLOW TOX_ENV OS ## From 19357f7d0e34b692bbfb806ebd83a81a36cc28a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Fri, 18 Oct 2019 20:36:22 -0700 Subject: [PATCH 009/419] Don't have this script --- tox.ini | 2 -- 1 file changed, 2 deletions(-) diff --git a/tox.ini b/tox.ini index 1261e194..cfbd7ea6 100644 --- a/tox.ini +++ b/tox.ini @@ -71,8 +71,6 @@ deps = codecov==2.0.15 commands = - "{toxinidir}/bin/environment" - coverage --rcfile {toxinidir}/.tox-coveragerc combine --append coverage --rcfile {toxinidir}/.tox-coveragerc xml -o "{env:COVERAGE_XML}" codecov --file="{env:COVERAGE_XML}" --env GITHUB_REF GITHUB_COMMIT GITHUB_USER GITHUB_WORKFLOW TOX_ENV OS From 53c7766c4cb70ceeeafdb32f31ff0051f177dbf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Fri, 18 Oct 2019 20:43:35 -0700 Subject: [PATCH 010/419] Put .coveragerc where it's expected so we don't need --rcfile, which Windows doesn't seem to have. --- .tox-coveragerc => .coveragerc | 0 tox.ini | 10 +++++----- 2 files changed, 5 insertions(+), 5 deletions(-) rename .tox-coveragerc => .coveragerc (100%) diff --git a/.tox-coveragerc b/.coveragerc similarity index 100% rename from .tox-coveragerc rename to .coveragerc diff --git a/tox.ini b/tox.ini index cfbd7ea6..c48c027c 100644 --- a/tox.ini +++ b/tox.ini @@ -46,11 +46,11 @@ setenv = codecov: COVERAGE_XML={envlogdir}/coverage_report.xml commands = - test: coverage run --parallel --rcfile {toxinidir}/.tox-coveragerc -m pytest --doctest-modules {envsitepackagesdir}/hyperlink {posargs} + test: coverage run --parallel -m pytest --doctest-modules {envsitepackagesdir}/hyperlink {posargs} # Run coverage reports, ignore exit status - test: - coverage --rcfile {toxinidir}/.tox-coveragerc html -d "{env:COVERAGE_HTML}" - test: - coverage --rcfile {toxinidir}/.tox-coveragerc report --skip-covered + test: - coverage html -d "{env:COVERAGE_HTML}" + test: - coverage report --skip-covered test: python -c 'print("Coverage reports are at: {env:COVERAGE_HTML}/index.html")' @@ -71,8 +71,8 @@ deps = codecov==2.0.15 commands = - coverage --rcfile {toxinidir}/.tox-coveragerc combine --append - coverage --rcfile {toxinidir}/.tox-coveragerc xml -o "{env:COVERAGE_XML}" + coverage combine --append + coverage xml -o "{env:COVERAGE_XML}" codecov --file="{env:COVERAGE_XML}" --env GITHUB_REF GITHUB_COMMIT GITHUB_USER GITHUB_WORKFLOW TOX_ENV OS From c5c0d45b646c76d561dcee6c9a594757d33690ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Fri, 18 Oct 2019 20:56:12 -0700 Subject: [PATCH 011/419] Nothing fun happens without test- --- .travis.yml | 14 +++++++------- appveyor.yml | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index da0dd113..600f0565 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,19 +9,19 @@ language: python matrix: include: - python: "2.7" - env: TOXENV=py27,codecov-py27 + env: TOXENV=test-py27,codecov-py27 - python: "3.4" - env: TOXENV=py34,codecov-py34 + env: TOXENV=test-py34,codecov-py34 - python: "3.5" - env: TOXENV=py35,codecov-py35 + env: TOXENV=test-py35,codecov-py35 - python: "3.6" - env: TOXENV=py36,codecov-py36 + env: TOXENV=test-py36,codecov-py36 - python: "3.7" - env: TOXENV=py37,codecov-py37 + env: TOXENV=test-py37,codecov-py37 - python: "pypy" - env: TOXENV=pypy,codecov-pypy + env: TOXENV=test-pypy,codecov-pypy - python: "pypy3" - env: TOXENV=pypy3,codecov-pypy3 + env: TOXENV=test-pypy3,codecov-pypy3 - python: "2.7" env: TOXENV=packaging-py27 diff --git a/appveyor.yml b/appveyor.yml index b0530fe0..5aaadfd6 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,10 +7,10 @@ environment: matrix: - PYTHON: "C:\\Python27-x64" - TOX_ENV: "py27,codecov-py27" + TOX_ENV: "test-py27,codecov-py27" - PYTHON: "C:\\Python36-x64" - TOX_ENV: "py36,codecov-py36" + TOX_ENV: "test-py36,codecov-py36" init: From a0abd95629b21a4aa5f5aba575f9a4f655e7c0b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Fri, 18 Oct 2019 20:57:03 -0700 Subject: [PATCH 012/419] typo --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index c48c027c..ada0ecef 100644 --- a/tox.ini +++ b/tox.ini @@ -39,7 +39,7 @@ passenv = codecov: GITHUB_* setenv = - test COVERAGE_FILE={toxworkdir}/coverage/coverage.{envname} + test: COVERAGE_FILE={toxworkdir}/coverage/coverage.{envname} codecov: COVERAGE_FILE={toxworkdir}/coverage/coverage test: COVERAGE_HTML={envlogdir}/coverage_report_html From 33bb1abe9c9ba88f3e6ebf06dcb7583ef434e375 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Fri, 18 Oct 2019 21:27:38 -0700 Subject: [PATCH 013/419] Create a directory for coverage files --- tox.ini | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tox.ini b/tox.ini index ada0ecef..746d87e8 100644 --- a/tox.ini +++ b/tox.ini @@ -2,6 +2,8 @@ envlist = test-py{26,27,34,35,36,37,py,py3},packaging +skip_missing_interpreters = {tty:True:False} + ## # Build (default environment) @@ -46,6 +48,10 @@ setenv = codecov: COVERAGE_XML={envlogdir}/coverage_report.xml commands = + # Create a directory for coverage files + test: python -c 'import os; d=os.path.dirname("{env:COVERAGE_FILE}"); os.makedirs(d) if not os.path.exists(d) else None' + + # Run pytest via coverage test: coverage run --parallel -m pytest --doctest-modules {envsitepackagesdir}/hyperlink {posargs} # Run coverage reports, ignore exit status From c64a950566c8832c6c524b3188d0384f0237df0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Fri, 18 Oct 2019 21:52:15 -0700 Subject: [PATCH 014/419] Use assertEqual(), not assertEquals(), which is deprecated. --- hyperlink/test/test_url.py | 46 +++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/hyperlink/test/test_url.py b/hyperlink/test/test_url.py index 09405857..344392f9 100644 --- a/hyperlink/test/test_url.py +++ b/hyperlink/test/test_url.py @@ -947,65 +947,65 @@ def test_invalid_port(self): def test_idna(self): u1 = URL.from_text('http://bücher.ch') - self.assertEquals(u1.host, 'bücher.ch') - self.assertEquals(u1.to_text(), 'http://bücher.ch') - self.assertEquals(u1.to_uri().to_text(), 'http://xn--bcher-kva.ch') + self.assertEqual(u1.host, 'bücher.ch') + self.assertEqual(u1.to_text(), 'http://bücher.ch') + self.assertEqual(u1.to_uri().to_text(), 'http://xn--bcher-kva.ch') u2 = URL.from_text('https://xn--bcher-kva.ch') - self.assertEquals(u2.host, 'xn--bcher-kva.ch') - self.assertEquals(u2.to_text(), 'https://xn--bcher-kva.ch') - self.assertEquals(u2.to_iri().to_text(), u'https://bücher.ch') + self.assertEqual(u2.host, 'xn--bcher-kva.ch') + self.assertEqual(u2.to_text(), 'https://xn--bcher-kva.ch') + self.assertEqual(u2.to_iri().to_text(), u'https://bücher.ch') def test_netloc_slashes(self): # basic sanity checks url = URL.from_text('mailto:mahmoud@hatnote.com') - self.assertEquals(url.scheme, 'mailto') - self.assertEquals(url.to_text(), 'mailto:mahmoud@hatnote.com') + self.assertEqual(url.scheme, 'mailto') + self.assertEqual(url.to_text(), 'mailto:mahmoud@hatnote.com') url = URL.from_text('http://hatnote.com') - self.assertEquals(url.scheme, 'http') - self.assertEquals(url.to_text(), 'http://hatnote.com') + self.assertEqual(url.scheme, 'http') + self.assertEqual(url.to_text(), 'http://hatnote.com') # test that unrecognized schemes stay consistent with '//' url = URL.from_text('newscheme:a:b:c') - self.assertEquals(url.scheme, 'newscheme') - self.assertEquals(url.to_text(), 'newscheme:a:b:c') + self.assertEqual(url.scheme, 'newscheme') + self.assertEqual(url.to_text(), 'newscheme:a:b:c') url = URL.from_text('newerscheme://a/b/c') - self.assertEquals(url.scheme, 'newerscheme') - self.assertEquals(url.to_text(), 'newerscheme://a/b/c') + self.assertEqual(url.scheme, 'newerscheme') + self.assertEqual(url.to_text(), 'newerscheme://a/b/c') # test that reasonable guesses are made url = URL.from_text('git+ftp://gitstub.biz/glyph/lefkowitz') - self.assertEquals(url.scheme, 'git+ftp') - self.assertEquals(url.to_text(), + self.assertEqual(url.scheme, 'git+ftp') + self.assertEqual(url.to_text(), 'git+ftp://gitstub.biz/glyph/lefkowitz') url = URL.from_text('what+mailto:freerealestate@enotuniq.org') - self.assertEquals(url.scheme, 'what+mailto') - self.assertEquals(url.to_text(), + self.assertEqual(url.scheme, 'what+mailto') + self.assertEqual(url.to_text(), 'what+mailto:freerealestate@enotuniq.org') url = URL(scheme='ztp', path=('x', 'y', 'z'), rooted=True) - self.assertEquals(url.to_text(), 'ztp:/x/y/z') + self.assertEqual(url.to_text(), 'ztp:/x/y/z') # also works when the input doesn't include '//' url = URL(scheme='git+ftp', path=('x', 'y', 'z' ,''), rooted=True, uses_netloc=True) # broken bc urlunsplit - self.assertEquals(url.to_text(), 'git+ftp:///x/y/z/') + self.assertEqual(url.to_text(), 'git+ftp:///x/y/z/') # really why would this ever come up but ok url = URL.from_text('file:///path/to/heck') url2 = url.replace(scheme='mailto') - self.assertEquals(url2.to_text(), 'mailto:/path/to/heck') + self.assertEqual(url2.to_text(), 'mailto:/path/to/heck') url_text = 'unregisteredscheme:///a/b/c' url = URL.from_text(url_text) no_netloc_url = url.replace(uses_netloc=False) - self.assertEquals(no_netloc_url.to_text(), 'unregisteredscheme:/a/b/c') + self.assertEqual(no_netloc_url.to_text(), 'unregisteredscheme:/a/b/c') netloc_url = url.replace(uses_netloc=True) - self.assertEquals(netloc_url.to_text(), url_text) + self.assertEqual(netloc_url.to_text(), url_text) return From dc54309f896df16f2a0350438c8662edeae6ca02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Fri, 18 Oct 2019 23:34:19 -0700 Subject: [PATCH 015/419] Let pytest-cov generate coverage data; no need to use coverage run. Re-add coverage_report environment for combined coverage. --- .coveragerc | 12 ------------ tox.ini | 41 +++++++++++++++++++++++++++-------------- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/.coveragerc b/.coveragerc index 44178a43..398ff08a 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,14 +1,2 @@ [run] branch = True -source = - hyperlink - ../hyperlink -omit = - */flycheck_* - -[paths] -source = - ../hyperlink - */lib/python*/site-packages/hyperlink - */Lib/site-packages/hyperlink - */pypy/site-packages/hyperlink diff --git a/tox.ini b/tox.ini index 746d87e8..07b4fe89 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,9 @@ [tox] -envlist = test-py{26,27,34,35,36,37,py,py3},packaging +envlist = + test-py{26,27,34,35,36,37,py,py3} + coverage_report + packaging skip_missing_interpreters = {tty:True:False} @@ -41,23 +44,33 @@ passenv = codecov: GITHUB_* setenv = - test: COVERAGE_FILE={toxworkdir}/coverage/coverage.{envname} - codecov: COVERAGE_FILE={toxworkdir}/coverage/coverage - - test: COVERAGE_HTML={envlogdir}/coverage_report_html + test: COVERAGE_FILE={toxworkdir}/coverage.{envname} + {coverage_report,codecov}: COVERAGE_FILE={toxworkdir}/coverage codecov: COVERAGE_XML={envlogdir}/coverage_report.xml commands = - # Create a directory for coverage files - test: python -c 'import os; d=os.path.dirname("{env:COVERAGE_FILE}"); os.makedirs(d) if not os.path.exists(d) else None' + test: pytest --cov=hyperlink --cov-report=term-missing:skip-covered --doctest-modules {posargs:hyperlink} + + +## +# Generate HTML coverage report +## + +[testenv:coverage_report] + +description = generate coverage report - # Run pytest via coverage - test: coverage run --parallel -m pytest --doctest-modules {envsitepackagesdir}/hyperlink {posargs} +basepython = python + +skip_install = True - # Run coverage reports, ignore exit status - test: - coverage html -d "{env:COVERAGE_HTML}" - test: - coverage report --skip-covered - test: python -c 'print("Coverage reports are at: {env:COVERAGE_HTML}/index.html")' +deps = + coverage==4.5.4 + +commands = + coverage combine + coverage report + coverage html ## @@ -77,7 +90,7 @@ deps = codecov==2.0.15 commands = - coverage combine --append + coverage combine coverage xml -o "{env:COVERAGE_XML}" codecov --file="{env:COVERAGE_XML}" --env GITHUB_REF GITHUB_COMMIT GITHUB_USER GITHUB_WORKFLOW TOX_ENV OS From 9bc5597e50b1481c4f3a0d1f4a7d10d79be0ad89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sat, 19 Oct 2019 00:02:20 -0700 Subject: [PATCH 016/419] codecov doesn't need the -py* suffx --- .travis.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 600f0565..d90cccab 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,19 +9,19 @@ language: python matrix: include: - python: "2.7" - env: TOXENV=test-py27,codecov-py27 + env: TOXENV=test-py27,codecov - python: "3.4" - env: TOXENV=test-py34,codecov-py34 + env: TOXENV=test-py34,codecov - python: "3.5" - env: TOXENV=test-py35,codecov-py35 + env: TOXENV=test-py35,codecov - python: "3.6" - env: TOXENV=test-py36,codecov-py36 + env: TOXENV=test-py36,codecov - python: "3.7" - env: TOXENV=test-py37,codecov-py37 + env: TOXENV=test-py37,codecov - python: "pypy" - env: TOXENV=test-pypy,codecov-pypy + env: TOXENV=test-pypy,codecov - python: "pypy3" - env: TOXENV=test-pypy3,codecov-pypy3 + env: TOXENV=test-pypy3,codecov - python: "2.7" env: TOXENV=packaging-py27 From 139e4b29d91e9252f8c682041a53064f408fecbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sat, 19 Oct 2019 10:43:59 -0700 Subject: [PATCH 017/419] Codecov doesn't need the -py* suffx on wnidows, either. Since we submit to codecov with each run, we don't need an on_sucess submission. Set OS in init. Update virtualenv also. --- appveyor.yml | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 5aaadfd6..a4f02da5 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,18 +2,25 @@ # http://www.appveyor.com/docs/installed-software#python # This configuration based on: -# https://github.com/audreyr/cookiecutter/commit/3c4685f536afda3be93da3fe3039cec0ab0d60a3 +# https://github.com/cookiecutter/cookiecutter/blob/5e65edf4c340993f462ddeaf44f99eb6f9da66f9/appveyor.yml environment: matrix: - PYTHON: "C:\\Python27-x64" - TOX_ENV: "test-py27,codecov-py27" + TOX_ENV: "test-py27,codecov" + + - PYTHON: "C:\\Python35-x64" + TOX_ENV: "test-py35,codecov" - PYTHON: "C:\\Python36-x64" - TOX_ENV: "test-py36,codecov-py36" + TOX_ENV: "test-py36,codecov" + + - PYTHON: "C:\\Python37-x64" + TOX_ENV: "test-py37,codecov" init: + - set OS=WINDOWS - set PATH=%PYTHON%;%PYTHON%\Scripts;%PATH% - "git config --system http.sslcainfo \"C:\\Program Files\\Git\\mingw64\\ssl\\certs\\ca-bundle.crt\"" - "%PYTHON%/python -V" @@ -21,7 +28,7 @@ init: install: - "%PYTHON%/Scripts/easy_install -U pip" - - "%PYTHON%/Scripts/pip install -U --force-reinstall tox wheel" + - "%PYTHON%/Scripts/pip install -U --force-reinstall tox virtualenv wheel" build: false # Not a C# project, build stuff at the test step instead. @@ -29,18 +36,5 @@ build: false # Not a C# project, build stuff at the test step instead. test_script: - "%PYTHON%/Scripts/tox -e %TOX_ENV%" -after_test: - - "%PYTHON%/python setup.py bdist_wheel" - - ps: "ls dist" - -on_success: - # Report coverage results to codecov.io - # and export tox environment variables - - set OS=WINDOWS - - "%PYTHON%/Scripts/tox -e codecov" - artifacts: - path: dist\* - -#on_success: -# - TODO: upload the content of dist/*.whl to a public wheelhouse From b4f51189fd651de164169943769494e394a99c18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sat, 19 Oct 2019 10:56:55 -0700 Subject: [PATCH 018/419] Print environment variables in init for debugging. --- appveyor.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/appveyor.yml b/appveyor.yml index a4f02da5..3116a1c2 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -25,6 +25,7 @@ init: - "git config --system http.sslcainfo \"C:\\Program Files\\Git\\mingw64\\ssl\\certs\\ca-bundle.crt\"" - "%PYTHON%/python -V" - "%PYTHON%/python -c \"import struct;print(8 * struct.calcsize(\'P\'))\"" + - set install: - "%PYTHON%/Scripts/easy_install -U pip" From 74b28121c24db5a4e12d870d163904c507d97005 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sat, 19 Oct 2019 11:06:31 -0700 Subject: [PATCH 019/419] Pass CI environment variables. --- tox.ini | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tox.ini b/tox.ini index 07b4fe89..22bc4624 100644 --- a/tox.ini +++ b/tox.ini @@ -37,11 +37,14 @@ passenv = codecov: OS # See https://github.com/codecov/codecov-python/blob/master/README.md#using-tox - codecov: TOXENV - codecov: CI - codecov: TRAVIS TRAVIS_* - codecov: CODECOV_TOKEN + # And CI-specific docs: + # https://help.github.com/en/articles/virtual-environments-for-github-actions + # https://docs.travis-ci.com/user/environment-variables#default-environment-variables + # https://www.appveyor.com/docs/environment-variables/ + codecov: TOXENV CODECOV_* CI codecov: GITHUB_* + codecov: TRAVIS TRAVIS_* + codecov: APPVEYOR APPVEYOR_* setenv = test: COVERAGE_FILE={toxworkdir}/coverage.{envname} From c3f97b652e7acabbf8dfd0feb054c56037a5b790 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sat, 19 Oct 2019 11:31:40 -0700 Subject: [PATCH 020/419] cleanup --- tox.ini | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/tox.ini b/tox.ini index 22bc4624..a6430a43 100644 --- a/tox.ini +++ b/tox.ini @@ -14,6 +14,8 @@ skip_missing_interpreters = {tty:True:False} [testenv] +description = run tests + basepython = py26: python2.6 py27: python2.7 @@ -33,12 +35,9 @@ deps = test: pytest-cov==2.8.1 passenv = - # Use in AppVeyor config - codecov: OS - # See https://github.com/codecov/codecov-python/blob/master/README.md#using-tox # And CI-specific docs: - # https://help.github.com/en/articles/virtual-environments-for-github-actions + # https://help.github.com/en/articles/virtual-environments-for-github-actions#default-environment-variables # https://docs.travis-ci.com/user/environment-variables#default-environment-variables # https://www.appveyor.com/docs/environment-variables/ codecov: TOXENV CODECOV_* CI @@ -46,13 +45,18 @@ passenv = codecov: TRAVIS TRAVIS_* codecov: APPVEYOR APPVEYOR_* + # Used in our AppVeyor config + codecov: OS + setenv = test: COVERAGE_FILE={toxworkdir}/coverage.{envname} {coverage_report,codecov}: COVERAGE_FILE={toxworkdir}/coverage codecov: COVERAGE_XML={envlogdir}/coverage_report.xml commands = - test: pytest --cov=hyperlink --cov-report=term-missing:skip-covered --doctest-modules {posargs:hyperlink} + test: pytest \ + --cov=hyperlink --cov-report=term-missing:skip-covered \ + --doctest-modules {posargs:hyperlink} ## @@ -95,7 +99,11 @@ deps = commands = coverage combine coverage xml -o "{env:COVERAGE_XML}" - codecov --file="{env:COVERAGE_XML}" --env GITHUB_REF GITHUB_COMMIT GITHUB_USER GITHUB_WORKFLOW TOX_ENV OS + codecov --file="{env:COVERAGE_XML}" --env \ + GITHUB_REF GITHUB_COMMIT GITHUB_USER GITHUB_WORKFLOW \ + TRAVIS_BRANCH TRAVIS_BUILD_WEB_URL TRAVIS_COMMIT TRAVIS_COMMIT_MESSAGE \ + APPVEYOR_REPO_BRANCH APPVEYOR_REPO_COMMIT \ + APPVEYOR_REPO_COMMIT_AUTHOR_EMAIL APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED ## From b45ad42b3ca6e484a415a188dfc5674ec1cd8c02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sat, 19 Oct 2019 11:45:53 -0700 Subject: [PATCH 021/419] OK can't break lines here. Weak. --- tox.ini | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tox.ini b/tox.ini index a6430a43..57b6c8e8 100644 --- a/tox.ini +++ b/tox.ini @@ -54,9 +54,7 @@ setenv = codecov: COVERAGE_XML={envlogdir}/coverage_report.xml commands = - test: pytest \ - --cov=hyperlink --cov-report=term-missing:skip-covered \ - --doctest-modules {posargs:hyperlink} + test: pytest --cov=hyperlink --cov-report=term-missing:skip-covered --doctest-modules {posargs:hyperlink} ## From 4ff1c63fd154d637dcd5ae3d6b9de9cb72cba305 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sat, 19 Oct 2019 18:18:53 -0700 Subject: [PATCH 022/419] Add PY_MODULE to env, so we change the package name in one place. --- tox.ini | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tox.ini b/tox.ini index 57b6c8e8..7d8e3476 100644 --- a/tox.ini +++ b/tox.ini @@ -49,16 +49,18 @@ passenv = codecov: OS setenv = + PY_MODULE=hyperlink + test: COVERAGE_FILE={toxworkdir}/coverage.{envname} {coverage_report,codecov}: COVERAGE_FILE={toxworkdir}/coverage codecov: COVERAGE_XML={envlogdir}/coverage_report.xml commands = - test: pytest --cov=hyperlink --cov-report=term-missing:skip-covered --doctest-modules {posargs:hyperlink} + test: pytest --cov={env:PY_MODULE} --cov-report=term-missing:skip-covered --doctest-modules {posargs:src/{env:PY_MODULE}} ## -# Generate HTML coverage report +# Coverage report ## [testenv:coverage_report] @@ -79,7 +81,7 @@ commands = ## -# Publish to Codecov +# Codecov ## [testenv:codecov] @@ -110,6 +112,8 @@ commands = [testenv:packaging] +description = check for potential packaging problems + basepython = python deps = From fdeb2002a4204df50f2082851a83706fcaaa0ff2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sat, 19 Oct 2019 18:19:49 -0700 Subject: [PATCH 023/419] Omit flycheck files. --- .coveragerc | 1 + 1 file changed, 1 insertion(+) diff --git a/.coveragerc b/.coveragerc index 398ff08a..7dcb567b 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,2 +1,3 @@ [run] branch = True +omit = */flycheck_* From f0dff45684eac34a062c5f3f34de07ac0f520a73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sat, 19 Oct 2019 18:25:08 -0700 Subject: [PATCH 024/419] Oops, premature src/ injection! --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 7d8e3476..64f1dd13 100644 --- a/tox.ini +++ b/tox.ini @@ -56,7 +56,7 @@ setenv = codecov: COVERAGE_XML={envlogdir}/coverage_report.xml commands = - test: pytest --cov={env:PY_MODULE} --cov-report=term-missing:skip-covered --doctest-modules {posargs:src/{env:PY_MODULE}} + test: pytest --cov={env:PY_MODULE} --cov-report=term-missing:skip-covered --doctest-modules {posargs:{env:PY_MODULE}} ## From e393ac92f59a2d412344f1bdd5473bafa4117516 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sun, 20 Oct 2019 09:14:42 -0700 Subject: [PATCH 025/419] changedir before test run to ensure we aren't relying on being in the source root --- tox.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tox.ini b/tox.ini index 64f1dd13..5cb4d895 100644 --- a/tox.ini +++ b/tox.ini @@ -55,6 +55,8 @@ setenv = {coverage_report,codecov}: COVERAGE_FILE={toxworkdir}/coverage codecov: COVERAGE_XML={envlogdir}/coverage_report.xml +changedir = {envtmpdir} + commands = test: pytest --cov={env:PY_MODULE} --cov-report=term-missing:skip-covered --doctest-modules {posargs:{env:PY_MODULE}} From 6f5a12b8d45bac5120790e0705d4fb33f0c93913 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sun, 20 Oct 2019 09:38:00 -0700 Subject: [PATCH 026/419] Just do the src/ thing --- setup.py | 5 +++-- {hyperlink => src/hyperlink}/__init__.py | 0 {hyperlink => src/hyperlink}/_url.py | 0 {hyperlink => src/hyperlink}/test/__init__.py | 0 {hyperlink => src/hyperlink}/test/common.py | 0 {hyperlink => src/hyperlink}/test/test_common.py | 0 {hyperlink => src/hyperlink}/test/test_decoded_url.py | 0 {hyperlink => src/hyperlink}/test/test_parse.py | 0 .../hyperlink}/test/test_scheme_registration.py | 0 {hyperlink => src/hyperlink}/test/test_url.py | 0 tox.ini | 4 +--- 11 files changed, 4 insertions(+), 5 deletions(-) rename {hyperlink => src/hyperlink}/__init__.py (100%) rename {hyperlink => src/hyperlink}/_url.py (100%) rename {hyperlink => src/hyperlink}/test/__init__.py (100%) rename {hyperlink => src/hyperlink}/test/common.py (100%) rename {hyperlink => src/hyperlink}/test/test_common.py (100%) rename {hyperlink => src/hyperlink}/test/test_decoded_url.py (100%) rename {hyperlink => src/hyperlink}/test/test_parse.py (100%) rename {hyperlink => src/hyperlink}/test/test_scheme_registration.py (100%) rename {hyperlink => src/hyperlink}/test/test_url.py (100%) diff --git a/setup.py b/setup.py index f24e345a..85d29b57 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ See the docs at http://hyperlink.readthedocs.io. """ -from setuptools import setup +from setuptools import find_packages, setup __author__ = 'Mahmoud Hashemi and Glyph Lefkowitz' @@ -24,7 +24,8 @@ author=__author__, author_email=__contact__, url=__url__, - packages=['hyperlink', 'hyperlink.test'], + packages=find_packages(where="src"), + package_dir={"": "src"}, include_package_data=True, zip_safe=False, license=__license__, diff --git a/hyperlink/__init__.py b/src/hyperlink/__init__.py similarity index 100% rename from hyperlink/__init__.py rename to src/hyperlink/__init__.py diff --git a/hyperlink/_url.py b/src/hyperlink/_url.py similarity index 100% rename from hyperlink/_url.py rename to src/hyperlink/_url.py diff --git a/hyperlink/test/__init__.py b/src/hyperlink/test/__init__.py similarity index 100% rename from hyperlink/test/__init__.py rename to src/hyperlink/test/__init__.py diff --git a/hyperlink/test/common.py b/src/hyperlink/test/common.py similarity index 100% rename from hyperlink/test/common.py rename to src/hyperlink/test/common.py diff --git a/hyperlink/test/test_common.py b/src/hyperlink/test/test_common.py similarity index 100% rename from hyperlink/test/test_common.py rename to src/hyperlink/test/test_common.py diff --git a/hyperlink/test/test_decoded_url.py b/src/hyperlink/test/test_decoded_url.py similarity index 100% rename from hyperlink/test/test_decoded_url.py rename to src/hyperlink/test/test_decoded_url.py diff --git a/hyperlink/test/test_parse.py b/src/hyperlink/test/test_parse.py similarity index 100% rename from hyperlink/test/test_parse.py rename to src/hyperlink/test/test_parse.py diff --git a/hyperlink/test/test_scheme_registration.py b/src/hyperlink/test/test_scheme_registration.py similarity index 100% rename from hyperlink/test/test_scheme_registration.py rename to src/hyperlink/test/test_scheme_registration.py diff --git a/hyperlink/test/test_url.py b/src/hyperlink/test/test_url.py similarity index 100% rename from hyperlink/test/test_url.py rename to src/hyperlink/test/test_url.py diff --git a/tox.ini b/tox.ini index 5cb4d895..7d8e3476 100644 --- a/tox.ini +++ b/tox.ini @@ -55,10 +55,8 @@ setenv = {coverage_report,codecov}: COVERAGE_FILE={toxworkdir}/coverage codecov: COVERAGE_XML={envlogdir}/coverage_report.xml -changedir = {envtmpdir} - commands = - test: pytest --cov={env:PY_MODULE} --cov-report=term-missing:skip-covered --doctest-modules {posargs:{env:PY_MODULE}} + test: pytest --cov={env:PY_MODULE} --cov-report=term-missing:skip-covered --doctest-modules {posargs:src/{env:PY_MODULE}} ## From e213a58ed3fc9b3183c5f4fc4200639307b3d78c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sun, 20 Oct 2019 17:13:52 -0700 Subject: [PATCH 027/419] Add Flake8 to Tox config. --- src/hyperlink/_url.py | 120 +++++++++++++++++-------- src/hyperlink/test/common.py | 2 - src/hyperlink/test/test_decoded_url.py | 36 ++++++-- src/hyperlink/test/test_parse.py | 6 +- src/hyperlink/test/test_url.py | 90 ++++++++++++------- tox.ini | 63 +++++++++++++ 6 files changed, 237 insertions(+), 80 deletions(-) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index 50e8535e..bc2a0ca1 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -13,7 +13,7 @@ As seen here, the API revolves around the lightweight and immutable :class:`URL` type, documented below. -""" +""" # noqa: E501 import re import sys @@ -29,8 +29,7 @@ except ImportError: # Python 2 from collections import Mapping -# Note: IDNAError is a subclass of UnicodeError -from idna import encode as idna_encode, decode as idna_decode, IDNAError +from idna import encode as idna_encode, decode as idna_decode if inet_pton is None: @@ -38,7 +37,7 @@ # this code only applies on Windows Python 2.7 import ctypes - class _sockaddr(ctypes.Structure): + class _sockaddr(ctypes.Structure): # noqa: N801 _fields_ = [("sa_family", ctypes.c_short), ("__pad1", ctypes.c_ushort), ("ipv4_addr", ctypes.c_byte * 4), @@ -54,7 +53,10 @@ def inet_pton(address_family, ip_string): addr.sa_family = address_family addr_size = ctypes.c_int(ctypes.sizeof(addr)) - if WSAStringToAddressA(ip_string, address_family, None, ctypes.byref(addr), ctypes.byref(addr_size)) != 0: + if WSAStringToAddressA( + ip_string, address_family, None, + ctypes.byref(addr), ctypes.byref(addr_size) + ) != 0: raise socket.error(ctypes.FormatError()) if address_family == socket.AF_INET: @@ -338,7 +340,6 @@ def _encode_userinfo_part(text, maximal=True): else t for t in text]) - # This port list painstakingly curated by hand searching through # https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml # and @@ -489,19 +490,25 @@ def iter_pairs(iterable): return iter(iterable) -def _decode_unreserved(text, normalize_case=False, encode_stray_percents=False): +def _decode_unreserved( + text, normalize_case=False, encode_stray_percents=False +): return _percent_decode(text, normalize_case=normalize_case, encode_stray_percents=encode_stray_percents, _decode_map=_UNRESERVED_DECODE_MAP) -def _decode_userinfo_part(text, normalize_case=False, encode_stray_percents=False): +def _decode_userinfo_part( + text, normalize_case=False, encode_stray_percents=False +): return _percent_decode(text, normalize_case=normalize_case, encode_stray_percents=encode_stray_percents, _decode_map=_USERINFO_DECODE_MAP) -def _decode_path_part(text, normalize_case=False, encode_stray_percents=False): +def _decode_path_part( + text, normalize_case=False, encode_stray_percents=False +): """ >>> _decode_path_part(u'%61%77%2f%7a') u'aw%2fz' @@ -513,19 +520,25 @@ def _decode_path_part(text, normalize_case=False, encode_stray_percents=False): _decode_map=_PATH_DECODE_MAP) -def _decode_query_key(text, normalize_case=False, encode_stray_percents=False): +def _decode_query_key( + text, normalize_case=False, encode_stray_percents=False +): return _percent_decode(text, normalize_case=normalize_case, encode_stray_percents=encode_stray_percents, _decode_map=_QUERY_KEY_DECODE_MAP) -def _decode_query_value(text, normalize_case=False, encode_stray_percents=False): +def _decode_query_value( + text, normalize_case=False, encode_stray_percents=False +): return _percent_decode(text, normalize_case=normalize_case, encode_stray_percents=encode_stray_percents, _decode_map=_QUERY_VALUE_DECODE_MAP) -def _decode_fragment_part(text, normalize_case=False, encode_stray_percents=False): +def _decode_fragment_part( + text, normalize_case=False, encode_stray_percents=False +): return _percent_decode(text, normalize_case=normalize_case, encode_stray_percents=encode_stray_percents, _decode_map=_FRAGMENT_DECODE_MAP) @@ -566,7 +579,9 @@ def _percent_decode(text, normalize_case=False, subencoding='utf-8', """ try: - quoted_bytes = text.encode('utf-8' if subencoding is False else subencoding) + quoted_bytes = text.encode( + 'utf-8' if subencoding is False else subencoding + ) except UnicodeEncodeError: return text @@ -654,7 +669,7 @@ def _decode_host(host): u'mahmoud.io >> idna.decode(u'Méhmoud.io', uts46=True) u'm\xe9hmoud.io' - """ + """ # noqa: E501 if not host: return u'' try: @@ -685,7 +700,7 @@ def _resolve_dot_segments(path): removed and resolved. .. _RFC 3986 section 5.2.4, Remove Dot Segments: https://tools.ietf.org/html/rfc3986#section-5.2.4 - """ + """ # noqa: E501 segs = [] for seg in path: @@ -742,7 +757,7 @@ def parse_host(host): class URL(object): - """From blogs to billboards, URLs are so common, that it's easy to + r"""From blogs to billboards, URLs are so common, that it's easy to overlook their complexity and power. With hyperlink's :class:`URL` type, working with URLs doesn't have to be hard. @@ -794,7 +809,7 @@ class URL(object): .. _RFC 3986: https://tools.ietf.org/html/rfc3986 .. _RFC 3987: https://tools.ietf.org/html/rfc3987 - """ + """ # noqa: E501 def __init__(self, scheme=None, host=None, path=(), query=(), fragment=u'', port=None, rooted=None, userinfo=u'', uses_netloc=None): @@ -1009,8 +1024,10 @@ def __eq__(self, other): 'fragment', 'port', 'uses_netloc']: if getattr(self, attr) != getattr(other, attr): return False - if self.path == other.path or (self.path in _ROOT_PATHS - and other.path in _ROOT_PATHS): + if ( + self.path == other.path or + (self.path in _ROOT_PATHS and other.path in _ROOT_PATHS) + ): return True return False @@ -1127,11 +1144,13 @@ def from_text(cls, text): try: au_gs = au_m.groupdict() except AttributeError: - raise URLParseError('invalid authority %r in url: %r' - % (au_text, text)) + raise URLParseError( + 'invalid authority %r in url: %r' % (au_text, text) + ) if au_gs['bad_host']: - raise URLParseError('invalid host %r in url: %r' - % (au_gs['bad_host'], text)) + raise URLParseError( + 'invalid host %r in url: %r' % (au_gs['bad_host'], text) + ) userinfo = au_gs['userinfo'] or u'' @@ -1206,18 +1225,21 @@ def normalize(self, scheme=True, host=True, path=True, query=True, .. _RFC 3986 6.2.3: https://tools.ietf.org/html/rfc3986#section-6.2.3 .. _RFC 3986 2.4: https://tools.ietf.org/html/rfc3986#section-2.4 - """ + """ # noqa: E501 kw = {} if scheme: kw['scheme'] = self.scheme.lower() if host: kw['host'] = self.host.lower() + def _dec_unres(target): return _decode_unreserved(target, normalize_case=True, encode_stray_percents=percents) if path: if self.path: - kw['path'] = [_dec_unres(p) for p in _resolve_dot_segments(self.path)] + kw['path'] = [ + _dec_unres(p) for p in _resolve_dot_segments(self.path) + ] else: kw['path'] = (u'',) if query: @@ -1357,7 +1379,11 @@ def to_uri(self): self.userinfo.split(':', 1)]) new_path = _encode_path_parts(self.path, has_scheme=bool(self.scheme), rooted=False, joined=False, maximal=True) - new_host = self.host if not self.host else idna_encode(self.host, uts46=True).decode("ascii") + new_host = ( + self.host + if not self.host + else idna_encode(self.host, uts46=True).decode("ascii") + ) return self.replace( userinfo=new_userinfo, host=new_host, @@ -1391,7 +1417,7 @@ def to_iri(self): Returns: URL: A new instance with its path segments, query parameters, and hostname decoded for display purposes. - """ + """ # noqa: E501 new_userinfo = u':'.join([_decode_userinfo_part(p) for p in self.userinfo.split(':', 1)]) host_text = _decode_host(self.host) @@ -1443,8 +1469,10 @@ def to_text(self, with_password=False): if v is None: query_parts.append(_encode_query_key(k, maximal=False)) else: - query_parts.append(u'='.join((_encode_query_key(k, maximal=False), - _encode_query_value(v, maximal=False)))) + query_parts.append(u'='.join(( + _encode_query_key(k, maximal=False), + _encode_query_value(v, maximal=False) + ))) query_string = u'&'.join(query_parts) fragment = self.fragment @@ -1605,12 +1633,19 @@ def remove(self, name, value=_UNSET, limit=None): if value is _UNSET: nq = [(k, v) for (k, v) in self.query if k != name] else: - nq = [(k, v) for (k, v) in self.query if not (k == name and v == value)] + nq = [ + (k, v) for (k, v) in self.query + if not (k == name and v == value) + ] else: nq, removed_count = [], 0 for k, v in self.query: - if k == name and (value is _UNSET or v == value) and removed_count < limit: + if ( + k == name and + (value is _UNSET or v == value) and + removed_count < limit + ): removed_count += 1 # drop it else: nq.append((k, v)) # keep it @@ -1682,7 +1717,9 @@ def to_iri(self, *a, **kw): return self._url.to_iri(*a, **kw) def click(self, href=u''): - "Return a new DecodedURL wrapping the result of :meth:`~hyperlink.URL.click()`" + """Return a new DecodedURL wrapping the result of + :meth:`~hyperlink.URL.click()` + """ if isinstance(href, DecodedURL): href = href._url return self.__class__(self._url.click(href=href)) @@ -1705,7 +1742,9 @@ def child(self, *segments): return self.__class__(self._url.child(*new_segs)) def normalize(self, *a, **kw): - "Return a new `DecodedURL` wrapping the result of :meth:`~hyperlink.URL.normalize()`" + """Return a new `DecodedURL` wrapping the result of + :meth:`~hyperlink.URL.normalize()` + """ return self.__class__(self._url.normalize(*a, **kw)) @property @@ -1788,7 +1827,6 @@ def replace(self, scheme=_UNSET, host=_UNSET, path=_UNSET, query=_UNSET, containing a `:`. As with the rest of the methods on DecodedURL, if you pass a reserved character, it will be automatically encoded instead of an error being raised. - """ if path is not _UNSET: path = [_encode_reserved(p) for p in path] @@ -1818,7 +1856,8 @@ def get(self, name): return [v for (k, v) in self.query if name == k] def add(self, name, value=None): - "Return a new DecodedURL with the query parameter *name* and *value* added." + """Return a new DecodedURL with the query parameter *name* and *value* + added.""" return self.replace(query=self.query + ((name, value),)) def set(self, name, value=None): @@ -1839,11 +1878,18 @@ def remove(self, name, value=_UNSET, limit=None): if value is _UNSET: nq = [(k, v) for (k, v) in self.query if k != name] else: - nq = [(k, v) for (k, v) in self.query if not (k == name and v == value)] + nq = [ + (k, v) for (k, v) in self.query + if not (k == name and v == value) + ] else: nq, removed_count = [], 0 for k, v in self.query: - if k == name and (value is _UNSET or v == value) and removed_count < limit: + if ( + k == name and + (value is _UNSET or v == value) and + removed_count < limit + ): removed_count += 1 # drop it else: nq.append((k, v)) # keep it diff --git a/src/hyperlink/test/common.py b/src/hyperlink/test/common.py index 28eba527..c2674d8f 100644 --- a/src/hyperlink/test/common.py +++ b/src/hyperlink/test/common.py @@ -1,5 +1,3 @@ - - from unittest import TestCase diff --git a/src/hyperlink/test/test_decoded_url.py b/src/hyperlink/test/test_decoded_url.py index 4e6f8b97..f44f8862 100644 --- a/src/hyperlink/test/test_decoded_url.py +++ b/src/hyperlink/test/test_decoded_url.py @@ -7,7 +7,10 @@ from .common import HyperlinkTestCase BASIC_URL = 'http://example.com/#' -TOTAL_URL = "https://%75%73%65%72:%00%00%00%00@xn--bcher-kva.ch:8080/a/nice%20nice/./path/?zot=23%25&zut#frég" +TOTAL_URL = ( + "https://%75%73%65%72:%00%00%00%00@xn--bcher-kva.ch:8080/" + "a/nice%20nice/./path/?zot=23%25&zut#frég" +) class TestURL(HyperlinkTestCase): @@ -60,7 +63,10 @@ def test_passthroughs(self): durl2 = DecodedURL.from_text(TOTAL_URL, lazy=True) assert durl2 == durl2.encoded_url.get_decoded_url(lazy=True) - assert str(DecodedURL.from_text(BASIC_URL).child(' ')) == 'http://example.com/%20' + assert ( + str(DecodedURL.from_text(BASIC_URL).child(' ')) == + 'http://example.com/%20' + ) assert not (durl == 1) assert durl != 1 @@ -88,11 +94,25 @@ def test_query_manipulation(self): assert durl.set('arg', 'd').get('arg') == ['d'] - durl = DecodedURL.from_text(u"https://example.com/a/b/?fóó=1&bar=2&fóó=3") - assert durl.remove("fóó") == DecodedURL.from_text("https://example.com/a/b/?bar=2") - assert durl.remove("fóó", value="1") == DecodedURL.from_text("https://example.com/a/b/?bar=2&fóó=3") - assert durl.remove("fóó", limit=1) == DecodedURL.from_text("https://example.com/a/b/?bar=2&fóó=3") - assert durl.remove("fóó", value="1", limit=0) == DecodedURL.from_text("https://example.com/a/b/?fóó=1&bar=2&fóó=3") + durl = DecodedURL.from_text( + u"https://example.com/a/b/?fóó=1&bar=2&fóó=3" + ) + assert ( + durl.remove("fóó") == + DecodedURL.from_text("https://example.com/a/b/?bar=2") + ) + assert ( + durl.remove("fóó", value="1") == + DecodedURL.from_text("https://example.com/a/b/?bar=2&fóó=3") + ) + assert ( + durl.remove("fóó", limit=1) == + DecodedURL.from_text("https://example.com/a/b/?bar=2&fóó=3") + ) + assert ( + durl.remove("fóó", value="1", limit=0) == + DecodedURL.from_text("https://example.com/a/b/?fóó=1&bar=2&fóó=3") + ) def test_equality_and_hashability(self): durl = DecodedURL.from_text(TOTAL_URL) @@ -103,7 +123,7 @@ def test_equality_and_hashability(self): assert durl == durl assert durl == durl2 assert durl != burl - assert durl != None + assert durl is not None assert durl != durl._url durl_map = {} diff --git a/src/hyperlink/test/test_parse.py b/src/hyperlink/test/test_parse.py index cd2e9c97..d151f4ee 100644 --- a/src/hyperlink/test/test_parse.py +++ b/src/hyperlink/test/test_parse.py @@ -6,13 +6,17 @@ from hyperlink import parse, EncodedURL, DecodedURL BASIC_URL = 'http://example.com/#' -TOTAL_URL = "https://%75%73%65%72:%00%00%00%00@xn--bcher-kva.ch:8080/a/nice%20nice/./path/?zot=23%25&zut#frég" +TOTAL_URL = ( + "https://%75%73%65%72:%00%00%00%00@xn--bcher-kva.ch:8080" + "/a/nice%20nice/./path/?zot=23%25&zut#frég" +) UNDECODABLE_FRAG_URL = TOTAL_URL + '%C3' # the %C3 above percent-decodes to an unpaired \xc3 byte which makes this # invalid utf8 class TestURL(HyperlinkTestCase): + def test_parse(self): purl = parse(TOTAL_URL) assert isinstance(purl, DecodedURL) diff --git a/src/hyperlink/test/test_url.py b/src/hyperlink/test/test_url.py index 344392f9..73cbd936 100644 --- a/src/hyperlink/test/test_url.py +++ b/src/hyperlink/test/test_url.py @@ -10,9 +10,7 @@ from .common import HyperlinkTestCase from .. import URL, URLParseError -# automatically import the py27 windows implementation when appropriate -from .. import _url -from .._url import inet_pton, SCHEME_PORT_MAP, parse_host +from .._url import inet_pton, SCHEME_PORT_MAP PY2 = (sys.version_info[0] == 2) @@ -162,7 +160,7 @@ def assertUnicoded(self, u): repr(u)) for seg in u.path: self.assertEqual(type(seg), unicode, repr(u)) - for (k, v) in u.query: + for (_k, v) in u.query: self.assertEqual(type(seg), unicode, repr(u)) self.assertTrue(v is None or isinstance(v, unicode), repr(u)) self.assertEqual(type(u.fragment), unicode, repr(u)) @@ -235,8 +233,8 @@ def test_initPercent(self): def test_repr(self): """ L{URL.__repr__} will display the canonical form of the URL, wrapped in - a L{URL.from_text} invocation, so that it is C{eval}-able but still easy - to read. + a L{URL.from_text} invocation, so that it is C{eval}-able but still + easy to read. """ self.assertEqual( repr(URL(scheme='http', host='foo', path=['bar'], @@ -366,16 +364,16 @@ def test_click(self): urlpath = URL.from_text(BASIC_URL) # A null uri should be valid (return here). self.assertEqual("http://www.foo.com/a/nice/path/?zot=23&zut", - urlpath.click("").to_text()) + urlpath.click("").to_text()) # A simple relative path remove the query. self.assertEqual("http://www.foo.com/a/nice/path/click", - urlpath.click("click").to_text()) + urlpath.click("click").to_text()) # An absolute path replace path and query. self.assertEqual("http://www.foo.com/click", - urlpath.click("/click").to_text()) + urlpath.click("/click").to_text()) # Replace just the query. self.assertEqual("http://www.foo.com/a/nice/path/?burp", - urlpath.click("?burp").to_text()) + urlpath.click("?burp").to_text()) # One full url to another should not generate '//' between authority. # and path self.assertTrue("//foobar" not in @@ -562,7 +560,9 @@ def test_parseEqualSignInParamValue(self): self.assertEqual(u.query, (('foo', 'x=x=x'), ('bar', 'y'))) self.assertEqual(u.to_text(), 'http://localhost/?foo=x=x=x&bar=y') - u = URL.from_text('https://example.com/?argument=3&argument=4&operator=%3D') + u = URL.from_text( + 'https://example.com/?argument=3&argument=4&operator=%3D' + ) iri = u.to_iri() self.assertEqual(iri.get('operator'), ['=']) # assert that the equals is not unnecessarily escaped @@ -757,8 +757,8 @@ def test_mailto(self): """ Although L{URL} instances are mainly for dealing with HTTP, other schemes (such as C{mailto:}) should work as well. For example, - L{URL.from_text}/L{URL.to_text} round-trips cleanly for a C{mailto:} URL - representing an email address. + L{URL.from_text}/L{URL.to_text} round-trips cleanly for a C{mailto:} + URL representing an email address. """ self.assertEqual(URL.from_text(u"mailto:user@example.com").to_text(), u"mailto:user@example.com") @@ -869,8 +869,7 @@ def test_technicallyTextIsIterableBut(self): URL(path='foo') self.assertEqual( str(raised.exception), - "expected iterable of text for path, not: {0}" - .format(repr('foo')) + "expected iterable of text for path, not: {0}".format(repr('foo')) ) def test_netloc(self): @@ -936,12 +935,13 @@ def test_invalid_ipv6(self): '::256.0.0.1'] for ip in invalid_ipv6_ips: url_text = 'http://[' + ip + ']' - self.assertRaises(socket.error, inet_pton, - socket.AF_INET6, ip) + self.assertRaises(socket.error, inet_pton, socket.AF_INET6, ip) self.assertRaises(URLParseError, URL.from_text, url_text) def test_invalid_port(self): - self.assertRaises(URLParseError, URL.from_text, 'ftp://portmouth:smash') + self.assertRaises( + URLParseError, URL.from_text, 'ftp://portmouth:smash' + ) self.assertRaises(ValueError, URL.from_text, 'http://reader.googlewebsite.com:neverforget') @@ -979,18 +979,18 @@ def test_netloc_slashes(self): url = URL.from_text('git+ftp://gitstub.biz/glyph/lefkowitz') self.assertEqual(url.scheme, 'git+ftp') self.assertEqual(url.to_text(), - 'git+ftp://gitstub.biz/glyph/lefkowitz') + 'git+ftp://gitstub.biz/glyph/lefkowitz') url = URL.from_text('what+mailto:freerealestate@enotuniq.org') self.assertEqual(url.scheme, 'what+mailto') self.assertEqual(url.to_text(), - 'what+mailto:freerealestate@enotuniq.org') + 'what+mailto:freerealestate@enotuniq.org') url = URL(scheme='ztp', path=('x', 'y', 'z'), rooted=True) self.assertEqual(url.to_text(), 'ztp:/x/y/z') # also works when the input doesn't include '//' - url = URL(scheme='git+ftp', path=('x', 'y', 'z' ,''), + url = URL(scheme='git+ftp', path=('x', 'y', 'z', ''), rooted=True, uses_netloc=True) # broken bc urlunsplit self.assertEqual(url.to_text(), 'git+ftp:///x/y/z/') @@ -1022,9 +1022,18 @@ def test_encoded_userinfo(self): assert url.userinfo == 'user:pass' url = url.replace(userinfo='us%20her:pass') iri = url.to_iri() - assert iri.to_text(with_password=True) == 'http://us her:pass@example.com' - assert iri.to_text(with_password=False) == 'http://us her:@example.com' - assert iri.to_uri().to_text(with_password=True) == 'http://us%20her:pass@example.com' + assert ( + iri.to_text(with_password=True) == + 'http://us her:pass@example.com' + ) + assert ( + iri.to_text(with_password=False) == + 'http://us her:@example.com' + ) + assert ( + iri.to_uri().to_text(with_password=True) == + 'http://us%20her:pass@example.com' + ) def test_hash(self): url_map = {} @@ -1173,13 +1182,27 @@ def test_normalize(self): assert norm_delimited_url.to_text() == '/a%2Fb/cd%3F?k%3D=v%23#test' # test invalid percent encoding during normalize - assert URL(path=('', '%te%sts')).normalize(percents=False).to_text() == '/%te%sts' - assert URL(path=('', '%te%sts')).normalize().to_text() == '/%25te%25sts' + assert ( + URL(path=('', '%te%sts')).normalize(percents=False).to_text() == + '/%te%sts' + ) + assert ( + URL(path=('', '%te%sts')).normalize().to_text() == '/%25te%25sts' + ) - percenty_url = URL(scheme='ftp', path=['%%%', '%a%b'], query=[('%', '%%')], fragment='%', userinfo='%:%') + percenty_url = URL( + scheme='ftp', path=['%%%', '%a%b'], query=[('%', '%%')], + fragment='%', userinfo='%:%', + ) - assert percenty_url.to_text(with_password=True) == 'ftp://%:%@/%%%/%a%b?%=%%#%' - assert percenty_url.normalize().to_text(with_password=True) == 'ftp://%25:%25@/%25%25%25/%25a%25b?%25=%25%25#%25' + assert ( + percenty_url.to_text(with_password=True) == + 'ftp://%:%@/%%%/%a%b?%=%%#%' + ) + assert ( + percenty_url.normalize().to_text(with_password=True) == + 'ftp://%25:%25@/%25%25%25/%25a%25b?%25=%25%25#%25' + ) def test_str(self): # see also issue #49 @@ -1196,8 +1219,7 @@ def test_str(self): assert isinstance(bytes(url), bytes) def test_idna_corners(self): - text = u'http://abé.com/' - url = URL.from_text(text) + url = URL.from_text(u'http://abé.com/') assert url.to_iri().host == u'abé.com' assert url.to_uri().host == u'xn--ab-cja.com' @@ -1207,4 +1229,8 @@ def test_idna_corners(self): assert url.to_uri().get_decoded_url().host == u'ドメイン.テスト.co.jp' - assert URL.from_text('http://Example.com').to_uri().get_decoded_url().host == 'example.com' + text = 'http://Example.com' + assert ( + URL.from_text(text).to_uri().get_decoded_url().host == + 'example.com' + ) diff --git a/tox.ini b/tox.ini index 7d8e3476..b8a2bc26 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,7 @@ [tox] envlist = + flake8 test-py{26,27,34,35,36,37,py,py3} coverage_report packaging @@ -59,6 +60,68 @@ commands = test: pytest --cov={env:PY_MODULE} --cov-report=term-missing:skip-covered --doctest-modules {posargs:src/{env:PY_MODULE}} +## +# Flake8 linting +## + +[testenv:flake8] + +description = run Flake8 (linter) + +basepython = python3.7 + +skip_install = True + +# Pin pydocstyle to version 3: see https://gitlab.com/pycqa/flake8-docstrings/issues/36 +deps = + flake8-bugbear==19.8.0 + #flake8-docstrings==1.4.0 + #flake8-import-order==0.18.1 + #flake8-pep3101==1.2.1 + flake8==3.7.8 + mccabe==0.6.1 + pep8-naming==0.8.2 + pydocstyle==4.0.1 + +commands = + flake8 {posargs:src/{env:PY_MODULE}} + + +[flake8] + +# !!! BRING THE PAIN !!! +select = A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z + +show-source = True +doctests = True + +# Codes: http://flake8.pycqa.org/en/latest/user/error-codes.html +ignore = + # function name should be lowercase + N802, + + # argument name should be lowercase + N803, + + # variable in function should be lowercase + N806, + + # variable in class scope should not be mixedCase + N815, + + # variable in global scope should not be mixedCase + N816, + + # line break after binary operator + W504, + + # End of list (allows last item to end with trailing ',') + EOL + +# flake8-import-order: local module name space +application-import-names = deploy + + ## # Coverage report ## From 38cdd106de13ed2bf3e9ce7697c1ead05a39d6ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sun, 20 Oct 2019 17:26:47 -0700 Subject: [PATCH 028/419] Use CapsCase for SockAddr --- src/hyperlink/_url.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index bc2a0ca1..7f94a234 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -37,7 +37,7 @@ # this code only applies on Windows Python 2.7 import ctypes - class _sockaddr(ctypes.Structure): # noqa: N801 + class SockAddr(ctypes.Structure): _fields_ = [("sa_family", ctypes.c_short), ("__pad1", ctypes.c_ushort), ("ipv4_addr", ctypes.c_byte * 4), @@ -48,7 +48,7 @@ class _sockaddr(ctypes.Structure): # noqa: N801 WSAAddressToStringA = ctypes.windll.ws2_32.WSAAddressToStringA def inet_pton(address_family, ip_string): - addr = _sockaddr() + addr = SockAddr() ip_string = ip_string.encode('ascii') addr.sa_family = address_family addr_size = ctypes.c_int(ctypes.sizeof(addr)) From 20cb7fe68ad11fa01c360e695ebc3b0f20319d87 Mon Sep 17 00:00:00 2001 From: Hristo Georgiev Date: Mon, 21 Oct 2019 18:27:19 +0100 Subject: [PATCH 029/419] Fix a typo in the docs --- TODO.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index e8ac57ab..f5d2fdda 100644 --- a/TODO.md +++ b/TODO.md @@ -29,7 +29,7 @@ * Speed up percent encoding with urlutils approach * More default ports * resolve dots on (empty) click -* better error on URL constructor (single string argument leads to succesful instantiation with invalid scheme) +* better error on URL constructor (single string argument leads to successful instantiation with invalid scheme) * pct encode userinfo * `__hash__` * README From e0cee8a995020d0f469196c9cc89b49df2dc43b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sat, 26 Oct 2019 20:34:42 -0700 Subject: [PATCH 030/419] Move definition of inet_pton into its own file so that the Windows-specific treatment can happen without cluttering _url.py. Makes use of TYPE_CHECKING not add to the noise so much. --- src/hyperlink/_socket.py | 39 +++++++++++++++++++++++++++++++++++++++ src/hyperlink/_url.py | 39 +-------------------------------------- 2 files changed, 40 insertions(+), 38 deletions(-) create mode 100644 src/hyperlink/_socket.py diff --git a/src/hyperlink/_socket.py b/src/hyperlink/_socket.py new file mode 100644 index 00000000..0d2cb48c --- /dev/null +++ b/src/hyperlink/_socket.py @@ -0,0 +1,39 @@ +try: + from socket import inet_pton +except ImportError: + from typing import TYPE_CHECKING + if not TYPE_CHECKING: + # based on https://gist.github.com/nnemkin/4966028 + # this code only applies on Windows Python 2.7 + import ctypes + import socket + + class SockAddr(ctypes.Structure): + _fields_ = [ + ("sa_family", ctypes.c_short), + ("__pad1", ctypes.c_ushort), + ("ipv4_addr", ctypes.c_byte * 4), + ("ipv6_addr", ctypes.c_byte * 16), + ("__pad2", ctypes.c_ulong), + ] + + WSAStringToAddressA = ctypes.windll.ws2_32.WSAStringToAddressA + WSAAddressToStringA = ctypes.windll.ws2_32.WSAAddressToStringA + + def inet_pton(address_family: int, ip_string: str) -> bytes: + addr = SockAddr() + ip_string_bytes = ip_string.encode('ascii') + addr.sa_family = address_family + addr_size = ctypes.c_int(ctypes.sizeof(addr)) + + if WSAStringToAddressA( + ip_string_bytes, address_family, None, + ctypes.byref(addr), ctypes.byref(addr_size) + ) != 0: + raise socket.error(ctypes.FormatError()) + + if address_family == socket.AF_INET: + return ctypes.string_at(addr.ipv4_addr, 4) + if address_family == socket.AF_INET6: + return ctypes.string_at(addr.ipv6_addr, 16) + raise socket.error('unknown address family') diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index 7f94a234..dc714e58 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -20,10 +20,7 @@ import string import socket from unicodedata import normalize -try: - from socket import inet_pton -except ImportError: - inet_pton = None # defined below +from ._socket import inet_pton try: from collections.abc import Mapping except ImportError: # Python 2 @@ -32,40 +29,6 @@ from idna import encode as idna_encode, decode as idna_decode -if inet_pton is None: - # based on https://gist.github.com/nnemkin/4966028 - # this code only applies on Windows Python 2.7 - import ctypes - - class SockAddr(ctypes.Structure): - _fields_ = [("sa_family", ctypes.c_short), - ("__pad1", ctypes.c_ushort), - ("ipv4_addr", ctypes.c_byte * 4), - ("ipv6_addr", ctypes.c_byte * 16), - ("__pad2", ctypes.c_ulong)] - - WSAStringToAddressA = ctypes.windll.ws2_32.WSAStringToAddressA - WSAAddressToStringA = ctypes.windll.ws2_32.WSAAddressToStringA - - def inet_pton(address_family, ip_string): - addr = SockAddr() - ip_string = ip_string.encode('ascii') - addr.sa_family = address_family - addr_size = ctypes.c_int(ctypes.sizeof(addr)) - - if WSAStringToAddressA( - ip_string, address_family, None, - ctypes.byref(addr), ctypes.byref(addr_size) - ) != 0: - raise socket.error(ctypes.FormatError()) - - if address_family == socket.AF_INET: - return ctypes.string_at(addr.ipv4_addr, 4) - if address_family == socket.AF_INET6: - return ctypes.string_at(addr.ipv6_addr, 16) - raise socket.error('unknown address family') - - PY2 = (sys.version_info[0] == 2) unicode = type(u'') try: From a893c30fffef590628e0159714140766084f1d8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sat, 26 Oct 2019 20:35:11 -0700 Subject: [PATCH 031/419] .tox is a directory --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 0ef6fd4d..caba5783 100644 --- a/.gitignore +++ b/.gitignore @@ -33,7 +33,7 @@ pip-log.txt # Unit test / coverage reports .coverage -.tox +.tox/ nosetests.xml # Translations From 03ee5d14b9be21848074c3eaaab521c9559ef2a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sat, 26 Oct 2019 20:37:00 -0700 Subject: [PATCH 032/419] Bring mypy into the tox config --- .gitignore | 1 + setup.py | 5 +++- tox.ini | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index caba5783..35a65f26 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ tmp.py htmlcov/ .coverage.* *.py[cod] +.mypy_cache # emacs *~ diff --git a/setup.py b/setup.py index 85d29b57..db3b4214 100644 --- a/setup.py +++ b/setup.py @@ -30,7 +30,10 @@ zip_safe=False, license=__license__, platforms='any', - install_requires=['idna>=2.5'], + install_requires=[ + 'idna>=2.5', + 'typing ; python_version<"3.5"', + ], classifiers=[ 'Topic :: Utilities', 'Intended Audience :: Developers', diff --git a/tox.ini b/tox.ini index b8a2bc26..0ccd741b 100644 --- a/tox.ini +++ b/tox.ini @@ -31,6 +31,7 @@ basepython = deps = test: coverage==4.5.4 test: idna==2.8 + test: typing==3.7.4.1 test: {py26,py27,py34}: pytest==4.6.6 test: {py35,py36,py37,py38}: pytest==5.2.1 test: pytest-cov==2.8.1 @@ -122,6 +123,74 @@ ignore = application-import-names = deploy +## +# Mypy static type checking +## + +[testenv:mypy] + +description = run Mypy (static type checker) + +basepython = python3.7 + +skip_install = True + + +deps = + mypy==0.730 + + +commands = + mypy \ + --config-file="{toxinidir}/tox.ini" \ + --cache-dir="{toxworkdir}/mypy_cache" \ + {tty:--pretty:} \ + {posargs:src} + + +[mypy] + +# Global settings + +no_implicit_optional = True +show_column_numbers = True +show_error_codes = True +strict_optional = True +warn_no_return = True +warn_redundant_casts = True +warn_return_any = True +warn_unused_ignores = True +warn_unreachable = True + +# Enable these over time +check_untyped_defs = False +disallow_incomplete_defs = False +disallow_untyped_defs = False + +# Disable checks until effected files fully adopt mypy + +[mypy-hyperlink._url] +allow_untyped_defs = True + +[mypy-hyperlink.test.common] +allow_untyped_defs = True + +[mypy-hyperlink.test.test_common] +allow_untyped_defs = True + +[mypy-hyperlink.test.test_decoded_url] +allow_untyped_defs = True + +[mypy-hyperlink.test.test_scheme_registration] +allow_untyped_defs = True + +[mypy-hyperlink.test.test_url] +allow_untyped_defs = True + +[mypy-idna] +ignore_missing_imports = True + + ## # Coverage report ## From 16a83d93aa20375b944e35f199bc0419b92489db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sat, 26 Oct 2019 20:37:33 -0700 Subject: [PATCH 033/419] Make mypy happy --- src/hyperlink/_url.py | 5 +++-- src/hyperlink/test/test_common.py | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index dc714e58..1729fa21 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -19,6 +19,7 @@ import sys import string import socket +from typing import Callable, Text, Type from unicodedata import normalize from ._socket import inet_pton try: @@ -32,10 +33,10 @@ PY2 = (sys.version_info[0] == 2) unicode = type(u'') try: - unichr + unichr: Callable[[int], Text] except NameError: unichr = chr # py3 -NoneType = type(None) +NoneType: Type[None] = type(None) # from boltons.typeutils diff --git a/src/hyperlink/test/test_common.py b/src/hyperlink/test/test_common.py index 1d61583c..215a919f 100644 --- a/src/hyperlink/test/test_common.py +++ b/src/hyperlink/test/test_common.py @@ -79,7 +79,9 @@ def test_assertRaisesContextManager(self): with self.hyperlink_test.assertRaises(_ExpectedException) as cm: raise _ExpectedException - self.assertTrue(isinstance(cm.exception, _ExpectedException)) + self.assertTrue( # type: ignore[misc] (unreachable) + isinstance(cm.exception, _ExpectedException) + ) def test_assertRaisesContextManagerUnexpectedException(self): """When used as a context manager with a block that raises an From 45f709aeb7202789cf1e8fbb15bb620df489cf58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sat, 26 Oct 2019 20:37:50 -0700 Subject: [PATCH 034/419] Unnecessary bare return --- src/hyperlink/test/test_parse.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/hyperlink/test/test_parse.py b/src/hyperlink/test/test_parse.py index d151f4ee..fa3fb176 100644 --- a/src/hyperlink/test/test_parse.py +++ b/src/hyperlink/test/test_parse.py @@ -35,5 +35,3 @@ def test_parse(self): with self.assertRaises(UnicodeDecodeError): purl3.fragment - - return From f6e87a671af5ba3e0792380b66e03dce50180806 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sat, 26 Oct 2019 20:53:48 -0700 Subject: [PATCH 035/419] Silent flake8 from having opinions about type comments; let mypy complain is need be. --- src/hyperlink/_socket.py | 3 ++- src/hyperlink/_url.py | 8 ++++---- src/hyperlink/test/test_common.py | 2 +- tox.ini | 3 +++ 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/hyperlink/_socket.py b/src/hyperlink/_socket.py index 0d2cb48c..f4b94daa 100644 --- a/src/hyperlink/_socket.py +++ b/src/hyperlink/_socket.py @@ -20,7 +20,8 @@ class SockAddr(ctypes.Structure): WSAStringToAddressA = ctypes.windll.ws2_32.WSAStringToAddressA WSAAddressToStringA = ctypes.windll.ws2_32.WSAAddressToStringA - def inet_pton(address_family: int, ip_string: str) -> bytes: + def inet_pton(address_family, ip_string): + # type: (int, str) -> bytes addr = SockAddr() ip_string_bytes = ip_string.encode('ascii') addr.sa_family = address_family diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index 1729fa21..2d4e712a 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -33,10 +33,10 @@ PY2 = (sys.version_info[0] == 2) unicode = type(u'') try: - unichr: Callable[[int], Text] -except NameError: - unichr = chr # py3 -NoneType: Type[None] = type(None) + unichr +except NameError: # Py3 + unichr = chr # type: Callable[[int], Text] +NoneType = type(None) # type: Type[None] # from boltons.typeutils diff --git a/src/hyperlink/test/test_common.py b/src/hyperlink/test/test_common.py index 215a919f..65d157ca 100644 --- a/src/hyperlink/test/test_common.py +++ b/src/hyperlink/test/test_common.py @@ -79,7 +79,7 @@ def test_assertRaisesContextManager(self): with self.hyperlink_test.assertRaises(_ExpectedException) as cm: raise _ExpectedException - self.assertTrue( # type: ignore[misc] (unreachable) + self.assertTrue( # type: ignore[misc] unreachable isinstance(cm.exception, _ExpectedException) ) diff --git a/tox.ini b/tox.ini index 0ccd741b..f70d340b 100644 --- a/tox.ini +++ b/tox.ini @@ -98,6 +98,9 @@ doctests = True # Codes: http://flake8.pycqa.org/en/latest/user/error-codes.html ignore = + # syntax error in type comment + F723, + # function name should be lowercase N802, From ed00543b89c9f8734f90dd1afd5333807dfd459f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sat, 26 Oct 2019 21:18:34 -0700 Subject: [PATCH 036/419] Add hints to test modules. --- src/hyperlink/test/test_common.py | 13 +++- src/hyperlink/test/test_decoded_url.py | 13 ++++ src/hyperlink/test/test_parse.py | 1 + .../test/test_scheme_registration.py | 8 ++ src/hyperlink/test/test_url.py | 73 +++++++++++++++++++ tox.ini | 21 +----- 6 files changed, 110 insertions(+), 19 deletions(-) diff --git a/src/hyperlink/test/test_common.py b/src/hyperlink/test/test_common.py index 65d157ca..ac8e9b3c 100644 --- a/src/hyperlink/test/test_common.py +++ b/src/hyperlink/test/test_common.py @@ -1,6 +1,7 @@ """ Tests for hyperlink.test.common """ +from typing import Any from unittest import TestCase from .common import HyperlinkTestCase @@ -21,9 +22,11 @@ class TestHyperlink(TestCase): """Tests for HyperlinkTestCase""" def setUp(self): + # type: () -> None self.hyperlink_test = HyperlinkTestCase("run") def test_assertRaisesWithCallable(self): + # type: () -> None """HyperlinkTestCase.assertRaises does not raise an AssertionError when given a callable that, when called with the provided arguments, raises the expected exception. @@ -32,6 +35,7 @@ def test_assertRaisesWithCallable(self): called_with = [] def raisesExpected(*args, **kwargs): + # type: (Any, Any) -> None called_with.append((args, kwargs)) raise _ExpectedException @@ -40,12 +44,14 @@ def raisesExpected(*args, **kwargs): self.assertEqual(called_with, [((1,), {"keyword": True})]) def test_assertRaisesWithCallableUnexpectedException(self): + # type: () -> None """When given a callable that raises an unexpected exception, HyperlinkTestCase.assertRaises raises that exception. """ def doesNotRaiseExpected(*args, **kwargs): + # type: (Any, Any) -> None raise _UnexpectedException try: @@ -55,13 +61,15 @@ def doesNotRaiseExpected(*args, **kwargs): pass def test_assertRaisesWithCallableDoesNotRaise(self): + # type: () -> None """HyperlinkTestCase.assertRaises raises an AssertionError when given a callable that, when called, does not raise any exception. """ def doesNotRaise(*args, **kwargs): - return True + # type: (Any, Any) -> None + pass try: self.hyperlink_test.assertRaises(_ExpectedException, @@ -70,6 +78,7 @@ def doesNotRaise(*args, **kwargs): pass def test_assertRaisesContextManager(self): + # type: () -> None """HyperlinkTestCase.assertRaises does not raise an AssertionError when used as a context manager with a suite that raises the expected exception. The context manager stores the exception @@ -84,6 +93,7 @@ def test_assertRaisesContextManager(self): ) def test_assertRaisesContextManagerUnexpectedException(self): + # type: () -> None """When used as a context manager with a block that raises an unexpected exception, HyperlinkTestCase.assertRaises raises that unexpected exception. @@ -96,6 +106,7 @@ def test_assertRaisesContextManagerUnexpectedException(self): pass def test_assertRaisesContextManagerDoesNotRaise(self): + # type: () -> None """HyperlinkTestcase.assertRaises raises an AssertionError when used as a context manager with a block that does not raise any exception. diff --git a/src/hyperlink/test/test_decoded_url.py b/src/hyperlink/test/test_decoded_url.py index f44f8862..17f7f19b 100644 --- a/src/hyperlink/test/test_decoded_url.py +++ b/src/hyperlink/test/test_decoded_url.py @@ -16,6 +16,7 @@ class TestURL(HyperlinkTestCase): def test_durl_basic(self): + # type: () -> None bdurl = DecodedURL.from_text(BASIC_URL) assert bdurl.scheme == 'http' assert bdurl.host == 'example.com' @@ -36,6 +37,8 @@ def test_durl_basic(self): assert durl.userinfo == ('user', '\0\0\0\0') def test_passthroughs(self): + # type: () -> None + # just basic tests for the methods that more or less pass straight # through to the underlying URL @@ -72,10 +75,12 @@ def test_passthroughs(self): assert durl != 1 def test_repr(self): + # type: () -> None durl = DecodedURL.from_text(TOTAL_URL) assert repr(durl) == 'DecodedURL(url=' + repr(durl._url) + ')' def test_query_manipulation(self): + # type: () -> None durl = DecodedURL.from_text(TOTAL_URL) assert durl.get('zot') == ['23%'] @@ -115,6 +120,7 @@ def test_query_manipulation(self): ) def test_equality_and_hashability(self): + # type: () -> None durl = DecodedURL.from_text(TOTAL_URL) durl2 = DecodedURL.from_text(TOTAL_URL) burl = DecodedURL.from_text(BASIC_URL) @@ -141,6 +147,7 @@ def test_equality_and_hashability(self): assert len(durl_map) == 3 def test_replace_roundtrip(self): + # type: () -> None durl = DecodedURL.from_text(TOTAL_URL) durl2 = durl.replace(scheme=durl.scheme, @@ -156,12 +163,14 @@ def test_replace_roundtrip(self): assert durl == durl2 def test_replace_userinfo(self): + # type: () -> None durl = DecodedURL.from_text(TOTAL_URL) with self.assertRaises(ValueError): durl.replace(userinfo=['user', 'pw', 'thiswillcauseafailure']) return def test_twisted_compat(self): + # type: () -> None durl = DecodedURL.from_text(TOTAL_URL) assert durl == DecodedURL.fromText(TOTAL_URL) @@ -170,9 +179,12 @@ def test_twisted_compat(self): assert durl.to_text() == durl.asText() def test_percent_decode_bytes(self): + # type: () -> None assert _percent_decode('%00', subencoding=False) == b'\0' def test_percent_decode_mixed(self): + # type: () -> None + # See https://github.com/python-hyper/hyperlink/pull/59 for a # nice discussion of the possibilities assert _percent_decode('abcdé%C3%A9éfg') == 'abcdéééfg' @@ -191,6 +203,7 @@ def test_percent_decode_mixed(self): assert _percent_decode('é%25é', subencoding='ascii') == 'é%25é' def test_click_decoded_url(self): + # type: () -> None durl = DecodedURL.from_text(TOTAL_URL) durl_dest = DecodedURL.from_text('/tëst') diff --git a/src/hyperlink/test/test_parse.py b/src/hyperlink/test/test_parse.py index fa3fb176..8fdbf351 100644 --- a/src/hyperlink/test/test_parse.py +++ b/src/hyperlink/test/test_parse.py @@ -18,6 +18,7 @@ class TestURL(HyperlinkTestCase): def test_parse(self): + # type: () -> None purl = parse(TOTAL_URL) assert isinstance(purl, DecodedURL) assert purl.user == 'user' diff --git a/src/hyperlink/test/test_scheme_registration.py b/src/hyperlink/test/test_scheme_registration.py index d344353c..68eb1695 100644 --- a/src/hyperlink/test/test_scheme_registration.py +++ b/src/hyperlink/test/test_scheme_registration.py @@ -10,14 +10,17 @@ class TestSchemeRegistration(HyperlinkTestCase): def setUp(self): + # type: () -> None self._orig_scheme_port_map = dict(_url.SCHEME_PORT_MAP) self._orig_no_netloc_schemes = set(_url.NO_NETLOC_SCHEMES) def tearDown(self): + # type: () -> None _url.SCHEME_PORT_MAP = self._orig_scheme_port_map _url.NO_NETLOC_SCHEMES = self._orig_no_netloc_schemes def test_register_scheme_basic(self): + # type: () -> None register_scheme('deltron', uses_netloc=True, default_port=3030) u1 = URL.from_text('deltron://example.com') @@ -40,25 +43,30 @@ def test_register_scheme_basic(self): assert u4.to_text() == 'nonetron://example.com' def test_register_no_netloc_scheme(self): + # type: () -> None register_scheme('noloctron', uses_netloc=False) u4 = URL(scheme='noloctron') u4 = u4.replace(path=("example", "path")) assert u4.to_text() == 'noloctron:example/path' def test_register_no_netloc_with_port(self): + # type: () -> None with self.assertRaises(ValueError): register_scheme('badnetlocless', uses_netloc=False, default_port=7) def test_invalid_uses_netloc(self): + # type: () -> None with self.assertRaises(ValueError): register_scheme('badnetloc', uses_netloc=None) with self.assertRaises(ValueError): register_scheme('badnetloc', uses_netloc=object()) def test_register_invalid_uses_netloc(self): + # type: () -> None with self.assertRaises(ValueError): register_scheme('lol', uses_netloc=lambda: 'nope') def test_register_invalid_port(self): + # type: () -> None with self.assertRaises(ValueError): register_scheme('nope', default_port=lambda: 'lol') diff --git a/src/hyperlink/test/test_url.py b/src/hyperlink/test/test_url.py index 73cbd936..2e393eed 100644 --- a/src/hyperlink/test/test_url.py +++ b/src/hyperlink/test/test_url.py @@ -193,6 +193,7 @@ def assertURL(self, u, scheme, host, path, query, self.assertEqual(actual, expected) def test_initDefaults(self): + # type: () -> None """ L{URL} should have appropriate default values. """ @@ -205,6 +206,7 @@ def check(u): check(URL('http', '', [], [], '')) def test_init(self): + # type: () -> None """ L{URL} should accept L{unicode} parameters. """ @@ -219,6 +221,7 @@ def test_init(self): [('\u03bb', '\u03c0')], '\u22a5', 80) def test_initPercent(self): + # type: () -> None """ L{URL} should accept (and not interpret) percent characters. """ @@ -231,6 +234,7 @@ def test_initPercent(self): '%66', None) def test_repr(self): + # type: () -> None """ L{URL.__repr__} will display the canonical form of the URL, wrapped in a L{URL.from_text} invocation, so that it is C{eval}-able but still @@ -244,6 +248,7 @@ def test_repr(self): ) def test_from_text(self): + # type: () -> None """ Round-tripping L{URL.from_text} with C{str} results in an equivalent URL. @@ -252,6 +257,7 @@ def test_from_text(self): self.assertEqual(BASIC_URL, urlpath.to_text()) def test_roundtrip(self): + # type: () -> None """ L{URL.to_text} should invert L{URL.from_text}. """ @@ -260,6 +266,7 @@ def test_roundtrip(self): self.assertEqual(test, result) def test_roundtrip_double_iri(self): + # type: () -> None for test in ROUNDTRIP_TESTS: url = URL.from_text(test) iri = url.to_iri() @@ -272,6 +279,7 @@ def test_roundtrip_double_iri(self): return def test_equality(self): + # type: () -> None """ Two URLs decoded using L{URL.from_text} will be equal (C{==}) if they decoded same URL string, and unequal (C{!=}) if they decoded different @@ -286,6 +294,7 @@ def test_equality(self): ) def test_fragmentEquality(self): + # type: () -> None """ An URL created with the empty string for a fragment compares equal to an URL created with an unspecified fragment. @@ -295,6 +304,7 @@ def test_fragmentEquality(self): URL.from_text(u"http://localhost/")) def test_child(self): + # type: () -> None """ L{URL.child} appends a new path segment, but does not affect the query or fragment. @@ -314,6 +324,7 @@ def test_child(self): ) def test_multiChild(self): + # type: () -> None """ L{URL.child} receives multiple segments as C{*args} and appends each in turn. @@ -323,6 +334,7 @@ def test_multiChild(self): 'http://example.com/a/b/c/d/e') def test_childInitRoot(self): + # type: () -> None """ L{URL.child} of a L{URL} without a path produces a L{URL} with a single path segment. @@ -332,6 +344,7 @@ def test_childInitRoot(self): self.assertEqual("http://www.foo.com/c", childURL.to_text()) def test_emptyChild(self): + # type: () -> None """ L{URL.child} without any new segments returns the original L{URL}. """ @@ -339,6 +352,7 @@ def test_emptyChild(self): self.assertEqual(url.child(), url) def test_sibling(self): + # type: () -> None """ L{URL.sibling} of a L{URL} replaces the last path segment, but does not affect the query or fragment. @@ -357,6 +371,7 @@ def test_sibling(self): ) def test_click(self): + # type: () -> None """ L{URL.click} interprets the given string as a relative URI-reference and returns a new L{URL} interpreting C{self} as the base absolute URI. @@ -406,6 +421,7 @@ def test_click(self): self.assertEqual(u3.to_text(), 'http://localhost/foo/bar') def test_clickRFC3986(self): + # type: () -> None """ L{URL.click} should correctly resolve the examples in RFC 3986. """ @@ -414,6 +430,7 @@ def test_clickRFC3986(self): self.assertEqual(base.click(ref).to_text(), expected) def test_clickSchemeRelPath(self): + # type: () -> None """ L{URL.click} should not accept schemes with relative paths. """ @@ -422,6 +439,7 @@ def test_clickSchemeRelPath(self): self.assertRaises(NotImplementedError, base.click, 'http:h') def test_cloneUnchanged(self): + # type: () -> None """ Verify that L{URL.replace} doesn't change any of the arguments it is passed. @@ -437,6 +455,7 @@ def test_cloneUnchanged(self): self.assertEqual(urlpath.replace(), urlpath) def test_clickCollapse(self): + # type: () -> None """ L{URL.click} collapses C{.} and C{..} according to RFC 3986 section 5.2.4. @@ -472,6 +491,7 @@ def test_clickCollapse(self): ) def test_queryAdd(self): + # type: () -> None """ L{URL.add} adds query parameters. """ @@ -504,6 +524,7 @@ def test_queryAdd(self): .to_text()) def test_querySet(self): + # type: () -> None """ L{URL.set} replaces query parameters by name. """ @@ -524,6 +545,7 @@ def test_querySet(self): ) def test_queryRemove(self): + # type: () -> None """ L{URL.remove} removes instances of a query parameter. """ @@ -549,6 +571,7 @@ def test_queryRemove(self): ) def test_parseEqualSignInParamValue(self): + # type: () -> None """ Every C{=}-sign after the first in a query parameter is simply included in the value of the parameter. @@ -569,12 +592,14 @@ def test_parseEqualSignInParamValue(self): self.assertEqual(iri.to_uri().get('operator'), ['=']) def test_empty(self): + # type: () -> None """ An empty L{URL} should serialize as the empty string. """ self.assertEqual(URL().to_text(), '') def test_justQueryText(self): + # type: () -> None """ An L{URL} with query text should serialize as just query text. """ @@ -582,6 +607,7 @@ def test_justQueryText(self): self.assertEqual(u.to_text(), '?hello=world') def test_identicalEqual(self): + # type: () -> None """ L{URL} compares equal to itself. """ @@ -589,6 +615,7 @@ def test_identicalEqual(self): self.assertEqual(u, u) def test_similarEqual(self): + # type: () -> None """ URLs with equivalent components should compare equal. """ @@ -597,6 +624,7 @@ def test_similarEqual(self): self.assertEqual(u1, u2) def test_differentNotEqual(self): + # type: () -> None """ L{URL}s that refer to different resources are both unequal (C{!=}) and also not equal (not C{==}). @@ -607,6 +635,7 @@ def test_differentNotEqual(self): self.assertNotEqual(u1, u2) def test_otherTypesNotEqual(self): + # type: () -> None """ L{URL} is not equal (C{==}) to other types. """ @@ -617,6 +646,7 @@ def test_otherTypesNotEqual(self): self.assertNotEqual(u, object()) def test_identicalNotUnequal(self): + # type: () -> None """ Identical L{URL}s are not unequal (C{!=}) to each other. """ @@ -624,6 +654,7 @@ def test_identicalNotUnequal(self): self.assertFalse(u != u, "%r == itself" % u) def test_similarNotUnequal(self): + # type: () -> None """ Structurally similar L{URL}s are not unequal (C{!=}) to each other. """ @@ -632,6 +663,7 @@ def test_similarNotUnequal(self): self.assertFalse(u1 != u2, "%r == %r" % (u1, u2)) def test_differentUnequal(self): + # type: () -> None """ Structurally different L{URL}s are unequal (C{!=}) to each other. """ @@ -640,6 +672,7 @@ def test_differentUnequal(self): self.assertTrue(u1 != u2, "%r == %r" % (u1, u2)) def test_otherTypesUnequal(self): + # type: () -> None """ L{URL} is unequal (C{!=}) to other types. """ @@ -648,6 +681,7 @@ def test_otherTypesUnequal(self): self.assertTrue(u != object(), "URL must be differ from an object.") def test_asURI(self): + # type: () -> None """ L{URL.asURI} produces an URI which converts any URI unicode encoding into pure US-ASCII and returns a new L{URL}. @@ -669,6 +703,7 @@ def test_asURI(self): '%r != %r' % (actualURI, expectedURI)) def test_asIRI(self): + # type: () -> None """ L{URL.asIRI} decodes any percent-encoded text in the URI, making it more suitable for reading by humans, and returns a new L{URL}. @@ -689,6 +724,7 @@ def test_asIRI(self): '%r != %r' % (actualIRI, expectedIRI)) def test_badUTF8AsIRI(self): + # type: () -> None """ Bad UTF-8 in a path segment, query parameter, or fragment results in that portion of the URI remaining percent-encoded in the IRI. @@ -704,6 +740,7 @@ def test_badUTF8AsIRI(self): '%r != %r' % (actualIRI, expectedIRI)) def test_alreadyIRIAsIRI(self): + # type: () -> None """ A L{URL} composed of non-ASCII text will result in non-ASCII text. """ @@ -717,6 +754,7 @@ def test_alreadyIRIAsIRI(self): self.assertEqual(alsoIRI.to_text(), unicodey) def test_alreadyURIAsURI(self): + # type: () -> None """ A L{URL} composed of encoded text will remain encoded. """ @@ -726,6 +764,7 @@ def test_alreadyURIAsURI(self): self.assertEqual(actualURI, expectedURI) def test_userinfo(self): + # type: () -> None """ L{URL.from_text} will parse the C{userinfo} portion of the URI separately from the host and port. @@ -746,6 +785,7 @@ def test_userinfo(self): ) def test_portText(self): + # type: () -> None """ L{URL.from_text} parses custom port numbers as integers. """ @@ -754,6 +794,7 @@ def test_portText(self): self.assertEqual(portURL.to_text(), u"http://www.example.com:8080/") def test_mailto(self): + # type: () -> None """ Although L{URL} instances are mainly for dealing with HTTP, other schemes (such as C{mailto:}) should work as well. For example, @@ -764,6 +805,7 @@ def test_mailto(self): u"mailto:user@example.com") def test_queryIterable(self): + # type: () -> None """ When a L{URL} is created with a C{query} argument, the C{query} argument is converted into an N-tuple of 2-tuples, sensibly @@ -776,6 +818,7 @@ def test_queryIterable(self): self.assertEqual(url.query, expected) def test_pathIterable(self): + # type: () -> None """ When a L{URL} is created with a C{path} argument, the C{path} is converted into a tuple. @@ -784,6 +827,7 @@ def test_pathIterable(self): self.assertEqual(url.path, ('hello', 'world')) def test_invalidArguments(self): + # type: () -> None """ Passing an argument of the wrong type to any of the constructor arguments of L{URL} will raise a descriptive L{TypeError}. @@ -860,6 +904,7 @@ def check(param, expectation=defaultExpectation): assertRaised(raised, defaultExpectation, "relative URL") def test_technicallyTextIsIterableBut(self): + # type: () -> None """ Technically, L{str} (or L{unicode}, as appropriate) is iterable, but C{URL(path="foo")} resulting in C{URL.from_text("f/o/o")} is never what @@ -873,6 +918,7 @@ def test_technicallyTextIsIterableBut(self): ) def test_netloc(self): + # type: () -> None url = URL(scheme='https') self.assertEqual(url.uses_netloc, True) @@ -892,6 +938,7 @@ def test_netloc(self): self.assertEqual(url.uses_netloc, False) def test_ipv6_with_port(self): + # type: () -> None t = 'https://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:80/' url = URL.from_text(t) assert url.host == '2001:0db8:85a3:0000:0000:8a2e:0370:7334' @@ -899,6 +946,7 @@ def test_ipv6_with_port(self): assert SCHEME_PORT_MAP[url.scheme] != url.port def test_basic(self): + # type: () -> None text = 'https://user:pass@example.com/path/to/here?k=v#nice' url = URL.from_text(text) assert url.scheme == 'https' @@ -922,12 +970,15 @@ def test_basic(self): assert url.path == ('path', 'to', 'here') def test_invalid_url(self): + # type: () -> None self.assertRaises(URLParseError, URL.from_text, '#\n\n') def test_invalid_authority_url(self): + # type: () -> None self.assertRaises(URLParseError, URL.from_text, 'http://abc:\n\n/#') def test_invalid_ipv6(self): + # type: () -> None invalid_ipv6_ips = ['2001::0234:C1ab::A0:aabc:003F', '2001::1::3F', ':', @@ -939,6 +990,7 @@ def test_invalid_ipv6(self): self.assertRaises(URLParseError, URL.from_text, url_text) def test_invalid_port(self): + # type: () -> None self.assertRaises( URLParseError, URL.from_text, 'ftp://portmouth:smash' ) @@ -946,6 +998,7 @@ def test_invalid_port(self): 'http://reader.googlewebsite.com:neverforget') def test_idna(self): + # type: () -> None u1 = URL.from_text('http://bücher.ch') self.assertEqual(u1.host, 'bücher.ch') self.assertEqual(u1.to_text(), 'http://bücher.ch') @@ -957,6 +1010,8 @@ def test_idna(self): self.assertEqual(u2.to_iri().to_text(), u'https://bücher.ch') def test_netloc_slashes(self): + # type: () -> None + # basic sanity checks url = URL.from_text('mailto:mahmoud@hatnote.com') self.assertEqual(url.scheme, 'mailto') @@ -1010,6 +1065,7 @@ def test_netloc_slashes(self): return def test_wrong_constructor(self): + # type: () -> None with self.assertRaises(ValueError): # whole URL not allowed URL(BASIC_URL) @@ -1018,6 +1074,7 @@ def test_wrong_constructor(self): URL('HTTP_____more_like_imHoTTeP') def test_encoded_userinfo(self): + # type: () -> None url = URL.from_text('http://user:pass@example.com') assert url.userinfo == 'user:pass' url = url.replace(userinfo='us%20her:pass') @@ -1036,6 +1093,7 @@ def test_encoded_userinfo(self): ) def test_hash(self): + # type: () -> None url_map = {} url1 = URL.from_text('http://blog.hatnote.com/ask?utm_source=geocity') assert hash(url1) == hash(url1) # sanity @@ -1053,6 +1111,7 @@ def test_hash(self): assert hash(URL()) == hash(URL()) # slightly more sanity def test_dir(self): + # type: () -> None url = URL() res = dir(url) @@ -1064,12 +1123,15 @@ def test_dir(self): assert 'asIRI' not in res def test_twisted_compat(self): + # type: () -> None url = URL.fromText(u'http://example.com/a%20té%C3%A9st') assert url.asText() == 'http://example.com/a%20té%C3%A9st' assert url.asURI().asText() == 'http://example.com/a%20t%C3%A9%C3%A9st' # TODO: assert url.asIRI().asText() == u'http://example.com/a%20téést' def test_set_ordering(self): + # type: () -> None + # TODO url = URL.from_text('http://example.com/?a=b&c') url = url.set(u'x', u'x') @@ -1079,6 +1141,7 @@ def test_set_ordering(self): # assert url.to_text() == u'http://example.com/?a=b&c&x=x&x=y' def test_schemeless_path(self): + # type: () -> None "See issue #4" u1 = URL.from_text("urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob") u2 = URL.from_text(u1.to_text()) @@ -1097,6 +1160,7 @@ def test_schemeless_path(self): assert u5 == u6 # colons stay decoded bc they're not in the first seg def test_emoji_domain(self): + # type: () -> None "See issue #7, affecting only narrow builds (2.6-3.3)" url = URL.from_text('https://xn--vi8hiv.ws') iri = url.to_iri() @@ -1104,6 +1168,7 @@ def test_emoji_domain(self): # as long as we don't get ValueErrors, we're good def test_delim_in_param(self): + # type: () -> None "Per issue #6 and #8" self.assertRaises(ValueError, URL, scheme=u'http', host=u'a/c') self.assertRaises(ValueError, URL, path=(u"?",)) @@ -1111,6 +1176,7 @@ def test_delim_in_param(self): self.assertRaises(ValueError, URL, query=((u"&", "test"))) def test_empty_paths_eq(self): + # type: () -> None u1 = URL.from_text('http://example.com/') u2 = URL.from_text('http://example.com') @@ -1132,11 +1198,14 @@ def test_empty_paths_eq(self): assert u1 == u2 def test_from_text_type(self): + # type: () -> None assert URL.from_text(u'#ok').fragment == u'ok' # sanity self.assertRaises(TypeError, URL.from_text, b'bytes://x.y.z') self.assertRaises(TypeError, URL.from_text, object()) def test_from_text_bad_authority(self): + # type: () -> None + # bad ipv6 brackets self.assertRaises(URLParseError, URL.from_text, 'http://[::1/') self.assertRaises(URLParseError, URL.from_text, 'http://::1]/') @@ -1151,6 +1220,7 @@ def test_from_text_bad_authority(self): self.assertRaises(URLParseError, URL.from_text, 'http://127.0.0.1::80') def test_normalize(self): + # type: () -> None url = URL.from_text('HTTP://Example.com/A%61/./../A%61?B%62=C%63#D%64') assert url.get('Bb') == [] assert url.get('B%62') == ['C%63'] @@ -1205,6 +1275,8 @@ def test_normalize(self): ) def test_str(self): + # type: () -> None + # see also issue #49 text = u'http://example.com/á/y%20a%20y/?b=%25' url = URL.from_text(text) @@ -1219,6 +1291,7 @@ def test_str(self): assert isinstance(bytes(url), bytes) def test_idna_corners(self): + # type: () -> None url = URL.from_text(u'http://abé.com/') assert url.to_iri().host == u'abé.com' assert url.to_uri().host == u'xn--ab-cja.com' diff --git a/tox.ini b/tox.ini index f70d340b..2c822563 100644 --- a/tox.ini +++ b/tox.ini @@ -155,6 +155,7 @@ commands = # Global settings +disallow_untyped_defs = True no_implicit_optional = True show_column_numbers = True show_error_codes = True @@ -162,34 +163,18 @@ strict_optional = True warn_no_return = True warn_redundant_casts = True warn_return_any = True -warn_unused_ignores = True warn_unreachable = True +warn_unused_ignores = True # Enable these over time check_untyped_defs = False disallow_incomplete_defs = False -disallow_untyped_defs = False -# Disable checks until effected files fully adopt mypy +# Disable some checks until effected files fully adopt mypy [mypy-hyperlink._url] allow_untyped_defs = True -[mypy-hyperlink.test.common] -allow_untyped_defs = True - -[mypy-hyperlink.test.test_common] -allow_untyped_defs = True - -[mypy-hyperlink.test.test_decoded_url] -allow_untyped_defs = True - -[mypy-hyperlink.test.test_scheme_registration] -allow_untyped_defs = True - -[mypy-hyperlink.test.test_url] -allow_untyped_defs = True - [mypy-idna] ignore_missing_imports = True From 6075f0560f720ef979cc941bcad258c692424c4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sat, 26 Oct 2019 22:13:29 -0700 Subject: [PATCH 037/419] More hints --- src/hyperlink/test/common.py | 13 ++++++++++--- src/hyperlink/test/test_url.py | 21 +++++++++++++++++++-- tox.ini | 2 +- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/hyperlink/test/common.py b/src/hyperlink/test/common.py index c2674d8f..c9696fcf 100644 --- a/src/hyperlink/test/common.py +++ b/src/hyperlink/test/common.py @@ -1,3 +1,4 @@ +from typing import Any, Callable, Optional, Type from unittest import TestCase @@ -5,8 +6,11 @@ class HyperlinkTestCase(TestCase): """This type mostly exists to provide a backwards-compatible assertRaises method for Python 2.6 testing. """ - def assertRaises(self, excClass, callableObj=None, *args, **kwargs): - """Fail unless an exception of class excClass is raised + def assertRaises( # type: ignore[override] Doesn't match superclass, meh + self, expected_exception, callableObj=None, *args, **kwargs + ): + # type: (Type[BaseException], Optional[Callable], Any, Any) -> Any + """Fail unless an exception of class expected_exception is raised by callableObj when invoked with arguments args and keyword arguments kwargs. If a different type of exception is raised, it will not be caught, and the test case will be @@ -28,7 +32,7 @@ def assertRaises(self, excClass, callableObj=None, *args, **kwargs): the_exception = cm.exception self.assertEqual(the_exception.error_code, 3) """ - context = _AssertRaisesContext(excClass, self) + context = _AssertRaisesContext(expected_exception, self) if callableObj is None: return context with context: @@ -39,13 +43,16 @@ class _AssertRaisesContext(object): "A context manager used to implement HyperlinkTestCase.assertRaises." def __init__(self, expected, test_case): + # type: (Type[BaseException], TestCase) -> None self.expected = expected self.failureException = test_case.failureException def __enter__(self): + # type: () -> "_AssertRaisesContext" return self def __exit__(self, exc_type, exc_value, tb): + # type: (Optional[Type[BaseException]], Any, Any) -> bool if exc_type is None: exc_name = self.expected.__name__ raise self.failureException("%s not raised" % (exc_name,)) diff --git a/src/hyperlink/test/test_url.py b/src/hyperlink/test/test_url.py index 2e393eed..511515c8 100644 --- a/src/hyperlink/test/test_url.py +++ b/src/hyperlink/test/test_url.py @@ -7,6 +7,7 @@ import sys import socket +from typing import Any, Iterable, Optional, Text, Tuple from .common import HyperlinkTestCase from .. import URL, URLParseError @@ -149,6 +150,7 @@ class TestURL(HyperlinkTestCase): """ def assertUnicoded(self, u): + # type: (URL) -> None """ The given L{URL}'s components should be L{unicode}. @@ -165,8 +167,18 @@ def assertUnicoded(self, u): self.assertTrue(v is None or isinstance(v, unicode), repr(u)) self.assertEqual(type(u.fragment), unicode, repr(u)) - def assertURL(self, u, scheme, host, path, query, - fragment, port, userinfo=''): + def assertURL( + self, + u, # type: URL + scheme, # type: Text + host, # type: Text + path, # type: Iterable[Text] + query, # type: Iterable[Tuple[Text, Optional[Text]]] + fragment, # type: Text + port, # type: Optional[int] + userinfo='', # type: Text + ): + # type: (...) -> None """ The given L{URL} should have the given components. @@ -198,6 +210,7 @@ def test_initDefaults(self): L{URL} should have appropriate default values. """ def check(u): + # type: (URL) -> None self.assertUnicoded(u) self.assertURL(u, 'http', '', [], [], '', 80, '') @@ -839,20 +852,24 @@ def test_invalidArguments(self): """ class Unexpected(object): def __str__(self): + # type: () -> str return "wrong" def __repr__(self): + # type: () -> str return "" defaultExpectation = "unicode" if bytes is str else "str" def assertRaised(raised, expectation, name): + # type: (Any, Text, Text) -> None self.assertEqual(str(raised.exception), "expected {0} for {1}, got {2}".format( expectation, name, "")) def check(param, expectation=defaultExpectation): + # type: (Any, str) -> None with self.assertRaises(TypeError) as raised: URL(**{param: Unexpected()}) diff --git a/tox.ini b/tox.ini index 2c822563..52c6e393 100644 --- a/tox.ini +++ b/tox.ini @@ -155,6 +155,7 @@ commands = # Global settings +disallow_incomplete_defs = True disallow_untyped_defs = True no_implicit_optional = True show_column_numbers = True @@ -168,7 +169,6 @@ warn_unused_ignores = True # Enable these over time check_untyped_defs = False -disallow_incomplete_defs = False # Disable some checks until effected files fully adopt mypy From 461962c7a0715c4cbcbcb0ccea915513e2e5fc1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sat, 26 Oct 2019 23:09:18 -0700 Subject: [PATCH 038/419] cast bad values to expected types to appease mypy --- src/hyperlink/test/test_scheme_registration.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/hyperlink/test/test_scheme_registration.py b/src/hyperlink/test/test_scheme_registration.py index 68eb1695..491bccaa 100644 --- a/src/hyperlink/test/test_scheme_registration.py +++ b/src/hyperlink/test/test_scheme_registration.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals +from typing import cast from .. import _url @@ -57,16 +58,16 @@ def test_register_no_netloc_with_port(self): def test_invalid_uses_netloc(self): # type: () -> None with self.assertRaises(ValueError): - register_scheme('badnetloc', uses_netloc=None) + register_scheme('badnetloc', uses_netloc=cast(bool, None)) with self.assertRaises(ValueError): - register_scheme('badnetloc', uses_netloc=object()) + register_scheme('badnetloc', uses_netloc=cast(bool, object())) def test_register_invalid_uses_netloc(self): # type: () -> None with self.assertRaises(ValueError): - register_scheme('lol', uses_netloc=lambda: 'nope') + register_scheme('lol', uses_netloc=cast(bool, lambda: 'nope')) def test_register_invalid_port(self): # type: () -> None with self.assertRaises(ValueError): - register_scheme('nope', default_port=lambda: 'lol') + register_scheme('nope', default_port=cast(int, lambda: 'lol')) From 3567582ab13ebc274cccc2ec9a089aa170ab945d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sun, 27 Oct 2019 11:20:14 -0700 Subject: [PATCH 039/419] Don't try to get coverage on type check section --- src/hyperlink/_socket.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/hyperlink/_socket.py b/src/hyperlink/_socket.py index f4b94daa..8534f816 100644 --- a/src/hyperlink/_socket.py +++ b/src/hyperlink/_socket.py @@ -2,7 +2,9 @@ from socket import inet_pton except ImportError: from typing import TYPE_CHECKING - if not TYPE_CHECKING: + if TYPE_CHECKING: # pragma: no cover + pass + else: # based on https://gist.github.com/nnemkin/4966028 # this code only applies on Windows Python 2.7 import ctypes From 9c35a804eea20c733f8d72d26b3188858da0bd9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Mon, 28 Oct 2019 11:51:50 -0700 Subject: [PATCH 040/419] Don't use lambda functions, which create a coverage problem. --- src/hyperlink/test/test_scheme_registration.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hyperlink/test/test_scheme_registration.py b/src/hyperlink/test/test_scheme_registration.py index 491bccaa..a8bbbef5 100644 --- a/src/hyperlink/test/test_scheme_registration.py +++ b/src/hyperlink/test/test_scheme_registration.py @@ -65,9 +65,9 @@ def test_invalid_uses_netloc(self): def test_register_invalid_uses_netloc(self): # type: () -> None with self.assertRaises(ValueError): - register_scheme('lol', uses_netloc=cast(bool, lambda: 'nope')) + register_scheme('lol', uses_netloc=cast(bool, object())) def test_register_invalid_port(self): # type: () -> None with self.assertRaises(ValueError): - register_scheme('nope', default_port=cast(int, lambda: 'lol')) + register_scheme('nope', default_port=cast(bool, object())) From fad88c786d2ac72edf91c89f6b56c9789b27f538 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Mon, 28 Oct 2019 20:47:55 -0700 Subject: [PATCH 041/419] Add test for inet_pton --- src/hyperlink/test/test_socket.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/hyperlink/test/test_socket.py diff --git a/src/hyperlink/test/test_socket.py b/src/hyperlink/test/test_socket.py new file mode 100644 index 00000000..166f6c4e --- /dev/null +++ b/src/hyperlink/test/test_socket.py @@ -0,0 +1,29 @@ +try: + from socket import inet_pton +except ImportError: + import socket + + from .common import HyperlinkTestCase + from .._socket import inet_pton + + + class TestSocket(HyperlinkTestCase): + def test_inet_pton_ipv4_valid(self): + data = inet_pton(socket.AF_INET, "127.0.0.1") + assert isinstance(data, bytes) + + def test_inet_pton_ipv4_bogus(self): + with self.assertRaises(socket.error): + inet_pton(socket.AF_INET, "blah") + + def test_inet_pton_ipv6_valid(self): + data = inet_pton(socket.AF_INET6, "::1") + assert isinstance(data, bytes) + + def test_inet_pton_ipv6_bogus(self): + with self.assertRaises(socket.error): + inet_pton(socket.AF_INET6, "blah") + + def test_inet_pton_bogus_family(self): + with self.assertRaises(socket.error): + inet_pton("blah", "blah") From f1d1ffa7edd5e74daa5c41d573eac3362b7c951e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Mon, 28 Oct 2019 21:10:56 -0700 Subject: [PATCH 042/419] Has to be an int --- src/hyperlink/test/test_socket.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hyperlink/test/test_socket.py b/src/hyperlink/test/test_socket.py index 166f6c4e..68f08629 100644 --- a/src/hyperlink/test/test_socket.py +++ b/src/hyperlink/test/test_socket.py @@ -26,4 +26,4 @@ def test_inet_pton_ipv6_bogus(self): def test_inet_pton_bogus_family(self): with self.assertRaises(socket.error): - inet_pton("blah", "blah") + inet_pton(-1, "blah") From 65c7306b263bf8d09342660bbff33af3aa63bd9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Mon, 28 Oct 2019 21:23:18 -0700 Subject: [PATCH 043/419] Try harder to find a bogus family --- src/hyperlink/test/test_socket.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/hyperlink/test/test_socket.py b/src/hyperlink/test/test_socket.py index 68f08629..79e127e1 100644 --- a/src/hyperlink/test/test_socket.py +++ b/src/hyperlink/test/test_socket.py @@ -25,5 +25,12 @@ def test_inet_pton_ipv6_bogus(self): inet_pton(socket.AF_INET6, "blah") def test_inet_pton_bogus_family(self): + # Find an integer not associated with a known address family + i = int(socket.AF_INET6) + 100 + while True: + if i != socket.AF_INET and i != socket.AF_INET6: + break + i += 100 + with self.assertRaises(socket.error): - inet_pton(-1, "blah") + inet_pton(i, "blah") From c1f773939a0dca2d79e777b310b19e0c558676e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Mon, 28 Oct 2019 21:25:03 -0700 Subject: [PATCH 044/419] Add type hints --- src/hyperlink/test/test_socket.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/hyperlink/test/test_socket.py b/src/hyperlink/test/test_socket.py index 79e127e1..a79e211b 100644 --- a/src/hyperlink/test/test_socket.py +++ b/src/hyperlink/test/test_socket.py @@ -9,22 +9,27 @@ class TestSocket(HyperlinkTestCase): def test_inet_pton_ipv4_valid(self): + # type: () -> None data = inet_pton(socket.AF_INET, "127.0.0.1") assert isinstance(data, bytes) def test_inet_pton_ipv4_bogus(self): + # type: () -> None with self.assertRaises(socket.error): inet_pton(socket.AF_INET, "blah") def test_inet_pton_ipv6_valid(self): + # type: () -> None data = inet_pton(socket.AF_INET6, "::1") assert isinstance(data, bytes) def test_inet_pton_ipv6_bogus(self): + # type: () -> None with self.assertRaises(socket.error): inet_pton(socket.AF_INET6, "blah") def test_inet_pton_bogus_family(self): + # type: () -> None # Find an integer not associated with a known address family i = int(socket.AF_INET6) + 100 while True: From e4c0014c59a0ac717b4bdd3a91d678b056fa58f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Mon, 28 Oct 2019 21:33:06 -0700 Subject: [PATCH 045/419] Cause mypy to check the Windows code on non-Windows systems. --- src/hyperlink/test/test_socket.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/hyperlink/test/test_socket.py b/src/hyperlink/test/test_socket.py index a79e211b..35dc29d1 100644 --- a/src/hyperlink/test/test_socket.py +++ b/src/hyperlink/test/test_socket.py @@ -1,6 +1,11 @@ +# mypy: always-true=inet_pton + try: from socket import inet_pton except ImportError: + inet_pton = None # type: ignore[assignment] + +if inet_pton: import socket from .common import HyperlinkTestCase From ddd6c7f969323ad59d8d7807aa688b91809743f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Mon, 28 Oct 2019 21:53:21 -0700 Subject: [PATCH 046/419] Start with a valid number so the incrementing code below gets use because coverage. --- src/hyperlink/test/test_socket.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hyperlink/test/test_socket.py b/src/hyperlink/test/test_socket.py index 35dc29d1..55a2472e 100644 --- a/src/hyperlink/test/test_socket.py +++ b/src/hyperlink/test/test_socket.py @@ -36,7 +36,7 @@ def test_inet_pton_ipv6_bogus(self): def test_inet_pton_bogus_family(self): # type: () -> None # Find an integer not associated with a known address family - i = int(socket.AF_INET6) + 100 + i = int(socket.AF_INET6) while True: if i != socket.AF_INET and i != socket.AF_INET6: break From f296f0f62fc3f3a993898f433fe78d70409aa94c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20S=C3=A1nchez=20Vega?= Date: Tue, 29 Oct 2019 17:10:45 -0700 Subject: [PATCH 047/419] not, yo --- src/hyperlink/test/test_socket.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hyperlink/test/test_socket.py b/src/hyperlink/test/test_socket.py index 55a2472e..d61dda2c 100644 --- a/src/hyperlink/test/test_socket.py +++ b/src/hyperlink/test/test_socket.py @@ -5,7 +5,7 @@ except ImportError: inet_pton = None # type: ignore[assignment] -if inet_pton: +if not inet_pton: import socket from .common import HyperlinkTestCase From deeca8c3ec6b3089a3df6549a06921c3b1324c43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Wed, 30 Oct 2019 08:29:38 -0700 Subject: [PATCH 048/419] Don't send an invalid address in addition to an invalid family. --- src/hyperlink/test/test_socket.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hyperlink/test/test_socket.py b/src/hyperlink/test/test_socket.py index d61dda2c..a66e1b05 100644 --- a/src/hyperlink/test/test_socket.py +++ b/src/hyperlink/test/test_socket.py @@ -43,4 +43,4 @@ def test_inet_pton_bogus_family(self): i += 100 with self.assertRaises(socket.error): - inet_pton(i, "blah") + inet_pton(i, "127.0.0.1") From a6e027e1b1b87411f0a09a0efc86eb3e63a44dd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Wed, 30 Oct 2019 09:48:26 -0700 Subject: [PATCH 049/419] Do our handling of address_family before WSAStringToAddressA gets a crack at it. --- src/hyperlink/_socket.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/hyperlink/_socket.py b/src/hyperlink/_socket.py index 8534f816..769b9d54 100644 --- a/src/hyperlink/_socket.py +++ b/src/hyperlink/_socket.py @@ -29,14 +29,18 @@ def inet_pton(address_family, ip_string): addr.sa_family = address_family addr_size = ctypes.c_int(ctypes.sizeof(addr)) + try: + attribute, size = { + socket.AF_INET: ("ipv4_addr", 4), + socket.AF_INET6: ("ipv6_addr", 16), + }[address_family] + except KeyError: + raise socket.error("unknown address family") + if WSAStringToAddressA( ip_string_bytes, address_family, None, ctypes.byref(addr), ctypes.byref(addr_size) ) != 0: raise socket.error(ctypes.FormatError()) - if address_family == socket.AF_INET: - return ctypes.string_at(addr.ipv4_addr, 4) - if address_family == socket.AF_INET6: - return ctypes.string_at(addr.ipv6_addr, 16) - raise socket.error('unknown address family') + return ctypes.string_at(getattr(addr, attribute), size) From 873780bf05f3f76ffea61acbf8a16590aa88bf79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Fri, 1 Nov 2019 12:27:17 -0700 Subject: [PATCH 050/419] Add more type hints --- src/hyperlink/_url.py | 117 +++++++++++++++++++----------- src/hyperlink/test/test_socket.py | 3 +- src/hyperlink/test/test_url.py | 24 +++--- tox.ini | 3 + 4 files changed, 92 insertions(+), 55 deletions(-) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index 2d4e712a..341175e3 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -18,14 +18,21 @@ import re import sys import string -import socket -from typing import Callable, Text, Type +from socket import AF_INET, AF_INET6, error as SocketError +try: + from socket import AddressFamily +except ImportError: + AddressFamily = int # type: ignore[assignment,misc] Python 2 +from typing import ( + Callable, Iterable, Iterator, List, Mapping, Optional, + Sequence, Text, Tuple, Type, Union, +) from unicodedata import normalize from ._socket import inet_pton try: - from collections.abc import Mapping + from collections.abc import Mapping as MappingABC except ImportError: # Python 2 - from collections import Mapping + from collections import Mapping as MappingABC from idna import encode as idna_encode, decode as idna_decode @@ -37,6 +44,10 @@ except NameError: # Py3 unichr = chr # type: Callable[[int], Text] NoneType = type(None) # type: Type[None] +QueryParameter = Union[ + Mapping[Text, Optional[Text]], + Iterable[Tuple[Text, Optional[Text]]], +] # from boltons.typeutils @@ -187,6 +198,7 @@ def _make_quote_map(safe_chars): def _encode_reserved(text, maximal=True): + # type: (Text, bool) -> Text """A very comprehensive percent encoding for encoding all delimiters. Used for arguments to DecodedURL, where a % means a percent sign, and not the character used by URLs for escaping @@ -200,6 +212,7 @@ def _encode_reserved(text, maximal=True): def _encode_path_part(text, maximal=True): + # type: (Text, bool) -> Text "Percent-encode a single segment of a URL path." if maximal: bytestr = normalize('NFC', text).encode('utf8') @@ -209,6 +222,7 @@ def _encode_path_part(text, maximal=True): def _encode_schemeless_path_part(text, maximal=True): + # type: (Text, bool) -> Text """Percent-encode the first segment of a URL path for a URL without a scheme specified. """ @@ -261,6 +275,7 @@ def _encode_path_parts(text_parts, rooted=False, has_scheme=True, def _encode_query_key(text, maximal=True): + # type: (Text, bool) -> Text """ Percent-encode a single query string key or value. """ @@ -272,6 +287,7 @@ def _encode_query_key(text, maximal=True): def _encode_query_value(text, maximal=True): + # type: (Text, bool) -> Text """ Percent-encode a single query string key or value. """ @@ -283,6 +299,7 @@ def _encode_query_value(text, maximal=True): def _encode_fragment_part(text, maximal=True): + # type: (Text, bool) -> Text """Quote the fragment part of the URL. Fragments don't have subdelimiters, so the whole URL fragment can be passed. """ @@ -294,6 +311,7 @@ def _encode_fragment_part(text, maximal=True): def _encode_userinfo_part(text, maximal=True): + # type: (Text, bool) -> Text """Quote special characters in either the username or password section of the URL. """ @@ -369,6 +387,7 @@ def register_scheme(text, uses_netloc=True, default_port=None): def scheme_uses_netloc(scheme, default=None): + # type: (Text, Optional[bool]) -> Optional[bool] """Whether or not a URL uses :code:`:` or :code:`://` to separate the scheme from the rest of the URL depends on the scheme's own standard definition. There is no way to infer this behavior @@ -442,6 +461,7 @@ def _textcheck(name, value, delims=frozenset(), nullable=False): def iter_pairs(iterable): + # type: (Iterable) -> Iterator """ Iterate over the (key, value) pairs in ``iterable``. @@ -449,7 +469,7 @@ def iter_pairs(iterable): iterable yields (key, value) pairs. This behaviour is similar to what Python's ``dict()`` constructor does. """ - if isinstance(iterable, Mapping): + if isinstance(iterable, MappingABC): iterable = iterable.items() return iter(iterable) @@ -590,6 +610,7 @@ def _percent_decode(text, normalize_case=False, subencoding='utf-8', def _decode_host(host): + # type: (Text) -> Text """Decode a host from ASCII-encodable text to IDNA-decoded text. If the host text is not ASCII, it is returned unchanged, as it is presumed that it is already IDNA-decoded. @@ -653,19 +674,20 @@ def _decode_host(host): def _resolve_dot_segments(path): + # type: (Sequence[Text]) -> Sequence[Text] """Normalize the URL path by resolving segments of '.' and '..'. For more details, see `RFC 3986 section 5.2.4, Remove Dot Segments`_. Args: - path (list): path segments in string form + path: sequence of path segments in text form Returns: - list: a new list of path segments with the '.' and '..' elements + list: a new sequence of path segments with the '.' and '..' elements removed and resolved. .. _RFC 3986 section 5.2.4, Remove Dot Segments: https://tools.ietf.org/html/rfc3986#section-5.2.4 """ # noqa: E501 - segs = [] + segs = [] # type: List[Text] for seg in path: if seg == u'.': @@ -683,6 +705,7 @@ def _resolve_dot_segments(path): def parse_host(host): + # type: (Text) -> Tuple[Optional[AddressFamily], Text] """Parse the host into a tuple of ``(family, host)``, where family is the appropriate :mod:`socket` module constant when the host is an IP address. Family is ``None`` when the host is not an IP. @@ -692,6 +715,7 @@ def parse_host(host): Returns: tuple: family (socket constant or None), host (string) + >>> import socket >>> parse_host('googlewebsite.com') == (None, 'googlewebsite.com') True >>> parse_host('::1') == (socket.AF_INET6, '::1') @@ -701,22 +725,24 @@ def parse_host(host): """ if not host: return None, u'' + if u':' in host: try: - inet_pton(socket.AF_INET6, host) - except socket.error as se: + inet_pton(AF_INET6, host) + except SocketError as se: raise URLParseError('invalid IPv6 host: %r (%r)' % (host, se)) except UnicodeEncodeError: pass # TODO: this can't be a real host right? else: - family = socket.AF_INET6 - return family, host - try: - inet_pton(socket.AF_INET, host) - except (socket.error, UnicodeEncodeError): - family = None # not an IP + family = AF_INET6 # type: Optional[AddressFamily] else: - family = socket.AF_INET + try: + inet_pton(AF_INET, host) + except (SocketError, UnicodeEncodeError): + family = None # not an IP + else: + family = AF_INET + return family, host @@ -749,37 +775,46 @@ class URL(object): constructor arguments is below. Args: - scheme (unicode): The text name of the scheme. - host (unicode): The host portion of the network location - port (int): The port part of the network location. If - ``None`` or no port is passed, the port will default to - the default port of the scheme, if it is known. See the - ``SCHEME_PORT_MAP`` and :func:`register_default_port` - for more info. - path (tuple): A tuple of strings representing the - slash-separated parts of the path. - query (tuple): The query parameters, as a dictionary or - as an iterable of key-value pairs. - fragment (unicode): The fragment part of the URL. - rooted (bool): Whether or not the path begins with a slash. - userinfo (unicode): The username or colon-separated - username:password pair. - uses_netloc (bool): Indicates whether two slashes appear - between the scheme and the host (``http://eg.com`` vs - ``mailto:e@g.com``). Set automatically based on scheme. - - All of these parts are also exposed as read-only attributes of - URL instances, along with several useful methods. + scheme: The text name of the scheme. + host: The host portion of the network location + port: The port part of the network location. If ``None`` or no port is + passed, the port will default to the default port of the scheme, if + it is known. See the ``SCHEME_PORT_MAP`` and + :func:`register_default_port` for more info. + path: A tuple of strings representing the slash-separated parts of the + path. + query: The query parameters, as a dictionary or as an iterable of + key-value pairs. + fragment: The fragment part of the URL. + rooted: Whether or not the path begins with a slash. + userinfo: The username or colon-separated username:password pair. + uses_netloc: Indicates whether two slashes appear between the scheme + and the host (``http://eg.com`` vs. ``mailto:e@g.com``). + Set automatically based on scheme. + + All of these parts are also exposed as read-only attributes of URL + instances, along with several useful methods. .. _RFC 3986: https://tools.ietf.org/html/rfc3986 .. _RFC 3987: https://tools.ietf.org/html/rfc3987 """ # noqa: E501 - def __init__(self, scheme=None, host=None, path=(), query=(), fragment=u'', - port=None, rooted=None, userinfo=u'', uses_netloc=None): + def __init__( + self, + scheme=None, # type: Optional[Text] + host=None, # type: Optional[Text] + path=(), # type: Iterable[Text] + query=(), # type: QueryParameter + fragment=u"", # type: Text + port=None, # type: Optional[int] + rooted=None, # type: Optional[bool] + userinfo=u"", # type: Text + uses_netloc=None, # type: Optional[bool] + ): + # type: (...) -> None if host is not None and scheme is None: scheme = u'http' # TODO: why - if port is None: + if port is None and scheme is not None: port = SCHEME_PORT_MAP.get(scheme) if host and query and not path: # per RFC 3986 6.2.3, "a URI that uses the generic syntax diff --git a/src/hyperlink/test/test_socket.py b/src/hyperlink/test/test_socket.py index a66e1b05..8ec9a829 100644 --- a/src/hyperlink/test/test_socket.py +++ b/src/hyperlink/test/test_socket.py @@ -3,7 +3,7 @@ try: from socket import inet_pton except ImportError: - inet_pton = None # type: ignore[assignment] + inet_pton = None # type: ignore[assignment] optional if not inet_pton: import socket @@ -11,7 +11,6 @@ from .common import HyperlinkTestCase from .._socket import inet_pton - class TestSocket(HyperlinkTestCase): def test_inet_pton_ipv4_valid(self): # type: () -> None diff --git a/src/hyperlink/test/test_url.py b/src/hyperlink/test/test_url.py index 511515c8..6ee4d172 100644 --- a/src/hyperlink/test/test_url.py +++ b/src/hyperlink/test/test_url.py @@ -7,7 +7,7 @@ import sys import socket -from typing import Any, Iterable, Optional, Text, Tuple +from typing import Any, Iterable, Optional, Text, Tuple, cast from .common import HyperlinkTestCase from .. import URL, URLParseError @@ -825,7 +825,7 @@ def test_queryIterable(self): handling dictionaries. """ expected = (('alpha', 'beta'),) - url = URL(query=[['alpha', 'beta']]) + url = URL(query=[('alpha', 'beta')]) self.assertEqual(url.query, expected) url = URL(query={'alpha': 'beta'}) self.assertEqual(url.query, expected) @@ -871,7 +871,7 @@ def assertRaised(raised, expectation, name): def check(param, expectation=defaultExpectation): # type: (Any, str) -> None with self.assertRaises(TypeError) as raised: - URL(**{param: Unexpected()}) + URL(**{param: Unexpected()}) # type: ignore[arg-type] ok assertRaised(raised, expectation, param) @@ -883,41 +883,41 @@ def check(param, expectation=defaultExpectation): check("port", "int or NoneType") with self.assertRaises(TypeError) as raised: - URL(path=[Unexpected()]) + URL(path=[cast(Text, Unexpected())]) assertRaised(raised, defaultExpectation, "path segment") with self.assertRaises(TypeError) as raised: - URL(query=[(u"name", Unexpected())]) + URL(query=[(u"name", cast(Text, Unexpected()))]) assertRaised(raised, defaultExpectation + " or NoneType", "query parameter value") with self.assertRaises(TypeError) as raised: - URL(query=[(Unexpected(), u"value")]) + URL(query=[(cast(Text, Unexpected()), u"value")]) assertRaised(raised, defaultExpectation, "query parameter name") # No custom error message for this one, just want to make sure # non-2-tuples don't get through. with self.assertRaises(TypeError): - URL(query=[Unexpected()]) + URL(query=[cast(Tuple[Text, Text], Unexpected())]) with self.assertRaises(ValueError): - URL(query=[('k', 'v', 'vv')]) + URL(query=[cast(Tuple[Text, Text], ('k', 'v', 'vv'))]) with self.assertRaises(ValueError): - URL(query=[('k',)]) + URL(query=[cast(Tuple[Text, Text], ('k',))]) url = URL.from_text("https://valid.example.com/") with self.assertRaises(TypeError) as raised: - url.child(Unexpected()) + url.child(cast(Text, Unexpected())) assertRaised(raised, defaultExpectation, "path segment") with self.assertRaises(TypeError) as raised: - url.sibling(Unexpected()) + url.sibling(cast(Text, Unexpected())) assertRaised(raised, defaultExpectation, "path segment") with self.assertRaises(TypeError) as raised: - url.click(Unexpected()) + url.click(cast(Text, Unexpected())) assertRaised(raised, defaultExpectation, "relative URL") def test_technicallyTextIsIterableBut(self): diff --git a/tox.ini b/tox.ini index 52c6e393..07b9fd50 100644 --- a/tox.ini +++ b/tox.ini @@ -110,6 +110,9 @@ ignore = # variable in function should be lowercase N806, + # lowercase imported as non lowercase + N812, + # variable in class scope should not be mixedCase N815, From 913eb294db511f789d07fc216fa9e8197405c355 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sat, 2 Nov 2019 08:28:29 -0700 Subject: [PATCH 051/419] Undo a change to avoid tripping up coverage/patch. --- src/hyperlink/_url.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index 341175e3..05229ce7 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -18,7 +18,8 @@ import re import sys import string -from socket import AF_INET, AF_INET6, error as SocketError +import socket +from socket import AF_INET, AF_INET6 try: from socket import AddressFamily except ImportError: @@ -729,7 +730,7 @@ def parse_host(host): if u':' in host: try: inet_pton(AF_INET6, host) - except SocketError as se: + except socket.error as se: raise URLParseError('invalid IPv6 host: %r (%r)' % (host, se)) except UnicodeEncodeError: pass # TODO: this can't be a real host right? @@ -738,7 +739,7 @@ def parse_host(host): else: try: inet_pton(AF_INET, host) - except (SocketError, UnicodeEncodeError): + except (socket.error, UnicodeEncodeError): family = None # not an IP else: family = AF_INET From f459aa1d11f13b7c6ee5cf13f24fdc2970497f96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sat, 2 Nov 2019 13:19:49 -0700 Subject: [PATCH 052/419] Add type hints to _encode_path_parts. Get rid of joined argument, as it changed the input and output types and was overriden to False in all but one case. --- src/hyperlink/_url.py | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index 05229ce7..c57c9c5a 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -234,8 +234,14 @@ def _encode_schemeless_path_part(text, maximal=True): if t in _SCHEMELESS_PATH_DELIMS else t for t in text]) -def _encode_path_parts(text_parts, rooted=False, has_scheme=True, - has_authority=True, joined=True, maximal=True): +def _encode_path_parts( + text_parts, # type: Sequence[Text] + rooted=False, # type: bool + has_scheme=True, # type: bool + has_authority=True, # type: bool + maximal=True, # type: bool +): + # type: (...) -> Sequence[Text] """ Percent-encode a tuple of path parts into a complete path. @@ -257,12 +263,12 @@ def _encode_path_parts(text_parts, rooted=False, has_scheme=True, first path segment cannot contain a colon (":") character. """ if not text_parts: - return u'' if joined else text_parts + return () if rooted: - text_parts = (u'',) + text_parts + text_parts = (u'',) + tuple(text_parts) # elif has_authority and text_parts: # raise Exception('see rfc above') # TODO: too late to fail like this? - encoded_parts = [] + encoded_parts = [] # type: List[Text] if has_scheme: encoded_parts = [_encode_path_part(part, maximal=maximal) if part else part for part in text_parts] @@ -270,8 +276,6 @@ def _encode_path_parts(text_parts, rooted=False, has_scheme=True, encoded_parts = [_encode_schemeless_path_part(text_parts[0])] encoded_parts.extend([_encode_path_part(part, maximal=maximal) if part else part for part in text_parts[1:]]) - if joined: - return u'/'.join(encoded_parts) return tuple(encoded_parts) @@ -1278,7 +1282,7 @@ def child(self, *segments): return self segments = [_textcheck('path segment', s) for s in segments] - new_segs = _encode_path_parts(segments, joined=False, maximal=False) + new_segs = _encode_path_parts(segments, maximal=False) new_path = self.path[:-1 if (self.path and self.path[-1] == u'') else None] + new_segs return self.replace(path=new_path) @@ -1378,7 +1382,7 @@ def to_uri(self): new_userinfo = u':'.join([_encode_userinfo_part(p) for p in self.userinfo.split(':', 1)]) new_path = _encode_path_parts(self.path, has_scheme=bool(self.scheme), - rooted=False, joined=False, maximal=True) + rooted=False, maximal=True) new_host = ( self.host if not self.host @@ -1459,11 +1463,13 @@ def to_text(self, with_password=False): """ scheme = self.scheme authority = self.authority(with_password) - path = _encode_path_parts(self.path, - rooted=self.rooted, - has_scheme=bool(scheme), - has_authority=bool(authority), - maximal=False) + path = "/".join(_encode_path_parts( + self.path, + rooted=self.rooted, + has_scheme=bool(scheme), + has_authority=bool(authority), + maximal=False + )) query_parts = [] for k, v in self.query: if v is None: From 915d3ceef5939e58e51b7248567838252983f094 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sat, 2 Nov 2019 15:02:55 -0700 Subject: [PATCH 053/419] Add type hints to _encode_path_parts. Get rid of joined argument, as it changed the input and output types and was overriden to False in all but one case. --- src/hyperlink/_url.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index c57c9c5a..a81f4ac7 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -52,7 +52,8 @@ # from boltons.typeutils -def make_sentinel(name='_MISSING', var_name=None): +def make_sentinel(name='_MISSING', var_name=""): + # type: (str, str) -> object """Creates and returns a new **instance** of a new class, suitable for usage as a "sentinel", a kind of singleton often used to indicate a value is missing when ``None`` is a valid input. @@ -84,18 +85,24 @@ def make_sentinel(name='_MISSING', var_name=None): """ class Sentinel(object): def __init__(self): + # type: () -> None self.name = name self.var_name = var_name def __repr__(self): + # type: () -> str if self.var_name: return self.var_name return '%s(%r)' % (self.__class__.__name__, self.name) if var_name: - def __reduce__(self): + # superclass type hints don't allow str return type, but it is + # allowed in the docs, hence the ignore[override] below + def __reduce__(self): # type: ignore[override] intentional + # type: () -> str return self.var_name def __nonzero__(self): + # type: () -> bool return False __bool__ = __nonzero__ From eb46d28f63761262e13c1f611c6850888ce4b9d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sat, 2 Nov 2019 16:08:21 -0700 Subject: [PATCH 054/419] More type hints --- src/hyperlink/_url.py | 85 ++++++++++++++++++++++++++----------------- 1 file changed, 51 insertions(+), 34 deletions(-) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index a81f4ac7..494656f8 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -25,8 +25,8 @@ except ImportError: AddressFamily = int # type: ignore[assignment,misc] Python 2 from typing import ( - Callable, Iterable, Iterator, List, Mapping, Optional, - Sequence, Text, Tuple, Type, Union, + Any, AnyStr, Callable, Iterable, Iterator, List, Mapping, Optional, + Sequence, Set, Text, Tuple, Type, TypeVar, Union, ) from unicodedata import normalize from ._socket import inet_pton @@ -39,7 +39,6 @@ PY2 = (sys.version_info[0] == 2) -unicode = type(u'') try: unichr except NameError: # Py3 @@ -49,6 +48,7 @@ Mapping[Text, Optional[Text]], Iterable[Tuple[Text, Optional[Text]]], ] +T = TypeVar('T') # from boltons.typeutils @@ -161,6 +161,7 @@ def __nonzero__(self): def _make_decode_map(delims, allow_percent=False): + # type: (Iterable[Text], bool) -> Mapping[bytes, bytes] ret = dict(_HEX_CHAR_MAP) if not allow_percent: delims = set(delims) | set([u'%']) @@ -358,6 +359,7 @@ def _encode_userinfo_part(text, maximal=True): def register_scheme(text, uses_netloc=True, default_port=None): + # type: (Text, bool, Optional[int]) -> None """Registers new scheme information, resulting in correct port and slash behavior from the URL object. There are dozens of standard schemes preregistered, so this function is mostly meant for @@ -366,13 +368,12 @@ def register_scheme(text, uses_netloc=True, default_port=None): `file an issue`_! Args: - text (unicode): Text representing the scheme. + text: Text representing the scheme. (the 'http' in 'http://hatnote.com') - uses_netloc (bool): Does the scheme support specifying a - network host? For instance, "http" does, "mailto" does - not. Defaults to True. - default_port (int): The default port, if any, for netloc-using - schemes. + uses_netloc: Does the scheme support specifying a + network host? For instance, "http" does, "mailto" does not. + Defaults to True. + default_port: The default port, if any, for netloc-using schemes. .. _file an issue: https://github.com/mahmoud/hyperlink/issues @@ -395,8 +396,6 @@ def register_scheme(text, uses_netloc=True, default_port=None): else: raise ValueError('uses_netloc expected bool, not: %r' % uses_netloc) - return - def scheme_uses_netloc(scheme, default=None): # type: (Text, Optional[bool]) -> Optional[bool] @@ -438,6 +437,7 @@ class URLParseError(ValueError): def _optional(argument, default): + # type: (Any, Any) -> Any if argument is _UNSET: return default else: @@ -445,6 +445,7 @@ def _optional(argument, default): def _typecheck(name, value, *types): + # type: (Text, T, Type) -> T """ Check that the given *value* is one of the given *types*, or raise an exception describing the problem using *name*. @@ -459,9 +460,11 @@ def _typecheck(name, value, *types): def _textcheck(name, value, delims=frozenset(), nullable=False): - if not isinstance(value, unicode): + # type: (Text, T, Iterable[Text], bool) -> T + if not isinstance(value, Text): if nullable and value is None: - return value # used by query string values + # used by query string values + return value # type: ignore[misc] unreachable else: str_name = "unicode" if PY2 else "str" exp = str_name + ' or NoneType' if nullable else str_name @@ -469,7 +472,7 @@ def _textcheck(name, value, delims=frozenset(), nullable=False): if delims and set(value) & set(delims): # TODO: test caching into regexes raise ValueError('one or more reserved delimiters %s present in %s: %r' % (''.join(delims), name, value)) - return value + return value # type: ignore[return-value] T vs. Text def iter_pairs(iterable): @@ -489,6 +492,7 @@ def iter_pairs(iterable): def _decode_unreserved( text, normalize_case=False, encode_stray_percents=False ): + # type: (Text, bool, bool) -> Union[str, bytes] return _percent_decode(text, normalize_case=normalize_case, encode_stray_percents=encode_stray_percents, _decode_map=_UNRESERVED_DECODE_MAP) @@ -497,6 +501,7 @@ def _decode_unreserved( def _decode_userinfo_part( text, normalize_case=False, encode_stray_percents=False ): + # type: (Text, bool, bool) -> Union[str, bytes] return _percent_decode(text, normalize_case=normalize_case, encode_stray_percents=encode_stray_percents, _decode_map=_USERINFO_DECODE_MAP) @@ -505,6 +510,7 @@ def _decode_userinfo_part( def _decode_path_part( text, normalize_case=False, encode_stray_percents=False ): + # type: (Text, bool, bool) -> Union[str, bytes] """ >>> _decode_path_part(u'%61%77%2f%7a') u'aw%2fz' @@ -519,6 +525,7 @@ def _decode_path_part( def _decode_query_key( text, normalize_case=False, encode_stray_percents=False ): + # type: (Text, bool, bool) -> Union[str, bytes] return _percent_decode(text, normalize_case=normalize_case, encode_stray_percents=encode_stray_percents, _decode_map=_QUERY_KEY_DECODE_MAP) @@ -527,6 +534,7 @@ def _decode_query_key( def _decode_query_value( text, normalize_case=False, encode_stray_percents=False ): + # type: (Text, bool, bool) -> Union[str, bytes] return _percent_decode(text, normalize_case=normalize_case, encode_stray_percents=encode_stray_percents, _decode_map=_QUERY_VALUE_DECODE_MAP) @@ -535,14 +543,21 @@ def _decode_query_value( def _decode_fragment_part( text, normalize_case=False, encode_stray_percents=False ): + # type: (Text, bool, bool) -> Union[str, bytes] return _percent_decode(text, normalize_case=normalize_case, encode_stray_percents=encode_stray_percents, _decode_map=_FRAGMENT_DECODE_MAP) -def _percent_decode(text, normalize_case=False, subencoding='utf-8', - raise_subencoding_exc=False, encode_stray_percents=False, - _decode_map=_HEX_CHAR_MAP): +def _percent_decode( + text, # type: Text + normalize_case=False, # type: bool + subencoding="utf-8", # type: Union[bool, Text] + raise_subencoding_exc=False, # type: bool + encode_stray_percents=False, # type: bool + _decode_map=_HEX_CHAR_MAP # type: Mapping[bytes, bytes] +): + # type: (...) -> Union[str, bytes] """Convert percent-encoded text characters to their normal, human-readable equivalents. @@ -560,24 +575,26 @@ def _percent_decode(text, normalize_case=False, subencoding='utf-8', u'abc def' Args: - text (unicode): Text with percent-encoding present. - normalize_case (bool): Whether undecoded percent segments, such - as encoded delimiters, should be uppercased, per RFC 3986 - Section 2.1. See :func:`_decode_path_part` for an example. - subencoding (unicode): The name of the encoding underlying the - percent-encoding. Pass `False` to get back raw bytes. - raise_subencoding_exc (bool): Whether an error in decoding the bytes - underlying the percent-decoding should be raised. + text: Text with percent-encoding present. + normalize_case: Whether undecoded percent segments, such as encoded + delimiters, should be uppercased, per RFC 3986 Section 2.1. + See :func:`_decode_path_part` for an example. + subencoding: The name of the encoding underlying the percent-encoding. + Pass `False` to get back raw bytes. + raise_subencoding_exc: Whether an error in decoding the bytes + underlying the percent-decoding should be raised. Returns: - unicode: The percent-decoded version of *text*, decoded by - *subencoding*, unless `subencoding=False` which returns bytes. + The percent-decoded version of *text*, decoded by *subencoding*, + unless `subencoding=False` which returns bytes. """ + if subencoding is False: + subencoding = "utf-8" + assert isinstance(subencoding, Text) + try: - quoted_bytes = text.encode( - 'utf-8' if subencoding is False else subencoding - ) + quoted_bytes = text.encode(subencoding) except UnicodeEncodeError: return text @@ -694,7 +711,7 @@ def _resolve_dot_segments(path): path: sequence of path segments in text form Returns: - list: a new sequence of path segments with the '.' and '..' elements + A new sequence of path segments with the '.' and '..' elements removed and resolved. .. _RFC 3986 section 5.2.4, Remove Dot Segments: https://tools.ietf.org/html/rfc3986#section-5.2.4 @@ -725,7 +742,7 @@ def parse_host(host): Will raise :class:`URLParseError` on invalid IPv6 constants. Returns: - tuple: family (socket constant or None), host (string) + family (socket constant or None), host (string) >>> import socket >>> parse_host('googlewebsite.com') == (None, 'googlewebsite.com') @@ -853,7 +870,7 @@ def __init__( % (self._scheme, self.__class__.__name__)) _, self._host = parse_host(_textcheck('host', host, '/?#@')) - if isinstance(path, unicode): + if isinstance(path, Text): raise TypeError("expected iterable of text for path, not: %r" % (path,)) self._path = tuple((_textcheck("path segment", segment, '/?#') @@ -1018,7 +1035,7 @@ def authority(self, with_password=False, **kw): else: hostport = [self.host] if self.port != SCHEME_PORT_MAP.get(self.scheme): - hostport.append(unicode(self.port)) + hostport.append(Text(self.port)) authority = [] if self.userinfo: userinfo = self.userinfo From 0492f6f8ab46428c6a45bdf90cb27d30db0c8cb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sat, 2 Nov 2019 16:46:26 -0700 Subject: [PATCH 055/419] Revert "More type hints" This reverts commit eb46d28f63761262e13c1f611c6850888ce4b9d6. --- src/hyperlink/_url.py | 85 +++++++++++++++++-------------------------- 1 file changed, 34 insertions(+), 51 deletions(-) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index 494656f8..a81f4ac7 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -25,8 +25,8 @@ except ImportError: AddressFamily = int # type: ignore[assignment,misc] Python 2 from typing import ( - Any, AnyStr, Callable, Iterable, Iterator, List, Mapping, Optional, - Sequence, Set, Text, Tuple, Type, TypeVar, Union, + Callable, Iterable, Iterator, List, Mapping, Optional, + Sequence, Text, Tuple, Type, Union, ) from unicodedata import normalize from ._socket import inet_pton @@ -39,6 +39,7 @@ PY2 = (sys.version_info[0] == 2) +unicode = type(u'') try: unichr except NameError: # Py3 @@ -48,7 +49,6 @@ Mapping[Text, Optional[Text]], Iterable[Tuple[Text, Optional[Text]]], ] -T = TypeVar('T') # from boltons.typeutils @@ -161,7 +161,6 @@ def __nonzero__(self): def _make_decode_map(delims, allow_percent=False): - # type: (Iterable[Text], bool) -> Mapping[bytes, bytes] ret = dict(_HEX_CHAR_MAP) if not allow_percent: delims = set(delims) | set([u'%']) @@ -359,7 +358,6 @@ def _encode_userinfo_part(text, maximal=True): def register_scheme(text, uses_netloc=True, default_port=None): - # type: (Text, bool, Optional[int]) -> None """Registers new scheme information, resulting in correct port and slash behavior from the URL object. There are dozens of standard schemes preregistered, so this function is mostly meant for @@ -368,12 +366,13 @@ def register_scheme(text, uses_netloc=True, default_port=None): `file an issue`_! Args: - text: Text representing the scheme. + text (unicode): Text representing the scheme. (the 'http' in 'http://hatnote.com') - uses_netloc: Does the scheme support specifying a - network host? For instance, "http" does, "mailto" does not. - Defaults to True. - default_port: The default port, if any, for netloc-using schemes. + uses_netloc (bool): Does the scheme support specifying a + network host? For instance, "http" does, "mailto" does + not. Defaults to True. + default_port (int): The default port, if any, for netloc-using + schemes. .. _file an issue: https://github.com/mahmoud/hyperlink/issues @@ -396,6 +395,8 @@ def register_scheme(text, uses_netloc=True, default_port=None): else: raise ValueError('uses_netloc expected bool, not: %r' % uses_netloc) + return + def scheme_uses_netloc(scheme, default=None): # type: (Text, Optional[bool]) -> Optional[bool] @@ -437,7 +438,6 @@ class URLParseError(ValueError): def _optional(argument, default): - # type: (Any, Any) -> Any if argument is _UNSET: return default else: @@ -445,7 +445,6 @@ def _optional(argument, default): def _typecheck(name, value, *types): - # type: (Text, T, Type) -> T """ Check that the given *value* is one of the given *types*, or raise an exception describing the problem using *name*. @@ -460,11 +459,9 @@ def _typecheck(name, value, *types): def _textcheck(name, value, delims=frozenset(), nullable=False): - # type: (Text, T, Iterable[Text], bool) -> T - if not isinstance(value, Text): + if not isinstance(value, unicode): if nullable and value is None: - # used by query string values - return value # type: ignore[misc] unreachable + return value # used by query string values else: str_name = "unicode" if PY2 else "str" exp = str_name + ' or NoneType' if nullable else str_name @@ -472,7 +469,7 @@ def _textcheck(name, value, delims=frozenset(), nullable=False): if delims and set(value) & set(delims): # TODO: test caching into regexes raise ValueError('one or more reserved delimiters %s present in %s: %r' % (''.join(delims), name, value)) - return value # type: ignore[return-value] T vs. Text + return value def iter_pairs(iterable): @@ -492,7 +489,6 @@ def iter_pairs(iterable): def _decode_unreserved( text, normalize_case=False, encode_stray_percents=False ): - # type: (Text, bool, bool) -> Union[str, bytes] return _percent_decode(text, normalize_case=normalize_case, encode_stray_percents=encode_stray_percents, _decode_map=_UNRESERVED_DECODE_MAP) @@ -501,7 +497,6 @@ def _decode_unreserved( def _decode_userinfo_part( text, normalize_case=False, encode_stray_percents=False ): - # type: (Text, bool, bool) -> Union[str, bytes] return _percent_decode(text, normalize_case=normalize_case, encode_stray_percents=encode_stray_percents, _decode_map=_USERINFO_DECODE_MAP) @@ -510,7 +505,6 @@ def _decode_userinfo_part( def _decode_path_part( text, normalize_case=False, encode_stray_percents=False ): - # type: (Text, bool, bool) -> Union[str, bytes] """ >>> _decode_path_part(u'%61%77%2f%7a') u'aw%2fz' @@ -525,7 +519,6 @@ def _decode_path_part( def _decode_query_key( text, normalize_case=False, encode_stray_percents=False ): - # type: (Text, bool, bool) -> Union[str, bytes] return _percent_decode(text, normalize_case=normalize_case, encode_stray_percents=encode_stray_percents, _decode_map=_QUERY_KEY_DECODE_MAP) @@ -534,7 +527,6 @@ def _decode_query_key( def _decode_query_value( text, normalize_case=False, encode_stray_percents=False ): - # type: (Text, bool, bool) -> Union[str, bytes] return _percent_decode(text, normalize_case=normalize_case, encode_stray_percents=encode_stray_percents, _decode_map=_QUERY_VALUE_DECODE_MAP) @@ -543,21 +535,14 @@ def _decode_query_value( def _decode_fragment_part( text, normalize_case=False, encode_stray_percents=False ): - # type: (Text, bool, bool) -> Union[str, bytes] return _percent_decode(text, normalize_case=normalize_case, encode_stray_percents=encode_stray_percents, _decode_map=_FRAGMENT_DECODE_MAP) -def _percent_decode( - text, # type: Text - normalize_case=False, # type: bool - subencoding="utf-8", # type: Union[bool, Text] - raise_subencoding_exc=False, # type: bool - encode_stray_percents=False, # type: bool - _decode_map=_HEX_CHAR_MAP # type: Mapping[bytes, bytes] -): - # type: (...) -> Union[str, bytes] +def _percent_decode(text, normalize_case=False, subencoding='utf-8', + raise_subencoding_exc=False, encode_stray_percents=False, + _decode_map=_HEX_CHAR_MAP): """Convert percent-encoded text characters to their normal, human-readable equivalents. @@ -575,26 +560,24 @@ def _percent_decode( u'abc def' Args: - text: Text with percent-encoding present. - normalize_case: Whether undecoded percent segments, such as encoded - delimiters, should be uppercased, per RFC 3986 Section 2.1. - See :func:`_decode_path_part` for an example. - subencoding: The name of the encoding underlying the percent-encoding. - Pass `False` to get back raw bytes. - raise_subencoding_exc: Whether an error in decoding the bytes - underlying the percent-decoding should be raised. + text (unicode): Text with percent-encoding present. + normalize_case (bool): Whether undecoded percent segments, such + as encoded delimiters, should be uppercased, per RFC 3986 + Section 2.1. See :func:`_decode_path_part` for an example. + subencoding (unicode): The name of the encoding underlying the + percent-encoding. Pass `False` to get back raw bytes. + raise_subencoding_exc (bool): Whether an error in decoding the bytes + underlying the percent-decoding should be raised. Returns: - The percent-decoded version of *text*, decoded by *subencoding*, - unless `subencoding=False` which returns bytes. + unicode: The percent-decoded version of *text*, decoded by + *subencoding*, unless `subencoding=False` which returns bytes. """ - if subencoding is False: - subencoding = "utf-8" - assert isinstance(subencoding, Text) - try: - quoted_bytes = text.encode(subencoding) + quoted_bytes = text.encode( + 'utf-8' if subencoding is False else subencoding + ) except UnicodeEncodeError: return text @@ -711,7 +694,7 @@ def _resolve_dot_segments(path): path: sequence of path segments in text form Returns: - A new sequence of path segments with the '.' and '..' elements + list: a new sequence of path segments with the '.' and '..' elements removed and resolved. .. _RFC 3986 section 5.2.4, Remove Dot Segments: https://tools.ietf.org/html/rfc3986#section-5.2.4 @@ -742,7 +725,7 @@ def parse_host(host): Will raise :class:`URLParseError` on invalid IPv6 constants. Returns: - family (socket constant or None), host (string) + tuple: family (socket constant or None), host (string) >>> import socket >>> parse_host('googlewebsite.com') == (None, 'googlewebsite.com') @@ -870,7 +853,7 @@ def __init__( % (self._scheme, self.__class__.__name__)) _, self._host = parse_host(_textcheck('host', host, '/?#@')) - if isinstance(path, Text): + if isinstance(path, unicode): raise TypeError("expected iterable of text for path, not: %r" % (path,)) self._path = tuple((_textcheck("path segment", segment, '/?#') @@ -1035,7 +1018,7 @@ def authority(self, with_password=False, **kw): else: hostport = [self.host] if self.port != SCHEME_PORT_MAP.get(self.scheme): - hostport.append(Text(self.port)) + hostport.append(unicode(self.port)) authority = [] if self.userinfo: userinfo = self.userinfo From 63c471dc3ebddbc96df33b9e0647a1c6a8df4ffa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sat, 2 Nov 2019 16:49:45 -0700 Subject: [PATCH 056/419] =?UTF-8?q?unicode=20=E2=9E=9C=20Text?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hyperlink/_url.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index a81f4ac7..9d9e060f 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -39,7 +39,6 @@ PY2 = (sys.version_info[0] == 2) -unicode = type(u'') try: unichr except NameError: # Py3 @@ -459,7 +458,7 @@ def _typecheck(name, value, *types): def _textcheck(name, value, delims=frozenset(), nullable=False): - if not isinstance(value, unicode): + if not isinstance(value, Text): if nullable and value is None: return value # used by query string values else: @@ -853,7 +852,7 @@ def __init__( % (self._scheme, self.__class__.__name__)) _, self._host = parse_host(_textcheck('host', host, '/?#@')) - if isinstance(path, unicode): + if isinstance(path, Text): raise TypeError("expected iterable of text for path, not: %r" % (path,)) self._path = tuple((_textcheck("path segment", segment, '/?#') @@ -1018,7 +1017,7 @@ def authority(self, with_password=False, **kw): else: hostport = [self.host] if self.port != SCHEME_PORT_MAP.get(self.scheme): - hostport.append(unicode(self.port)) + hostport.append(Text(self.port)) authority = [] if self.userinfo: userinfo = self.userinfo From 5fa0ce26b7f73ec9985e779e90116c8c9c25d332 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sat, 2 Nov 2019 17:14:12 -0700 Subject: [PATCH 057/419] This time with less oops --- src/hyperlink/_url.py | 48 +++++++++++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index 9d9e060f..89622a18 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -25,8 +25,8 @@ except ImportError: AddressFamily = int # type: ignore[assignment,misc] Python 2 from typing import ( - Callable, Iterable, Iterator, List, Mapping, Optional, - Sequence, Text, Tuple, Type, Union, + Any, Callable, Iterable, Iterator, List, Mapping, Optional, + Sequence, Text, Tuple, Type, TypeVar, Union, cast, ) from unicodedata import normalize from ._socket import inet_pton @@ -48,6 +48,7 @@ Mapping[Text, Optional[Text]], Iterable[Tuple[Text, Optional[Text]]], ] +T = TypeVar('T') # from boltons.typeutils @@ -160,6 +161,7 @@ def __nonzero__(self): def _make_decode_map(delims, allow_percent=False): + # type: (Iterable[Text], bool) -> Mapping[bytes, bytes] ret = dict(_HEX_CHAR_MAP) if not allow_percent: delims = set(delims) | set([u'%']) @@ -357,6 +359,7 @@ def _encode_userinfo_part(text, maximal=True): def register_scheme(text, uses_netloc=True, default_port=None): + # type: (Text, bool, Optional[int]) -> None """Registers new scheme information, resulting in correct port and slash behavior from the URL object. There are dozens of standard schemes preregistered, so this function is mostly meant for @@ -365,13 +368,12 @@ def register_scheme(text, uses_netloc=True, default_port=None): `file an issue`_! Args: - text (unicode): Text representing the scheme. + text: Text representing the scheme. (the 'http' in 'http://hatnote.com') - uses_netloc (bool): Does the scheme support specifying a - network host? For instance, "http" does, "mailto" does - not. Defaults to True. - default_port (int): The default port, if any, for netloc-using - schemes. + uses_netloc: Does the scheme support specifying a network host? + For instance, "http" does, "mailto" does not. + Defaults to True. + default_port: The default port, if any, for netloc-using schemes. .. _file an issue: https://github.com/mahmoud/hyperlink/issues @@ -437,6 +439,7 @@ class URLParseError(ValueError): def _optional(argument, default): + # type: (Any, Any) -> Any if argument is _UNSET: return default else: @@ -444,6 +447,7 @@ def _optional(argument, default): def _typecheck(name, value, *types): + # type: (Text, T, Type) -> T """ Check that the given *value* is one of the given *types*, or raise an exception describing the problem using *name*. @@ -458,9 +462,11 @@ def _typecheck(name, value, *types): def _textcheck(name, value, delims=frozenset(), nullable=False): + # type: (Text, T, Iterable[Text], bool) -> T if not isinstance(value, Text): if nullable and value is None: - return value # used by query string values + # used by query string values + return value # type: ignore[misc] unreachable else: str_name = "unicode" if PY2 else "str" exp = str_name + ' or NoneType' if nullable else str_name @@ -468,7 +474,7 @@ def _textcheck(name, value, delims=frozenset(), nullable=False): if delims and set(value) & set(delims): # TODO: test caching into regexes raise ValueError('one or more reserved delimiters %s present in %s: %r' % (''.join(delims), name, value)) - return value + return value # type: ignore[return-value] T vs. Text def iter_pairs(iterable): @@ -488,6 +494,7 @@ def iter_pairs(iterable): def _decode_unreserved( text, normalize_case=False, encode_stray_percents=False ): + # type: (Text, bool, bool) -> Union[str, bytes] return _percent_decode(text, normalize_case=normalize_case, encode_stray_percents=encode_stray_percents, _decode_map=_UNRESERVED_DECODE_MAP) @@ -496,6 +503,7 @@ def _decode_unreserved( def _decode_userinfo_part( text, normalize_case=False, encode_stray_percents=False ): + # type: (Text, bool, bool) -> Union[str, bytes] return _percent_decode(text, normalize_case=normalize_case, encode_stray_percents=encode_stray_percents, _decode_map=_USERINFO_DECODE_MAP) @@ -504,6 +512,7 @@ def _decode_userinfo_part( def _decode_path_part( text, normalize_case=False, encode_stray_percents=False ): + # type: (Text, bool, bool) -> Union[str, bytes] """ >>> _decode_path_part(u'%61%77%2f%7a') u'aw%2fz' @@ -518,6 +527,7 @@ def _decode_path_part( def _decode_query_key( text, normalize_case=False, encode_stray_percents=False ): + # type: (Text, bool, bool) -> Union[str, bytes] return _percent_decode(text, normalize_case=normalize_case, encode_stray_percents=encode_stray_percents, _decode_map=_QUERY_KEY_DECODE_MAP) @@ -526,6 +536,7 @@ def _decode_query_key( def _decode_query_value( text, normalize_case=False, encode_stray_percents=False ): + # type: (Text, bool, bool) -> Union[str, bytes] return _percent_decode(text, normalize_case=normalize_case, encode_stray_percents=encode_stray_percents, _decode_map=_QUERY_VALUE_DECODE_MAP) @@ -534,14 +545,21 @@ def _decode_query_value( def _decode_fragment_part( text, normalize_case=False, encode_stray_percents=False ): + # type: (Text, bool, bool) -> Union[str, bytes] return _percent_decode(text, normalize_case=normalize_case, encode_stray_percents=encode_stray_percents, _decode_map=_FRAGMENT_DECODE_MAP) -def _percent_decode(text, normalize_case=False, subencoding='utf-8', - raise_subencoding_exc=False, encode_stray_percents=False, - _decode_map=_HEX_CHAR_MAP): +def _percent_decode( + text, # type: Text + normalize_case=False, # type: bool + subencoding="utf-8", # type: Union[bool, Text] + raise_subencoding_exc=False, # type: bool + encode_stray_percents=False, # type: bool + _decode_map=_HEX_CHAR_MAP # type: Mapping[bytes, bytes] +): + # type: (...) -> Union[str, bytes] """Convert percent-encoded text characters to their normal, human-readable equivalents. @@ -575,7 +593,7 @@ def _percent_decode(text, normalize_case=False, subencoding='utf-8', """ try: quoted_bytes = text.encode( - 'utf-8' if subencoding is False else subencoding + 'utf-8' if subencoding is False else cast(Text, subencoding) ) except UnicodeEncodeError: return text @@ -613,7 +631,7 @@ def _percent_decode(text, normalize_case=False, subencoding='utf-8', if subencoding is False: return unquoted_bytes try: - return unquoted_bytes.decode(subencoding) + return unquoted_bytes.decode(cast(Text, subencoding)) except UnicodeDecodeError: if raise_subencoding_exc: raise From 8ad6b227ebd4bcdc9a32f3cfed8412ba633ea0b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20S=C3=A1nchez=20Vega?= Date: Sat, 2 Nov 2019 17:24:52 -0700 Subject: [PATCH 058/419] Add py3.8 to the matrix (#83) * Add py3.8 to travis build matrix --- .travis.yml | 2 ++ appveyor.yml | 3 +++ tox.ini | 7 ++++--- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index d90cccab..028ed0b8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,6 +18,8 @@ matrix: env: TOXENV=test-py36,codecov - python: "3.7" env: TOXENV=test-py37,codecov + - python: "3.8" + env: TOXENV=test-py38,codecov - python: "pypy" env: TOXENV=test-pypy,codecov - python: "pypy3" diff --git a/appveyor.yml b/appveyor.yml index 3116a1c2..3c3a63cf 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -18,6 +18,9 @@ environment: - PYTHON: "C:\\Python37-x64" TOX_ENV: "test-py37,codecov" + #- PYTHON: "C:\\Python38-x64" + # TOX_ENV: "test-py38,codecov" + init: - set OS=WINDOWS diff --git a/tox.ini b/tox.ini index 52c6e393..bb56d322 100644 --- a/tox.ini +++ b/tox.ini @@ -2,7 +2,7 @@ envlist = flake8 - test-py{26,27,34,35,36,37,py,py3} + test-py{26,27,34,35,36,37,38,py,py3} coverage_report packaging @@ -25,6 +25,7 @@ basepython = py36: python3.6 py37: python3.7 py38: python3.8 + py39: python3.9 pypy: pypy pypy3: pypy3 @@ -69,7 +70,7 @@ commands = description = run Flake8 (linter) -basepython = python3.7 +basepython = python3.8 skip_install = True @@ -134,7 +135,7 @@ application-import-names = deploy description = run Mypy (static type checker) -basepython = python3.7 +basepython = python3.8 skip_install = True From 8bf25e4064e32326adefd6a7c7f2125afefd3178 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sun, 3 Nov 2019 09:17:19 -0800 Subject: [PATCH 059/419] More hints --- src/hyperlink/_url.py | 69 ++++++++++++++++++++++++++++++++----------- tox.ini | 2 +- 2 files changed, 52 insertions(+), 19 deletions(-) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index 89622a18..ddd52860 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -891,14 +891,16 @@ def __init__( return def get_decoded_url(self, lazy=False): + # type: (bool) -> DecodedURL try: return self._decoded_url except AttributeError: - self._decoded_url = DecodedURL(self, lazy=lazy) + self._decoded_url = DecodedURL(self, lazy=lazy) # type: DecodedURL return self._decoded_url @property def scheme(self): + # type: () -> Text """The scheme is a string, and the first part of an absolute URL, the part before the first colon, and the part which defines the semantics of the rest of the URL. Examples include "http", @@ -909,6 +911,7 @@ def scheme(self): @property def host(self): + # type: () -> Text """The host is a string, and the second standard part of an absolute URL. When present, a valid host must be a domain name, or an IP (v4 or v6). It occurs before the first slash, or the second @@ -918,6 +921,7 @@ def host(self): @property def port(self): + # type: () -> Optional[int] """The port is an integer that is commonly used in connecting to the :attr:`host`, and almost never appears without it. @@ -942,6 +946,7 @@ def port(self): @property def path(self): + # type: () -> Sequence[Text] """A tuple of strings, created by splitting the slash-separated hierarchical path. Started by the first slash after the host, terminated by a "?", which indicates the start of the @@ -951,6 +956,7 @@ def path(self): @property def query(self): + # type: () -> QueryParameter """Tuple of pairs, created by splitting the ampersand-separated mapping of keys and optional values representing non-hierarchical data used to identify the resource. Keys are @@ -966,6 +972,7 @@ def query(self): @property def fragment(self): + # type: () -> Text """A string, the last part of the URL, indicated by the first "#" after the :attr:`~hyperlink.URL.path` or :attr:`~hyperlink.URL.query`. Enables indirect identification @@ -976,6 +983,7 @@ def fragment(self): @property def rooted(self): + # type: () -> bool """Whether or not the path starts with a forward slash (``/``). This is taken from the terminology in the BNF grammar, @@ -989,6 +997,7 @@ def rooted(self): @property def userinfo(self): + # type: () -> Text """The colon-separated string forming the username-password combination. """ @@ -996,18 +1005,21 @@ def userinfo(self): @property def uses_netloc(self): + # type: () -> Optional[bool] """ """ return self._uses_netloc @property def user(self): + # type: () -> Text """ The user portion of :attr:`~hyperlink.URL.userinfo`. """ return self.userinfo.split(u':')[0] def authority(self, with_password=False, **kw): + # type: (bool, Any) -> Text """Compute and return the appropriate host/port/userinfo combination. >>> url = URL.from_text(u'http://user:pass@localhost:8080/a/b?x=y') @@ -1046,6 +1058,7 @@ def authority(self, with_password=False, **kw): return u"@".join(authority) def __eq__(self, other): + # type: (Any) -> bool if not isinstance(other, self.__class__): return NotImplemented for attr in ['scheme', 'userinfo', 'host', 'query', @@ -1060,17 +1073,20 @@ def __eq__(self, other): return False def __ne__(self, other): + # type: (Any) -> bool if not isinstance(other, self.__class__): return NotImplemented return not self.__eq__(other) def __hash__(self): + # type: () -> int return hash((self.__class__, self.scheme, self.userinfo, self.host, self.path, self.query, self.fragment, self.port, self.rooted, self.uses_netloc)) @property def absolute(self): + # type: () -> bool """Whether or not the URL is "absolute". Absolute URLs are complete enough to resolve to a network resource without being relative to a base URI. @@ -1084,9 +1100,19 @@ def absolute(self): """ return bool(self.scheme and self.host) - def replace(self, scheme=_UNSET, host=_UNSET, path=_UNSET, query=_UNSET, - fragment=_UNSET, port=_UNSET, rooted=_UNSET, userinfo=_UNSET, - uses_netloc=_UNSET): + def replace( # type: ignore[assignment] _UNSET is private + self, + scheme=_UNSET, # type: Optional[Text] + host=_UNSET, # type: Optional[Text] + path=_UNSET, # type: Iterable[Text] + query=_UNSET, # type: QueryParameter + fragment=_UNSET, # type: Text + port=_UNSET, # type: Optional[int] + rooted=_UNSET, # type: Optional[bool] + userinfo=_UNSET, # type: Text + uses_netloc=_UNSET # type: Optional[bool] + ): + # type: (...) -> URL """:class:`URL` objects are immutable, which means that attributes are designed to be set only once, at construction. Instead of modifying an existing URL, one simply creates a copy with the @@ -1114,7 +1140,7 @@ def replace(self, scheme=_UNSET, host=_UNSET, path=_UNSET, query=_UNSET, ``mailto:e@g.com``) Returns: - URL: a copy of the current :class:`URL`, with new values for + a copy of the current :class:`URL`, with new values for parameters passed. """ @@ -1132,6 +1158,7 @@ def replace(self, scheme=_UNSET, host=_UNSET, path=_UNSET, query=_UNSET, @classmethod def from_text(cls, text): + # type: (Text) -> URL """Whereas the :class:`URL` constructor is useful for constructing URLs from parts, :meth:`~URL.from_text` supports parsing whole URLs from their string form:: @@ -1162,19 +1189,17 @@ def from_text(cls, text): """ um = _URL_RE.match(_textcheck('text', text)) - try: - gs = um.groupdict() - except AttributeError: + if um is None: raise URLParseError('could not parse url: %r' % text) + gs = um.groupdict() au_text = gs['authority'] or u'' au_m = _AUTHORITY_RE.match(au_text) - try: - au_gs = au_m.groupdict() - except AttributeError: + if au_m is None: raise URLParseError( 'invalid authority %r in url: %r' % (au_text, text) ) + au_gs = au_m.groupdict() if au_gs['bad_host']: raise URLParseError( 'invalid host %r in url: %r' % (au_gs['bad_host'], text) @@ -1186,7 +1211,7 @@ def from_text(cls, text): port = au_gs['port'] if port is not None: try: - port = int(port) + port = int(port) # type: ignore[assignment] TODO, also below except ValueError: if not port: # TODO: excessive? raise URLParseError('port must not be empty: %r' % au_text) @@ -1197,9 +1222,9 @@ def from_text(cls, text): uses_netloc = bool(gs['_netloc_sep']) if gs['path']: - path = gs['path'].split(u"/") + path = tuple(gs['path'].split(u"/")) if not path[0]: - path.pop(0) + path = path[1:] rooted = True else: rooted = False @@ -1207,12 +1232,20 @@ def from_text(cls, text): path = () rooted = bool(au_text) if gs['query']: - query = ((qe.split(u"=", 1) if u'=' in qe else (qe, None)) - for qe in gs['query'].split(u"&")) + query = [ + ( + cast(Tuple[str, str], qe.split(u"=", 1)) + if u'=' in qe else + (qe, None) + ) + for qe in gs['query'].split(u"&") + ] # type: Iterable[Tuple[str, Optional[str]]] else: query = () - return cls(scheme, host, path, query, fragment, port, - rooted, userinfo, uses_netloc) + return cls( + scheme, host, path, query, fragment, + port, rooted, userinfo, uses_netloc, # type: ignore[arg-type] TODO + ) def normalize(self, scheme=True, host=True, path=True, query=True, fragment=True, userinfo=True, percents=True): diff --git a/tox.ini b/tox.ini index 07b9fd50..097758be 100644 --- a/tox.ini +++ b/tox.ini @@ -69,7 +69,7 @@ commands = description = run Flake8 (linter) -basepython = python3.7 +basepython = python3.8 skip_install = True From 50acc09360e759961a5d4f36377785acefbd1c6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sun, 3 Nov 2019 11:06:03 -0800 Subject: [PATCH 060/419] Finish typing for URL --- src/hyperlink/_url.py | 292 +++++++++++++++++---------------- src/hyperlink/test/test_url.py | 2 +- 2 files changed, 155 insertions(+), 139 deletions(-) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index ddd52860..f8fc9454 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -25,7 +25,7 @@ except ImportError: AddressFamily = int # type: ignore[assignment,misc] Python 2 from typing import ( - Any, Callable, Iterable, Iterator, List, Mapping, Optional, + Any, Callable, Dict, Iterable, Iterator, List, Mapping, Optional, Sequence, Text, Tuple, Type, TypeVar, Union, cast, ) from unicodedata import normalize @@ -59,9 +59,9 @@ def make_sentinel(name='_MISSING', var_name=""): a value is missing when ``None`` is a valid input. Args: - name (str): Name of the Sentinel - var_name (str): Set this name to the name of the variable in - its respective module enable pickleability. + name: Name of the Sentinel + var_name: Set this name to the name of the variable in its respective + module enable pickle-ability. >>> make_sentinel(var_name='_MISSING') _MISSING @@ -74,14 +74,13 @@ def make_sentinel(name='_MISSING', var_name=""): .. note:: - By design, additional calls to ``make_sentinel`` with the same - values will not produce equivalent objects. - - >>> make_sentinel('TEST') == make_sentinel('TEST') - False - >>> type(make_sentinel('TEST')) == type(make_sentinel('TEST')) - False + By design, additional calls to ``make_sentinel`` with the same + values will not produce equivalent objects. + >>> make_sentinel('TEST') == make_sentinel('TEST') + False + >>> type(make_sentinel('TEST')) == type(make_sentinel('TEST')) + False """ class Sentinel(object): def __init__(self): @@ -369,14 +368,13 @@ def register_scheme(text, uses_netloc=True, default_port=None): Args: text: Text representing the scheme. - (the 'http' in 'http://hatnote.com') + (the 'http' in 'http://hatnote.com') uses_netloc: Does the scheme support specifying a network host? - For instance, "http" does, "mailto" does not. - Defaults to True. + For instance, "http" does, "mailto" does not. + Defaults to True. default_port: The default port, if any, for netloc-using schemes. .. _file an issue: https://github.com/mahmoud/hyperlink/issues - """ text = text.lower() if default_port is not None: @@ -577,19 +575,18 @@ def _percent_decode( u'abc def' Args: - text (unicode): Text with percent-encoding present. - normalize_case (bool): Whether undecoded percent segments, such - as encoded delimiters, should be uppercased, per RFC 3986 - Section 2.1. See :func:`_decode_path_part` for an example. - subencoding (unicode): The name of the encoding underlying the - percent-encoding. Pass `False` to get back raw bytes. - raise_subencoding_exc (bool): Whether an error in decoding the bytes - underlying the percent-decoding should be raised. + text: Text with percent-encoding present. + normalize_case: Whether undecoded percent segments, such as encoded + delimiters, should be uppercased, per RFC 3986 Section 2.1. + See :func:`_decode_path_part` for an example. + subencoding: The name of the encoding underlying the percent-encoding. + Pass `False` to get back raw bytes. + raise_subencoding_exc: Whether an error in decoding the bytes + underlying the percent-decoding should be raised. Returns: - unicode: The percent-decoded version of *text*, decoded by - *subencoding*, unless `subencoding=False` which returns bytes. - + The percent-decoded version of *text*, decoded by *subencoding*, unless + `subencoding=False` which returns bytes. """ try: quoted_bytes = text.encode( @@ -711,8 +708,8 @@ def _resolve_dot_segments(path): path: sequence of path segments in text form Returns: - list: a new sequence of path segments with the '.' and '..' elements - removed and resolved. + A new sequence of path segments with the '.' and '..' elements removed + and resolved. .. _RFC 3986 section 5.2.4, Remove Dot Segments: https://tools.ietf.org/html/rfc3986#section-5.2.4 """ # noqa: E501 @@ -940,7 +937,6 @@ def port(self): Per the standard, when the port is the same as the schemes default port, it will be omitted in the text URL. - """ return self._port @@ -956,7 +952,7 @@ def path(self): @property def query(self): - # type: () -> QueryParameter + # type: () -> Tuple[Tuple[Text, Optional[Text]], ...] """Tuple of pairs, created by splitting the ampersand-separated mapping of keys and optional values representing non-hierarchical data used to identify the resource. Keys are @@ -977,7 +973,6 @@ def fragment(self): after the :attr:`~hyperlink.URL.path` or :attr:`~hyperlink.URL.query`. Enables indirect identification of a secondary resource, like an anchor within an HTML page. - """ return self._fragment @@ -991,7 +986,6 @@ def rooted(self): and "absolute URI" are somewhat ambiguous. :attr:`path` does not contain the implicit prefixed ``"/"`` since that is somewhat awkward to work with. - """ return self._rooted @@ -1029,13 +1023,12 @@ def authority(self, with_password=False, **kw): u'user:pass@localhost:8080' Args: - with_password (bool): Whether the return value of this - method include the password in the URL, if it is - set. Defaults to False. + with_password: Whether the return value of this method include the + password in the URL, if it is set. Defaults to False. Returns: - str: The authority (network location and user information) portion - of the URL. + The authority (network location and user information) portion of the + URL. """ # first, a bit of twisted compat with_password = kw.pop('includeSecrets', with_password) @@ -1122,27 +1115,22 @@ def replace( # type: ignore[assignment] _UNSET is private the value on the current URL. Args: - scheme (unicode): The text name of the scheme. - host (unicode): The host portion of the network location - port (int): The port part of the network location. - path (tuple): A tuple of strings representing the - slash-separated parts of the path. - query (tuple): The query parameters, as a tuple of - key-value pairs. - query (tuple): The query parameters, as a dictionary or - as an iterable of key-value pairs. - fragment (unicode): The fragment part of the URL. - rooted (bool): Whether or not the path begins with a slash. - userinfo (unicode): The username or colon-separated - username:password pair. - uses_netloc (bool): Indicates whether two slashes appear - between the scheme and the host (``http://eg.com`` vs - ``mailto:e@g.com``) + scheme: The text name of the scheme. + host: The host portion of the network location + path: A tuple of strings representing the slash-separated parts of + the path. + query: The query parameters, as a dictionary or as an iterable of + key-value pairs. + fragment: The fragment part of the URL. + port: The port part of the network location. + rooted: Whether or not the path begins with a slash. + userinfo: The username or colon-separated username:password pair. + uses_netloc: Indicates whether two slashes appear between the + scheme and the host (``http://eg.com`` vs ``mailto:e@g.com``) Returns: - a copy of the current :class:`URL`, with new values for - parameters passed. - + A copy of the current :class:`URL`, with new values for + parameters passed. """ return self.__class__( scheme=_optional(scheme, self.scheme), @@ -1163,10 +1151,10 @@ def from_text(cls, text): URLs from parts, :meth:`~URL.from_text` supports parsing whole URLs from their string form:: - >>> URL.from_text(u'http://example.com') - URL.from_text(u'http://example.com') - >>> URL.from_text(u'?a=b&x=y') - URL.from_text(u'?a=b&x=y') + >>> URL.from_text(u'http://example.com') + URL.from_text(u'http://example.com') + >>> URL.from_text(u'?a=b&x=y') + URL.from_text(u'?a=b&x=y') As you can see above, it's also used as the :func:`repr` of :class:`URL` objects. The natural counterpart to @@ -1174,10 +1162,10 @@ def from_text(cls, text): sure to decode those bytestrings. Args: - text (unicode): A valid URL string. + text: A valid URL string. Returns: - URL: The structured object version of the parsed string. + The structured object version of the parsed string. .. note:: @@ -1186,7 +1174,6 @@ def from_text(cls, text): look like URLs are still valid URLs. As a result, this method only raises :class:`URLParseError` on invalid port and IPv6 values in the host portion of the URL. - """ um = _URL_RE.match(_textcheck('text', text)) if um is None: @@ -1211,7 +1198,7 @@ def from_text(cls, text): port = au_gs['port'] if port is not None: try: - port = int(port) # type: ignore[assignment] TODO, also below + port = int(port) # type: ignore[assignment] FIXME, also below except ValueError: if not port: # TODO: excessive? raise URLParseError('port must not be empty: %r' % au_text) @@ -1244,11 +1231,13 @@ def from_text(cls, text): query = () return cls( scheme, host, path, query, fragment, - port, rooted, userinfo, uses_netloc, # type: ignore[arg-type] TODO + port, # type: ignore[arg-type] FIXME, also above + rooted, userinfo, uses_netloc, ) def normalize(self, scheme=True, host=True, path=True, query=True, fragment=True, userinfo=True, percents=True): + # type: (bool, bool, bool, bool, bool, bool, bool) -> URL """Return a new URL object with several standard normalizations applied: @@ -1265,15 +1254,14 @@ def normalize(self, scheme=True, host=True, path=True, query=True, name. Args: - scheme (bool): Convert the scheme to lowercase - host (bool): Convert the host to lowercase - path (bool): Normalize the path (see above for details) - query (bool): Normalize the query string - fragment (bool): Normalize the fragment - userinfo (bool): Normalize the userinfo - percents (bool): Encode isolated percent signs - for any percent-encoded fields which are being - normalized (defaults to True). + scheme: Convert the scheme to lowercase + host: Convert the host to lowercase + path: Normalize the path (see above for details) + query: Normalize the query string + fragment: Normalize the fragment + userinfo: Normalize the userinfo + percents: Encode isolated percent signs for any percent-encoded + fields which are being normalized (defaults to True). >>> url = URL.from_text(u'Http://example.COM/a/../b/./c%2f?%61%') >>> print(url.normalize().to_text()) @@ -1285,9 +1273,8 @@ def normalize(self, scheme=True, host=True, path=True, query=True, .. _RFC 3986 6.2.2.3: https://tools.ietf.org/html/rfc3986#section-6.2.2.3 .. _RFC 3986 6.2.3: https://tools.ietf.org/html/rfc3986#section-6.2.3 .. _RFC 3986 2.4: https://tools.ietf.org/html/rfc3986#section-2.4 - """ # noqa: E501 - kw = {} + kw = {} # type: Dict[str, Any] if scheme: kw['scheme'] = self.scheme.lower() if host: @@ -1315,6 +1302,7 @@ def _dec_unres(target): return self.replace(**kw) def child(self, *segments): + # type: (Text) -> URL """Make a new :class:`URL` where the given path segments are a child of this URL, preserving other parts of the URL, including the query string and fragment. @@ -1327,58 +1315,61 @@ def child(self, *segments): u'http://localhost/a/b/c/d?x=y' Args: - segments (unicode): Additional parts to be joined and added to - the path, like :func:`os.path.join`. Special characters - in segments will be percent encoded. + segments: Additional parts to be joined and added to + the path, like :func:`os.path.join`. Special characters + in segments will be percent encoded. Returns: - URL: A copy of the current URL with the extra path segments. - + A copy of the current URL with the extra path segments. """ if not segments: return self - segments = [_textcheck('path segment', s) for s in segments] - new_segs = _encode_path_parts(segments, maximal=False) - new_path = self.path[:-1 if (self.path and self.path[-1] == u'') - else None] + new_segs + segments = [ # type: ignore[assignment] variable is tuple + _textcheck('path segment', s) for s in segments + ] + new_path = tuple(self.path) + if self.path and self.path[-1] == u"": + new_path = new_path[:-1] + new_path += tuple(_encode_path_parts(segments, maximal=False)) return self.replace(path=new_path) def sibling(self, segment): + # type: (Text) -> URL """Make a new :class:`URL` with a single path segment that is a sibling of this URL path. Args: - segment (unicode): A single path segment. + segment: A single path segment. Returns: - URL: A copy of the current URL with the last path segment - replaced by *segment*. Special characters such as - ``/?#`` will be percent encoded. - + A copy of the current URL with the last path segment + replaced by *segment*. Special characters such as + ``/?#`` will be percent encoded. """ _textcheck('path segment', segment) - new_path = self.path[:-1] + (_encode_path_part(segment),) + new_path = tuple(self.path)[:-1] + (_encode_path_part(segment),) return self.replace(path=new_path) def click(self, href=u''): + # type: (Text) -> URL """Resolve the given URL relative to this URL. The resulting URI should match what a web browser would generate if you visited the current URL and clicked on *href*. - >>> url = URL.from_text(u'http://blog.hatnote.com/') - >>> url.click(u'/post/155074058790').to_text() - u'http://blog.hatnote.com/post/155074058790' - >>> url = URL.from_text(u'http://localhost/a/b/c/') - >>> url.click(u'../d/./e').to_text() - u'http://localhost/a/b/d/e' + >>> url = URL.from_text(u'http://blog.hatnote.com/') + >>> url.click(u'/post/155074058790').to_text() + u'http://blog.hatnote.com/post/155074058790' + >>> url = URL.from_text(u'http://localhost/a/b/c/') + >>> url.click(u'../d/./e').to_text() + u'http://localhost/a/b/d/e' Args: - href (unicode): A string representing a clicked URL. + href: A string representing a clicked URL. Return: - URL: A copy of the current URL with navigation logic applied. + A copy of the current URL with navigation logic applied. For more information, see `RFC 3986 section 5`_. @@ -1386,7 +1377,7 @@ def click(self, href=u''): """ if href: if isinstance(href, URL): - clicked = href + clicked = href # type: ignore[misc] unreachable else: # TODO: This error message is not completely accurate, # as URL objects are now also valid, but Twisted's @@ -1409,7 +1400,7 @@ def click(self, href=u''): if clicked.rooted: path = clicked.path elif clicked.path: - path = self.path[:-1] + clicked.path + path = tuple(self.path)[:-1] + tuple(clicked.path) else: path = self.path if not query: @@ -1422,6 +1413,7 @@ def click(self, href=u''): fragment=clicked.fragment) def to_uri(self): + # type: () -> URL u"""Make a new :class:`URL` instance with all non-ASCII characters appropriately percent-encoded. This is useful to do in preparation for sending a :class:`URL` over a network protocol. @@ -1457,6 +1449,7 @@ def to_uri(self): ) def to_iri(self): + # type: () -> URL u"""Make a new :class:`URL` instance with all but a few reserved characters decoded into human-readable format. @@ -1479,21 +1472,32 @@ def to_iri(self): URL: A new instance with its path segments, query parameters, and hostname decoded for display purposes. """ # noqa: E501 - new_userinfo = u':'.join([_decode_userinfo_part(p) for p in - self.userinfo.split(':', 1)]) + new_userinfo = u':'.join([ + cast(Text, _decode_userinfo_part(p)) + for p in self.userinfo.split(':', 1) + ]) host_text = _decode_host(self.host) - return self.replace(userinfo=new_userinfo, - host=host_text, - path=[_decode_path_part(segment) - for segment in self.path], - query=[(_decode_query_key(k), - _decode_query_value(v) - if v is not None else None) - for k, v in self.query], - fragment=_decode_fragment_part(self.fragment)) + return self.replace( + userinfo=new_userinfo, + host=host_text, + path=[ + cast(Text, _decode_path_part(segment)) + for segment in self.path + ], + query=[ + ( + cast(Text, _decode_query_key(k)), + cast(Optional[Text], _decode_query_value(v)) + if v is not None else None + ) + for k, v in self.query + ], + fragment=cast(Text, _decode_fragment_part(self.fragment)), + ) def to_text(self, with_password=False): + # type: (bool) -> Text """Render this URL to its textual representation. By default, the URL text will *not* include a password, if one @@ -1507,12 +1511,12 @@ def to_text(self, with_password=False): password)." Args: - with_password (bool): Whether or not to include the - password in the URL text. Defaults to False. + with_password: Whether or not to include the password in the URL + text. Defaults to False. Returns: - str: The serialized textual representation of this URL, - such as ``u"http://example.com/some/path?some=query"``. + The serialized textual representation of this URL, such as + ``u"http://example.com/some/path?some=query"``. The natural counterpart to :class:`URL.from_text()`. @@ -1540,7 +1544,7 @@ def to_text(self, with_password=False): fragment = self.fragment - parts = [] + parts = [] # type: List[Text] _add = parts.append if scheme: _add(scheme) @@ -1563,6 +1567,7 @@ def to_text(self, with_password=False): return u''.join(parts) def __repr__(self): + # type: () -> str """Convert this URL to an representation that shows all of its constituent parts, as well as being a valid argument to :func:`eval`. @@ -1570,6 +1575,7 @@ def __repr__(self): return '%s.from_text(%r)' % (self.__class__.__name__, self.to_text()) def _to_bytes(self): + # type: () -> bytes """ Allows for direct usage of URL objects with libraries like requests, which automatically stringify URL parameters. See @@ -1590,12 +1596,15 @@ def _to_bytes(self): @classmethod def fromText(cls, s): + # type: (Text) -> URL return cls.from_text(s) def asText(self, includeSecrets=False): + # type: (bool) -> Text return self.to_text(with_password=includeSecrets) def __dir__(self): + # type: () -> Sequence[Text] try: ret = object.__dir__(self) except AttributeError: @@ -1607,6 +1616,7 @@ def __dir__(self): # # End Twisted Compat Code def add(self, name, value=None): + # type: (Text, Optional[Text]) -> URL """Make a new :class:`URL` instance with a given query argument, *name*, added to it with the value *value*, like so:: @@ -1616,18 +1626,19 @@ def add(self, name, value=None): URL.from_text(u'https://example.com/?x=y&x=z') Args: - name (unicode): The name of the query parameter to add. The - part before the ``=``. - value (unicode): The value of the query parameter to add. The - part after the ``=``. Defaults to ``None``, meaning no + name: The name of the query parameter to add. + The part before the ``=``. + value: The value of the query parameter to add. + The part after the ``=``. Defaults to ``None``, meaning no value. Returns: - URL: A new :class:`URL` instance with the parameter added. + A new :class:`URL` instance with the parameter added. """ return self.replace(query=self.query + ((name, value),)) def set(self, name, value=None): + # type: (Text, Optional[Text]) -> URL """Make a new :class:`URL` instance with the query parameter *name* set to *value*. All existing occurences, if any are replaced by the single name-value pair. @@ -1638,23 +1649,24 @@ def set(self, name, value=None): URL.from_text(u'https://example.com/?x=z') Args: - name (unicode): The name of the query parameter to set. The - part before the ``=``. - value (unicode): The value of the query parameter to set. The - part after the ``=``. Defaults to ``None``, meaning no + name: The name of the query parameter to set. + The part before the ``=``. + value: The value of the query parameter to set. + The part after the ``=``. Defaults to ``None``, meaning no value. Returns: - URL: A new :class:`URL` instance with the parameter set. + A new :class:`URL` instance with the parameter set. """ # Preserve the original position of the query key in the list - q = [(k, v) for (k, v) in self.query if k != name] + q = [(k, v) for (k, v) in self._query if k != name] idx = next((i for (i, (k, v)) in enumerate(self.query) if k == name), -1) q[idx:idx] = [(name, value)] return self.replace(query=q) def get(self, name): + # type: (Text) -> List[Optional[Text]] """Get a list of values for the given query parameter, *name*:: >>> url = URL.from_text(u'?x=1&x=2') @@ -1667,27 +1679,31 @@ def get(self, name): list is always returned, and this method raises no exceptions. Args: - name (unicode): The name of the query parameter to get. + name: The name of the query parameter to get. Returns: - list: A list of all the values associated with the key, in - string form. - + A list of all the values associated with the key, in string form. """ return [value for (key, value) in self.query if name == key] - def remove(self, name, value=_UNSET, limit=None): + def remove( # type: ignore[assignment] _UNSET is private + self, + name, # type: Text + value=_UNSET, # type: Text + limit=None, # type: Optional[int] + ): + # type: (...) -> URL """Make a new :class:`URL` instance with occurrences of the query parameter *name* removed, or, if *value* is set, parameters matching *name* and *value*. No exception is raised if the parameter is not already set. Args: - name (unicode): The name of the query parameter to remove. - value (unicode): Optional value to additionally filter - on. Setting this removes query parameters which match - both name and value. - limit (int): Optional maximum number of parameters to remove. + name: The name of the query parameter to remove. + value: Optional value to additionally filter on. + Setting this removes query parameters which match both name + and value. + limit: Optional maximum number of parameters to remove. Returns: URL: A new :class:`URL` instance with the parameter removed. diff --git a/src/hyperlink/test/test_url.py b/src/hyperlink/test/test_url.py index 6ee4d172..cdb25147 100644 --- a/src/hyperlink/test/test_url.py +++ b/src/hyperlink/test/test_url.py @@ -430,7 +430,7 @@ def test_click(self): # test click on a URL instance u = URL.fromText('http://localhost/foo/?abc=def') u2 = URL.from_text('bar') - u3 = u.click(u2) + u3 = u.click(u2) # type: ignore[arg-type] URL vs Text self.assertEqual(u3.to_text(), 'http://localhost/foo/bar') def test_clickRFC3986(self): From 7f7121abc62be06e510a26d822e8c7a6cc5f9e90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Mon, 4 Nov 2019 12:12:12 -0800 Subject: [PATCH 061/419] Type hints at 100%. --- src/hyperlink/_url.py | 220 +++++++++++++++++-------- src/hyperlink/test/test_decoded_url.py | 15 +- tox.ini | 3 - 3 files changed, 165 insertions(+), 73 deletions(-) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index f8fc9454..f8342312 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -44,9 +44,10 @@ except NameError: # Py3 unichr = chr # type: Callable[[int], Text] NoneType = type(None) # type: Type[None] -QueryParameter = Union[ - Mapping[Text, Optional[Text]], - Iterable[Tuple[Text, Optional[Text]]], +QueryPairs = Tuple[Tuple[Text, Optional[Text]], ...] # internal representation +QueryParameters = Union[ + Mapping[Text, Optional[Text]], QueryPairs, + Sequence[Tuple[Text, Optional[Text]]], ] T = TypeVar('T') @@ -174,7 +175,8 @@ def _make_decode_map(delims, allow_percent=False): def _make_quote_map(safe_chars): - ret = {} + # type: (Iterable[Text]) -> Mapping[Union[int, Text], Text] + ret = {} # type: Dict[Union[int, Text], Text] # v is included in the dict for py3 mostly, because bytestrings # are iterables of ints, of course! for i, v in zip(range(256), range(256)): @@ -830,7 +832,7 @@ def __init__( scheme=None, # type: Optional[Text] host=None, # type: Optional[Text] path=(), # type: Iterable[Text] - query=(), # type: QueryParameter + query=(), # type: QueryParameters fragment=u"", # type: Text port=None, # type: Optional[int] rooted=None, # type: Optional[bool] @@ -952,7 +954,7 @@ def path(self): @property def query(self): - # type: () -> Tuple[Tuple[Text, Optional[Text]], ...] + # type: () -> QueryPairs """Tuple of pairs, created by splitting the ampersand-separated mapping of keys and optional values representing non-hierarchical data used to identify the resource. Keys are @@ -1098,7 +1100,7 @@ def replace( # type: ignore[assignment] _UNSET is private scheme=_UNSET, # type: Optional[Text] host=_UNSET, # type: Optional[Text] path=_UNSET, # type: Iterable[Text] - query=_UNSET, # type: QueryParameter + query=_UNSET, # type: QueryParameters fragment=_UNSET, # type: Text port=_UNSET, # type: Optional[int] rooted=_UNSET, # type: Optional[bool] @@ -1219,14 +1221,14 @@ def from_text(cls, text): path = () rooted = bool(au_text) if gs['query']: - query = [ + query = tuple( ( cast(Tuple[str, str], qe.split(u"=", 1)) if u'=' in qe else (qe, None) ) for qe in gs['query'].split(u"&") - ] # type: Iterable[Tuple[str, Optional[str]]] + ) # type: QueryPairs else: query = () return cls( @@ -1281,8 +1283,10 @@ def normalize(self, scheme=True, host=True, path=True, query=True, kw['host'] = self.host.lower() def _dec_unres(target): - return _decode_unreserved(target, normalize_case=True, - encode_stray_percents=percents) + # type: (Text) -> Union[Text] + return cast(Text, _decode_unreserved( + target, normalize_case=True, encode_stray_percents=percents) + ) if path: if self.path: kw['path'] = [ @@ -1485,14 +1489,14 @@ def to_iri(self): cast(Text, _decode_path_part(segment)) for segment in self.path ], - query=[ + query=tuple( ( cast(Text, _decode_query_key(k)), cast(Optional[Text], _decode_query_value(v)) if v is not None else None ) for k, v in self.query - ], + ), fragment=cast(Text, _decode_fragment_part(self.fragment)), ) @@ -1750,12 +1754,13 @@ class DecodedURL(object): special characters encoded with codecs other than UTF-8. Args: - url (URL): A :class:`URL` object to wrap. - lazy (bool): Set to True to avoid pre-decode all parts of the - URL to check for validity. Defaults to False. + url: A :class:`URL` object to wrap. + lazy: Set to True to avoid pre-decode all parts of the URL to check for + validity. Defaults to False. """ def __init__(self, url, lazy=False): + # type: (URL, bool) -> None self._url = url if not lazy: # cache the following, while triggering any decoding @@ -1765,45 +1770,52 @@ def __init__(self, url, lazy=False): @classmethod def from_text(cls, text, lazy=False): + # type: (Text, bool) -> DecodedURL """\ Make a `DecodedURL` instance from any text string containing a URL. Args: - text (unicode): Text containing the URL - lazy (bool): Whether to pre-decode all parts of the URL to - check for validity. Defaults to True. + text: Text containing the URL + lazy: Whether to pre-decode all parts of the URL to check for + validity. Defaults to True. """ _url = URL.from_text(text) return cls(_url, lazy=lazy) @property def encoded_url(self): + # type: () -> URL """Access the underlying :class:`URL` object, which has any special characters encoded. """ return self._url - def to_text(self, *a, **kw): + def to_text(self, with_password=False): + # type: (bool) -> Text "Passthrough to :meth:`~hyperlink.URL.to_text()`" - return self._url.to_text(*a, **kw) + return self._url.to_text(with_password) - def to_uri(self, *a, **kw): + def to_uri(self): + # type: () -> URL "Passthrough to :meth:`~hyperlink.URL.to_uri()`" - return self._url.to_uri(*a, **kw) + return self._url.to_uri() - def to_iri(self, *a, **kw): + def to_iri(self): + # type: () -> URL "Passthrough to :meth:`~hyperlink.URL.to_iri()`" - return self._url.to_iri(*a, **kw) + return self._url.to_iri() def click(self, href=u''): + # type: (Text) -> DecodedURL """Return a new DecodedURL wrapping the result of :meth:`~hyperlink.URL.click()` """ if isinstance(href, DecodedURL): - href = href._url + href = href._url # type: ignore[misc] unreachable return self.__class__(self._url.click(href=href)) def sibling(self, segment): + # type: (Text) -> DecodedURL """Automatically encode any reserved characters in *segment* and return a new `DecodedURL` wrapping the result of :meth:`~hyperlink.URL.sibling()` @@ -1811,6 +1823,7 @@ def sibling(self, segment): return self.__class__(self._url.sibling(_encode_reserved(segment))) def child(self, *segments): + # type: (Text) -> DecodedURL """Automatically encode any reserved characters in *segments* and return a new `DecodedURL` wrapping the result of :meth:`~hyperlink.URL.child()`. @@ -1820,86 +1833,137 @@ def child(self, *segments): new_segs = [_encode_reserved(s) for s in segments] return self.__class__(self._url.child(*new_segs)) - def normalize(self, *a, **kw): + def normalize(self, scheme=True, host=True, path=True, query=True, + fragment=True, userinfo=True, percents=True): + # type: (bool, bool, bool, bool, bool, bool, bool) -> DecodedURL """Return a new `DecodedURL` wrapping the result of :meth:`~hyperlink.URL.normalize()` """ - return self.__class__(self._url.normalize(*a, **kw)) + return self.__class__(self._url.normalize( + scheme, host, path, query, fragment, userinfo, percents + )) @property def absolute(self): + # type: () -> bool return self._url.absolute @property def scheme(self): + # type: () -> Text return self._url.scheme @property def host(self): + # type: () -> Text return _decode_host(self._url.host) @property def port(self): + # type: () -> Optional[int] return self._url.port @property def rooted(self): + # type: () -> bool return self._url.rooted @property def path(self): + # type: () -> Sequence[Text] try: - return self._path + return cast( + Tuple[Text, ...], + self._path # type: ignore[has-type] can't determine + ) except AttributeError: pass - self._path = tuple([_percent_decode(p, raise_subencoding_exc=True) - for p in self._url.path]) + self._path = tuple([ + cast(Text, _percent_decode(p, raise_subencoding_exc=True)) + for p in self._url.path + ]) return self._path @property def query(self): + # type: () -> QueryPairs try: - return self._query + return cast( + QueryPairs, + self._query # type: ignore[has-type] can't determine + ) except AttributeError: pass - _q = [tuple(_percent_decode(x, raise_subencoding_exc=True) - if x is not None else None - for x in (k, v)) - for k, v in self._url.query] - self._query = tuple(_q) + self._query = cast(QueryPairs, tuple( + tuple( + cast(Text, _percent_decode(x, raise_subencoding_exc=True)) + if x is not None else None + for x in (k, v) + ) + for k, v in self._url.query + )) return self._query @property def fragment(self): + # type: () -> Text try: - return self._fragment + return cast( + Text, + self._fragment # type: ignore[has-type] can't determine + ) except AttributeError: pass frag = self._url.fragment - self._fragment = _percent_decode(frag, raise_subencoding_exc=True) + self._fragment = cast( + Text, _percent_decode(frag, raise_subencoding_exc=True) + ) return self._fragment @property def userinfo(self): + # type: () -> Union[Tuple[str], Tuple[str, str]] try: - return self._userinfo + return cast( + Union[Tuple[str], Tuple[str, str]], + self._userinfo # type: ignore[has-type] can't determine + ) except AttributeError: pass - self._userinfo = tuple([_percent_decode(p, raise_subencoding_exc=True) - for p in self._url.userinfo.split(':', 1)]) + self._userinfo = cast( + Union[Tuple[str], Tuple[str, str]], + tuple( + tuple( + cast(Text, _percent_decode(p, raise_subencoding_exc=True)) + for p in self._url.userinfo.split(':', 1) + ) + ) + ) return self._userinfo @property def user(self): + # type: () -> Text return self.userinfo[0] @property def uses_netloc(self): - return self._url.uses_netloc + # type: () -> bool + return cast(bool, self._url.uses_netloc) - def replace(self, scheme=_UNSET, host=_UNSET, path=_UNSET, query=_UNSET, - fragment=_UNSET, port=_UNSET, rooted=_UNSET, userinfo=_UNSET, - uses_netloc=_UNSET): + def replace( # type: ignore[assignment] _UNSET is private + self, + scheme=_UNSET, # type: Optional[Text] + host=_UNSET, # type: Optional[Text] + path=_UNSET, # type: Iterable[Text] + query=_UNSET, # type: QueryParameters + fragment=_UNSET, # type: Text + port=_UNSET, # type: Optional[int] + rooted=_UNSET, # type: Optional[bool] + userinfo=_UNSET, # type: Union[Tuple[str], Tuple[str, str]] + uses_netloc=_UNSET # type: Optional[bool] + ): + # type: (...) -> DecodedURL """While the signature is the same, this `replace()` differs a little from URL.replace. For instance, it accepts userinfo as a tuple, not as a string, handling the case of having a username @@ -1908,17 +1972,23 @@ def replace(self, scheme=_UNSET, host=_UNSET, path=_UNSET, query=_UNSET, automatically encoded instead of an error being raised. """ if path is not _UNSET: - path = [_encode_reserved(p) for p in path] + path = tuple(_encode_reserved(p) for p in path) if query is not _UNSET: - query = [[_encode_reserved(x) - if x is not None else None - for x in (k, v)] - for k, v in iter_pairs(query)] + query = cast(QueryPairs, tuple( + tuple( + _encode_reserved(x) + if x is not None else None + for x in (k, v) + ) + for k, v in iter_pairs(query) + )) if userinfo is not _UNSET: if len(userinfo) > 2: raise ValueError('userinfo expected sequence of ["user"] or' - ' ["user", "password"], got %r' % userinfo) - userinfo = u':'.join([_encode_reserved(p) for p in userinfo]) + ' ["user", "password"], got %r' % (userinfo,)) + userinfo_text = u':'.join([_encode_reserved(p) for p in userinfo]) + else: + userinfo_text = cast(Text, _UNSET) new_url = self._url.replace(scheme=scheme, host=host, path=path, @@ -1926,20 +1996,23 @@ def replace(self, scheme=_UNSET, host=_UNSET, path=_UNSET, query=_UNSET, fragment=fragment, port=port, rooted=rooted, - userinfo=userinfo, + userinfo=userinfo_text, uses_netloc=uses_netloc) return self.__class__(url=new_url) def get(self, name): + # type: (Text) -> List[Optional[Text]] "Get the value of all query parameters whose name matches *name*" return [v for (k, v) in self.query if name == k] def add(self, name, value=None): + # type: (Text, Optional[Text]) -> DecodedURL """Return a new DecodedURL with the query parameter *name* and *value* added.""" return self.replace(query=self.query + ((name, value),)) def set(self, name, value=None): + # type: (Text, Optional[Text]) -> DecodedURL "Return a new DecodedURL with query parameter *name* set to *value*" query = self.query q = [(k, v) for (k, v) in query if k != name] @@ -1947,7 +2020,13 @@ def set(self, name, value=None): q[idx:idx] = [(name, value)] return self.replace(query=q) - def remove(self, name, value=_UNSET, limit=None): + def remove( # type: ignore[assignment] _UNSET is private + self, + name, # type: Text + value=_UNSET, # type: Text + limit=None, # type: Optional[int] + ): + # type: (...) -> DecodedURL """Return a new DecodedURL with query parameter *name* removed. Optionally also filter for *value*, as well as cap the number @@ -1976,25 +2055,30 @@ def remove(self, name, value=_UNSET, limit=None): return self.replace(query=nq) def __repr__(self): + # type: () -> str cn = self.__class__.__name__ return '%s(url=%r)' % (cn, self._url) def __str__(self): + # type: () -> str # TODO: the underlying URL's __str__ needs to change to make # this work as the URL, see #55 return str(self._url) def __eq__(self, other): + # type: (Any) -> bool if not isinstance(other, self.__class__): return NotImplemented return self.normalize().to_uri() == other.normalize().to_uri() def __ne__(self, other): + # type: (Any) -> bool if not isinstance(other, self.__class__): return NotImplemented return not self.__eq__(other) def __hash__(self): + # type: () -> int return hash((self.__class__, self.scheme, self.userinfo, self.host, self.path, self.query, self.fragment, self.port, self.rooted, self.uses_netloc)) @@ -2005,12 +2089,15 @@ def __hash__(self): @classmethod def fromText(cls, s, lazy=False): + # type: (Text, bool) -> DecodedURL return cls.from_text(s, lazy=lazy) def asText(self, includeSecrets=False): + # type: (bool) -> Text return self.to_text(with_password=includeSecrets) def __dir__(self): + # type: () -> Sequence[Text] try: ret = object.__dir__(self) except AttributeError: @@ -2023,21 +2110,22 @@ def __dir__(self): def parse(url, decoded=True, lazy=False): + # type: (Text, bool, bool) -> Union[URL, DecodedURL] """Automatically turn text into a structured URL object. Args: - decoded (bool): Whether or not to return a :class:`DecodedURL`, - which automatically handles all - encoding/decoding/quoting/unquoting for all the various - accessors of parts of the URL, or an :class:`EncodedURL`, - which has the same API, but requires handling of special - characters for different parts of the URL. - - lazy (bool): In the case of `decoded=True`, this controls - whether the URL is decoded immediately or as accessed. The - default, `lazy=False`, checks all encoded parts of the URL - for decodability. + decoded: Whether or not to return a :class:`DecodedURL`, + which automatically handles all + encoding/decoding/quoting/unquoting for all the various + accessors of parts of the URL, or an :class:`EncodedURL`, + which has the same API, but requires handling of special + characters for different parts of the URL. + + lazy: In the case of `decoded=True`, this controls + whether the URL is decoded immediately or as accessed. The + default, `lazy=False`, checks all encoded parts of the URL + for decodability. """ enc_url = EncodedURL.from_text(url) if not decoded: diff --git a/src/hyperlink/test/test_decoded_url.py b/src/hyperlink/test/test_decoded_url.py index 17f7f19b..98c1565d 100644 --- a/src/hyperlink/test/test_decoded_url.py +++ b/src/hyperlink/test/test_decoded_url.py @@ -2,7 +2,8 @@ from __future__ import unicode_literals -from .. import DecodedURL +from typing import Dict, Union +from .. import DecodedURL, URL from .._url import _percent_decode from .common import HyperlinkTestCase @@ -132,7 +133,9 @@ def test_equality_and_hashability(self): assert durl is not None assert durl != durl._url - durl_map = {} + AnyURL = Union[URL, DecodedURL] + + durl_map = {} # type: Dict[AnyURL, AnyURL] durl_map[durl] = durl durl_map[durl2] = durl2 @@ -166,7 +169,11 @@ def test_replace_userinfo(self): # type: () -> None durl = DecodedURL.from_text(TOTAL_URL) with self.assertRaises(ValueError): - durl.replace(userinfo=['user', 'pw', 'thiswillcauseafailure']) + durl.replace( + userinfo=( # type: ignore[arg-type] intentional + 'user', 'pw', 'thiswillcauseafailure' + ) + ) return def test_twisted_compat(self): @@ -207,7 +214,7 @@ def test_click_decoded_url(self): durl = DecodedURL.from_text(TOTAL_URL) durl_dest = DecodedURL.from_text('/tëst') - clicked = durl.click(durl_dest) + clicked = durl.click(durl_dest) # type: ignore[arg-type] URL vs Text assert clicked.host == durl.host assert clicked.path == durl_dest.path assert clicked.path == ('tëst',) diff --git a/tox.ini b/tox.ini index 392a1630..8514ab9c 100644 --- a/tox.ini +++ b/tox.ini @@ -176,9 +176,6 @@ check_untyped_defs = False # Disable some checks until effected files fully adopt mypy -[mypy-hyperlink._url] -allow_untyped_defs = True - [mypy-idna] ignore_missing_imports = True From 47574602f0f0925420e70fa8a92bf6b6bc2bb1c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Mon, 4 Nov 2019 12:24:55 -0800 Subject: [PATCH 062/419] Get rid of subencoding=False code path in _percent_decode(). It isn't used by any of the rest of the code, and it makes _percent_decode() return either text or bytes, which then meant a lot of type casting elsewhere. --- src/hyperlink/_url.py | 55 ++++++++++---------------- src/hyperlink/test/test_decoded_url.py | 7 ---- 2 files changed, 21 insertions(+), 41 deletions(-) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index f8342312..1573e2c2 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -494,7 +494,7 @@ def iter_pairs(iterable): def _decode_unreserved( text, normalize_case=False, encode_stray_percents=False ): - # type: (Text, bool, bool) -> Union[str, bytes] + # type: (Text, bool, bool) -> Text return _percent_decode(text, normalize_case=normalize_case, encode_stray_percents=encode_stray_percents, _decode_map=_UNRESERVED_DECODE_MAP) @@ -503,7 +503,7 @@ def _decode_unreserved( def _decode_userinfo_part( text, normalize_case=False, encode_stray_percents=False ): - # type: (Text, bool, bool) -> Union[str, bytes] + # type: (Text, bool, bool) -> Text return _percent_decode(text, normalize_case=normalize_case, encode_stray_percents=encode_stray_percents, _decode_map=_USERINFO_DECODE_MAP) @@ -512,7 +512,7 @@ def _decode_userinfo_part( def _decode_path_part( text, normalize_case=False, encode_stray_percents=False ): - # type: (Text, bool, bool) -> Union[str, bytes] + # type: (Text, bool, bool) -> Text """ >>> _decode_path_part(u'%61%77%2f%7a') u'aw%2fz' @@ -527,7 +527,7 @@ def _decode_path_part( def _decode_query_key( text, normalize_case=False, encode_stray_percents=False ): - # type: (Text, bool, bool) -> Union[str, bytes] + # type: (Text, bool, bool) -> Text return _percent_decode(text, normalize_case=normalize_case, encode_stray_percents=encode_stray_percents, _decode_map=_QUERY_KEY_DECODE_MAP) @@ -536,7 +536,7 @@ def _decode_query_key( def _decode_query_value( text, normalize_case=False, encode_stray_percents=False ): - # type: (Text, bool, bool) -> Union[str, bytes] + # type: (Text, bool, bool) -> Text return _percent_decode(text, normalize_case=normalize_case, encode_stray_percents=encode_stray_percents, _decode_map=_QUERY_VALUE_DECODE_MAP) @@ -545,7 +545,7 @@ def _decode_query_value( def _decode_fragment_part( text, normalize_case=False, encode_stray_percents=False ): - # type: (Text, bool, bool) -> Union[str, bytes] + # type: (Text, bool, bool) -> Text return _percent_decode(text, normalize_case=normalize_case, encode_stray_percents=encode_stray_percents, _decode_map=_FRAGMENT_DECODE_MAP) @@ -554,12 +554,12 @@ def _decode_fragment_part( def _percent_decode( text, # type: Text normalize_case=False, # type: bool - subencoding="utf-8", # type: Union[bool, Text] + subencoding="utf-8", # type: Text raise_subencoding_exc=False, # type: bool encode_stray_percents=False, # type: bool _decode_map=_HEX_CHAR_MAP # type: Mapping[bytes, bytes] ): - # type: (...) -> Union[str, bytes] + # type: (...) -> Text """Convert percent-encoded text characters to their normal, human-readable equivalents. @@ -582,18 +582,14 @@ def _percent_decode( delimiters, should be uppercased, per RFC 3986 Section 2.1. See :func:`_decode_path_part` for an example. subencoding: The name of the encoding underlying the percent-encoding. - Pass `False` to get back raw bytes. raise_subencoding_exc: Whether an error in decoding the bytes underlying the percent-decoding should be raised. Returns: - The percent-decoded version of *text*, decoded by *subencoding*, unless - `subencoding=False` which returns bytes. + The percent-decoded version of *text*, decoded by *subencoding*. """ try: - quoted_bytes = text.encode( - 'utf-8' if subencoding is False else cast(Text, subencoding) - ) + quoted_bytes = text.encode(subencoding) except UnicodeEncodeError: return text @@ -627,10 +623,8 @@ def _percent_decode( unquoted_bytes = b''.join(res) - if subencoding is False: - return unquoted_bytes try: - return unquoted_bytes.decode(cast(Text, subencoding)) + return unquoted_bytes.decode(subencoding) except UnicodeDecodeError: if raise_subencoding_exc: raise @@ -1284,8 +1278,8 @@ def normalize(self, scheme=True, host=True, path=True, query=True, def _dec_unres(target): # type: (Text) -> Union[Text] - return cast(Text, _decode_unreserved( - target, normalize_case=True, encode_stray_percents=percents) + return _decode_unreserved( + target, normalize_case=True, encode_stray_percents=percents ) if path: if self.path: @@ -1477,27 +1471,22 @@ def to_iri(self): hostname decoded for display purposes. """ # noqa: E501 new_userinfo = u':'.join([ - cast(Text, _decode_userinfo_part(p)) - for p in self.userinfo.split(':', 1) + _decode_userinfo_part(p) for p in self.userinfo.split(':', 1) ]) host_text = _decode_host(self.host) return self.replace( userinfo=new_userinfo, host=host_text, - path=[ - cast(Text, _decode_path_part(segment)) - for segment in self.path - ], + path=[_decode_path_part(segment) for segment in self.path], query=tuple( ( - cast(Text, _decode_query_key(k)), - cast(Optional[Text], _decode_query_value(v)) + _decode_query_key(k), _decode_query_value(v) if v is not None else None ) for k, v in self.query ), - fragment=cast(Text, _decode_fragment_part(self.fragment)), + fragment=_decode_fragment_part(self.fragment), ) def to_text(self, with_password=False): @@ -1879,7 +1868,7 @@ def path(self): except AttributeError: pass self._path = tuple([ - cast(Text, _percent_decode(p, raise_subencoding_exc=True)) + _percent_decode(p, raise_subencoding_exc=True) for p in self._url.path ]) return self._path @@ -1896,7 +1885,7 @@ def query(self): pass self._query = cast(QueryPairs, tuple( tuple( - cast(Text, _percent_decode(x, raise_subencoding_exc=True)) + _percent_decode(x, raise_subencoding_exc=True) if x is not None else None for x in (k, v) ) @@ -1915,9 +1904,7 @@ def fragment(self): except AttributeError: pass frag = self._url.fragment - self._fragment = cast( - Text, _percent_decode(frag, raise_subencoding_exc=True) - ) + self._fragment = _percent_decode(frag, raise_subencoding_exc=True) return self._fragment @property @@ -1934,7 +1921,7 @@ def userinfo(self): Union[Tuple[str], Tuple[str, str]], tuple( tuple( - cast(Text, _percent_decode(p, raise_subencoding_exc=True)) + _percent_decode(p, raise_subencoding_exc=True) for p in self._url.userinfo.split(':', 1) ) ) diff --git a/src/hyperlink/test/test_decoded_url.py b/src/hyperlink/test/test_decoded_url.py index 98c1565d..7942c03a 100644 --- a/src/hyperlink/test/test_decoded_url.py +++ b/src/hyperlink/test/test_decoded_url.py @@ -185,10 +185,6 @@ def test_twisted_compat(self): assert 'asText' not in dir(durl) assert durl.to_text() == durl.asText() - def test_percent_decode_bytes(self): - # type: () -> None - assert _percent_decode('%00', subencoding=False) == b'\0' - def test_percent_decode_mixed(self): # type: () -> None @@ -203,9 +199,6 @@ def test_percent_decode_mixed(self): with self.assertRaises(UnicodeDecodeError): _percent_decode('abcdé%C3éfg', raise_subencoding_exc=True) - # check that getting raw bytes works ok - assert _percent_decode('a%00b', subencoding=False) == b'a\x00b' - # when not encodable as subencoding assert _percent_decode('é%25é', subencoding='ascii') == 'é%25é' From 1e96fdead706e83e6df20206c3328e7677f81f95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Mon, 4 Nov 2019 12:26:58 -0800 Subject: [PATCH 063/419] Don't need Union here. --- src/hyperlink/_url.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index 1573e2c2..ace9f49c 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -1277,7 +1277,7 @@ def normalize(self, scheme=True, host=True, path=True, query=True, kw['host'] = self.host.lower() def _dec_unres(target): - # type: (Text) -> Union[Text] + # type: (Text) -> Text return _decode_unreserved( target, normalize_case=True, encode_stray_percents=percents ) From 5331f893d8dffe3e0123caaf4c9be928c3a88310 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Mon, 4 Nov 2019 12:56:46 -0800 Subject: [PATCH 064/419] href is allowed to be a URL --- src/hyperlink/_url.py | 8 ++++---- src/hyperlink/test/test_decoded_url.py | 2 +- src/hyperlink/test/test_url.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index ace9f49c..91a4635c 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -1350,7 +1350,7 @@ def sibling(self, segment): return self.replace(path=new_path) def click(self, href=u''): - # type: (Text) -> URL + # type: (Union[Text, URL]) -> URL """Resolve the given URL relative to this URL. The resulting URI should match what a web browser would @@ -1375,7 +1375,7 @@ def click(self, href=u''): """ if href: if isinstance(href, URL): - clicked = href # type: ignore[misc] unreachable + clicked = href else: # TODO: This error message is not completely accurate, # as URL objects are now also valid, but Twisted's @@ -1795,12 +1795,12 @@ def to_iri(self): return self._url.to_iri() def click(self, href=u''): - # type: (Text) -> DecodedURL + # type: (Union[Text, URL, DecodedURL]) -> DecodedURL """Return a new DecodedURL wrapping the result of :meth:`~hyperlink.URL.click()` """ if isinstance(href, DecodedURL): - href = href._url # type: ignore[misc] unreachable + href = href._url return self.__class__(self._url.click(href=href)) def sibling(self, segment): diff --git a/src/hyperlink/test/test_decoded_url.py b/src/hyperlink/test/test_decoded_url.py index 7942c03a..f00052ad 100644 --- a/src/hyperlink/test/test_decoded_url.py +++ b/src/hyperlink/test/test_decoded_url.py @@ -207,7 +207,7 @@ def test_click_decoded_url(self): durl = DecodedURL.from_text(TOTAL_URL) durl_dest = DecodedURL.from_text('/tëst') - clicked = durl.click(durl_dest) # type: ignore[arg-type] URL vs Text + clicked = durl.click(durl_dest) assert clicked.host == durl.host assert clicked.path == durl_dest.path assert clicked.path == ('tëst',) diff --git a/src/hyperlink/test/test_url.py b/src/hyperlink/test/test_url.py index cdb25147..6ee4d172 100644 --- a/src/hyperlink/test/test_url.py +++ b/src/hyperlink/test/test_url.py @@ -430,7 +430,7 @@ def test_click(self): # test click on a URL instance u = URL.fromText('http://localhost/foo/?abc=def') u2 = URL.from_text('bar') - u3 = u.click(u2) # type: ignore[arg-type] URL vs Text + u3 = u.click(u2) self.assertEqual(u3.to_text(), 'http://localhost/foo/bar') def test_clickRFC3986(self): From 6bafade9b4606afca34a387f61bf517d02e18249 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Mon, 4 Nov 2019 13:02:57 -0800 Subject: [PATCH 065/419] No longer need this change --- src/hyperlink/_url.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index 91a4635c..a7bb914b 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -1652,7 +1652,7 @@ def set(self, name, value=None): A new :class:`URL` instance with the parameter set. """ # Preserve the original position of the query key in the list - q = [(k, v) for (k, v) in self._query if k != name] + q = [(k, v) for (k, v) in self.query if k != name] idx = next((i for (i, (k, v)) in enumerate(self.query) if k == name), -1) q[idx:idx] = [(name, value)] From c856e9b39ac3a44db945d956b1f110d558f0b8e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Mon, 4 Nov 2019 13:11:11 -0800 Subject: [PATCH 066/419] Not needed --- tox.ini | 3 --- 1 file changed, 3 deletions(-) diff --git a/tox.ini b/tox.ini index 8514ab9c..150129d4 100644 --- a/tox.ini +++ b/tox.ini @@ -111,9 +111,6 @@ ignore = # variable in function should be lowercase N806, - # lowercase imported as non lowercase - N812, - # variable in class scope should not be mixedCase N815, From b7c0789a967c65dcf3f914fdc5ba7d0cc5d584df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Mon, 4 Nov 2019 13:56:08 -0800 Subject: [PATCH 067/419] Enable check_untyped_defs --- tox.ini | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tox.ini b/tox.ini index 150129d4..0719c70d 100644 --- a/tox.ini +++ b/tox.ini @@ -156,6 +156,7 @@ commands = # Global settings +check_untyped_defs = True disallow_incomplete_defs = True disallow_untyped_defs = True no_implicit_optional = True @@ -168,9 +169,6 @@ warn_return_any = True warn_unreachable = True warn_unused_ignores = True -# Enable these over time -check_untyped_defs = False - # Disable some checks until effected files fully adopt mypy [mypy-idna] From 67e482c792158e5cc66c97aec99933dd5daaee03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Mon, 4 Nov 2019 13:58:04 -0800 Subject: [PATCH 068/419] Better comment --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 0719c70d..8637f403 100644 --- a/tox.ini +++ b/tox.ini @@ -169,7 +169,7 @@ warn_return_any = True warn_unreachable = True warn_unused_ignores = True -# Disable some checks until effected files fully adopt mypy +# Don't complain about dependencies known to lack type hints [mypy-idna] ignore_missing_imports = True From 67098bead4bf3cb35450ace85642591265bea005 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Wed, 6 Nov 2019 12:46:45 -0800 Subject: [PATCH 069/419] Add hypothesis strategies --- .gitignore | 2 +- src/hyperlink/idna-tables-properties.csv | 2471 ++++++++++++++++++++++ src/hyperlink/strategies.py | 280 +++ src/hyperlink/test/test_socket.py | 3 +- src/hyperlink/test/test_strategies.py | 172 ++ tox.ini | 11 + 6 files changed, 2936 insertions(+), 3 deletions(-) create mode 100644 src/hyperlink/idna-tables-properties.csv create mode 100644 src/hyperlink/strategies.py create mode 100644 src/hyperlink/test/test_strategies.py diff --git a/.gitignore b/.gitignore index 35a65f26..6154e510 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,7 @@ tmp.py htmlcov/ .coverage.* *.py[cod] -.mypy_cache +/.hypothesis/ # emacs *~ diff --git a/src/hyperlink/idna-tables-properties.csv b/src/hyperlink/idna-tables-properties.csv new file mode 100644 index 00000000..062ee6bf --- /dev/null +++ b/src/hyperlink/idna-tables-properties.csv @@ -0,0 +1,2471 @@ +Codepoint,Property,Description +0000-002C,DISALLOWED,NULL..COMMA +002D,PVALID,HYPHEN-MINUS +002E-002F,DISALLOWED,FULL STOP..SOLIDUS +0030-0039,PVALID,DIGIT ZERO..DIGIT NINE +003A-0060,DISALLOWED,COLON..GRAVE ACCENT +0061-007A,PVALID,LATIN SMALL LETTER A..LATIN SMALL LETTER Z +007B-00B6,DISALLOWED,LEFT CURLY BRACKET..PILCROW SIGN +00B7,CONTEXTO,MIDDLE DOT +00B8-00DE,DISALLOWED,CEDILLA..LATIN CAPITAL LETTER THORN +00DF-00F6,PVALID,LATIN SMALL LETTER SHARP S..LATIN SMALL LETTER O WITH DIAERESIS +00F7,DISALLOWED,DIVISION SIGN +00F8-00FF,PVALID,LATIN SMALL LETTER O WITH STROKE..LATIN SMALL LETTER Y WITH DIAERESIS +0100,DISALLOWED,LATIN CAPITAL LETTER A WITH MACRON +0101,PVALID,LATIN SMALL LETTER A WITH MACRON +0102,DISALLOWED,LATIN CAPITAL LETTER A WITH BREVE +0103,PVALID,LATIN SMALL LETTER A WITH BREVE +0104,DISALLOWED,LATIN CAPITAL LETTER A WITH OGONEK +0105,PVALID,LATIN SMALL LETTER A WITH OGONEK +0106,DISALLOWED,LATIN CAPITAL LETTER C WITH ACUTE +0107,PVALID,LATIN SMALL LETTER C WITH ACUTE +0108,DISALLOWED,LATIN CAPITAL LETTER C WITH CIRCUMFLEX +0109,PVALID,LATIN SMALL LETTER C WITH CIRCUMFLEX +010A,DISALLOWED,LATIN CAPITAL LETTER C WITH DOT ABOVE +010B,PVALID,LATIN SMALL LETTER C WITH DOT ABOVE +010C,DISALLOWED,LATIN CAPITAL LETTER C WITH CARON +010D,PVALID,LATIN SMALL LETTER C WITH CARON +010E,DISALLOWED,LATIN CAPITAL LETTER D WITH CARON +010F,PVALID,LATIN SMALL LETTER D WITH CARON +0110,DISALLOWED,LATIN CAPITAL LETTER D WITH STROKE +0111,PVALID,LATIN SMALL LETTER D WITH STROKE +0112,DISALLOWED,LATIN CAPITAL LETTER E WITH MACRON +0113,PVALID,LATIN SMALL LETTER E WITH MACRON +0114,DISALLOWED,LATIN CAPITAL LETTER E WITH BREVE +0115,PVALID,LATIN SMALL LETTER E WITH BREVE +0116,DISALLOWED,LATIN CAPITAL LETTER E WITH DOT ABOVE +0117,PVALID,LATIN SMALL LETTER E WITH DOT ABOVE +0118,DISALLOWED,LATIN CAPITAL LETTER E WITH OGONEK +0119,PVALID,LATIN SMALL LETTER E WITH OGONEK +011A,DISALLOWED,LATIN CAPITAL LETTER E WITH CARON +011B,PVALID,LATIN SMALL LETTER E WITH CARON +011C,DISALLOWED,LATIN CAPITAL LETTER G WITH CIRCUMFLEX +011D,PVALID,LATIN SMALL LETTER G WITH CIRCUMFLEX +011E,DISALLOWED,LATIN CAPITAL LETTER G WITH BREVE +011F,PVALID,LATIN SMALL LETTER G WITH BREVE +0120,DISALLOWED,LATIN CAPITAL LETTER G WITH DOT ABOVE +0121,PVALID,LATIN SMALL LETTER G WITH DOT ABOVE +0122,DISALLOWED,LATIN CAPITAL LETTER G WITH CEDILLA +0123,PVALID,LATIN SMALL LETTER G WITH CEDILLA +0124,DISALLOWED,LATIN CAPITAL LETTER H WITH CIRCUMFLEX +0125,PVALID,LATIN SMALL LETTER H WITH CIRCUMFLEX +0126,DISALLOWED,LATIN CAPITAL LETTER H WITH STROKE +0127,PVALID,LATIN SMALL LETTER H WITH STROKE +0128,DISALLOWED,LATIN CAPITAL LETTER I WITH TILDE +0129,PVALID,LATIN SMALL LETTER I WITH TILDE +012A,DISALLOWED,LATIN CAPITAL LETTER I WITH MACRON +012B,PVALID,LATIN SMALL LETTER I WITH MACRON +012C,DISALLOWED,LATIN CAPITAL LETTER I WITH BREVE +012D,PVALID,LATIN SMALL LETTER I WITH BREVE +012E,DISALLOWED,LATIN CAPITAL LETTER I WITH OGONEK +012F,PVALID,LATIN SMALL LETTER I WITH OGONEK +0130,DISALLOWED,LATIN CAPITAL LETTER I WITH DOT ABOVE +0131,PVALID,LATIN SMALL LETTER DOTLESS I +0132-0134,DISALLOWED,LATIN CAPITAL LIGATURE IJ..LATIN CAPITAL LETTER J WITH CIRCUMFLEX +0135,PVALID,LATIN SMALL LETTER J WITH CIRCUMFLEX +0136,DISALLOWED,LATIN CAPITAL LETTER K WITH CEDILLA +0137-0138,PVALID,LATIN SMALL LETTER K WITH CEDILLA..LATIN SMALL LETTER KRA +0139,DISALLOWED,LATIN CAPITAL LETTER L WITH ACUTE +013A,PVALID,LATIN SMALL LETTER L WITH ACUTE +013B,DISALLOWED,LATIN CAPITAL LETTER L WITH CEDILLA +013C,PVALID,LATIN SMALL LETTER L WITH CEDILLA +013D,DISALLOWED,LATIN CAPITAL LETTER L WITH CARON +013E,PVALID,LATIN SMALL LETTER L WITH CARON +013F-0141,DISALLOWED,LATIN CAPITAL LETTER L WITH MIDDLE DOT..LATIN CAPITAL LETTER L WITH STROKE +0142,PVALID,LATIN SMALL LETTER L WITH STROKE +0143,DISALLOWED,LATIN CAPITAL LETTER N WITH ACUTE +0144,PVALID,LATIN SMALL LETTER N WITH ACUTE +0145,DISALLOWED,LATIN CAPITAL LETTER N WITH CEDILLA +0146,PVALID,LATIN SMALL LETTER N WITH CEDILLA +0147,DISALLOWED,LATIN CAPITAL LETTER N WITH CARON +0148,PVALID,LATIN SMALL LETTER N WITH CARON +0149-014A,DISALLOWED,LATIN SMALL LETTER N PRECEDED BY APOSTROPHE..LATIN CAPITAL LETTER ENG +014B,PVALID,LATIN SMALL LETTER ENG +014C,DISALLOWED,LATIN CAPITAL LETTER O WITH MACRON +014D,PVALID,LATIN SMALL LETTER O WITH MACRON +014E,DISALLOWED,LATIN CAPITAL LETTER O WITH BREVE +014F,PVALID,LATIN SMALL LETTER O WITH BREVE +0150,DISALLOWED,LATIN CAPITAL LETTER O WITH DOUBLE ACUTE +0151,PVALID,LATIN SMALL LETTER O WITH DOUBLE ACUTE +0152,DISALLOWED,LATIN CAPITAL LIGATURE OE +0153,PVALID,LATIN SMALL LIGATURE OE +0154,DISALLOWED,LATIN CAPITAL LETTER R WITH ACUTE +0155,PVALID,LATIN SMALL LETTER R WITH ACUTE +0156,DISALLOWED,LATIN CAPITAL LETTER R WITH CEDILLA +0157,PVALID,LATIN SMALL LETTER R WITH CEDILLA +0158,DISALLOWED,LATIN CAPITAL LETTER R WITH CARON +0159,PVALID,LATIN SMALL LETTER R WITH CARON +015A,DISALLOWED,LATIN CAPITAL LETTER S WITH ACUTE +015B,PVALID,LATIN SMALL LETTER S WITH ACUTE +015C,DISALLOWED,LATIN CAPITAL LETTER S WITH CIRCUMFLEX +015D,PVALID,LATIN SMALL LETTER S WITH CIRCUMFLEX +015E,DISALLOWED,LATIN CAPITAL LETTER S WITH CEDILLA +015F,PVALID,LATIN SMALL LETTER S WITH CEDILLA +0160,DISALLOWED,LATIN CAPITAL LETTER S WITH CARON +0161,PVALID,LATIN SMALL LETTER S WITH CARON +0162,DISALLOWED,LATIN CAPITAL LETTER T WITH CEDILLA +0163,PVALID,LATIN SMALL LETTER T WITH CEDILLA +0164,DISALLOWED,LATIN CAPITAL LETTER T WITH CARON +0165,PVALID,LATIN SMALL LETTER T WITH CARON +0166,DISALLOWED,LATIN CAPITAL LETTER T WITH STROKE +0167,PVALID,LATIN SMALL LETTER T WITH STROKE +0168,DISALLOWED,LATIN CAPITAL LETTER U WITH TILDE +0169,PVALID,LATIN SMALL LETTER U WITH TILDE +016A,DISALLOWED,LATIN CAPITAL LETTER U WITH MACRON +016B,PVALID,LATIN SMALL LETTER U WITH MACRON +016C,DISALLOWED,LATIN CAPITAL LETTER U WITH BREVE +016D,PVALID,LATIN SMALL LETTER U WITH BREVE +016E,DISALLOWED,LATIN CAPITAL LETTER U WITH RING ABOVE +016F,PVALID,LATIN SMALL LETTER U WITH RING ABOVE +0170,DISALLOWED,LATIN CAPITAL LETTER U WITH DOUBLE ACUTE +0171,PVALID,LATIN SMALL LETTER U WITH DOUBLE ACUTE +0172,DISALLOWED,LATIN CAPITAL LETTER U WITH OGONEK +0173,PVALID,LATIN SMALL LETTER U WITH OGONEK +0174,DISALLOWED,LATIN CAPITAL LETTER W WITH CIRCUMFLEX +0175,PVALID,LATIN SMALL LETTER W WITH CIRCUMFLEX +0176,DISALLOWED,LATIN CAPITAL LETTER Y WITH CIRCUMFLEX +0177,PVALID,LATIN SMALL LETTER Y WITH CIRCUMFLEX +0178-0179,DISALLOWED,LATIN CAPITAL LETTER Y WITH DIAERESIS..LATIN CAPITAL LETTER Z WITH ACUTE +017A,PVALID,LATIN SMALL LETTER Z WITH ACUTE +017B,DISALLOWED,LATIN CAPITAL LETTER Z WITH DOT ABOVE +017C,PVALID,LATIN SMALL LETTER Z WITH DOT ABOVE +017D,DISALLOWED,LATIN CAPITAL LETTER Z WITH CARON +017E,PVALID,LATIN SMALL LETTER Z WITH CARON +017F,DISALLOWED,LATIN SMALL LETTER LONG S +0180,PVALID,LATIN SMALL LETTER B WITH STROKE +0181-0182,DISALLOWED,LATIN CAPITAL LETTER B WITH HOOK..LATIN CAPITAL LETTER B WITH TOPBAR +0183,PVALID,LATIN SMALL LETTER B WITH TOPBAR +0184,DISALLOWED,LATIN CAPITAL LETTER TONE SIX +0185,PVALID,LATIN SMALL LETTER TONE SIX +0186-0187,DISALLOWED,LATIN CAPITAL LETTER OPEN O..LATIN CAPITAL LETTER C WITH HOOK +0188,PVALID,LATIN SMALL LETTER C WITH HOOK +0189-018B,DISALLOWED,LATIN CAPITAL LETTER AFRICAN D..LATIN CAPITAL LETTER D WITH TOPBAR +018C-018D,PVALID,LATIN SMALL LETTER D WITH TOPBAR..LATIN SMALL LETTER TURNED DELTA +018E-0191,DISALLOWED,LATIN CAPITAL LETTER REVERSED E..LATIN CAPITAL LETTER F WITH HOOK +0192,PVALID,LATIN SMALL LETTER F WITH HOOK +0193-0194,DISALLOWED,LATIN CAPITAL LETTER G WITH HOOK..LATIN CAPITAL LETTER GAMMA +0195,PVALID,LATIN SMALL LETTER HV +0196-0198,DISALLOWED,LATIN CAPITAL LETTER IOTA..LATIN CAPITAL LETTER K WITH HOOK +0199-019B,PVALID,LATIN SMALL LETTER K WITH HOOK..LATIN SMALL LETTER LAMBDA WITH STROKE +019C-019D,DISALLOWED,LATIN CAPITAL LETTER TURNED M..LATIN CAPITAL LETTER N WITH LEFT HOOK +019E,PVALID,LATIN SMALL LETTER N WITH LONG RIGHT LEG +019F-01A0,DISALLOWED,LATIN CAPITAL LETTER O WITH MIDDLE TILDE..LATIN CAPITAL LETTER O WITH HORN +01A1,PVALID,LATIN SMALL LETTER O WITH HORN +01A2,DISALLOWED,LATIN CAPITAL LETTER OI +01A3,PVALID,LATIN SMALL LETTER OI +01A4,DISALLOWED,LATIN CAPITAL LETTER P WITH HOOK +01A5,PVALID,LATIN SMALL LETTER P WITH HOOK +01A6-01A7,DISALLOWED,LATIN LETTER YR..LATIN CAPITAL LETTER TONE TWO +01A8,PVALID,LATIN SMALL LETTER TONE TWO +01A9,DISALLOWED,LATIN CAPITAL LETTER ESH +01AA-01AB,PVALID,LATIN LETTER REVERSED ESH LOOP..LATIN SMALL LETTER T WITH PALATAL HOOK +01AC,DISALLOWED,LATIN CAPITAL LETTER T WITH HOOK +01AD,PVALID,LATIN SMALL LETTER T WITH HOOK +01AE-01AF,DISALLOWED,LATIN CAPITAL LETTER T WITH RETROFLEX HOOK..LATIN CAPITAL LETTER U WITH HORN +01B0,PVALID,LATIN SMALL LETTER U WITH HORN +01B1-01B3,DISALLOWED,LATIN CAPITAL LETTER UPSILON..LATIN CAPITAL LETTER Y WITH HOOK +01B4,PVALID,LATIN SMALL LETTER Y WITH HOOK +01B5,DISALLOWED,LATIN CAPITAL LETTER Z WITH STROKE +01B6,PVALID,LATIN SMALL LETTER Z WITH STROKE +01B7-01B8,DISALLOWED,LATIN CAPITAL LETTER EZH..LATIN CAPITAL LETTER EZH REVERSED +01B9-01BB,PVALID,LATIN SMALL LETTER EZH REVERSED..LATIN LETTER TWO WITH STROKE +01BC,DISALLOWED,LATIN CAPITAL LETTER TONE FIVE +01BD-01C3,PVALID,LATIN SMALL LETTER TONE FIVE..LATIN LETTER RETROFLEX CLICK +01C4-01CD,DISALLOWED,LATIN CAPITAL LETTER DZ WITH CARON..LATIN CAPITAL LETTER A WITH CARON +01CE,PVALID,LATIN SMALL LETTER A WITH CARON +01CF,DISALLOWED,LATIN CAPITAL LETTER I WITH CARON +01D0,PVALID,LATIN SMALL LETTER I WITH CARON +01D1,DISALLOWED,LATIN CAPITAL LETTER O WITH CARON +01D2,PVALID,LATIN SMALL LETTER O WITH CARON +01D3,DISALLOWED,LATIN CAPITAL LETTER U WITH CARON +01D4,PVALID,LATIN SMALL LETTER U WITH CARON +01D5,DISALLOWED,LATIN CAPITAL LETTER U WITH DIAERESIS AND MACRON +01D6,PVALID,LATIN SMALL LETTER U WITH DIAERESIS AND MACRON +01D7,DISALLOWED,LATIN CAPITAL LETTER U WITH DIAERESIS AND ACUTE +01D8,PVALID,LATIN SMALL LETTER U WITH DIAERESIS AND ACUTE +01D9,DISALLOWED,LATIN CAPITAL LETTER U WITH DIAERESIS AND CARON +01DA,PVALID,LATIN SMALL LETTER U WITH DIAERESIS AND CARON +01DB,DISALLOWED,LATIN CAPITAL LETTER U WITH DIAERESIS AND GRAVE +01DC-01DD,PVALID,LATIN SMALL LETTER U WITH DIAERESIS AND GRAVE..LATIN SMALL LETTER TURNED E +01DE,DISALLOWED,LATIN CAPITAL LETTER A WITH DIAERESIS AND MACRON +01DF,PVALID,LATIN SMALL LETTER A WITH DIAERESIS AND MACRON +01E0,DISALLOWED,LATIN CAPITAL LETTER A WITH DOT ABOVE AND MACRON +01E1,PVALID,LATIN SMALL LETTER A WITH DOT ABOVE AND MACRON +01E2,DISALLOWED,LATIN CAPITAL LETTER AE WITH MACRON +01E3,PVALID,LATIN SMALL LETTER AE WITH MACRON +01E4,DISALLOWED,LATIN CAPITAL LETTER G WITH STROKE +01E5,PVALID,LATIN SMALL LETTER G WITH STROKE +01E6,DISALLOWED,LATIN CAPITAL LETTER G WITH CARON +01E7,PVALID,LATIN SMALL LETTER G WITH CARON +01E8,DISALLOWED,LATIN CAPITAL LETTER K WITH CARON +01E9,PVALID,LATIN SMALL LETTER K WITH CARON +01EA,DISALLOWED,LATIN CAPITAL LETTER O WITH OGONEK +01EB,PVALID,LATIN SMALL LETTER O WITH OGONEK +01EC,DISALLOWED,LATIN CAPITAL LETTER O WITH OGONEK AND MACRON +01ED,PVALID,LATIN SMALL LETTER O WITH OGONEK AND MACRON +01EE,DISALLOWED,LATIN CAPITAL LETTER EZH WITH CARON +01EF-01F0,PVALID,LATIN SMALL LETTER EZH WITH CARON..LATIN SMALL LETTER J WITH CARON +01F1-01F4,DISALLOWED,LATIN CAPITAL LETTER DZ..LATIN CAPITAL LETTER G WITH ACUTE +01F5,PVALID,LATIN SMALL LETTER G WITH ACUTE +01F6-01F8,DISALLOWED,LATIN CAPITAL LETTER HWAIR..LATIN CAPITAL LETTER N WITH GRAVE +01F9,PVALID,LATIN SMALL LETTER N WITH GRAVE +01FA,DISALLOWED,LATIN CAPITAL LETTER A WITH RING ABOVE AND ACUTE +01FB,PVALID,LATIN SMALL LETTER A WITH RING ABOVE AND ACUTE +01FC,DISALLOWED,LATIN CAPITAL LETTER AE WITH ACUTE +01FD,PVALID,LATIN SMALL LETTER AE WITH ACUTE +01FE,DISALLOWED,LATIN CAPITAL LETTER O WITH STROKE AND ACUTE +01FF,PVALID,LATIN SMALL LETTER O WITH STROKE AND ACUTE +0200,DISALLOWED,LATIN CAPITAL LETTER A WITH DOUBLE GRAVE +0201,PVALID,LATIN SMALL LETTER A WITH DOUBLE GRAVE +0202,DISALLOWED,LATIN CAPITAL LETTER A WITH INVERTED BREVE +0203,PVALID,LATIN SMALL LETTER A WITH INVERTED BREVE +0204,DISALLOWED,LATIN CAPITAL LETTER E WITH DOUBLE GRAVE +0205,PVALID,LATIN SMALL LETTER E WITH DOUBLE GRAVE +0206,DISALLOWED,LATIN CAPITAL LETTER E WITH INVERTED BREVE +0207,PVALID,LATIN SMALL LETTER E WITH INVERTED BREVE +0208,DISALLOWED,LATIN CAPITAL LETTER I WITH DOUBLE GRAVE +0209,PVALID,LATIN SMALL LETTER I WITH DOUBLE GRAVE +020A,DISALLOWED,LATIN CAPITAL LETTER I WITH INVERTED BREVE +020B,PVALID,LATIN SMALL LETTER I WITH INVERTED BREVE +020C,DISALLOWED,LATIN CAPITAL LETTER O WITH DOUBLE GRAVE +020D,PVALID,LATIN SMALL LETTER O WITH DOUBLE GRAVE +020E,DISALLOWED,LATIN CAPITAL LETTER O WITH INVERTED BREVE +020F,PVALID,LATIN SMALL LETTER O WITH INVERTED BREVE +0210,DISALLOWED,LATIN CAPITAL LETTER R WITH DOUBLE GRAVE +0211,PVALID,LATIN SMALL LETTER R WITH DOUBLE GRAVE +0212,DISALLOWED,LATIN CAPITAL LETTER R WITH INVERTED BREVE +0213,PVALID,LATIN SMALL LETTER R WITH INVERTED BREVE +0214,DISALLOWED,LATIN CAPITAL LETTER U WITH DOUBLE GRAVE +0215,PVALID,LATIN SMALL LETTER U WITH DOUBLE GRAVE +0216,DISALLOWED,LATIN CAPITAL LETTER U WITH INVERTED BREVE +0217,PVALID,LATIN SMALL LETTER U WITH INVERTED BREVE +0218,DISALLOWED,LATIN CAPITAL LETTER S WITH COMMA BELOW +0219,PVALID,LATIN SMALL LETTER S WITH COMMA BELOW +021A,DISALLOWED,LATIN CAPITAL LETTER T WITH COMMA BELOW +021B,PVALID,LATIN SMALL LETTER T WITH COMMA BELOW +021C,DISALLOWED,LATIN CAPITAL LETTER YOGH +021D,PVALID,LATIN SMALL LETTER YOGH +021E,DISALLOWED,LATIN CAPITAL LETTER H WITH CARON +021F,PVALID,LATIN SMALL LETTER H WITH CARON +0220,DISALLOWED,LATIN CAPITAL LETTER N WITH LONG RIGHT LEG +0221,PVALID,LATIN SMALL LETTER D WITH CURL +0222,DISALLOWED,LATIN CAPITAL LETTER OU +0223,PVALID,LATIN SMALL LETTER OU +0224,DISALLOWED,LATIN CAPITAL LETTER Z WITH HOOK +0225,PVALID,LATIN SMALL LETTER Z WITH HOOK +0226,DISALLOWED,LATIN CAPITAL LETTER A WITH DOT ABOVE +0227,PVALID,LATIN SMALL LETTER A WITH DOT ABOVE +0228,DISALLOWED,LATIN CAPITAL LETTER E WITH CEDILLA +0229,PVALID,LATIN SMALL LETTER E WITH CEDILLA +022A,DISALLOWED,LATIN CAPITAL LETTER O WITH DIAERESIS AND MACRON +022B,PVALID,LATIN SMALL LETTER O WITH DIAERESIS AND MACRON +022C,DISALLOWED,LATIN CAPITAL LETTER O WITH TILDE AND MACRON +022D,PVALID,LATIN SMALL LETTER O WITH TILDE AND MACRON +022E,DISALLOWED,LATIN CAPITAL LETTER O WITH DOT ABOVE +022F,PVALID,LATIN SMALL LETTER O WITH DOT ABOVE +0230,DISALLOWED,LATIN CAPITAL LETTER O WITH DOT ABOVE AND MACRON +0231,PVALID,LATIN SMALL LETTER O WITH DOT ABOVE AND MACRON +0232,DISALLOWED,LATIN CAPITAL LETTER Y WITH MACRON +0233-0239,PVALID,LATIN SMALL LETTER Y WITH MACRON..LATIN SMALL LETTER QP DIGRAPH +023A-023B,DISALLOWED,LATIN CAPITAL LETTER A WITH STROKE..LATIN CAPITAL LETTER C WITH STROKE +023C,PVALID,LATIN SMALL LETTER C WITH STROKE +023D-023E,DISALLOWED,LATIN CAPITAL LETTER L WITH BAR..LATIN CAPITAL LETTER T WITH DIAGONAL STROKE +023F-0240,PVALID,LATIN SMALL LETTER S WITH SWASH TAIL..LATIN SMALL LETTER Z WITH SWASH TAIL +0241,DISALLOWED,LATIN CAPITAL LETTER GLOTTAL STOP +0242,PVALID,LATIN SMALL LETTER GLOTTAL STOP +0243-0246,DISALLOWED,LATIN CAPITAL LETTER B WITH STROKE..LATIN CAPITAL LETTER E WITH STROKE +0247,PVALID,LATIN SMALL LETTER E WITH STROKE +0248,DISALLOWED,LATIN CAPITAL LETTER J WITH STROKE +0249,PVALID,LATIN SMALL LETTER J WITH STROKE +024A,DISALLOWED,LATIN CAPITAL LETTER SMALL Q WITH HOOK TAIL +024B,PVALID,LATIN SMALL LETTER Q WITH HOOK TAIL +024C,DISALLOWED,LATIN CAPITAL LETTER R WITH STROKE +024D,PVALID,LATIN SMALL LETTER R WITH STROKE +024E,DISALLOWED,LATIN CAPITAL LETTER Y WITH STROKE +024F-02AF,PVALID,LATIN SMALL LETTER Y WITH STROKE..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL +02B0-02B8,DISALLOWED,MODIFIER LETTER SMALL H..MODIFIER LETTER SMALL Y +02B9-02C1,PVALID,MODIFIER LETTER PRIME..MODIFIER LETTER REVERSED GLOTTAL STOP +02C2-02C5,DISALLOWED,MODIFIER LETTER LEFT ARROWHEAD..MODIFIER LETTER DOWN ARROWHEAD +02C6-02D1,PVALID,MODIFIER LETTER CIRCUMFLEX ACCENT..MODIFIER LETTER HALF TRIANGULAR COLON +02D2-02EB,DISALLOWED,MODIFIER LETTER CENTRED RIGHT HALF RING..MODIFIER LETTER YANG DEPARTING TONE MARK +02EC,PVALID,MODIFIER LETTER VOICING +02ED,DISALLOWED,MODIFIER LETTER UNASPIRATED +02EE,PVALID,MODIFIER LETTER DOUBLE APOSTROPHE +02EF-02FF,DISALLOWED,MODIFIER LETTER LOW DOWN ARROWHEAD..MODIFIER LETTER LOW LEFT ARROW +0300-033F,PVALID,COMBINING GRAVE ACCENT..COMBINING DOUBLE OVERLINE +0340-0341,DISALLOWED,COMBINING GRAVE TONE MARK..COMBINING ACUTE TONE MARK +0342,PVALID,COMBINING GREEK PERISPOMENI +0343-0345,DISALLOWED,COMBINING GREEK KORONIS..COMBINING GREEK YPOGEGRAMMENI +0346-034E,PVALID,COMBINING BRIDGE ABOVE..COMBINING UPWARDS ARROW BELOW +034F,DISALLOWED,COMBINING GRAPHEME JOINER +0350-036F,PVALID,COMBINING RIGHT ARROWHEAD ABOVE..COMBINING LATIN SMALL LETTER X +0370,DISALLOWED,GREEK CAPITAL LETTER HETA +0371,PVALID,GREEK SMALL LETTER HETA +0372,DISALLOWED,GREEK CAPITAL LETTER ARCHAIC SAMPI +0373,PVALID,GREEK SMALL LETTER ARCHAIC SAMPI +0374,DISALLOWED,GREEK NUMERAL SIGN +0375,CONTEXTO,GREEK LOWER NUMERAL SIGN +0376,DISALLOWED,GREEK CAPITAL LETTER PAMPHYLIAN DIGAMMA +0377,PVALID,GREEK SMALL LETTER PAMPHYLIAN DIGAMMA +0378-0379,UNASSIGNED,.. +037A,DISALLOWED,GREEK YPOGEGRAMMENI +037B-037D,PVALID,GREEK SMALL REVERSED LUNATE SIGMA SYMBOL..GREEK SMALL REVERSED DOTTED LUNATE SIGMA SYMBOL +037E,DISALLOWED,GREEK QUESTION MARK +037F-0383,UNASSIGNED,.. +0384-038A,DISALLOWED,GREEK TONOS..GREEK CAPITAL LETTER IOTA WITH TONOS +038B,UNASSIGNED, +038C,DISALLOWED,GREEK CAPITAL LETTER OMICRON WITH TONOS +038D,UNASSIGNED, +038E-038F,DISALLOWED,GREEK CAPITAL LETTER UPSILON WITH TONOS..GREEK CAPITAL LETTER OMEGA WITH TONOS +0390,PVALID,GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS +0391-03A1,DISALLOWED,GREEK CAPITAL LETTER ALPHA..GREEK CAPITAL LETTER RHO +03A2,UNASSIGNED, +03A3-03AB,DISALLOWED,GREEK CAPITAL LETTER SIGMA..GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA +03AC-03CE,PVALID,GREEK SMALL LETTER ALPHA WITH TONOS..GREEK SMALL LETTER OMEGA WITH TONOS +03CF-03D6,DISALLOWED,GREEK CAPITAL KAI SYMBOL..GREEK PI SYMBOL +03D7,PVALID,GREEK KAI SYMBOL +03D8,DISALLOWED,GREEK LETTER ARCHAIC KOPPA +03D9,PVALID,GREEK SMALL LETTER ARCHAIC KOPPA +03DA,DISALLOWED,GREEK LETTER STIGMA +03DB,PVALID,GREEK SMALL LETTER STIGMA +03DC,DISALLOWED,GREEK LETTER DIGAMMA +03DD,PVALID,GREEK SMALL LETTER DIGAMMA +03DE,DISALLOWED,GREEK LETTER KOPPA +03DF,PVALID,GREEK SMALL LETTER KOPPA +03E0,DISALLOWED,GREEK LETTER SAMPI +03E1,PVALID,GREEK SMALL LETTER SAMPI +03E2,DISALLOWED,COPTIC CAPITAL LETTER SHEI +03E3,PVALID,COPTIC SMALL LETTER SHEI +03E4,DISALLOWED,COPTIC CAPITAL LETTER FEI +03E5,PVALID,COPTIC SMALL LETTER FEI +03E6,DISALLOWED,COPTIC CAPITAL LETTER KHEI +03E7,PVALID,COPTIC SMALL LETTER KHEI +03E8,DISALLOWED,COPTIC CAPITAL LETTER HORI +03E9,PVALID,COPTIC SMALL LETTER HORI +03EA,DISALLOWED,COPTIC CAPITAL LETTER GANGIA +03EB,PVALID,COPTIC SMALL LETTER GANGIA +03EC,DISALLOWED,COPTIC CAPITAL LETTER SHIMA +03ED,PVALID,COPTIC SMALL LETTER SHIMA +03EE,DISALLOWED,COPTIC CAPITAL LETTER DEI +03EF,PVALID,COPTIC SMALL LETTER DEI +03F0-03F2,DISALLOWED,GREEK KAPPA SYMBOL..GREEK LUNATE SIGMA SYMBOL +03F3,PVALID,GREEK LETTER YOT +03F4-03F7,DISALLOWED,GREEK CAPITAL THETA SYMBOL..GREEK CAPITAL LETTER SHO +03F8,PVALID,GREEK SMALL LETTER SHO +03F9-03FA,DISALLOWED,GREEK CAPITAL LUNATE SIGMA SYMBOL..GREEK CAPITAL LETTER SAN +03FB-03FC,PVALID,GREEK SMALL LETTER SAN..GREEK RHO WITH STROKE SYMBOL +03FD-042F,DISALLOWED,GREEK CAPITAL REVERSED LUNATE SIGMA SYMBOL..CYRILLIC CAPITAL LETTER YA +0430-045F,PVALID,CYRILLIC SMALL LETTER A..CYRILLIC SMALL LETTER DZHE +0460,DISALLOWED,CYRILLIC CAPITAL LETTER OMEGA +0461,PVALID,CYRILLIC SMALL LETTER OMEGA +0462,DISALLOWED,CYRILLIC CAPITAL LETTER YAT +0463,PVALID,CYRILLIC SMALL LETTER YAT +0464,DISALLOWED,CYRILLIC CAPITAL LETTER IOTIFIED E +0465,PVALID,CYRILLIC SMALL LETTER IOTIFIED E +0466,DISALLOWED,CYRILLIC CAPITAL LETTER LITTLE YUS +0467,PVALID,CYRILLIC SMALL LETTER LITTLE YUS +0468,DISALLOWED,CYRILLIC CAPITAL LETTER IOTIFIED LITTLE YUS +0469,PVALID,CYRILLIC SMALL LETTER IOTIFIED LITTLE YUS +046A,DISALLOWED,CYRILLIC CAPITAL LETTER BIG YUS +046B,PVALID,CYRILLIC SMALL LETTER BIG YUS +046C,DISALLOWED,CYRILLIC CAPITAL LETTER IOTIFIED BIG YUS +046D,PVALID,CYRILLIC SMALL LETTER IOTIFIED BIG YUS +046E,DISALLOWED,CYRILLIC CAPITAL LETTER KSI +046F,PVALID,CYRILLIC SMALL LETTER KSI +0470,DISALLOWED,CYRILLIC CAPITAL LETTER PSI +0471,PVALID,CYRILLIC SMALL LETTER PSI +0472,DISALLOWED,CYRILLIC CAPITAL LETTER FITA +0473,PVALID,CYRILLIC SMALL LETTER FITA +0474,DISALLOWED,CYRILLIC CAPITAL LETTER IZHITSA +0475,PVALID,CYRILLIC SMALL LETTER IZHITSA +0476,DISALLOWED,CYRILLIC CAPITAL LETTER IZHITSA WITH DOUBLE GRAVE ACCENT +0477,PVALID,CYRILLIC SMALL LETTER IZHITSA WITH DOUBLE GRAVE ACCENT +0478,DISALLOWED,CYRILLIC CAPITAL LETTER UK +0479,PVALID,CYRILLIC SMALL LETTER UK +047A,DISALLOWED,CYRILLIC CAPITAL LETTER ROUND OMEGA +047B,PVALID,CYRILLIC SMALL LETTER ROUND OMEGA +047C,DISALLOWED,CYRILLIC CAPITAL LETTER OMEGA WITH TITLO +047D,PVALID,CYRILLIC SMALL LETTER OMEGA WITH TITLO +047E,DISALLOWED,CYRILLIC CAPITAL LETTER OT +047F,PVALID,CYRILLIC SMALL LETTER OT +0480,DISALLOWED,CYRILLIC CAPITAL LETTER KOPPA +0481,PVALID,CYRILLIC SMALL LETTER KOPPA +0482,DISALLOWED,CYRILLIC THOUSANDS SIGN +0483-0487,PVALID,COMBINING CYRILLIC TITLO..COMBINING CYRILLIC POKRYTIE +0488-048A,DISALLOWED,COMBINING CYRILLIC HUNDRED THOUSANDS SIGN..CYRILLIC CAPITAL LETTER SHORT I WITH TAIL +048B,PVALID,CYRILLIC SMALL LETTER SHORT I WITH TAIL +048C,DISALLOWED,CYRILLIC CAPITAL LETTER SEMISOFT SIGN +048D,PVALID,CYRILLIC SMALL LETTER SEMISOFT SIGN +048E,DISALLOWED,CYRILLIC CAPITAL LETTER ER WITH TICK +048F,PVALID,CYRILLIC SMALL LETTER ER WITH TICK +0490,DISALLOWED,CYRILLIC CAPITAL LETTER GHE WITH UPTURN +0491,PVALID,CYRILLIC SMALL LETTER GHE WITH UPTURN +0492,DISALLOWED,CYRILLIC CAPITAL LETTER GHE WITH STROKE +0493,PVALID,CYRILLIC SMALL LETTER GHE WITH STROKE +0494,DISALLOWED,CYRILLIC CAPITAL LETTER GHE WITH MIDDLE HOOK +0495,PVALID,CYRILLIC SMALL LETTER GHE WITH MIDDLE HOOK +0496,DISALLOWED,CYRILLIC CAPITAL LETTER ZHE WITH DESCENDER +0497,PVALID,CYRILLIC SMALL LETTER ZHE WITH DESCENDER +0498,DISALLOWED,CYRILLIC CAPITAL LETTER ZE WITH DESCENDER +0499,PVALID,CYRILLIC SMALL LETTER ZE WITH DESCENDER +049A,DISALLOWED,CYRILLIC CAPITAL LETTER KA WITH DESCENDER +049B,PVALID,CYRILLIC SMALL LETTER KA WITH DESCENDER +049C,DISALLOWED,CYRILLIC CAPITAL LETTER KA WITH VERTICAL STROKE +049D,PVALID,CYRILLIC SMALL LETTER KA WITH VERTICAL STROKE +049E,DISALLOWED,CYRILLIC CAPITAL LETTER KA WITH STROKE +049F,PVALID,CYRILLIC SMALL LETTER KA WITH STROKE +04A0,DISALLOWED,CYRILLIC CAPITAL LETTER BASHKIR KA +04A1,PVALID,CYRILLIC SMALL LETTER BASHKIR KA +04A2,DISALLOWED,CYRILLIC CAPITAL LETTER EN WITH DESCENDER +04A3,PVALID,CYRILLIC SMALL LETTER EN WITH DESCENDER +04A4,DISALLOWED,CYRILLIC CAPITAL LIGATURE EN GHE +04A5,PVALID,CYRILLIC SMALL LIGATURE EN GHE +04A6,DISALLOWED,CYRILLIC CAPITAL LETTER PE WITH MIDDLE HOOK +04A7,PVALID,CYRILLIC SMALL LETTER PE WITH MIDDLE HOOK +04A8,DISALLOWED,CYRILLIC CAPITAL LETTER ABKHASIAN HA +04A9,PVALID,CYRILLIC SMALL LETTER ABKHASIAN HA +04AA,DISALLOWED,CYRILLIC CAPITAL LETTER ES WITH DESCENDER +04AB,PVALID,CYRILLIC SMALL LETTER ES WITH DESCENDER +04AC,DISALLOWED,CYRILLIC CAPITAL LETTER TE WITH DESCENDER +04AD,PVALID,CYRILLIC SMALL LETTER TE WITH DESCENDER +04AE,DISALLOWED,CYRILLIC CAPITAL LETTER STRAIGHT U +04AF,PVALID,CYRILLIC SMALL LETTER STRAIGHT U +04B0,DISALLOWED,CYRILLIC CAPITAL LETTER STRAIGHT U WITH STROKE +04B1,PVALID,CYRILLIC SMALL LETTER STRAIGHT U WITH STROKE +04B2,DISALLOWED,CYRILLIC CAPITAL LETTER HA WITH DESCENDER +04B3,PVALID,CYRILLIC SMALL LETTER HA WITH DESCENDER +04B4,DISALLOWED,CYRILLIC CAPITAL LIGATURE TE TSE +04B5,PVALID,CYRILLIC SMALL LIGATURE TE TSE +04B6,DISALLOWED,CYRILLIC CAPITAL LETTER CHE WITH DESCENDER +04B7,PVALID,CYRILLIC SMALL LETTER CHE WITH DESCENDER +04B8,DISALLOWED,CYRILLIC CAPITAL LETTER CHE WITH VERTICAL STROKE +04B9,PVALID,CYRILLIC SMALL LETTER CHE WITH VERTICAL STROKE +04BA,DISALLOWED,CYRILLIC CAPITAL LETTER SHHA +04BB,PVALID,CYRILLIC SMALL LETTER SHHA +04BC,DISALLOWED,CYRILLIC CAPITAL LETTER ABKHASIAN CHE +04BD,PVALID,CYRILLIC SMALL LETTER ABKHASIAN CHE +04BE,DISALLOWED,CYRILLIC CAPITAL LETTER ABKHASIAN CHE WITH DESCENDER +04BF,PVALID,CYRILLIC SMALL LETTER ABKHASIAN CHE WITH DESCENDER +04C0-04C1,DISALLOWED,CYRILLIC LETTER PALOCHKA..CYRILLIC CAPITAL LETTER ZHE WITH BREVE +04C2,PVALID,CYRILLIC SMALL LETTER ZHE WITH BREVE +04C3,DISALLOWED,CYRILLIC CAPITAL LETTER KA WITH HOOK +04C4,PVALID,CYRILLIC SMALL LETTER KA WITH HOOK +04C5,DISALLOWED,CYRILLIC CAPITAL LETTER EL WITH TAIL +04C6,PVALID,CYRILLIC SMALL LETTER EL WITH TAIL +04C7,DISALLOWED,CYRILLIC CAPITAL LETTER EN WITH HOOK +04C8,PVALID,CYRILLIC SMALL LETTER EN WITH HOOK +04C9,DISALLOWED,CYRILLIC CAPITAL LETTER EN WITH TAIL +04CA,PVALID,CYRILLIC SMALL LETTER EN WITH TAIL +04CB,DISALLOWED,CYRILLIC CAPITAL LETTER KHAKASSIAN CHE +04CC,PVALID,CYRILLIC SMALL LETTER KHAKASSIAN CHE +04CD,DISALLOWED,CYRILLIC CAPITAL LETTER EM WITH TAIL +04CE-04CF,PVALID,CYRILLIC SMALL LETTER EM WITH TAIL..CYRILLIC SMALL LETTER PALOCHKA +04D0,DISALLOWED,CYRILLIC CAPITAL LETTER A WITH BREVE +04D1,PVALID,CYRILLIC SMALL LETTER A WITH BREVE +04D2,DISALLOWED,CYRILLIC CAPITAL LETTER A WITH DIAERESIS +04D3,PVALID,CYRILLIC SMALL LETTER A WITH DIAERESIS +04D4,DISALLOWED,CYRILLIC CAPITAL LIGATURE A IE +04D5,PVALID,CYRILLIC SMALL LIGATURE A IE +04D6,DISALLOWED,CYRILLIC CAPITAL LETTER IE WITH BREVE +04D7,PVALID,CYRILLIC SMALL LETTER IE WITH BREVE +04D8,DISALLOWED,CYRILLIC CAPITAL LETTER SCHWA +04D9,PVALID,CYRILLIC SMALL LETTER SCHWA +04DA,DISALLOWED,CYRILLIC CAPITAL LETTER SCHWA WITH DIAERESIS +04DB,PVALID,CYRILLIC SMALL LETTER SCHWA WITH DIAERESIS +04DC,DISALLOWED,CYRILLIC CAPITAL LETTER ZHE WITH DIAERESIS +04DD,PVALID,CYRILLIC SMALL LETTER ZHE WITH DIAERESIS +04DE,DISALLOWED,CYRILLIC CAPITAL LETTER ZE WITH DIAERESIS +04DF,PVALID,CYRILLIC SMALL LETTER ZE WITH DIAERESIS +04E0,DISALLOWED,CYRILLIC CAPITAL LETTER ABKHASIAN DZE +04E1,PVALID,CYRILLIC SMALL LETTER ABKHASIAN DZE +04E2,DISALLOWED,CYRILLIC CAPITAL LETTER I WITH MACRON +04E3,PVALID,CYRILLIC SMALL LETTER I WITH MACRON +04E4,DISALLOWED,CYRILLIC CAPITAL LETTER I WITH DIAERESIS +04E5,PVALID,CYRILLIC SMALL LETTER I WITH DIAERESIS +04E6,DISALLOWED,CYRILLIC CAPITAL LETTER O WITH DIAERESIS +04E7,PVALID,CYRILLIC SMALL LETTER O WITH DIAERESIS +04E8,DISALLOWED,CYRILLIC CAPITAL LETTER BARRED O +04E9,PVALID,CYRILLIC SMALL LETTER BARRED O +04EA,DISALLOWED,CYRILLIC CAPITAL LETTER BARRED O WITH DIAERESIS +04EB,PVALID,CYRILLIC SMALL LETTER BARRED O WITH DIAERESIS +04EC,DISALLOWED,CYRILLIC CAPITAL LETTER E WITH DIAERESIS +04ED,PVALID,CYRILLIC SMALL LETTER E WITH DIAERESIS +04EE,DISALLOWED,CYRILLIC CAPITAL LETTER U WITH MACRON +04EF,PVALID,CYRILLIC SMALL LETTER U WITH MACRON +04F0,DISALLOWED,CYRILLIC CAPITAL LETTER U WITH DIAERESIS +04F1,PVALID,CYRILLIC SMALL LETTER U WITH DIAERESIS +04F2,DISALLOWED,CYRILLIC CAPITAL LETTER U WITH DOUBLE ACUTE +04F3,PVALID,CYRILLIC SMALL LETTER U WITH DOUBLE ACUTE +04F4,DISALLOWED,CYRILLIC CAPITAL LETTER CHE WITH DIAERESIS +04F5,PVALID,CYRILLIC SMALL LETTER CHE WITH DIAERESIS +04F6,DISALLOWED,CYRILLIC CAPITAL LETTER GHE WITH DESCENDER +04F7,PVALID,CYRILLIC SMALL LETTER GHE WITH DESCENDER +04F8,DISALLOWED,CYRILLIC CAPITAL LETTER YERU WITH DIAERESIS +04F9,PVALID,CYRILLIC SMALL LETTER YERU WITH DIAERESIS +04FA,DISALLOWED,CYRILLIC CAPITAL LETTER GHE WITH STROKE AND HOOK +04FB,PVALID,CYRILLIC SMALL LETTER GHE WITH STROKE AND HOOK +04FC,DISALLOWED,CYRILLIC CAPITAL LETTER HA WITH HOOK +04FD,PVALID,CYRILLIC SMALL LETTER HA WITH HOOK +04FE,DISALLOWED,CYRILLIC CAPITAL LETTER HA WITH STROKE +04FF,PVALID,CYRILLIC SMALL LETTER HA WITH STROKE +0500,DISALLOWED,CYRILLIC CAPITAL LETTER KOMI DE +0501,PVALID,CYRILLIC SMALL LETTER KOMI DE +0502,DISALLOWED,CYRILLIC CAPITAL LETTER KOMI DJE +0503,PVALID,CYRILLIC SMALL LETTER KOMI DJE +0504,DISALLOWED,CYRILLIC CAPITAL LETTER KOMI ZJE +0505,PVALID,CYRILLIC SMALL LETTER KOMI ZJE +0506,DISALLOWED,CYRILLIC CAPITAL LETTER KOMI DZJE +0507,PVALID,CYRILLIC SMALL LETTER KOMI DZJE +0508,DISALLOWED,CYRILLIC CAPITAL LETTER KOMI LJE +0509,PVALID,CYRILLIC SMALL LETTER KOMI LJE +050A,DISALLOWED,CYRILLIC CAPITAL LETTER KOMI NJE +050B,PVALID,CYRILLIC SMALL LETTER KOMI NJE +050C,DISALLOWED,CYRILLIC CAPITAL LETTER KOMI SJE +050D,PVALID,CYRILLIC SMALL LETTER KOMI SJE +050E,DISALLOWED,CYRILLIC CAPITAL LETTER KOMI TJE +050F,PVALID,CYRILLIC SMALL LETTER KOMI TJE +0510,DISALLOWED,CYRILLIC CAPITAL LETTER REVERSED ZE +0511,PVALID,CYRILLIC SMALL LETTER REVERSED ZE +0512,DISALLOWED,CYRILLIC CAPITAL LETTER EL WITH HOOK +0513,PVALID,CYRILLIC SMALL LETTER EL WITH HOOK +0514,DISALLOWED,CYRILLIC CAPITAL LETTER LHA +0515,PVALID,CYRILLIC SMALL LETTER LHA +0516,DISALLOWED,CYRILLIC CAPITAL LETTER RHA +0517,PVALID,CYRILLIC SMALL LETTER RHA +0518,DISALLOWED,CYRILLIC CAPITAL LETTER YAE +0519,PVALID,CYRILLIC SMALL LETTER YAE +051A,DISALLOWED,CYRILLIC CAPITAL LETTER QA +051B,PVALID,CYRILLIC SMALL LETTER QA +051C,DISALLOWED,CYRILLIC CAPITAL LETTER WE +051D,PVALID,CYRILLIC SMALL LETTER WE +051E,DISALLOWED,CYRILLIC CAPITAL LETTER ALEUT KA +051F,PVALID,CYRILLIC SMALL LETTER ALEUT KA +0520,DISALLOWED,CYRILLIC CAPITAL LETTER EL WITH MIDDLE HOOK +0521,PVALID,CYRILLIC SMALL LETTER EL WITH MIDDLE HOOK +0522,DISALLOWED,CYRILLIC CAPITAL LETTER EN WITH MIDDLE HOOK +0523,PVALID,CYRILLIC SMALL LETTER EN WITH MIDDLE HOOK +0524,DISALLOWED,CYRILLIC CAPITAL LETTER PE WITH DESCENDER +0525,PVALID,CYRILLIC SMALL LETTER PE WITH DESCENDER +0526,DISALLOWED,CYRILLIC CAPITAL LETTER SHHA WITH DESCENDER +0527,PVALID,CYRILLIC SMALL LETTER SHHA WITH DESCENDER +0528-0530,UNASSIGNED,.. +0531-0556,DISALLOWED,ARMENIAN CAPITAL LETTER AYB..ARMENIAN CAPITAL LETTER FEH +0557-0558,UNASSIGNED,.. +0559,PVALID,ARMENIAN MODIFIER LETTER LEFT HALF RING +055A-055F,DISALLOWED,ARMENIAN APOSTROPHE..ARMENIAN ABBREVIATION MARK +0560,UNASSIGNED, +0561-0586,PVALID,ARMENIAN SMALL LETTER AYB..ARMENIAN SMALL LETTER FEH +0587,DISALLOWED,ARMENIAN SMALL LIGATURE ECH YIWN +0588,UNASSIGNED, +0589-058A,DISALLOWED,ARMENIAN FULL STOP..ARMENIAN HYPHEN +058B-058E,UNASSIGNED,.. +058F,DISALLOWED,ARMENIAN DRAM SIGN +0590,UNASSIGNED, +0591-05BD,PVALID,HEBREW ACCENT ETNAHTA..HEBREW POINT METEG +05BE,DISALLOWED,HEBREW PUNCTUATION MAQAF +05BF,PVALID,HEBREW POINT RAFE +05C0,DISALLOWED,HEBREW PUNCTUATION PASEQ +05C1-05C2,PVALID,HEBREW POINT SHIN DOT..HEBREW POINT SIN DOT +05C3,DISALLOWED,HEBREW PUNCTUATION SOF PASUQ +05C4-05C5,PVALID,HEBREW MARK UPPER DOT..HEBREW MARK LOWER DOT +05C6,DISALLOWED,HEBREW PUNCTUATION NUN HAFUKHA +05C7,PVALID,HEBREW POINT QAMATS QATAN +05C8-05CF,UNASSIGNED,.. +05D0-05EA,PVALID,HEBREW LETTER ALEF..HEBREW LETTER TAV +05EB-05EF,UNASSIGNED,.. +05F0-05F2,PVALID,HEBREW LIGATURE YIDDISH DOUBLE VAV..HEBREW LIGATURE YIDDISH DOUBLE YOD +05F3-05F4,CONTEXTO,HEBREW PUNCTUATION GERESH..HEBREW PUNCTUATION GERSHAYIM +05F5-05FF,UNASSIGNED,.. +0600-0604,DISALLOWED,ARABIC NUMBER SIGN..ARABIC SIGN SAMVAT +0605,UNASSIGNED, +0606-060F,DISALLOWED,ARABIC-INDIC CUBE ROOT..ARABIC SIGN MISRA +0610-061A,PVALID,ARABIC SIGN SALLALLAHOU ALAYHE WASSALLAM..ARABIC SMALL KASRA +061B-061C,DISALLOWED,ARABIC SEMICOLON..ARABIC LETTER MARK +061D,UNASSIGNED, +061E-061F,DISALLOWED,ARABIC TRIPLE DOT PUNCTUATION MARK..ARABIC QUESTION MARK +0620-063F,PVALID,ARABIC LETTER KASHMIRI YEH..ARABIC LETTER FARSI YEH WITH THREE DOTS ABOVE +0640,DISALLOWED,ARABIC TATWEEL +0641-065F,PVALID,ARABIC LETTER FEH..ARABIC WAVY HAMZA BELOW +0660-0669,CONTEXTO,ARABIC-INDIC DIGIT ZERO..ARABIC-INDIC DIGIT NINE +066A-066D,DISALLOWED,ARABIC PERCENT SIGN..ARABIC FIVE POINTED STAR +066E-0674,PVALID,ARABIC LETTER DOTLESS BEH..ARABIC LETTER HIGH HAMZA +0675-0678,DISALLOWED,ARABIC LETTER HIGH HAMZA ALEF..ARABIC LETTER HIGH HAMZA YEH +0679-06D3,PVALID,ARABIC LETTER TTEH..ARABIC LETTER YEH BARREE WITH HAMZA ABOVE +06D4,DISALLOWED,ARABIC FULL STOP +06D5-06DC,PVALID,ARABIC LETTER AE..ARABIC SMALL HIGH SEEN +06DD-06DE,DISALLOWED,ARABIC END OF AYAH..ARABIC START OF RUB EL HIZB +06DF-06E8,PVALID,ARABIC SMALL HIGH ROUNDED ZERO..ARABIC SMALL HIGH NOON +06E9,DISALLOWED,ARABIC PLACE OF SAJDAH +06EA-06EF,PVALID,ARABIC EMPTY CENTRE LOW STOP..ARABIC LETTER REH WITH INVERTED V +06F0-06F9,CONTEXTO,EXTENDED ARABIC-INDIC DIGIT ZERO..EXTENDED ARABIC-INDIC DIGIT NINE +06FA-06FF,PVALID,ARABIC LETTER SHEEN WITH DOT BELOW..ARABIC LETTER HEH WITH INVERTED V +0700-070D,DISALLOWED,SYRIAC END OF PARAGRAPH..SYRIAC HARKLEAN ASTERISCUS +070E,UNASSIGNED, +070F,DISALLOWED,SYRIAC ABBREVIATION MARK +0710-074A,PVALID,SYRIAC LETTER ALAPH..SYRIAC BARREKH +074B-074C,UNASSIGNED,.. +074D-07B1,PVALID,SYRIAC LETTER SOGDIAN ZHAIN..THAANA LETTER NAA +07B2-07BF,UNASSIGNED,.. +07C0-07F5,PVALID,NKO DIGIT ZERO..NKO LOW TONE APOSTROPHE +07F6-07FA,DISALLOWED,NKO SYMBOL OO DENNEN..NKO LAJANYALAN +07FB-07FF,UNASSIGNED,.. +0800-082D,PVALID,SAMARITAN LETTER ALAF..SAMARITAN MARK NEQUDAA +082E-082F,UNASSIGNED,.. +0830-083E,DISALLOWED,SAMARITAN PUNCTUATION NEQUDAA..SAMARITAN PUNCTUATION ANNAAU +083F,UNASSIGNED, +0840-085B,PVALID,MANDAIC LETTER HALQA..MANDAIC GEMINATION MARK +085C-085D,UNASSIGNED,.. +085E,DISALLOWED,MANDAIC PUNCTUATION +085F-089F,UNASSIGNED,.. +08A0,PVALID,ARABIC LETTER BEH WITH SMALL V BELOW +08A1,UNASSIGNED, +08A2-08AC,PVALID,ARABIC LETTER JEEM WITH TWO DOTS ABOVE..ARABIC LETTER ROHINGYA YEH +08AD-08E3,UNASSIGNED,.. +08E4-08FE,PVALID,ARABIC CURLY FATHA..ARABIC DAMMA WITH DOT +08FF,UNASSIGNED, +0900-0957,PVALID,DEVANAGARI SIGN INVERTED CANDRABINDU..DEVANAGARI VOWEL SIGN UUE +0958-095F,DISALLOWED,DEVANAGARI LETTER QA..DEVANAGARI LETTER YYA +0960-0963,PVALID,DEVANAGARI LETTER VOCALIC RR..DEVANAGARI VOWEL SIGN VOCALIC LL +0964-0965,DISALLOWED,DEVANAGARI DANDA..DEVANAGARI DOUBLE DANDA +0966-096F,PVALID,DEVANAGARI DIGIT ZERO..DEVANAGARI DIGIT NINE +0970,DISALLOWED,DEVANAGARI ABBREVIATION SIGN +0971-0977,PVALID,DEVANAGARI SIGN HIGH SPACING DOT..DEVANAGARI LETTER UUE +0978,UNASSIGNED, +0979-097F,PVALID,DEVANAGARI LETTER ZHA..DEVANAGARI LETTER BBA +0980,UNASSIGNED, +0981-0983,PVALID,BENGALI SIGN CANDRABINDU..BENGALI SIGN VISARGA +0984,UNASSIGNED, +0985-098C,PVALID,BENGALI LETTER A..BENGALI LETTER VOCALIC L +098D-098E,UNASSIGNED,.. +098F-0990,PVALID,BENGALI LETTER E..BENGALI LETTER AI +0991-0992,UNASSIGNED,.. +0993-09A8,PVALID,BENGALI LETTER O..BENGALI LETTER NA +09A9,UNASSIGNED, +09AA-09B0,PVALID,BENGALI LETTER PA..BENGALI LETTER RA +09B1,UNASSIGNED, +09B2,PVALID,BENGALI LETTER LA +09B3-09B5,UNASSIGNED,.. +09B6-09B9,PVALID,BENGALI LETTER SHA..BENGALI LETTER HA +09BA-09BB,UNASSIGNED,.. +09BC-09C4,PVALID,BENGALI SIGN NUKTA..BENGALI VOWEL SIGN VOCALIC RR +09C5-09C6,UNASSIGNED,.. +09C7-09C8,PVALID,BENGALI VOWEL SIGN E..BENGALI VOWEL SIGN AI +09C9-09CA,UNASSIGNED,.. +09CB-09CE,PVALID,BENGALI VOWEL SIGN O..BENGALI LETTER KHANDA TA +09CF-09D6,UNASSIGNED,.. +09D7,PVALID,BENGALI AU LENGTH MARK +09D8-09DB,UNASSIGNED,.. +09DC-09DD,DISALLOWED,BENGALI LETTER RRA..BENGALI LETTER RHA +09DE,UNASSIGNED, +09DF,DISALLOWED,BENGALI LETTER YYA +09E0-09E3,PVALID,BENGALI LETTER VOCALIC RR..BENGALI VOWEL SIGN VOCALIC LL +09E4-09E5,UNASSIGNED,.. +09E6-09F1,PVALID,BENGALI DIGIT ZERO..BENGALI LETTER RA WITH LOWER DIAGONAL +09F2-09FB,DISALLOWED,BENGALI RUPEE MARK..BENGALI GANDA MARK +09FC-0A00,UNASSIGNED,.. +0A01-0A03,PVALID,GURMUKHI SIGN ADAK BINDI..GURMUKHI SIGN VISARGA +0A04,UNASSIGNED, +0A05-0A0A,PVALID,GURMUKHI LETTER A..GURMUKHI LETTER UU +0A0B-0A0E,UNASSIGNED,.. +0A0F-0A10,PVALID,GURMUKHI LETTER EE..GURMUKHI LETTER AI +0A11-0A12,UNASSIGNED,.. +0A13-0A28,PVALID,GURMUKHI LETTER OO..GURMUKHI LETTER NA +0A29,UNASSIGNED, +0A2A-0A30,PVALID,GURMUKHI LETTER PA..GURMUKHI LETTER RA +0A31,UNASSIGNED, +0A32,PVALID,GURMUKHI LETTER LA +0A33,DISALLOWED,GURMUKHI LETTER LLA +0A34,UNASSIGNED, +0A35,PVALID,GURMUKHI LETTER VA +0A36,DISALLOWED,GURMUKHI LETTER SHA +0A37,UNASSIGNED, +0A38-0A39,PVALID,GURMUKHI LETTER SA..GURMUKHI LETTER HA +0A3A-0A3B,UNASSIGNED,.. +0A3C,PVALID,GURMUKHI SIGN NUKTA +0A3D,UNASSIGNED, +0A3E-0A42,PVALID,GURMUKHI VOWEL SIGN AA..GURMUKHI VOWEL SIGN UU +0A43-0A46,UNASSIGNED,.. +0A47-0A48,PVALID,GURMUKHI VOWEL SIGN EE..GURMUKHI VOWEL SIGN AI +0A49-0A4A,UNASSIGNED,.. +0A4B-0A4D,PVALID,GURMUKHI VOWEL SIGN OO..GURMUKHI SIGN VIRAMA +0A4E-0A50,UNASSIGNED,.. +0A51,PVALID,GURMUKHI SIGN UDAAT +0A52-0A58,UNASSIGNED,.. +0A59-0A5B,DISALLOWED,GURMUKHI LETTER KHHA..GURMUKHI LETTER ZA +0A5C,PVALID,GURMUKHI LETTER RRA +0A5D,UNASSIGNED, +0A5E,DISALLOWED,GURMUKHI LETTER FA +0A5F-0A65,UNASSIGNED,.. +0A66-0A75,PVALID,GURMUKHI DIGIT ZERO..GURMUKHI SIGN YAKASH +0A76-0A80,UNASSIGNED,.. +0A81-0A83,PVALID,GUJARATI SIGN CANDRABINDU..GUJARATI SIGN VISARGA +0A84,UNASSIGNED, +0A85-0A8D,PVALID,GUJARATI LETTER A..GUJARATI VOWEL CANDRA E +0A8E,UNASSIGNED, +0A8F-0A91,PVALID,GUJARATI LETTER E..GUJARATI VOWEL CANDRA O +0A92,UNASSIGNED, +0A93-0AA8,PVALID,GUJARATI LETTER O..GUJARATI LETTER NA +0AA9,UNASSIGNED, +0AAA-0AB0,PVALID,GUJARATI LETTER PA..GUJARATI LETTER RA +0AB1,UNASSIGNED, +0AB2-0AB3,PVALID,GUJARATI LETTER LA..GUJARATI LETTER LLA +0AB4,UNASSIGNED, +0AB5-0AB9,PVALID,GUJARATI LETTER VA..GUJARATI LETTER HA +0ABA-0ABB,UNASSIGNED,.. +0ABC-0AC5,PVALID,GUJARATI SIGN NUKTA..GUJARATI VOWEL SIGN CANDRA E +0AC6,UNASSIGNED, +0AC7-0AC9,PVALID,GUJARATI VOWEL SIGN E..GUJARATI VOWEL SIGN CANDRA O +0ACA,UNASSIGNED, +0ACB-0ACD,PVALID,GUJARATI VOWEL SIGN O..GUJARATI SIGN VIRAMA +0ACE-0ACF,UNASSIGNED,.. +0AD0,PVALID,GUJARATI OM +0AD1-0ADF,UNASSIGNED,.. +0AE0-0AE3,PVALID,GUJARATI LETTER VOCALIC RR..GUJARATI VOWEL SIGN VOCALIC LL +0AE4-0AE5,UNASSIGNED,.. +0AE6-0AEF,PVALID,GUJARATI DIGIT ZERO..GUJARATI DIGIT NINE +0AF0-0AF1,DISALLOWED,GUJARATI ABBREVIATION SIGN..GUJARATI RUPEE SIGN +0AF2-0B00,UNASSIGNED,.. +0B01-0B03,PVALID,ORIYA SIGN CANDRABINDU..ORIYA SIGN VISARGA +0B04,UNASSIGNED, +0B05-0B0C,PVALID,ORIYA LETTER A..ORIYA LETTER VOCALIC L +0B0D-0B0E,UNASSIGNED,.. +0B0F-0B10,PVALID,ORIYA LETTER E..ORIYA LETTER AI +0B11-0B12,UNASSIGNED,.. +0B13-0B28,PVALID,ORIYA LETTER O..ORIYA LETTER NA +0B29,UNASSIGNED, +0B2A-0B30,PVALID,ORIYA LETTER PA..ORIYA LETTER RA +0B31,UNASSIGNED, +0B32-0B33,PVALID,ORIYA LETTER LA..ORIYA LETTER LLA +0B34,UNASSIGNED, +0B35-0B39,PVALID,ORIYA LETTER VA..ORIYA LETTER HA +0B3A-0B3B,UNASSIGNED,.. +0B3C-0B44,PVALID,ORIYA SIGN NUKTA..ORIYA VOWEL SIGN VOCALIC RR +0B45-0B46,UNASSIGNED,.. +0B47-0B48,PVALID,ORIYA VOWEL SIGN E..ORIYA VOWEL SIGN AI +0B49-0B4A,UNASSIGNED,.. +0B4B-0B4D,PVALID,ORIYA VOWEL SIGN O..ORIYA SIGN VIRAMA +0B4E-0B55,UNASSIGNED,.. +0B56-0B57,PVALID,ORIYA AI LENGTH MARK..ORIYA AU LENGTH MARK +0B58-0B5B,UNASSIGNED,.. +0B5C-0B5D,DISALLOWED,ORIYA LETTER RRA..ORIYA LETTER RHA +0B5E,UNASSIGNED, +0B5F-0B63,PVALID,ORIYA LETTER YYA..ORIYA VOWEL SIGN VOCALIC LL +0B64-0B65,UNASSIGNED,.. +0B66-0B6F,PVALID,ORIYA DIGIT ZERO..ORIYA DIGIT NINE +0B70,DISALLOWED,ORIYA ISSHAR +0B71,PVALID,ORIYA LETTER WA +0B72-0B77,DISALLOWED,ORIYA FRACTION ONE QUARTER..ORIYA FRACTION THREE SIXTEENTHS +0B78-0B81,UNASSIGNED,.. +0B82-0B83,PVALID,TAMIL SIGN ANUSVARA..TAMIL SIGN VISARGA +0B84,UNASSIGNED, +0B85-0B8A,PVALID,TAMIL LETTER A..TAMIL LETTER UU +0B8B-0B8D,UNASSIGNED,.. +0B8E-0B90,PVALID,TAMIL LETTER E..TAMIL LETTER AI +0B91,UNASSIGNED, +0B92-0B95,PVALID,TAMIL LETTER O..TAMIL LETTER KA +0B96-0B98,UNASSIGNED,.. +0B99-0B9A,PVALID,TAMIL LETTER NGA..TAMIL LETTER CA +0B9B,UNASSIGNED, +0B9C,PVALID,TAMIL LETTER JA +0B9D,UNASSIGNED, +0B9E-0B9F,PVALID,TAMIL LETTER NYA..TAMIL LETTER TTA +0BA0-0BA2,UNASSIGNED,.. +0BA3-0BA4,PVALID,TAMIL LETTER NNA..TAMIL LETTER TA +0BA5-0BA7,UNASSIGNED,.. +0BA8-0BAA,PVALID,TAMIL LETTER NA..TAMIL LETTER PA +0BAB-0BAD,UNASSIGNED,.. +0BAE-0BB9,PVALID,TAMIL LETTER MA..TAMIL LETTER HA +0BBA-0BBD,UNASSIGNED,.. +0BBE-0BC2,PVALID,TAMIL VOWEL SIGN AA..TAMIL VOWEL SIGN UU +0BC3-0BC5,UNASSIGNED,.. +0BC6-0BC8,PVALID,TAMIL VOWEL SIGN E..TAMIL VOWEL SIGN AI +0BC9,UNASSIGNED, +0BCA-0BCD,PVALID,TAMIL VOWEL SIGN O..TAMIL SIGN VIRAMA +0BCE-0BCF,UNASSIGNED,.. +0BD0,PVALID,TAMIL OM +0BD1-0BD6,UNASSIGNED,.. +0BD7,PVALID,TAMIL AU LENGTH MARK +0BD8-0BE5,UNASSIGNED,.. +0BE6-0BEF,PVALID,TAMIL DIGIT ZERO..TAMIL DIGIT NINE +0BF0-0BFA,DISALLOWED,TAMIL NUMBER TEN..TAMIL NUMBER SIGN +0BFB-0C00,UNASSIGNED,.. +0C01-0C03,PVALID,TELUGU SIGN CANDRABINDU..TELUGU SIGN VISARGA +0C04,UNASSIGNED, +0C05-0C0C,PVALID,TELUGU LETTER A..TELUGU LETTER VOCALIC L +0C0D,UNASSIGNED, +0C0E-0C10,PVALID,TELUGU LETTER E..TELUGU LETTER AI +0C11,UNASSIGNED, +0C12-0C28,PVALID,TELUGU LETTER O..TELUGU LETTER NA +0C29,UNASSIGNED, +0C2A-0C33,PVALID,TELUGU LETTER PA..TELUGU LETTER LLA +0C34,UNASSIGNED, +0C35-0C39,PVALID,TELUGU LETTER VA..TELUGU LETTER HA +0C3A-0C3C,UNASSIGNED,.. +0C3D-0C44,PVALID,TELUGU SIGN AVAGRAHA..TELUGU VOWEL SIGN VOCALIC RR +0C45,UNASSIGNED, +0C46-0C48,PVALID,TELUGU VOWEL SIGN E..TELUGU VOWEL SIGN AI +0C49,UNASSIGNED, +0C4A-0C4D,PVALID,TELUGU VOWEL SIGN O..TELUGU SIGN VIRAMA +0C4E-0C54,UNASSIGNED,.. +0C55-0C56,PVALID,TELUGU LENGTH MARK..TELUGU AI LENGTH MARK +0C57,UNASSIGNED, +0C58-0C59,PVALID,TELUGU LETTER TSA..TELUGU LETTER DZA +0C5A-0C5F,UNASSIGNED,.. +0C60-0C63,PVALID,TELUGU LETTER VOCALIC RR..TELUGU VOWEL SIGN VOCALIC LL +0C64-0C65,UNASSIGNED,.. +0C66-0C6F,PVALID,TELUGU DIGIT ZERO..TELUGU DIGIT NINE +0C70-0C77,UNASSIGNED,.. +0C78-0C7F,DISALLOWED,TELUGU FRACTION DIGIT ZERO FOR ODD POWERS OF FOUR..TELUGU SIGN TUUMU +0C80-0C81,UNASSIGNED,.. +0C82-0C83,PVALID,KANNADA SIGN ANUSVARA..KANNADA SIGN VISARGA +0C84,UNASSIGNED, +0C85-0C8C,PVALID,KANNADA LETTER A..KANNADA LETTER VOCALIC L +0C8D,UNASSIGNED, +0C8E-0C90,PVALID,KANNADA LETTER E..KANNADA LETTER AI +0C91,UNASSIGNED, +0C92-0CA8,PVALID,KANNADA LETTER O..KANNADA LETTER NA +0CA9,UNASSIGNED, +0CAA-0CB3,PVALID,KANNADA LETTER PA..KANNADA LETTER LLA +0CB4,UNASSIGNED, +0CB5-0CB9,PVALID,KANNADA LETTER VA..KANNADA LETTER HA +0CBA-0CBB,UNASSIGNED,.. +0CBC-0CC4,PVALID,KANNADA SIGN NUKTA..KANNADA VOWEL SIGN VOCALIC RR +0CC5,UNASSIGNED, +0CC6-0CC8,PVALID,KANNADA VOWEL SIGN E..KANNADA VOWEL SIGN AI +0CC9,UNASSIGNED, +0CCA-0CCD,PVALID,KANNADA VOWEL SIGN O..KANNADA SIGN VIRAMA +0CCE-0CD4,UNASSIGNED,.. +0CD5-0CD6,PVALID,KANNADA LENGTH MARK..KANNADA AI LENGTH MARK +0CD7-0CDD,UNASSIGNED,.. +0CDE,PVALID,KANNADA LETTER FA +0CDF,UNASSIGNED, +0CE0-0CE3,PVALID,KANNADA LETTER VOCALIC RR..KANNADA VOWEL SIGN VOCALIC LL +0CE4-0CE5,UNASSIGNED,.. +0CE6-0CEF,PVALID,KANNADA DIGIT ZERO..KANNADA DIGIT NINE +0CF0,UNASSIGNED, +0CF1-0CF2,PVALID,KANNADA SIGN JIHVAMULIYA..KANNADA SIGN UPADHMANIYA +0CF3-0D01,UNASSIGNED,.. +0D02-0D03,PVALID,MALAYALAM SIGN ANUSVARA..MALAYALAM SIGN VISARGA +0D04,UNASSIGNED, +0D05-0D0C,PVALID,MALAYALAM LETTER A..MALAYALAM LETTER VOCALIC L +0D0D,UNASSIGNED, +0D0E-0D10,PVALID,MALAYALAM LETTER E..MALAYALAM LETTER AI +0D11,UNASSIGNED, +0D12-0D3A,PVALID,MALAYALAM LETTER O..MALAYALAM LETTER TTTA +0D3B-0D3C,UNASSIGNED,.. +0D3D-0D44,PVALID,MALAYALAM SIGN AVAGRAHA..MALAYALAM VOWEL SIGN VOCALIC RR +0D45,UNASSIGNED, +0D46-0D48,PVALID,MALAYALAM VOWEL SIGN E..MALAYALAM VOWEL SIGN AI +0D49,UNASSIGNED, +0D4A-0D4E,PVALID,MALAYALAM VOWEL SIGN O..MALAYALAM LETTER DOT REPH +0D4F-0D56,UNASSIGNED,.. +0D57,PVALID,MALAYALAM AU LENGTH MARK +0D58-0D5F,UNASSIGNED,.. +0D60-0D63,PVALID,MALAYALAM LETTER VOCALIC RR..MALAYALAM VOWEL SIGN VOCALIC LL +0D64-0D65,UNASSIGNED,.. +0D66-0D6F,PVALID,MALAYALAM DIGIT ZERO..MALAYALAM DIGIT NINE +0D70-0D75,DISALLOWED,MALAYALAM NUMBER TEN..MALAYALAM FRACTION THREE QUARTERS +0D76-0D78,UNASSIGNED,.. +0D79,DISALLOWED,MALAYALAM DATE MARK +0D7A-0D7F,PVALID,MALAYALAM LETTER CHILLU NN..MALAYALAM LETTER CHILLU K +0D80-0D81,UNASSIGNED,.. +0D82-0D83,PVALID,SINHALA SIGN ANUSVARAYA..SINHALA SIGN VISARGAYA +0D84,UNASSIGNED, +0D85-0D96,PVALID,SINHALA LETTER AYANNA..SINHALA LETTER AUYANNA +0D97-0D99,UNASSIGNED,.. +0D9A-0DB1,PVALID,SINHALA LETTER ALPAPRAANA KAYANNA..SINHALA LETTER DANTAJA NAYANNA +0DB2,UNASSIGNED, +0DB3-0DBB,PVALID,SINHALA LETTER SANYAKA DAYANNA..SINHALA LETTER RAYANNA +0DBC,UNASSIGNED, +0DBD,PVALID,SINHALA LETTER DANTAJA LAYANNA +0DBE-0DBF,UNASSIGNED,.. +0DC0-0DC6,PVALID,SINHALA LETTER VAYANNA..SINHALA LETTER FAYANNA +0DC7-0DC9,UNASSIGNED,.. +0DCA,PVALID,SINHALA SIGN AL-LAKUNA +0DCB-0DCE,UNASSIGNED,.. +0DCF-0DD4,PVALID,SINHALA VOWEL SIGN AELA-PILLA..SINHALA VOWEL SIGN KETTI PAA-PILLA +0DD5,UNASSIGNED, +0DD6,PVALID,SINHALA VOWEL SIGN DIGA PAA-PILLA +0DD7,UNASSIGNED, +0DD8-0DDF,PVALID,SINHALA VOWEL SIGN GAETTA-PILLA..SINHALA VOWEL SIGN GAYANUKITTA +0DE0-0DF1,UNASSIGNED,.. +0DF2-0DF3,PVALID,SINHALA VOWEL SIGN DIGA GAETTA-PILLA..SINHALA VOWEL SIGN DIGA GAYANUKITTA +0DF4,DISALLOWED,SINHALA PUNCTUATION KUNDDALIYA +0DF5-0E00,UNASSIGNED,.. +0E01-0E32,PVALID,THAI CHARACTER KO KAI..THAI CHARACTER SARA AA +0E33,DISALLOWED,THAI CHARACTER SARA AM +0E34-0E3A,PVALID,THAI CHARACTER SARA I..THAI CHARACTER PHINTHU +0E3B-0E3E,UNASSIGNED,.. +0E3F,DISALLOWED,THAI CURRENCY SYMBOL BAHT +0E40-0E4E,PVALID,THAI CHARACTER SARA E..THAI CHARACTER YAMAKKAN +0E4F,DISALLOWED,THAI CHARACTER FONGMAN +0E50-0E59,PVALID,THAI DIGIT ZERO..THAI DIGIT NINE +0E5A-0E5B,DISALLOWED,THAI CHARACTER ANGKHANKHU..THAI CHARACTER KHOMUT +0E5C-0E80,UNASSIGNED,.. +0E81-0E82,PVALID,LAO LETTER KO..LAO LETTER KHO SUNG +0E83,UNASSIGNED, +0E84,PVALID,LAO LETTER KHO TAM +0E85-0E86,UNASSIGNED,.. +0E87-0E88,PVALID,LAO LETTER NGO..LAO LETTER CO +0E89,UNASSIGNED, +0E8A,PVALID,LAO LETTER SO TAM +0E8B-0E8C,UNASSIGNED,.. +0E8D,PVALID,LAO LETTER NYO +0E8E-0E93,UNASSIGNED,.. +0E94-0E97,PVALID,LAO LETTER DO..LAO LETTER THO TAM +0E98,UNASSIGNED, +0E99-0E9F,PVALID,LAO LETTER NO..LAO LETTER FO SUNG +0EA0,UNASSIGNED, +0EA1-0EA3,PVALID,LAO LETTER MO..LAO LETTER LO LING +0EA4,UNASSIGNED, +0EA5,PVALID,LAO LETTER LO LOOT +0EA6,UNASSIGNED, +0EA7,PVALID,LAO LETTER WO +0EA8-0EA9,UNASSIGNED,.. +0EAA-0EAB,PVALID,LAO LETTER SO SUNG..LAO LETTER HO SUNG +0EAC,UNASSIGNED, +0EAD-0EB2,PVALID,LAO LETTER O..LAO VOWEL SIGN AA +0EB3,DISALLOWED,LAO VOWEL SIGN AM +0EB4-0EB9,PVALID,LAO VOWEL SIGN I..LAO VOWEL SIGN UU +0EBA,UNASSIGNED, +0EBB-0EBD,PVALID,LAO VOWEL SIGN MAI KON..LAO SEMIVOWEL SIGN NYO +0EBE-0EBF,UNASSIGNED,.. +0EC0-0EC4,PVALID,LAO VOWEL SIGN E..LAO VOWEL SIGN AI +0EC5,UNASSIGNED, +0EC6,PVALID,LAO KO LA +0EC7,UNASSIGNED, +0EC8-0ECD,PVALID,LAO TONE MAI EK..LAO NIGGAHITA +0ECE-0ECF,UNASSIGNED,.. +0ED0-0ED9,PVALID,LAO DIGIT ZERO..LAO DIGIT NINE +0EDA-0EDB,UNASSIGNED,.. +0EDC-0EDD,DISALLOWED,LAO HO NO..LAO HO MO +0EDE-0EDF,PVALID,LAO LETTER KHMU GO..LAO LETTER KHMU NYO +0EE0-0EFF,UNASSIGNED,.. +0F00,PVALID,TIBETAN SYLLABLE OM +0F01-0F0A,DISALLOWED,TIBETAN MARK GTER YIG MGO TRUNCATED A..TIBETAN MARK BKA- SHOG YIG MGO +0F0B,PVALID,TIBETAN MARK INTERSYLLABIC TSHEG +0F0C-0F17,DISALLOWED,TIBETAN MARK DELIMITER TSHEG BSTAR..TIBETAN ASTROLOGICAL SIGN SGRA GCAN -CHAR RTAGS +0F18-0F19,PVALID,TIBETAN ASTROLOGICAL SIGN -KHYUD PA..TIBETAN ASTROLOGICAL SIGN SDONG TSHUGS +0F1A-0F1F,DISALLOWED,TIBETAN SIGN RDEL DKAR GCIG..TIBETAN SIGN RDEL DKAR RDEL NAG +0F20-0F29,PVALID,TIBETAN DIGIT ZERO..TIBETAN DIGIT NINE +0F2A-0F34,DISALLOWED,TIBETAN DIGIT HALF ONE..TIBETAN MARK BSDUS RTAGS +0F35,PVALID,TIBETAN MARK NGAS BZUNG NYI ZLA +0F36,DISALLOWED,TIBETAN MARK CARET -DZUD RTAGS BZHI MIG CAN +0F37,PVALID,TIBETAN MARK NGAS BZUNG SGOR RTAGS +0F38,DISALLOWED,TIBETAN MARK CHE MGO +0F39,PVALID,TIBETAN MARK TSA -PHRU +0F3A-0F3D,DISALLOWED,TIBETAN MARK GUG RTAGS GYON..TIBETAN MARK ANG KHANG GYAS +0F3E-0F42,PVALID,TIBETAN SIGN YAR TSHES..TIBETAN LETTER GA +0F43,DISALLOWED,TIBETAN LETTER GHA +0F44-0F47,PVALID,TIBETAN LETTER NGA..TIBETAN LETTER JA +0F48,UNASSIGNED, +0F49-0F4C,PVALID,TIBETAN LETTER NYA..TIBETAN LETTER DDA +0F4D,DISALLOWED,TIBETAN LETTER DDHA +0F4E-0F51,PVALID,TIBETAN LETTER NNA..TIBETAN LETTER DA +0F52,DISALLOWED,TIBETAN LETTER DHA +0F53-0F56,PVALID,TIBETAN LETTER NA..TIBETAN LETTER BA +0F57,DISALLOWED,TIBETAN LETTER BHA +0F58-0F5B,PVALID,TIBETAN LETTER MA..TIBETAN LETTER DZA +0F5C,DISALLOWED,TIBETAN LETTER DZHA +0F5D-0F68,PVALID,TIBETAN LETTER WA..TIBETAN LETTER A +0F69,DISALLOWED,TIBETAN LETTER KSSA +0F6A-0F6C,PVALID,TIBETAN LETTER FIXED-FORM RA..TIBETAN LETTER RRA +0F6D-0F70,UNASSIGNED,.. +0F71-0F72,PVALID,TIBETAN VOWEL SIGN AA..TIBETAN VOWEL SIGN I +0F73,DISALLOWED,TIBETAN VOWEL SIGN II +0F74,PVALID,TIBETAN VOWEL SIGN U +0F75-0F79,DISALLOWED,TIBETAN VOWEL SIGN UU..TIBETAN VOWEL SIGN VOCALIC LL +0F7A-0F80,PVALID,TIBETAN VOWEL SIGN E..TIBETAN VOWEL SIGN REVERSED I +0F81,DISALLOWED,TIBETAN VOWEL SIGN REVERSED II +0F82-0F84,PVALID,TIBETAN SIGN NYI ZLA NAA DA..TIBETAN MARK HALANTA +0F85,DISALLOWED,TIBETAN MARK PALUTA +0F86-0F92,PVALID,TIBETAN SIGN LCI RTAGS..TIBETAN SUBJOINED LETTER GA +0F93,DISALLOWED,TIBETAN SUBJOINED LETTER GHA +0F94-0F97,PVALID,TIBETAN SUBJOINED LETTER NGA..TIBETAN SUBJOINED LETTER JA +0F98,UNASSIGNED, +0F99-0F9C,PVALID,TIBETAN SUBJOINED LETTER NYA..TIBETAN SUBJOINED LETTER DDA +0F9D,DISALLOWED,TIBETAN SUBJOINED LETTER DDHA +0F9E-0FA1,PVALID,TIBETAN SUBJOINED LETTER NNA..TIBETAN SUBJOINED LETTER DA +0FA2,DISALLOWED,TIBETAN SUBJOINED LETTER DHA +0FA3-0FA6,PVALID,TIBETAN SUBJOINED LETTER NA..TIBETAN SUBJOINED LETTER BA +0FA7,DISALLOWED,TIBETAN SUBJOINED LETTER BHA +0FA8-0FAB,PVALID,TIBETAN SUBJOINED LETTER MA..TIBETAN SUBJOINED LETTER DZA +0FAC,DISALLOWED,TIBETAN SUBJOINED LETTER DZHA +0FAD-0FB8,PVALID,TIBETAN SUBJOINED LETTER WA..TIBETAN SUBJOINED LETTER A +0FB9,DISALLOWED,TIBETAN SUBJOINED LETTER KSSA +0FBA-0FBC,PVALID,TIBETAN SUBJOINED LETTER FIXED-FORM WA..TIBETAN SUBJOINED LETTER FIXED-FORM RA +0FBD,UNASSIGNED, +0FBE-0FC5,DISALLOWED,TIBETAN KU RU KHA..TIBETAN SYMBOL RDO RJE +0FC6,PVALID,TIBETAN SYMBOL PADMA GDAN +0FC7-0FCC,DISALLOWED,TIBETAN SYMBOL RDO RJE RGYA GRAM..TIBETAN SYMBOL NOR BU BZHI -KHYIL +0FCD,UNASSIGNED, +0FCE-0FDA,DISALLOWED,TIBETAN SIGN RDEL NAG RDEL DKAR..TIBETAN MARK TRAILING MCHAN RTAGS +0FDB-0FFF,UNASSIGNED,.. +1000-1049,PVALID,MYANMAR LETTER KA..MYANMAR DIGIT NINE +104A-104F,DISALLOWED,MYANMAR SIGN LITTLE SECTION..MYANMAR SYMBOL GENITIVE +1050-109D,PVALID,MYANMAR LETTER SHA..MYANMAR VOWEL SIGN AITON AI +109E-10C5,DISALLOWED,MYANMAR SYMBOL SHAN ONE..GEORGIAN CAPITAL LETTER HOE +10C6,UNASSIGNED, +10C7,DISALLOWED,GEORGIAN CAPITAL LETTER YN +10C8-10CC,UNASSIGNED,.. +10CD,DISALLOWED,GEORGIAN CAPITAL LETTER AEN +10CE-10CF,UNASSIGNED,.. +10D0-10FA,PVALID,GEORGIAN LETTER AN..GEORGIAN LETTER AIN +10FB-10FC,DISALLOWED,GEORGIAN PARAGRAPH SEPARATOR..MODIFIER LETTER GEORGIAN NAR +10FD-10FF,PVALID,GEORGIAN LETTER AEN..GEORGIAN LETTER LABIAL SIGN +1100-11FF,DISALLOWED,HANGUL CHOSEONG KIYEOK..HANGUL JONGSEONG SSANGNIEUN +1200-1248,PVALID,ETHIOPIC SYLLABLE HA..ETHIOPIC SYLLABLE QWA +1249,UNASSIGNED, +124A-124D,PVALID,ETHIOPIC SYLLABLE QWI..ETHIOPIC SYLLABLE QWE +124E-124F,UNASSIGNED,.. +1250-1256,PVALID,ETHIOPIC SYLLABLE QHA..ETHIOPIC SYLLABLE QHO +1257,UNASSIGNED, +1258,PVALID,ETHIOPIC SYLLABLE QHWA +1259,UNASSIGNED, +125A-125D,PVALID,ETHIOPIC SYLLABLE QHWI..ETHIOPIC SYLLABLE QHWE +125E-125F,UNASSIGNED,.. +1260-1288,PVALID,ETHIOPIC SYLLABLE BA..ETHIOPIC SYLLABLE XWA +1289,UNASSIGNED, +128A-128D,PVALID,ETHIOPIC SYLLABLE XWI..ETHIOPIC SYLLABLE XWE +128E-128F,UNASSIGNED,.. +1290-12B0,PVALID,ETHIOPIC SYLLABLE NA..ETHIOPIC SYLLABLE KWA +12B1,UNASSIGNED, +12B2-12B5,PVALID,ETHIOPIC SYLLABLE KWI..ETHIOPIC SYLLABLE KWE +12B6-12B7,UNASSIGNED,.. +12B8-12BE,PVALID,ETHIOPIC SYLLABLE KXA..ETHIOPIC SYLLABLE KXO +12BF,UNASSIGNED, +12C0,PVALID,ETHIOPIC SYLLABLE KXWA +12C1,UNASSIGNED, +12C2-12C5,PVALID,ETHIOPIC SYLLABLE KXWI..ETHIOPIC SYLLABLE KXWE +12C6-12C7,UNASSIGNED,.. +12C8-12D6,PVALID,ETHIOPIC SYLLABLE WA..ETHIOPIC SYLLABLE PHARYNGEAL O +12D7,UNASSIGNED, +12D8-1310,PVALID,ETHIOPIC SYLLABLE ZA..ETHIOPIC SYLLABLE GWA +1311,UNASSIGNED, +1312-1315,PVALID,ETHIOPIC SYLLABLE GWI..ETHIOPIC SYLLABLE GWE +1316-1317,UNASSIGNED,.. +1318-135A,PVALID,ETHIOPIC SYLLABLE GGA..ETHIOPIC SYLLABLE FYA +135B-135C,UNASSIGNED,.. +135D-135F,PVALID,ETHIOPIC COMBINING GEMINATION AND VOWEL LENGTH MARK..ETHIOPIC COMBINING GEMINATION MARK +1360-137C,DISALLOWED,ETHIOPIC SECTION MARK..ETHIOPIC NUMBER TEN THOUSAND +137D-137F,UNASSIGNED,.. +1380-138F,PVALID,ETHIOPIC SYLLABLE SEBATBEIT MWA..ETHIOPIC SYLLABLE PWE +1390-1399,DISALLOWED,ETHIOPIC TONAL MARK YIZET..ETHIOPIC TONAL MARK KURT +139A-139F,UNASSIGNED,.. +13A0-13F4,PVALID,CHEROKEE LETTER A..CHEROKEE LETTER YV +13F5-13FF,UNASSIGNED,.. +1400,DISALLOWED,CANADIAN SYLLABICS HYPHEN +1401-166C,PVALID,CANADIAN SYLLABICS E..CANADIAN SYLLABICS CARRIER TTSA +166D-166E,DISALLOWED,CANADIAN SYLLABICS CHI SIGN..CANADIAN SYLLABICS FULL STOP +166F-167F,PVALID,CANADIAN SYLLABICS QAI..CANADIAN SYLLABICS BLACKFOOT W +1680,DISALLOWED,OGHAM SPACE MARK +1681-169A,PVALID,OGHAM LETTER BEITH..OGHAM LETTER PEITH +169B-169C,DISALLOWED,OGHAM FEATHER MARK..OGHAM REVERSED FEATHER MARK +169D-169F,UNASSIGNED,.. +16A0-16EA,PVALID,RUNIC LETTER FEHU FEOH FE F..RUNIC LETTER X +16EB-16F0,DISALLOWED,RUNIC SINGLE PUNCTUATION..RUNIC BELGTHOR SYMBOL +16F1-16FF,UNASSIGNED,.. +1700-170C,PVALID,TAGALOG LETTER A..TAGALOG LETTER YA +170D,UNASSIGNED, +170E-1714,PVALID,TAGALOG LETTER LA..TAGALOG SIGN VIRAMA +1715-171F,UNASSIGNED,.. +1720-1734,PVALID,HANUNOO LETTER A..HANUNOO SIGN PAMUDPOD +1735-1736,DISALLOWED,PHILIPPINE SINGLE PUNCTUATION..PHILIPPINE DOUBLE PUNCTUATION +1737-173F,UNASSIGNED,.. +1740-1753,PVALID,BUHID LETTER A..BUHID VOWEL SIGN U +1754-175F,UNASSIGNED,.. +1760-176C,PVALID,TAGBANWA LETTER A..TAGBANWA LETTER YA +176D,UNASSIGNED, +176E-1770,PVALID,TAGBANWA LETTER LA..TAGBANWA LETTER SA +1771,UNASSIGNED, +1772-1773,PVALID,TAGBANWA VOWEL SIGN I..TAGBANWA VOWEL SIGN U +1774-177F,UNASSIGNED,.. +1780-17B3,PVALID,KHMER LETTER KA..KHMER INDEPENDENT VOWEL QAU +17B4-17B5,DISALLOWED,KHMER VOWEL INHERENT AQ..KHMER VOWEL INHERENT AA +17B6-17D3,PVALID,KHMER VOWEL SIGN AA..KHMER SIGN BATHAMASAT +17D4-17D6,DISALLOWED,KHMER SIGN KHAN..KHMER SIGN CAMNUC PII KUUH +17D7,PVALID,KHMER SIGN LEK TOO +17D8-17DB,DISALLOWED,KHMER SIGN BEYYAL..KHMER CURRENCY SYMBOL RIEL +17DC-17DD,PVALID,KHMER SIGN AVAKRAHASANYA..KHMER SIGN ATTHACAN +17DE-17DF,UNASSIGNED,.. +17E0-17E9,PVALID,KHMER DIGIT ZERO..KHMER DIGIT NINE +17EA-17EF,UNASSIGNED,.. +17F0-17F9,DISALLOWED,KHMER SYMBOL LEK ATTAK SON..KHMER SYMBOL LEK ATTAK PRAM-BUON +17FA-17FF,UNASSIGNED,.. +1800-180E,DISALLOWED,MONGOLIAN BIRGA..MONGOLIAN VOWEL SEPARATOR +180F,UNASSIGNED, +1810-1819,PVALID,MONGOLIAN DIGIT ZERO..MONGOLIAN DIGIT NINE +181A-181F,UNASSIGNED,.. +1820-1877,PVALID,MONGOLIAN LETTER A..MONGOLIAN LETTER MANCHU ZHA +1878-187F,UNASSIGNED,.. +1880-18AA,PVALID,MONGOLIAN LETTER ALI GALI ANUSVARA ONE..MONGOLIAN LETTER MANCHU ALI GALI LHA +18AB-18AF,UNASSIGNED,.. +18B0-18F5,PVALID,CANADIAN SYLLABICS OY..CANADIAN SYLLABICS CARRIER DENTAL S +18F6-18FF,UNASSIGNED,.. +1900-191C,PVALID,LIMBU VOWEL-CARRIER LETTER..LIMBU LETTER HA +191D-191F,UNASSIGNED,.. +1920-192B,PVALID,LIMBU VOWEL SIGN A..LIMBU SUBJOINED LETTER WA +192C-192F,UNASSIGNED,.. +1930-193B,PVALID,LIMBU SMALL LETTER KA..LIMBU SIGN SA-I +193C-193F,UNASSIGNED,.. +1940,DISALLOWED,LIMBU SIGN LOO +1941-1943,UNASSIGNED,.. +1944-1945,DISALLOWED,LIMBU EXCLAMATION MARK..LIMBU QUESTION MARK +1946-196D,PVALID,LIMBU DIGIT ZERO..TAI LE LETTER AI +196E-196F,UNASSIGNED,.. +1970-1974,PVALID,TAI LE LETTER TONE-2..TAI LE LETTER TONE-6 +1975-197F,UNASSIGNED,.. +1980-19AB,PVALID,NEW TAI LUE LETTER HIGH QA..NEW TAI LUE LETTER LOW SUA +19AC-19AF,UNASSIGNED,.. +19B0-19C9,PVALID,NEW TAI LUE VOWEL SIGN VOWEL SHORTENER..NEW TAI LUE TONE MARK-2 +19CA-19CF,UNASSIGNED,.. +19D0-19D9,PVALID,NEW TAI LUE DIGIT ZERO..NEW TAI LUE DIGIT NINE +19DA,DISALLOWED,NEW TAI LUE THAM DIGIT ONE +19DB-19DD,UNASSIGNED,.. +19DE-19FF,DISALLOWED,NEW TAI LUE SIGN LAE..KHMER SYMBOL DAP-PRAM ROC +1A00-1A1B,PVALID,BUGINESE LETTER KA..BUGINESE VOWEL SIGN AE +1A1C-1A1D,UNASSIGNED,.. +1A1E-1A1F,DISALLOWED,BUGINESE PALLAWA..BUGINESE END OF SECTION +1A20-1A5E,PVALID,TAI THAM LETTER HIGH KA..TAI THAM CONSONANT SIGN SA +1A5F,UNASSIGNED, +1A60-1A7C,PVALID,TAI THAM SIGN SAKOT..TAI THAM SIGN KHUEN-LUE KARAN +1A7D-1A7E,UNASSIGNED,.. +1A7F-1A89,PVALID,TAI THAM COMBINING CRYPTOGRAMMIC DOT..TAI THAM HORA DIGIT NINE +1A8A-1A8F,UNASSIGNED,.. +1A90-1A99,PVALID,TAI THAM THAM DIGIT ZERO..TAI THAM THAM DIGIT NINE +1A9A-1A9F,UNASSIGNED,.. +1AA0-1AA6,DISALLOWED,TAI THAM SIGN WIANG..TAI THAM SIGN REVERSED ROTATED RANA +1AA7,PVALID,TAI THAM SIGN MAI YAMOK +1AA8-1AAD,DISALLOWED,TAI THAM SIGN KAAN..TAI THAM SIGN CAANG +1AAE-1AFF,UNASSIGNED,.. +1B00-1B4B,PVALID,BALINESE SIGN ULU RICEM..BALINESE LETTER ASYURA SASAK +1B4C-1B4F,UNASSIGNED,.. +1B50-1B59,PVALID,BALINESE DIGIT ZERO..BALINESE DIGIT NINE +1B5A-1B6A,DISALLOWED,BALINESE PANTI..BALINESE MUSICAL SYMBOL DANG GEDE +1B6B-1B73,PVALID,BALINESE MUSICAL SYMBOL COMBINING TEGEH..BALINESE MUSICAL SYMBOL COMBINING GONG +1B74-1B7C,DISALLOWED,BALINESE MUSICAL SYMBOL RIGHT-HAND OPEN DUG..BALINESE MUSICAL SYMBOL LEFT-HAND OPEN PING +1B7D-1B7F,UNASSIGNED,.. +1B80-1BF3,PVALID,SUNDANESE SIGN PANYECEK..BATAK PANONGONAN +1BF4-1BFB,UNASSIGNED,.. +1BFC-1BFF,DISALLOWED,BATAK SYMBOL BINDU NA METEK..BATAK SYMBOL BINDU PANGOLAT +1C00-1C37,PVALID,LEPCHA LETTER KA..LEPCHA SIGN NUKTA +1C38-1C3A,UNASSIGNED,.. +1C3B-1C3F,DISALLOWED,LEPCHA PUNCTUATION TA-ROL..LEPCHA PUNCTUATION TSHOOK +1C40-1C49,PVALID,LEPCHA DIGIT ZERO..LEPCHA DIGIT NINE +1C4A-1C4C,UNASSIGNED,.. +1C4D-1C7D,PVALID,LEPCHA LETTER TTA..OL CHIKI AHAD +1C7E-1C7F,DISALLOWED,OL CHIKI PUNCTUATION MUCAAD..OL CHIKI PUNCTUATION DOUBLE MUCAAD +1C80-1CBF,UNASSIGNED,.. +1CC0-1CC7,DISALLOWED,SUNDANESE PUNCTUATION BINDU SURYA..SUNDANESE PUNCTUATION BINDU BA SATANGA +1CC8-1CCF,UNASSIGNED,.. +1CD0-1CD2,PVALID,VEDIC TONE KARSHANA..VEDIC TONE PRENKHA +1CD3,DISALLOWED,VEDIC SIGN NIHSHVASA +1CD4-1CF6,PVALID,VEDIC SIGN YAJURVEDIC MIDLINE SVARITA..VEDIC SIGN UPADHMANIYA +1CF7-1CFF,UNASSIGNED,.. +1D00-1D2B,PVALID,LATIN LETTER SMALL CAPITAL A..CYRILLIC LETTER SMALL CAPITAL EL +1D2C-1D2E,DISALLOWED,MODIFIER LETTER CAPITAL A..MODIFIER LETTER CAPITAL B +1D2F,PVALID,MODIFIER LETTER CAPITAL BARRED B +1D30-1D3A,DISALLOWED,MODIFIER LETTER CAPITAL D..MODIFIER LETTER CAPITAL N +1D3B,PVALID,MODIFIER LETTER CAPITAL REVERSED N +1D3C-1D4D,DISALLOWED,MODIFIER LETTER CAPITAL O..MODIFIER LETTER SMALL G +1D4E,PVALID,MODIFIER LETTER SMALL TURNED I +1D4F-1D6A,DISALLOWED,MODIFIER LETTER SMALL K..GREEK SUBSCRIPT SMALL LETTER CHI +1D6B-1D77,PVALID,LATIN SMALL LETTER UE..LATIN SMALL LETTER TURNED G +1D78,DISALLOWED,MODIFIER LETTER CYRILLIC EN +1D79-1D9A,PVALID,LATIN SMALL LETTER INSULAR G..LATIN SMALL LETTER EZH WITH RETROFLEX HOOK +1D9B-1DBF,DISALLOWED,MODIFIER LETTER SMALL TURNED ALPHA..MODIFIER LETTER SMALL THETA +1DC0-1DE6,PVALID,COMBINING DOTTED GRAVE ACCENT..COMBINING LATIN SMALL LETTER Z +1DE7-1DFB,UNASSIGNED,.. +1DFC-1DFF,PVALID,COMBINING DOUBLE INVERTED BREVE BELOW..COMBINING RIGHT ARROWHEAD AND DOWN ARROWHEAD BELOW +1E00,DISALLOWED,LATIN CAPITAL LETTER A WITH RING BELOW +1E01,PVALID,LATIN SMALL LETTER A WITH RING BELOW +1E02,DISALLOWED,LATIN CAPITAL LETTER B WITH DOT ABOVE +1E03,PVALID,LATIN SMALL LETTER B WITH DOT ABOVE +1E04,DISALLOWED,LATIN CAPITAL LETTER B WITH DOT BELOW +1E05,PVALID,LATIN SMALL LETTER B WITH DOT BELOW +1E06,DISALLOWED,LATIN CAPITAL LETTER B WITH LINE BELOW +1E07,PVALID,LATIN SMALL LETTER B WITH LINE BELOW +1E08,DISALLOWED,LATIN CAPITAL LETTER C WITH CEDILLA AND ACUTE +1E09,PVALID,LATIN SMALL LETTER C WITH CEDILLA AND ACUTE +1E0A,DISALLOWED,LATIN CAPITAL LETTER D WITH DOT ABOVE +1E0B,PVALID,LATIN SMALL LETTER D WITH DOT ABOVE +1E0C,DISALLOWED,LATIN CAPITAL LETTER D WITH DOT BELOW +1E0D,PVALID,LATIN SMALL LETTER D WITH DOT BELOW +1E0E,DISALLOWED,LATIN CAPITAL LETTER D WITH LINE BELOW +1E0F,PVALID,LATIN SMALL LETTER D WITH LINE BELOW +1E10,DISALLOWED,LATIN CAPITAL LETTER D WITH CEDILLA +1E11,PVALID,LATIN SMALL LETTER D WITH CEDILLA +1E12,DISALLOWED,LATIN CAPITAL LETTER D WITH CIRCUMFLEX BELOW +1E13,PVALID,LATIN SMALL LETTER D WITH CIRCUMFLEX BELOW +1E14,DISALLOWED,LATIN CAPITAL LETTER E WITH MACRON AND GRAVE +1E15,PVALID,LATIN SMALL LETTER E WITH MACRON AND GRAVE +1E16,DISALLOWED,LATIN CAPITAL LETTER E WITH MACRON AND ACUTE +1E17,PVALID,LATIN SMALL LETTER E WITH MACRON AND ACUTE +1E18,DISALLOWED,LATIN CAPITAL LETTER E WITH CIRCUMFLEX BELOW +1E19,PVALID,LATIN SMALL LETTER E WITH CIRCUMFLEX BELOW +1E1A,DISALLOWED,LATIN CAPITAL LETTER E WITH TILDE BELOW +1E1B,PVALID,LATIN SMALL LETTER E WITH TILDE BELOW +1E1C,DISALLOWED,LATIN CAPITAL LETTER E WITH CEDILLA AND BREVE +1E1D,PVALID,LATIN SMALL LETTER E WITH CEDILLA AND BREVE +1E1E,DISALLOWED,LATIN CAPITAL LETTER F WITH DOT ABOVE +1E1F,PVALID,LATIN SMALL LETTER F WITH DOT ABOVE +1E20,DISALLOWED,LATIN CAPITAL LETTER G WITH MACRON +1E21,PVALID,LATIN SMALL LETTER G WITH MACRON +1E22,DISALLOWED,LATIN CAPITAL LETTER H WITH DOT ABOVE +1E23,PVALID,LATIN SMALL LETTER H WITH DOT ABOVE +1E24,DISALLOWED,LATIN CAPITAL LETTER H WITH DOT BELOW +1E25,PVALID,LATIN SMALL LETTER H WITH DOT BELOW +1E26,DISALLOWED,LATIN CAPITAL LETTER H WITH DIAERESIS +1E27,PVALID,LATIN SMALL LETTER H WITH DIAERESIS +1E28,DISALLOWED,LATIN CAPITAL LETTER H WITH CEDILLA +1E29,PVALID,LATIN SMALL LETTER H WITH CEDILLA +1E2A,DISALLOWED,LATIN CAPITAL LETTER H WITH BREVE BELOW +1E2B,PVALID,LATIN SMALL LETTER H WITH BREVE BELOW +1E2C,DISALLOWED,LATIN CAPITAL LETTER I WITH TILDE BELOW +1E2D,PVALID,LATIN SMALL LETTER I WITH TILDE BELOW +1E2E,DISALLOWED,LATIN CAPITAL LETTER I WITH DIAERESIS AND ACUTE +1E2F,PVALID,LATIN SMALL LETTER I WITH DIAERESIS AND ACUTE +1E30,DISALLOWED,LATIN CAPITAL LETTER K WITH ACUTE +1E31,PVALID,LATIN SMALL LETTER K WITH ACUTE +1E32,DISALLOWED,LATIN CAPITAL LETTER K WITH DOT BELOW +1E33,PVALID,LATIN SMALL LETTER K WITH DOT BELOW +1E34,DISALLOWED,LATIN CAPITAL LETTER K WITH LINE BELOW +1E35,PVALID,LATIN SMALL LETTER K WITH LINE BELOW +1E36,DISALLOWED,LATIN CAPITAL LETTER L WITH DOT BELOW +1E37,PVALID,LATIN SMALL LETTER L WITH DOT BELOW +1E38,DISALLOWED,LATIN CAPITAL LETTER L WITH DOT BELOW AND MACRON +1E39,PVALID,LATIN SMALL LETTER L WITH DOT BELOW AND MACRON +1E3A,DISALLOWED,LATIN CAPITAL LETTER L WITH LINE BELOW +1E3B,PVALID,LATIN SMALL LETTER L WITH LINE BELOW +1E3C,DISALLOWED,LATIN CAPITAL LETTER L WITH CIRCUMFLEX BELOW +1E3D,PVALID,LATIN SMALL LETTER L WITH CIRCUMFLEX BELOW +1E3E,DISALLOWED,LATIN CAPITAL LETTER M WITH ACUTE +1E3F,PVALID,LATIN SMALL LETTER M WITH ACUTE +1E40,DISALLOWED,LATIN CAPITAL LETTER M WITH DOT ABOVE +1E41,PVALID,LATIN SMALL LETTER M WITH DOT ABOVE +1E42,DISALLOWED,LATIN CAPITAL LETTER M WITH DOT BELOW +1E43,PVALID,LATIN SMALL LETTER M WITH DOT BELOW +1E44,DISALLOWED,LATIN CAPITAL LETTER N WITH DOT ABOVE +1E45,PVALID,LATIN SMALL LETTER N WITH DOT ABOVE +1E46,DISALLOWED,LATIN CAPITAL LETTER N WITH DOT BELOW +1E47,PVALID,LATIN SMALL LETTER N WITH DOT BELOW +1E48,DISALLOWED,LATIN CAPITAL LETTER N WITH LINE BELOW +1E49,PVALID,LATIN SMALL LETTER N WITH LINE BELOW +1E4A,DISALLOWED,LATIN CAPITAL LETTER N WITH CIRCUMFLEX BELOW +1E4B,PVALID,LATIN SMALL LETTER N WITH CIRCUMFLEX BELOW +1E4C,DISALLOWED,LATIN CAPITAL LETTER O WITH TILDE AND ACUTE +1E4D,PVALID,LATIN SMALL LETTER O WITH TILDE AND ACUTE +1E4E,DISALLOWED,LATIN CAPITAL LETTER O WITH TILDE AND DIAERESIS +1E4F,PVALID,LATIN SMALL LETTER O WITH TILDE AND DIAERESIS +1E50,DISALLOWED,LATIN CAPITAL LETTER O WITH MACRON AND GRAVE +1E51,PVALID,LATIN SMALL LETTER O WITH MACRON AND GRAVE +1E52,DISALLOWED,LATIN CAPITAL LETTER O WITH MACRON AND ACUTE +1E53,PVALID,LATIN SMALL LETTER O WITH MACRON AND ACUTE +1E54,DISALLOWED,LATIN CAPITAL LETTER P WITH ACUTE +1E55,PVALID,LATIN SMALL LETTER P WITH ACUTE +1E56,DISALLOWED,LATIN CAPITAL LETTER P WITH DOT ABOVE +1E57,PVALID,LATIN SMALL LETTER P WITH DOT ABOVE +1E58,DISALLOWED,LATIN CAPITAL LETTER R WITH DOT ABOVE +1E59,PVALID,LATIN SMALL LETTER R WITH DOT ABOVE +1E5A,DISALLOWED,LATIN CAPITAL LETTER R WITH DOT BELOW +1E5B,PVALID,LATIN SMALL LETTER R WITH DOT BELOW +1E5C,DISALLOWED,LATIN CAPITAL LETTER R WITH DOT BELOW AND MACRON +1E5D,PVALID,LATIN SMALL LETTER R WITH DOT BELOW AND MACRON +1E5E,DISALLOWED,LATIN CAPITAL LETTER R WITH LINE BELOW +1E5F,PVALID,LATIN SMALL LETTER R WITH LINE BELOW +1E60,DISALLOWED,LATIN CAPITAL LETTER S WITH DOT ABOVE +1E61,PVALID,LATIN SMALL LETTER S WITH DOT ABOVE +1E62,DISALLOWED,LATIN CAPITAL LETTER S WITH DOT BELOW +1E63,PVALID,LATIN SMALL LETTER S WITH DOT BELOW +1E64,DISALLOWED,LATIN CAPITAL LETTER S WITH ACUTE AND DOT ABOVE +1E65,PVALID,LATIN SMALL LETTER S WITH ACUTE AND DOT ABOVE +1E66,DISALLOWED,LATIN CAPITAL LETTER S WITH CARON AND DOT ABOVE +1E67,PVALID,LATIN SMALL LETTER S WITH CARON AND DOT ABOVE +1E68,DISALLOWED,LATIN CAPITAL LETTER S WITH DOT BELOW AND DOT ABOVE +1E69,PVALID,LATIN SMALL LETTER S WITH DOT BELOW AND DOT ABOVE +1E6A,DISALLOWED,LATIN CAPITAL LETTER T WITH DOT ABOVE +1E6B,PVALID,LATIN SMALL LETTER T WITH DOT ABOVE +1E6C,DISALLOWED,LATIN CAPITAL LETTER T WITH DOT BELOW +1E6D,PVALID,LATIN SMALL LETTER T WITH DOT BELOW +1E6E,DISALLOWED,LATIN CAPITAL LETTER T WITH LINE BELOW +1E6F,PVALID,LATIN SMALL LETTER T WITH LINE BELOW +1E70,DISALLOWED,LATIN CAPITAL LETTER T WITH CIRCUMFLEX BELOW +1E71,PVALID,LATIN SMALL LETTER T WITH CIRCUMFLEX BELOW +1E72,DISALLOWED,LATIN CAPITAL LETTER U WITH DIAERESIS BELOW +1E73,PVALID,LATIN SMALL LETTER U WITH DIAERESIS BELOW +1E74,DISALLOWED,LATIN CAPITAL LETTER U WITH TILDE BELOW +1E75,PVALID,LATIN SMALL LETTER U WITH TILDE BELOW +1E76,DISALLOWED,LATIN CAPITAL LETTER U WITH CIRCUMFLEX BELOW +1E77,PVALID,LATIN SMALL LETTER U WITH CIRCUMFLEX BELOW +1E78,DISALLOWED,LATIN CAPITAL LETTER U WITH TILDE AND ACUTE +1E79,PVALID,LATIN SMALL LETTER U WITH TILDE AND ACUTE +1E7A,DISALLOWED,LATIN CAPITAL LETTER U WITH MACRON AND DIAERESIS +1E7B,PVALID,LATIN SMALL LETTER U WITH MACRON AND DIAERESIS +1E7C,DISALLOWED,LATIN CAPITAL LETTER V WITH TILDE +1E7D,PVALID,LATIN SMALL LETTER V WITH TILDE +1E7E,DISALLOWED,LATIN CAPITAL LETTER V WITH DOT BELOW +1E7F,PVALID,LATIN SMALL LETTER V WITH DOT BELOW +1E80,DISALLOWED,LATIN CAPITAL LETTER W WITH GRAVE +1E81,PVALID,LATIN SMALL LETTER W WITH GRAVE +1E82,DISALLOWED,LATIN CAPITAL LETTER W WITH ACUTE +1E83,PVALID,LATIN SMALL LETTER W WITH ACUTE +1E84,DISALLOWED,LATIN CAPITAL LETTER W WITH DIAERESIS +1E85,PVALID,LATIN SMALL LETTER W WITH DIAERESIS +1E86,DISALLOWED,LATIN CAPITAL LETTER W WITH DOT ABOVE +1E87,PVALID,LATIN SMALL LETTER W WITH DOT ABOVE +1E88,DISALLOWED,LATIN CAPITAL LETTER W WITH DOT BELOW +1E89,PVALID,LATIN SMALL LETTER W WITH DOT BELOW +1E8A,DISALLOWED,LATIN CAPITAL LETTER X WITH DOT ABOVE +1E8B,PVALID,LATIN SMALL LETTER X WITH DOT ABOVE +1E8C,DISALLOWED,LATIN CAPITAL LETTER X WITH DIAERESIS +1E8D,PVALID,LATIN SMALL LETTER X WITH DIAERESIS +1E8E,DISALLOWED,LATIN CAPITAL LETTER Y WITH DOT ABOVE +1E8F,PVALID,LATIN SMALL LETTER Y WITH DOT ABOVE +1E90,DISALLOWED,LATIN CAPITAL LETTER Z WITH CIRCUMFLEX +1E91,PVALID,LATIN SMALL LETTER Z WITH CIRCUMFLEX +1E92,DISALLOWED,LATIN CAPITAL LETTER Z WITH DOT BELOW +1E93,PVALID,LATIN SMALL LETTER Z WITH DOT BELOW +1E94,DISALLOWED,LATIN CAPITAL LETTER Z WITH LINE BELOW +1E95-1E99,PVALID,LATIN SMALL LETTER Z WITH LINE BELOW..LATIN SMALL LETTER Y WITH RING ABOVE +1E9A-1E9B,DISALLOWED,LATIN SMALL LETTER A WITH RIGHT HALF RING..LATIN SMALL LETTER LONG S WITH DOT ABOVE +1E9C-1E9D,PVALID,LATIN SMALL LETTER LONG S WITH DIAGONAL STROKE..LATIN SMALL LETTER LONG S WITH HIGH STROKE +1E9E,DISALLOWED,LATIN CAPITAL LETTER SHARP S +1E9F,PVALID,LATIN SMALL LETTER DELTA +1EA0,DISALLOWED,LATIN CAPITAL LETTER A WITH DOT BELOW +1EA1,PVALID,LATIN SMALL LETTER A WITH DOT BELOW +1EA2,DISALLOWED,LATIN CAPITAL LETTER A WITH HOOK ABOVE +1EA3,PVALID,LATIN SMALL LETTER A WITH HOOK ABOVE +1EA4,DISALLOWED,LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND ACUTE +1EA5,PVALID,LATIN SMALL LETTER A WITH CIRCUMFLEX AND ACUTE +1EA6,DISALLOWED,LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND GRAVE +1EA7,PVALID,LATIN SMALL LETTER A WITH CIRCUMFLEX AND GRAVE +1EA8,DISALLOWED,LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE +1EA9,PVALID,LATIN SMALL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE +1EAA,DISALLOWED,LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND TILDE +1EAB,PVALID,LATIN SMALL LETTER A WITH CIRCUMFLEX AND TILDE +1EAC,DISALLOWED,LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND DOT BELOW +1EAD,PVALID,LATIN SMALL LETTER A WITH CIRCUMFLEX AND DOT BELOW +1EAE,DISALLOWED,LATIN CAPITAL LETTER A WITH BREVE AND ACUTE +1EAF,PVALID,LATIN SMALL LETTER A WITH BREVE AND ACUTE +1EB0,DISALLOWED,LATIN CAPITAL LETTER A WITH BREVE AND GRAVE +1EB1,PVALID,LATIN SMALL LETTER A WITH BREVE AND GRAVE +1EB2,DISALLOWED,LATIN CAPITAL LETTER A WITH BREVE AND HOOK ABOVE +1EB3,PVALID,LATIN SMALL LETTER A WITH BREVE AND HOOK ABOVE +1EB4,DISALLOWED,LATIN CAPITAL LETTER A WITH BREVE AND TILDE +1EB5,PVALID,LATIN SMALL LETTER A WITH BREVE AND TILDE +1EB6,DISALLOWED,LATIN CAPITAL LETTER A WITH BREVE AND DOT BELOW +1EB7,PVALID,LATIN SMALL LETTER A WITH BREVE AND DOT BELOW +1EB8,DISALLOWED,LATIN CAPITAL LETTER E WITH DOT BELOW +1EB9,PVALID,LATIN SMALL LETTER E WITH DOT BELOW +1EBA,DISALLOWED,LATIN CAPITAL LETTER E WITH HOOK ABOVE +1EBB,PVALID,LATIN SMALL LETTER E WITH HOOK ABOVE +1EBC,DISALLOWED,LATIN CAPITAL LETTER E WITH TILDE +1EBD,PVALID,LATIN SMALL LETTER E WITH TILDE +1EBE,DISALLOWED,LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND ACUTE +1EBF,PVALID,LATIN SMALL LETTER E WITH CIRCUMFLEX AND ACUTE +1EC0,DISALLOWED,LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND GRAVE +1EC1,PVALID,LATIN SMALL LETTER E WITH CIRCUMFLEX AND GRAVE +1EC2,DISALLOWED,LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE +1EC3,PVALID,LATIN SMALL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE +1EC4,DISALLOWED,LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND TILDE +1EC5,PVALID,LATIN SMALL LETTER E WITH CIRCUMFLEX AND TILDE +1EC6,DISALLOWED,LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND DOT BELOW +1EC7,PVALID,LATIN SMALL LETTER E WITH CIRCUMFLEX AND DOT BELOW +1EC8,DISALLOWED,LATIN CAPITAL LETTER I WITH HOOK ABOVE +1EC9,PVALID,LATIN SMALL LETTER I WITH HOOK ABOVE +1ECA,DISALLOWED,LATIN CAPITAL LETTER I WITH DOT BELOW +1ECB,PVALID,LATIN SMALL LETTER I WITH DOT BELOW +1ECC,DISALLOWED,LATIN CAPITAL LETTER O WITH DOT BELOW +1ECD,PVALID,LATIN SMALL LETTER O WITH DOT BELOW +1ECE,DISALLOWED,LATIN CAPITAL LETTER O WITH HOOK ABOVE +1ECF,PVALID,LATIN SMALL LETTER O WITH HOOK ABOVE +1ED0,DISALLOWED,LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND ACUTE +1ED1,PVALID,LATIN SMALL LETTER O WITH CIRCUMFLEX AND ACUTE +1ED2,DISALLOWED,LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND GRAVE +1ED3,PVALID,LATIN SMALL LETTER O WITH CIRCUMFLEX AND GRAVE +1ED4,DISALLOWED,LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE +1ED5,PVALID,LATIN SMALL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE +1ED6,DISALLOWED,LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND TILDE +1ED7,PVALID,LATIN SMALL LETTER O WITH CIRCUMFLEX AND TILDE +1ED8,DISALLOWED,LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND DOT BELOW +1ED9,PVALID,LATIN SMALL LETTER O WITH CIRCUMFLEX AND DOT BELOW +1EDA,DISALLOWED,LATIN CAPITAL LETTER O WITH HORN AND ACUTE +1EDB,PVALID,LATIN SMALL LETTER O WITH HORN AND ACUTE +1EDC,DISALLOWED,LATIN CAPITAL LETTER O WITH HORN AND GRAVE +1EDD,PVALID,LATIN SMALL LETTER O WITH HORN AND GRAVE +1EDE,DISALLOWED,LATIN CAPITAL LETTER O WITH HORN AND HOOK ABOVE +1EDF,PVALID,LATIN SMALL LETTER O WITH HORN AND HOOK ABOVE +1EE0,DISALLOWED,LATIN CAPITAL LETTER O WITH HORN AND TILDE +1EE1,PVALID,LATIN SMALL LETTER O WITH HORN AND TILDE +1EE2,DISALLOWED,LATIN CAPITAL LETTER O WITH HORN AND DOT BELOW +1EE3,PVALID,LATIN SMALL LETTER O WITH HORN AND DOT BELOW +1EE4,DISALLOWED,LATIN CAPITAL LETTER U WITH DOT BELOW +1EE5,PVALID,LATIN SMALL LETTER U WITH DOT BELOW +1EE6,DISALLOWED,LATIN CAPITAL LETTER U WITH HOOK ABOVE +1EE7,PVALID,LATIN SMALL LETTER U WITH HOOK ABOVE +1EE8,DISALLOWED,LATIN CAPITAL LETTER U WITH HORN AND ACUTE +1EE9,PVALID,LATIN SMALL LETTER U WITH HORN AND ACUTE +1EEA,DISALLOWED,LATIN CAPITAL LETTER U WITH HORN AND GRAVE +1EEB,PVALID,LATIN SMALL LETTER U WITH HORN AND GRAVE +1EEC,DISALLOWED,LATIN CAPITAL LETTER U WITH HORN AND HOOK ABOVE +1EED,PVALID,LATIN SMALL LETTER U WITH HORN AND HOOK ABOVE +1EEE,DISALLOWED,LATIN CAPITAL LETTER U WITH HORN AND TILDE +1EEF,PVALID,LATIN SMALL LETTER U WITH HORN AND TILDE +1EF0,DISALLOWED,LATIN CAPITAL LETTER U WITH HORN AND DOT BELOW +1EF1,PVALID,LATIN SMALL LETTER U WITH HORN AND DOT BELOW +1EF2,DISALLOWED,LATIN CAPITAL LETTER Y WITH GRAVE +1EF3,PVALID,LATIN SMALL LETTER Y WITH GRAVE +1EF4,DISALLOWED,LATIN CAPITAL LETTER Y WITH DOT BELOW +1EF5,PVALID,LATIN SMALL LETTER Y WITH DOT BELOW +1EF6,DISALLOWED,LATIN CAPITAL LETTER Y WITH HOOK ABOVE +1EF7,PVALID,LATIN SMALL LETTER Y WITH HOOK ABOVE +1EF8,DISALLOWED,LATIN CAPITAL LETTER Y WITH TILDE +1EF9,PVALID,LATIN SMALL LETTER Y WITH TILDE +1EFA,DISALLOWED,LATIN CAPITAL LETTER MIDDLE-WELSH LL +1EFB,PVALID,LATIN SMALL LETTER MIDDLE-WELSH LL +1EFC,DISALLOWED,LATIN CAPITAL LETTER MIDDLE-WELSH V +1EFD,PVALID,LATIN SMALL LETTER MIDDLE-WELSH V +1EFE,DISALLOWED,LATIN CAPITAL LETTER Y WITH LOOP +1EFF-1F07,PVALID,LATIN SMALL LETTER Y WITH LOOP..GREEK SMALL LETTER ALPHA WITH DASIA AND PERISPOMENI +1F08-1F0F,DISALLOWED,GREEK CAPITAL LETTER ALPHA WITH PSILI..GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI +1F10-1F15,PVALID,GREEK SMALL LETTER EPSILON WITH PSILI..GREEK SMALL LETTER EPSILON WITH DASIA AND OXIA +1F16-1F17,UNASSIGNED,.. +1F18-1F1D,DISALLOWED,GREEK CAPITAL LETTER EPSILON WITH PSILI..GREEK CAPITAL LETTER EPSILON WITH DASIA AND OXIA +1F1E-1F1F,UNASSIGNED,.. +1F20-1F27,PVALID,GREEK SMALL LETTER ETA WITH PSILI..GREEK SMALL LETTER ETA WITH DASIA AND PERISPOMENI +1F28-1F2F,DISALLOWED,GREEK CAPITAL LETTER ETA WITH PSILI..GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI +1F30-1F37,PVALID,GREEK SMALL LETTER IOTA WITH PSILI..GREEK SMALL LETTER IOTA WITH DASIA AND PERISPOMENI +1F38-1F3F,DISALLOWED,GREEK CAPITAL LETTER IOTA WITH PSILI..GREEK CAPITAL LETTER IOTA WITH DASIA AND PERISPOMENI +1F40-1F45,PVALID,GREEK SMALL LETTER OMICRON WITH PSILI..GREEK SMALL LETTER OMICRON WITH DASIA AND OXIA +1F46-1F47,UNASSIGNED,.. +1F48-1F4D,DISALLOWED,GREEK CAPITAL LETTER OMICRON WITH PSILI..GREEK CAPITAL LETTER OMICRON WITH DASIA AND OXIA +1F4E-1F4F,UNASSIGNED,.. +1F50-1F57,PVALID,GREEK SMALL LETTER UPSILON WITH PSILI..GREEK SMALL LETTER UPSILON WITH DASIA AND PERISPOMENI +1F58,UNASSIGNED, +1F59,DISALLOWED,GREEK CAPITAL LETTER UPSILON WITH DASIA +1F5A,UNASSIGNED, +1F5B,DISALLOWED,GREEK CAPITAL LETTER UPSILON WITH DASIA AND VARIA +1F5C,UNASSIGNED, +1F5D,DISALLOWED,GREEK CAPITAL LETTER UPSILON WITH DASIA AND OXIA +1F5E,UNASSIGNED, +1F5F,DISALLOWED,GREEK CAPITAL LETTER UPSILON WITH DASIA AND PERISPOMENI +1F60-1F67,PVALID,GREEK SMALL LETTER OMEGA WITH PSILI..GREEK SMALL LETTER OMEGA WITH DASIA AND PERISPOMENI +1F68-1F6F,DISALLOWED,GREEK CAPITAL LETTER OMEGA WITH PSILI..GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI +1F70,PVALID,GREEK SMALL LETTER ALPHA WITH VARIA +1F71,DISALLOWED,GREEK SMALL LETTER ALPHA WITH OXIA +1F72,PVALID,GREEK SMALL LETTER EPSILON WITH VARIA +1F73,DISALLOWED,GREEK SMALL LETTER EPSILON WITH OXIA +1F74,PVALID,GREEK SMALL LETTER ETA WITH VARIA +1F75,DISALLOWED,GREEK SMALL LETTER ETA WITH OXIA +1F76,PVALID,GREEK SMALL LETTER IOTA WITH VARIA +1F77,DISALLOWED,GREEK SMALL LETTER IOTA WITH OXIA +1F78,PVALID,GREEK SMALL LETTER OMICRON WITH VARIA +1F79,DISALLOWED,GREEK SMALL LETTER OMICRON WITH OXIA +1F7A,PVALID,GREEK SMALL LETTER UPSILON WITH VARIA +1F7B,DISALLOWED,GREEK SMALL LETTER UPSILON WITH OXIA +1F7C,PVALID,GREEK SMALL LETTER OMEGA WITH VARIA +1F7D,DISALLOWED,GREEK SMALL LETTER OMEGA WITH OXIA +1F7E-1F7F,UNASSIGNED,.. +1F80-1FAF,DISALLOWED,GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI..GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI +1FB0-1FB1,PVALID,GREEK SMALL LETTER ALPHA WITH VRACHY..GREEK SMALL LETTER ALPHA WITH MACRON +1FB2-1FB4,DISALLOWED,GREEK SMALL LETTER ALPHA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER ALPHA WITH OXIA AND YPOGEGRAMMENI +1FB5,UNASSIGNED, +1FB6,PVALID,GREEK SMALL LETTER ALPHA WITH PERISPOMENI +1FB7-1FC4,DISALLOWED,GREEK SMALL LETTER ALPHA WITH PERISPOMENI AND YPOGEGRAMMENI..GREEK SMALL LETTER ETA WITH OXIA AND YPOGEGRAMMENI +1FC5,UNASSIGNED, +1FC6,PVALID,GREEK SMALL LETTER ETA WITH PERISPOMENI +1FC7-1FCF,DISALLOWED,GREEK SMALL LETTER ETA WITH PERISPOMENI AND YPOGEGRAMMENI..GREEK PSILI AND PERISPOMENI +1FD0-1FD2,PVALID,GREEK SMALL LETTER IOTA WITH VRACHY..GREEK SMALL LETTER IOTA WITH DIALYTIKA AND VARIA +1FD3,DISALLOWED,GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA +1FD4-1FD5,UNASSIGNED,.. +1FD6-1FD7,PVALID,GREEK SMALL LETTER IOTA WITH PERISPOMENI..GREEK SMALL LETTER IOTA WITH DIALYTIKA AND PERISPOMENI +1FD8-1FDB,DISALLOWED,GREEK CAPITAL LETTER IOTA WITH VRACHY..GREEK CAPITAL LETTER IOTA WITH OXIA +1FDC,UNASSIGNED, +1FDD-1FDF,DISALLOWED,GREEK DASIA AND VARIA..GREEK DASIA AND PERISPOMENI +1FE0-1FE2,PVALID,GREEK SMALL LETTER UPSILON WITH VRACHY..GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND VARIA +1FE3,DISALLOWED,GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND OXIA +1FE4-1FE7,PVALID,GREEK SMALL LETTER RHO WITH PSILI..GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND PERISPOMENI +1FE8-1FEF,DISALLOWED,GREEK CAPITAL LETTER UPSILON WITH VRACHY..GREEK VARIA +1FF0-1FF1,UNASSIGNED,.. +1FF2-1FF4,DISALLOWED,GREEK SMALL LETTER OMEGA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI +1FF5,UNASSIGNED, +1FF6,PVALID,GREEK SMALL LETTER OMEGA WITH PERISPOMENI +1FF7-1FFE,DISALLOWED,GREEK SMALL LETTER OMEGA WITH PERISPOMENI AND YPOGEGRAMMENI..GREEK DASIA +1FFF,UNASSIGNED, +2000-200B,DISALLOWED,EN QUAD..ZERO WIDTH SPACE +200C-200D,CONTEXTJ,ZERO WIDTH NON-JOINER..ZERO WIDTH JOINER +200E-2064,DISALLOWED,LEFT-TO-RIGHT MARK..INVISIBLE PLUS +2065,UNASSIGNED, +2066-2071,DISALLOWED,LEFT-TO-RIGHT ISOLATE..SUPERSCRIPT LATIN SMALL LETTER I +2072-2073,UNASSIGNED,.. +2074-208E,DISALLOWED,SUPERSCRIPT FOUR..SUBSCRIPT RIGHT PARENTHESIS +208F,UNASSIGNED, +2090-209C,DISALLOWED,LATIN SUBSCRIPT SMALL LETTER A..LATIN SUBSCRIPT SMALL LETTER T +209D-209F,UNASSIGNED,.. +20A0-20BA,DISALLOWED,EURO-CURRENCY SIGN..TURKISH LIRA SIGN +20BB-20CF,UNASSIGNED,.. +20D0-20F0,DISALLOWED,COMBINING LEFT HARPOON ABOVE..COMBINING ASTERISK ABOVE +20F1-20FF,UNASSIGNED,.. +2100-214D,DISALLOWED,ACCOUNT OF..AKTIESELSKAB +214E,PVALID,TURNED SMALL F +214F-2183,DISALLOWED,SYMBOL FOR SAMARITAN SOURCE..ROMAN NUMERAL REVERSED ONE HUNDRED +2184,PVALID,LATIN SMALL LETTER REVERSED C +2185-2189,DISALLOWED,ROMAN NUMERAL SIX LATE FORM..VULGAR FRACTION ZERO THIRDS +218A-218F,UNASSIGNED,.. +2190-23F3,DISALLOWED,LEFTWARDS ARROW..HOURGLASS WITH FLOWING SAND +23F4-23FF,UNASSIGNED,.. +2400-2426,DISALLOWED,SYMBOL FOR NULL..SYMBOL FOR SUBSTITUTE FORM TWO +2427-243F,UNASSIGNED,.. +2440-244A,DISALLOWED,OCR HOOK..OCR DOUBLE BACKSLASH +244B-245F,UNASSIGNED,.. +2460-26FF,DISALLOWED,CIRCLED DIGIT ONE..WHITE FLAG WITH HORIZONTAL MIDDLE BLACK STRIPE +2700,UNASSIGNED, +2701-2B4C,DISALLOWED,UPPER BLADE SCISSORS..RIGHTWARDS ARROW ABOVE REVERSE TILDE OPERATOR +2B4D-2B4F,UNASSIGNED,.. +2B50-2B59,DISALLOWED,WHITE MEDIUM STAR..HEAVY CIRCLED SALTIRE +2B5A-2BFF,UNASSIGNED,.. +2C00-2C2E,DISALLOWED,GLAGOLITIC CAPITAL LETTER AZU..GLAGOLITIC CAPITAL LETTER LATINATE MYSLITE +2C2F,UNASSIGNED, +2C30-2C5E,PVALID,GLAGOLITIC SMALL LETTER AZU..GLAGOLITIC SMALL LETTER LATINATE MYSLITE +2C5F,UNASSIGNED, +2C60,DISALLOWED,LATIN CAPITAL LETTER L WITH DOUBLE BAR +2C61,PVALID,LATIN SMALL LETTER L WITH DOUBLE BAR +2C62-2C64,DISALLOWED,LATIN CAPITAL LETTER L WITH MIDDLE TILDE..LATIN CAPITAL LETTER R WITH TAIL +2C65-2C66,PVALID,LATIN SMALL LETTER A WITH STROKE..LATIN SMALL LETTER T WITH DIAGONAL STROKE +2C67,DISALLOWED,LATIN CAPITAL LETTER H WITH DESCENDER +2C68,PVALID,LATIN SMALL LETTER H WITH DESCENDER +2C69,DISALLOWED,LATIN CAPITAL LETTER K WITH DESCENDER +2C6A,PVALID,LATIN SMALL LETTER K WITH DESCENDER +2C6B,DISALLOWED,LATIN CAPITAL LETTER Z WITH DESCENDER +2C6C,PVALID,LATIN SMALL LETTER Z WITH DESCENDER +2C6D-2C70,DISALLOWED,LATIN CAPITAL LETTER ALPHA..LATIN CAPITAL LETTER TURNED ALPHA +2C71,PVALID,LATIN SMALL LETTER V WITH RIGHT HOOK +2C72,DISALLOWED,LATIN CAPITAL LETTER W WITH HOOK +2C73-2C74,PVALID,LATIN SMALL LETTER W WITH HOOK..LATIN SMALL LETTER V WITH CURL +2C75,DISALLOWED,LATIN CAPITAL LETTER HALF H +2C76-2C7B,PVALID,LATIN SMALL LETTER HALF H..LATIN LETTER SMALL CAPITAL TURNED E +2C7C-2C80,DISALLOWED,LATIN SUBSCRIPT SMALL LETTER J..COPTIC CAPITAL LETTER ALFA +2C81,PVALID,COPTIC SMALL LETTER ALFA +2C82,DISALLOWED,COPTIC CAPITAL LETTER VIDA +2C83,PVALID,COPTIC SMALL LETTER VIDA +2C84,DISALLOWED,COPTIC CAPITAL LETTER GAMMA +2C85,PVALID,COPTIC SMALL LETTER GAMMA +2C86,DISALLOWED,COPTIC CAPITAL LETTER DALDA +2C87,PVALID,COPTIC SMALL LETTER DALDA +2C88,DISALLOWED,COPTIC CAPITAL LETTER EIE +2C89,PVALID,COPTIC SMALL LETTER EIE +2C8A,DISALLOWED,COPTIC CAPITAL LETTER SOU +2C8B,PVALID,COPTIC SMALL LETTER SOU +2C8C,DISALLOWED,COPTIC CAPITAL LETTER ZATA +2C8D,PVALID,COPTIC SMALL LETTER ZATA +2C8E,DISALLOWED,COPTIC CAPITAL LETTER HATE +2C8F,PVALID,COPTIC SMALL LETTER HATE +2C90,DISALLOWED,COPTIC CAPITAL LETTER THETHE +2C91,PVALID,COPTIC SMALL LETTER THETHE +2C92,DISALLOWED,COPTIC CAPITAL LETTER IAUDA +2C93,PVALID,COPTIC SMALL LETTER IAUDA +2C94,DISALLOWED,COPTIC CAPITAL LETTER KAPA +2C95,PVALID,COPTIC SMALL LETTER KAPA +2C96,DISALLOWED,COPTIC CAPITAL LETTER LAULA +2C97,PVALID,COPTIC SMALL LETTER LAULA +2C98,DISALLOWED,COPTIC CAPITAL LETTER MI +2C99,PVALID,COPTIC SMALL LETTER MI +2C9A,DISALLOWED,COPTIC CAPITAL LETTER NI +2C9B,PVALID,COPTIC SMALL LETTER NI +2C9C,DISALLOWED,COPTIC CAPITAL LETTER KSI +2C9D,PVALID,COPTIC SMALL LETTER KSI +2C9E,DISALLOWED,COPTIC CAPITAL LETTER O +2C9F,PVALID,COPTIC SMALL LETTER O +2CA0,DISALLOWED,COPTIC CAPITAL LETTER PI +2CA1,PVALID,COPTIC SMALL LETTER PI +2CA2,DISALLOWED,COPTIC CAPITAL LETTER RO +2CA3,PVALID,COPTIC SMALL LETTER RO +2CA4,DISALLOWED,COPTIC CAPITAL LETTER SIMA +2CA5,PVALID,COPTIC SMALL LETTER SIMA +2CA6,DISALLOWED,COPTIC CAPITAL LETTER TAU +2CA7,PVALID,COPTIC SMALL LETTER TAU +2CA8,DISALLOWED,COPTIC CAPITAL LETTER UA +2CA9,PVALID,COPTIC SMALL LETTER UA +2CAA,DISALLOWED,COPTIC CAPITAL LETTER FI +2CAB,PVALID,COPTIC SMALL LETTER FI +2CAC,DISALLOWED,COPTIC CAPITAL LETTER KHI +2CAD,PVALID,COPTIC SMALL LETTER KHI +2CAE,DISALLOWED,COPTIC CAPITAL LETTER PSI +2CAF,PVALID,COPTIC SMALL LETTER PSI +2CB0,DISALLOWED,COPTIC CAPITAL LETTER OOU +2CB1,PVALID,COPTIC SMALL LETTER OOU +2CB2,DISALLOWED,COPTIC CAPITAL LETTER DIALECT-P ALEF +2CB3,PVALID,COPTIC SMALL LETTER DIALECT-P ALEF +2CB4,DISALLOWED,COPTIC CAPITAL LETTER OLD COPTIC AIN +2CB5,PVALID,COPTIC SMALL LETTER OLD COPTIC AIN +2CB6,DISALLOWED,COPTIC CAPITAL LETTER CRYPTOGRAMMIC EIE +2CB7,PVALID,COPTIC SMALL LETTER CRYPTOGRAMMIC EIE +2CB8,DISALLOWED,COPTIC CAPITAL LETTER DIALECT-P KAPA +2CB9,PVALID,COPTIC SMALL LETTER DIALECT-P KAPA +2CBA,DISALLOWED,COPTIC CAPITAL LETTER DIALECT-P NI +2CBB,PVALID,COPTIC SMALL LETTER DIALECT-P NI +2CBC,DISALLOWED,COPTIC CAPITAL LETTER CRYPTOGRAMMIC NI +2CBD,PVALID,COPTIC SMALL LETTER CRYPTOGRAMMIC NI +2CBE,DISALLOWED,COPTIC CAPITAL LETTER OLD COPTIC OOU +2CBF,PVALID,COPTIC SMALL LETTER OLD COPTIC OOU +2CC0,DISALLOWED,COPTIC CAPITAL LETTER SAMPI +2CC1,PVALID,COPTIC SMALL LETTER SAMPI +2CC2,DISALLOWED,COPTIC CAPITAL LETTER CROSSED SHEI +2CC3,PVALID,COPTIC SMALL LETTER CROSSED SHEI +2CC4,DISALLOWED,COPTIC CAPITAL LETTER OLD COPTIC SHEI +2CC5,PVALID,COPTIC SMALL LETTER OLD COPTIC SHEI +2CC6,DISALLOWED,COPTIC CAPITAL LETTER OLD COPTIC ESH +2CC7,PVALID,COPTIC SMALL LETTER OLD COPTIC ESH +2CC8,DISALLOWED,COPTIC CAPITAL LETTER AKHMIMIC KHEI +2CC9,PVALID,COPTIC SMALL LETTER AKHMIMIC KHEI +2CCA,DISALLOWED,COPTIC CAPITAL LETTER DIALECT-P HORI +2CCB,PVALID,COPTIC SMALL LETTER DIALECT-P HORI +2CCC,DISALLOWED,COPTIC CAPITAL LETTER OLD COPTIC HORI +2CCD,PVALID,COPTIC SMALL LETTER OLD COPTIC HORI +2CCE,DISALLOWED,COPTIC CAPITAL LETTER OLD COPTIC HA +2CCF,PVALID,COPTIC SMALL LETTER OLD COPTIC HA +2CD0,DISALLOWED,COPTIC CAPITAL LETTER L-SHAPED HA +2CD1,PVALID,COPTIC SMALL LETTER L-SHAPED HA +2CD2,DISALLOWED,COPTIC CAPITAL LETTER OLD COPTIC HEI +2CD3,PVALID,COPTIC SMALL LETTER OLD COPTIC HEI +2CD4,DISALLOWED,COPTIC CAPITAL LETTER OLD COPTIC HAT +2CD5,PVALID,COPTIC SMALL LETTER OLD COPTIC HAT +2CD6,DISALLOWED,COPTIC CAPITAL LETTER OLD COPTIC GANGIA +2CD7,PVALID,COPTIC SMALL LETTER OLD COPTIC GANGIA +2CD8,DISALLOWED,COPTIC CAPITAL LETTER OLD COPTIC DJA +2CD9,PVALID,COPTIC SMALL LETTER OLD COPTIC DJA +2CDA,DISALLOWED,COPTIC CAPITAL LETTER OLD COPTIC SHIMA +2CDB,PVALID,COPTIC SMALL LETTER OLD COPTIC SHIMA +2CDC,DISALLOWED,COPTIC CAPITAL LETTER OLD NUBIAN SHIMA +2CDD,PVALID,COPTIC SMALL LETTER OLD NUBIAN SHIMA +2CDE,DISALLOWED,COPTIC CAPITAL LETTER OLD NUBIAN NGI +2CDF,PVALID,COPTIC SMALL LETTER OLD NUBIAN NGI +2CE0,DISALLOWED,COPTIC CAPITAL LETTER OLD NUBIAN NYI +2CE1,PVALID,COPTIC SMALL LETTER OLD NUBIAN NYI +2CE2,DISALLOWED,COPTIC CAPITAL LETTER OLD NUBIAN WAU +2CE3-2CE4,PVALID,COPTIC SMALL LETTER OLD NUBIAN WAU..COPTIC SYMBOL KAI +2CE5-2CEB,DISALLOWED,COPTIC SYMBOL MI RO..COPTIC CAPITAL LETTER CRYPTOGRAMMIC SHEI +2CEC,PVALID,COPTIC SMALL LETTER CRYPTOGRAMMIC SHEI +2CED,DISALLOWED,COPTIC CAPITAL LETTER CRYPTOGRAMMIC GANGIA +2CEE-2CF1,PVALID,COPTIC SMALL LETTER CRYPTOGRAMMIC GANGIA..COPTIC COMBINING SPIRITUS LENIS +2CF2,DISALLOWED,COPTIC CAPITAL LETTER BOHAIRIC KHEI +2CF3,PVALID,COPTIC SMALL LETTER BOHAIRIC KHEI +2CF4-2CF8,UNASSIGNED,.. +2CF9-2CFF,DISALLOWED,COPTIC OLD NUBIAN FULL STOP..COPTIC MORPHOLOGICAL DIVIDER +2D00-2D25,PVALID,GEORGIAN SMALL LETTER AN..GEORGIAN SMALL LETTER HOE +2D26,UNASSIGNED, +2D27,PVALID,GEORGIAN SMALL LETTER YN +2D28-2D2C,UNASSIGNED,.. +2D2D,PVALID,GEORGIAN SMALL LETTER AEN +2D2E-2D2F,UNASSIGNED,.. +2D30-2D67,PVALID,TIFINAGH LETTER YA..TIFINAGH LETTER YO +2D68-2D6E,UNASSIGNED,.. +2D6F-2D70,DISALLOWED,TIFINAGH MODIFIER LETTER LABIALIZATION MARK..TIFINAGH SEPARATOR MARK +2D71-2D7E,UNASSIGNED,.. +2D7F-2D96,PVALID,TIFINAGH CONSONANT JOINER..ETHIOPIC SYLLABLE GGWE +2D97-2D9F,UNASSIGNED,.. +2DA0-2DA6,PVALID,ETHIOPIC SYLLABLE SSA..ETHIOPIC SYLLABLE SSO +2DA7,UNASSIGNED, +2DA8-2DAE,PVALID,ETHIOPIC SYLLABLE CCA..ETHIOPIC SYLLABLE CCO +2DAF,UNASSIGNED, +2DB0-2DB6,PVALID,ETHIOPIC SYLLABLE ZZA..ETHIOPIC SYLLABLE ZZO +2DB7,UNASSIGNED, +2DB8-2DBE,PVALID,ETHIOPIC SYLLABLE CCHA..ETHIOPIC SYLLABLE CCHO +2DBF,UNASSIGNED, +2DC0-2DC6,PVALID,ETHIOPIC SYLLABLE QYA..ETHIOPIC SYLLABLE QYO +2DC7,UNASSIGNED, +2DC8-2DCE,PVALID,ETHIOPIC SYLLABLE KYA..ETHIOPIC SYLLABLE KYO +2DCF,UNASSIGNED, +2DD0-2DD6,PVALID,ETHIOPIC SYLLABLE XYA..ETHIOPIC SYLLABLE XYO +2DD7,UNASSIGNED, +2DD8-2DDE,PVALID,ETHIOPIC SYLLABLE GYA..ETHIOPIC SYLLABLE GYO +2DDF,UNASSIGNED, +2DE0-2DFF,PVALID,COMBINING CYRILLIC LETTER BE..COMBINING CYRILLIC LETTER IOTIFIED BIG YUS +2E00-2E2E,DISALLOWED,RIGHT ANGLE SUBSTITUTION MARKER..REVERSED QUESTION MARK +2E2F,PVALID,VERTICAL TILDE +2E30-2E3B,DISALLOWED,RING POINT..THREE-EM DASH +2E3C-2E7F,UNASSIGNED,.. +2E80-2E99,DISALLOWED,CJK RADICAL REPEAT..CJK RADICAL RAP +2E9A,UNASSIGNED, +2E9B-2EF3,DISALLOWED,CJK RADICAL CHOKE..CJK RADICAL C-SIMPLIFIED TURTLE +2EF4-2EFF,UNASSIGNED,.. +2F00-2FD5,DISALLOWED,KANGXI RADICAL ONE..KANGXI RADICAL FLUTE +2FD6-2FEF,UNASSIGNED,.. +2FF0-2FFB,DISALLOWED,IDEOGRAPHIC DESCRIPTION CHARACTER LEFT TO RIGHT..IDEOGRAPHIC DESCRIPTION CHARACTER OVERLAID +2FFC-2FFF,UNASSIGNED,.. +3000-3004,DISALLOWED,IDEOGRAPHIC SPACE..JAPANESE INDUSTRIAL STANDARD SYMBOL +3005-3007,PVALID,IDEOGRAPHIC ITERATION MARK..IDEOGRAPHIC NUMBER ZERO +3008-3029,DISALLOWED,LEFT ANGLE BRACKET..HANGZHOU NUMERAL NINE +302A-302D,PVALID,IDEOGRAPHIC LEVEL TONE MARK..IDEOGRAPHIC ENTERING TONE MARK +302E-303B,DISALLOWED,HANGUL SINGLE DOT TONE MARK..VERTICAL IDEOGRAPHIC ITERATION MARK +303C,PVALID,MASU MARK +303D-303F,DISALLOWED,PART ALTERNATION MARK..IDEOGRAPHIC HALF FILL SPACE +3040,UNASSIGNED, +3041-3096,PVALID,HIRAGANA LETTER SMALL A..HIRAGANA LETTER SMALL KE +3097-3098,UNASSIGNED,.. +3099-309A,PVALID,COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK..COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK +309B-309C,DISALLOWED,KATAKANA-HIRAGANA VOICED SOUND MARK..KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK +309D-309E,PVALID,HIRAGANA ITERATION MARK..HIRAGANA VOICED ITERATION MARK +309F-30A0,DISALLOWED,HIRAGANA DIGRAPH YORI..KATAKANA-HIRAGANA DOUBLE HYPHEN +30A1-30FA,PVALID,KATAKANA LETTER SMALL A..KATAKANA LETTER VO +30FB,CONTEXTO,KATAKANA MIDDLE DOT +30FC-30FE,PVALID,KATAKANA-HIRAGANA PROLONGED SOUND MARK..KATAKANA VOICED ITERATION MARK +30FF,DISALLOWED,KATAKANA DIGRAPH KOTO +3100-3104,UNASSIGNED,.. +3105-312D,PVALID,BOPOMOFO LETTER B..BOPOMOFO LETTER IH +312E-3130,UNASSIGNED,.. +3131-318E,DISALLOWED,HANGUL LETTER KIYEOK..HANGUL LETTER ARAEAE +318F,UNASSIGNED, +3190-319F,DISALLOWED,IDEOGRAPHIC ANNOTATION LINKING MARK..IDEOGRAPHIC ANNOTATION MAN MARK +31A0-31BA,PVALID,BOPOMOFO LETTER BU..BOPOMOFO LETTER ZY +31BB-31BF,UNASSIGNED,.. +31C0-31E3,DISALLOWED,CJK STROKE T..CJK STROKE Q +31E4-31EF,UNASSIGNED,.. +31F0-31FF,PVALID,KATAKANA LETTER SMALL KU..KATAKANA LETTER SMALL RO +3200-321E,DISALLOWED,PARENTHESIZED HANGUL KIYEOK..PARENTHESIZED KOREAN CHARACTER O HU +321F,UNASSIGNED, +3220-32FE,DISALLOWED,PARENTHESIZED IDEOGRAPH ONE..CIRCLED KATAKANA WO +32FF,UNASSIGNED, +3300-33FF,DISALLOWED,SQUARE APAATO..SQUARE GAL +3400-4DB5,PVALID,".." +4DB6-4DBF,UNASSIGNED,.. +4DC0-4DFF,DISALLOWED,HEXAGRAM FOR THE CREATIVE HEAVEN..HEXAGRAM FOR BEFORE COMPLETION +4E00-9FCC,PVALID,".." +9FCD-9FFF,UNASSIGNED,.. +A000-A48C,PVALID,YI SYLLABLE IT..YI SYLLABLE YYR +A48D-A48F,UNASSIGNED,.. +A490-A4C6,DISALLOWED,YI RADICAL QOT..YI RADICAL KE +A4C7-A4CF,UNASSIGNED,.. +A4D0-A4FD,PVALID,LISU LETTER BA..LISU LETTER TONE MYA JEU +A4FE-A4FF,DISALLOWED,LISU PUNCTUATION COMMA..LISU PUNCTUATION FULL STOP +A500-A60C,PVALID,VAI SYLLABLE EE..VAI SYLLABLE LENGTHENER +A60D-A60F,DISALLOWED,VAI COMMA..VAI QUESTION MARK +A610-A62B,PVALID,VAI SYLLABLE NDOLE FA..VAI SYLLABLE NDOLE DO +A62C-A63F,UNASSIGNED,.. +A640,DISALLOWED,CYRILLIC CAPITAL LETTER ZEMLYA +A641,PVALID,CYRILLIC SMALL LETTER ZEMLYA +A642,DISALLOWED,CYRILLIC CAPITAL LETTER DZELO +A643,PVALID,CYRILLIC SMALL LETTER DZELO +A644,DISALLOWED,CYRILLIC CAPITAL LETTER REVERSED DZE +A645,PVALID,CYRILLIC SMALL LETTER REVERSED DZE +A646,DISALLOWED,CYRILLIC CAPITAL LETTER IOTA +A647,PVALID,CYRILLIC SMALL LETTER IOTA +A648,DISALLOWED,CYRILLIC CAPITAL LETTER DJERV +A649,PVALID,CYRILLIC SMALL LETTER DJERV +A64A,DISALLOWED,CYRILLIC CAPITAL LETTER MONOGRAPH UK +A64B,PVALID,CYRILLIC SMALL LETTER MONOGRAPH UK +A64C,DISALLOWED,CYRILLIC CAPITAL LETTER BROAD OMEGA +A64D,PVALID,CYRILLIC SMALL LETTER BROAD OMEGA +A64E,DISALLOWED,CYRILLIC CAPITAL LETTER NEUTRAL YER +A64F,PVALID,CYRILLIC SMALL LETTER NEUTRAL YER +A650,DISALLOWED,CYRILLIC CAPITAL LETTER YERU WITH BACK YER +A651,PVALID,CYRILLIC SMALL LETTER YERU WITH BACK YER +A652,DISALLOWED,CYRILLIC CAPITAL LETTER IOTIFIED YAT +A653,PVALID,CYRILLIC SMALL LETTER IOTIFIED YAT +A654,DISALLOWED,CYRILLIC CAPITAL LETTER REVERSED YU +A655,PVALID,CYRILLIC SMALL LETTER REVERSED YU +A656,DISALLOWED,CYRILLIC CAPITAL LETTER IOTIFIED A +A657,PVALID,CYRILLIC SMALL LETTER IOTIFIED A +A658,DISALLOWED,CYRILLIC CAPITAL LETTER CLOSED LITTLE YUS +A659,PVALID,CYRILLIC SMALL LETTER CLOSED LITTLE YUS +A65A,DISALLOWED,CYRILLIC CAPITAL LETTER BLENDED YUS +A65B,PVALID,CYRILLIC SMALL LETTER BLENDED YUS +A65C,DISALLOWED,CYRILLIC CAPITAL LETTER IOTIFIED CLOSED LITTLE YUS +A65D,PVALID,CYRILLIC SMALL LETTER IOTIFIED CLOSED LITTLE YUS +A65E,DISALLOWED,CYRILLIC CAPITAL LETTER YN +A65F,PVALID,CYRILLIC SMALL LETTER YN +A660,DISALLOWED,CYRILLIC CAPITAL LETTER REVERSED TSE +A661,PVALID,CYRILLIC SMALL LETTER REVERSED TSE +A662,DISALLOWED,CYRILLIC CAPITAL LETTER SOFT DE +A663,PVALID,CYRILLIC SMALL LETTER SOFT DE +A664,DISALLOWED,CYRILLIC CAPITAL LETTER SOFT EL +A665,PVALID,CYRILLIC SMALL LETTER SOFT EL +A666,DISALLOWED,CYRILLIC CAPITAL LETTER SOFT EM +A667,PVALID,CYRILLIC SMALL LETTER SOFT EM +A668,DISALLOWED,CYRILLIC CAPITAL LETTER MONOCULAR O +A669,PVALID,CYRILLIC SMALL LETTER MONOCULAR O +A66A,DISALLOWED,CYRILLIC CAPITAL LETTER BINOCULAR O +A66B,PVALID,CYRILLIC SMALL LETTER BINOCULAR O +A66C,DISALLOWED,CYRILLIC CAPITAL LETTER DOUBLE MONOCULAR O +A66D-A66F,PVALID,CYRILLIC SMALL LETTER DOUBLE MONOCULAR O..COMBINING CYRILLIC VZMET +A670-A673,DISALLOWED,COMBINING CYRILLIC TEN MILLIONS SIGN..SLAVONIC ASTERISK +A674-A67D,PVALID,COMBINING CYRILLIC LETTER UKRAINIAN IE..COMBINING CYRILLIC PAYEROK +A67E,DISALLOWED,CYRILLIC KAVYKA +A67F,PVALID,CYRILLIC PAYEROK +A680,DISALLOWED,CYRILLIC CAPITAL LETTER DWE +A681,PVALID,CYRILLIC SMALL LETTER DWE +A682,DISALLOWED,CYRILLIC CAPITAL LETTER DZWE +A683,PVALID,CYRILLIC SMALL LETTER DZWE +A684,DISALLOWED,CYRILLIC CAPITAL LETTER ZHWE +A685,PVALID,CYRILLIC SMALL LETTER ZHWE +A686,DISALLOWED,CYRILLIC CAPITAL LETTER CCHE +A687,PVALID,CYRILLIC SMALL LETTER CCHE +A688,DISALLOWED,CYRILLIC CAPITAL LETTER DZZE +A689,PVALID,CYRILLIC SMALL LETTER DZZE +A68A,DISALLOWED,CYRILLIC CAPITAL LETTER TE WITH MIDDLE HOOK +A68B,PVALID,CYRILLIC SMALL LETTER TE WITH MIDDLE HOOK +A68C,DISALLOWED,CYRILLIC CAPITAL LETTER TWE +A68D,PVALID,CYRILLIC SMALL LETTER TWE +A68E,DISALLOWED,CYRILLIC CAPITAL LETTER TSWE +A68F,PVALID,CYRILLIC SMALL LETTER TSWE +A690,DISALLOWED,CYRILLIC CAPITAL LETTER TSSE +A691,PVALID,CYRILLIC SMALL LETTER TSSE +A692,DISALLOWED,CYRILLIC CAPITAL LETTER TCHE +A693,PVALID,CYRILLIC SMALL LETTER TCHE +A694,DISALLOWED,CYRILLIC CAPITAL LETTER HWE +A695,PVALID,CYRILLIC SMALL LETTER HWE +A696,DISALLOWED,CYRILLIC CAPITAL LETTER SHWE +A697,PVALID,CYRILLIC SMALL LETTER SHWE +A698-A69E,UNASSIGNED,.. +A69F-A6E5,PVALID,COMBINING CYRILLIC LETTER IOTIFIED E..BAMUM LETTER KI +A6E6-A6EF,DISALLOWED,BAMUM LETTER MO..BAMUM LETTER KOGHOM +A6F0-A6F1,PVALID,BAMUM COMBINING MARK KOQNDON..BAMUM COMBINING MARK TUKWENTIS +A6F2-A6F7,DISALLOWED,BAMUM NJAEMLI..BAMUM QUESTION MARK +A6F8-A6FF,UNASSIGNED,.. +A700-A716,DISALLOWED,MODIFIER LETTER CHINESE TONE YIN PING..MODIFIER LETTER EXTRA-LOW LEFT-STEM TONE BAR +A717-A71F,PVALID,MODIFIER LETTER DOT VERTICAL BAR..MODIFIER LETTER LOW INVERTED EXCLAMATION MARK +A720-A722,DISALLOWED,MODIFIER LETTER STRESS AND HIGH TONE..LATIN CAPITAL LETTER EGYPTOLOGICAL ALEF +A723,PVALID,LATIN SMALL LETTER EGYPTOLOGICAL ALEF +A724,DISALLOWED,LATIN CAPITAL LETTER EGYPTOLOGICAL AIN +A725,PVALID,LATIN SMALL LETTER EGYPTOLOGICAL AIN +A726,DISALLOWED,LATIN CAPITAL LETTER HENG +A727,PVALID,LATIN SMALL LETTER HENG +A728,DISALLOWED,LATIN CAPITAL LETTER TZ +A729,PVALID,LATIN SMALL LETTER TZ +A72A,DISALLOWED,LATIN CAPITAL LETTER TRESILLO +A72B,PVALID,LATIN SMALL LETTER TRESILLO +A72C,DISALLOWED,LATIN CAPITAL LETTER CUATRILLO +A72D,PVALID,LATIN SMALL LETTER CUATRILLO +A72E,DISALLOWED,LATIN CAPITAL LETTER CUATRILLO WITH COMMA +A72F-A731,PVALID,LATIN SMALL LETTER CUATRILLO WITH COMMA..LATIN LETTER SMALL CAPITAL S +A732,DISALLOWED,LATIN CAPITAL LETTER AA +A733,PVALID,LATIN SMALL LETTER AA +A734,DISALLOWED,LATIN CAPITAL LETTER AO +A735,PVALID,LATIN SMALL LETTER AO +A736,DISALLOWED,LATIN CAPITAL LETTER AU +A737,PVALID,LATIN SMALL LETTER AU +A738,DISALLOWED,LATIN CAPITAL LETTER AV +A739,PVALID,LATIN SMALL LETTER AV +A73A,DISALLOWED,LATIN CAPITAL LETTER AV WITH HORIZONTAL BAR +A73B,PVALID,LATIN SMALL LETTER AV WITH HORIZONTAL BAR +A73C,DISALLOWED,LATIN CAPITAL LETTER AY +A73D,PVALID,LATIN SMALL LETTER AY +A73E,DISALLOWED,LATIN CAPITAL LETTER REVERSED C WITH DOT +A73F,PVALID,LATIN SMALL LETTER REVERSED C WITH DOT +A740,DISALLOWED,LATIN CAPITAL LETTER K WITH STROKE +A741,PVALID,LATIN SMALL LETTER K WITH STROKE +A742,DISALLOWED,LATIN CAPITAL LETTER K WITH DIAGONAL STROKE +A743,PVALID,LATIN SMALL LETTER K WITH DIAGONAL STROKE +A744,DISALLOWED,LATIN CAPITAL LETTER K WITH STROKE AND DIAGONAL STROKE +A745,PVALID,LATIN SMALL LETTER K WITH STROKE AND DIAGONAL STROKE +A746,DISALLOWED,LATIN CAPITAL LETTER BROKEN L +A747,PVALID,LATIN SMALL LETTER BROKEN L +A748,DISALLOWED,LATIN CAPITAL LETTER L WITH HIGH STROKE +A749,PVALID,LATIN SMALL LETTER L WITH HIGH STROKE +A74A,DISALLOWED,LATIN CAPITAL LETTER O WITH LONG STROKE OVERLAY +A74B,PVALID,LATIN SMALL LETTER O WITH LONG STROKE OVERLAY +A74C,DISALLOWED,LATIN CAPITAL LETTER O WITH LOOP +A74D,PVALID,LATIN SMALL LETTER O WITH LOOP +A74E,DISALLOWED,LATIN CAPITAL LETTER OO +A74F,PVALID,LATIN SMALL LETTER OO +A750,DISALLOWED,LATIN CAPITAL LETTER P WITH STROKE THROUGH DESCENDER +A751,PVALID,LATIN SMALL LETTER P WITH STROKE THROUGH DESCENDER +A752,DISALLOWED,LATIN CAPITAL LETTER P WITH FLOURISH +A753,PVALID,LATIN SMALL LETTER P WITH FLOURISH +A754,DISALLOWED,LATIN CAPITAL LETTER P WITH SQUIRREL TAIL +A755,PVALID,LATIN SMALL LETTER P WITH SQUIRREL TAIL +A756,DISALLOWED,LATIN CAPITAL LETTER Q WITH STROKE THROUGH DESCENDER +A757,PVALID,LATIN SMALL LETTER Q WITH STROKE THROUGH DESCENDER +A758,DISALLOWED,LATIN CAPITAL LETTER Q WITH DIAGONAL STROKE +A759,PVALID,LATIN SMALL LETTER Q WITH DIAGONAL STROKE +A75A,DISALLOWED,LATIN CAPITAL LETTER R ROTUNDA +A75B,PVALID,LATIN SMALL LETTER R ROTUNDA +A75C,DISALLOWED,LATIN CAPITAL LETTER RUM ROTUNDA +A75D,PVALID,LATIN SMALL LETTER RUM ROTUNDA +A75E,DISALLOWED,LATIN CAPITAL LETTER V WITH DIAGONAL STROKE +A75F,PVALID,LATIN SMALL LETTER V WITH DIAGONAL STROKE +A760,DISALLOWED,LATIN CAPITAL LETTER VY +A761,PVALID,LATIN SMALL LETTER VY +A762,DISALLOWED,LATIN CAPITAL LETTER VISIGOTHIC Z +A763,PVALID,LATIN SMALL LETTER VISIGOTHIC Z +A764,DISALLOWED,LATIN CAPITAL LETTER THORN WITH STROKE +A765,PVALID,LATIN SMALL LETTER THORN WITH STROKE +A766,DISALLOWED,LATIN CAPITAL LETTER THORN WITH STROKE THROUGH DESCENDER +A767,PVALID,LATIN SMALL LETTER THORN WITH STROKE THROUGH DESCENDER +A768,DISALLOWED,LATIN CAPITAL LETTER VEND +A769,PVALID,LATIN SMALL LETTER VEND +A76A,DISALLOWED,LATIN CAPITAL LETTER ET +A76B,PVALID,LATIN SMALL LETTER ET +A76C,DISALLOWED,LATIN CAPITAL LETTER IS +A76D,PVALID,LATIN SMALL LETTER IS +A76E,DISALLOWED,LATIN CAPITAL LETTER CON +A76F,PVALID,LATIN SMALL LETTER CON +A770,DISALLOWED,MODIFIER LETTER US +A771-A778,PVALID,LATIN SMALL LETTER DUM..LATIN SMALL LETTER UM +A779,DISALLOWED,LATIN CAPITAL LETTER INSULAR D +A77A,PVALID,LATIN SMALL LETTER INSULAR D +A77B,DISALLOWED,LATIN CAPITAL LETTER INSULAR F +A77C,PVALID,LATIN SMALL LETTER INSULAR F +A77D-A77E,DISALLOWED,LATIN CAPITAL LETTER INSULAR G..LATIN CAPITAL LETTER TURNED INSULAR G +A77F,PVALID,LATIN SMALL LETTER TURNED INSULAR G +A780,DISALLOWED,LATIN CAPITAL LETTER TURNED L +A781,PVALID,LATIN SMALL LETTER TURNED L +A782,DISALLOWED,LATIN CAPITAL LETTER INSULAR R +A783,PVALID,LATIN SMALL LETTER INSULAR R +A784,DISALLOWED,LATIN CAPITAL LETTER INSULAR S +A785,PVALID,LATIN SMALL LETTER INSULAR S +A786,DISALLOWED,LATIN CAPITAL LETTER INSULAR T +A787-A788,PVALID,LATIN SMALL LETTER INSULAR T..MODIFIER LETTER LOW CIRCUMFLEX ACCENT +A789-A78B,DISALLOWED,MODIFIER LETTER COLON..LATIN CAPITAL LETTER SALTILLO +A78C,PVALID,LATIN SMALL LETTER SALTILLO +A78D,DISALLOWED,LATIN CAPITAL LETTER TURNED H +A78E,PVALID,LATIN SMALL LETTER L WITH RETROFLEX HOOK AND BELT +A78F,UNASSIGNED, +A790,DISALLOWED,LATIN CAPITAL LETTER N WITH DESCENDER +A791,PVALID,LATIN SMALL LETTER N WITH DESCENDER +A792,DISALLOWED,LATIN CAPITAL LETTER C WITH BAR +A793,PVALID,LATIN SMALL LETTER C WITH BAR +A794-A79F,UNASSIGNED,.. +A7A0,DISALLOWED,LATIN CAPITAL LETTER G WITH OBLIQUE STROKE +A7A1,PVALID,LATIN SMALL LETTER G WITH OBLIQUE STROKE +A7A2,DISALLOWED,LATIN CAPITAL LETTER K WITH OBLIQUE STROKE +A7A3,PVALID,LATIN SMALL LETTER K WITH OBLIQUE STROKE +A7A4,DISALLOWED,LATIN CAPITAL LETTER N WITH OBLIQUE STROKE +A7A5,PVALID,LATIN SMALL LETTER N WITH OBLIQUE STROKE +A7A6,DISALLOWED,LATIN CAPITAL LETTER R WITH OBLIQUE STROKE +A7A7,PVALID,LATIN SMALL LETTER R WITH OBLIQUE STROKE +A7A8,DISALLOWED,LATIN CAPITAL LETTER S WITH OBLIQUE STROKE +A7A9,PVALID,LATIN SMALL LETTER S WITH OBLIQUE STROKE +A7AA,DISALLOWED,LATIN CAPITAL LETTER H WITH HOOK +A7AB-A7F7,UNASSIGNED,.. +A7F8-A7F9,DISALLOWED,MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE +A7FA-A827,PVALID,LATIN LETTER SMALL CAPITAL TURNED M..SYLOTI NAGRI VOWEL SIGN OO +A828-A82B,DISALLOWED,SYLOTI NAGRI POETRY MARK-1..SYLOTI NAGRI POETRY MARK-4 +A82C-A82F,UNASSIGNED,.. +A830-A839,DISALLOWED,NORTH INDIC FRACTION ONE QUARTER..NORTH INDIC QUANTITY MARK +A83A-A83F,UNASSIGNED,.. +A840-A873,PVALID,PHAGS-PA LETTER KA..PHAGS-PA LETTER CANDRABINDU +A874-A877,DISALLOWED,PHAGS-PA SINGLE HEAD MARK..PHAGS-PA MARK DOUBLE SHAD +A878-A87F,UNASSIGNED,.. +A880-A8C4,PVALID,SAURASHTRA SIGN ANUSVARA..SAURASHTRA SIGN VIRAMA +A8C5-A8CD,UNASSIGNED,.. +A8CE-A8CF,DISALLOWED,SAURASHTRA DANDA..SAURASHTRA DOUBLE DANDA +A8D0-A8D9,PVALID,SAURASHTRA DIGIT ZERO..SAURASHTRA DIGIT NINE +A8DA-A8DF,UNASSIGNED,.. +A8E0-A8F7,PVALID,COMBINING DEVANAGARI DIGIT ZERO..DEVANAGARI SIGN CANDRABINDU AVAGRAHA +A8F8-A8FA,DISALLOWED,DEVANAGARI SIGN PUSHPIKA..DEVANAGARI CARET +A8FB,PVALID,DEVANAGARI HEADSTROKE +A8FC-A8FF,UNASSIGNED,.. +A900-A92D,PVALID,KAYAH LI DIGIT ZERO..KAYAH LI TONE CALYA PLOPHU +A92E-A92F,DISALLOWED,KAYAH LI SIGN CWI..KAYAH LI SIGN SHYA +A930-A953,PVALID,REJANG LETTER KA..REJANG VIRAMA +A954-A95E,UNASSIGNED,.. +A95F-A97C,DISALLOWED,REJANG SECTION MARK..HANGUL CHOSEONG SSANGYEORINHIEUH +A97D-A97F,UNASSIGNED,.. +A980-A9C0,PVALID,JAVANESE SIGN PANYANGGA..JAVANESE PANGKON +A9C1-A9CD,DISALLOWED,JAVANESE LEFT RERENGGAN..JAVANESE TURNED PADA PISELEH +A9CE,UNASSIGNED, +A9CF-A9D9,PVALID,JAVANESE PANGRANGKEP..JAVANESE DIGIT NINE +A9DA-A9DD,UNASSIGNED,.. +A9DE-A9DF,DISALLOWED,JAVANESE PADA TIRTA TUMETES..JAVANESE PADA ISEN-ISEN +A9E0-A9FF,UNASSIGNED,.. +AA00-AA36,PVALID,CHAM LETTER A..CHAM CONSONANT SIGN WA +AA37-AA3F,UNASSIGNED,.. +AA40-AA4D,PVALID,CHAM LETTER FINAL K..CHAM CONSONANT SIGN FINAL H +AA4E-AA4F,UNASSIGNED,.. +AA50-AA59,PVALID,CHAM DIGIT ZERO..CHAM DIGIT NINE +AA5A-AA5B,UNASSIGNED,.. +AA5C-AA5F,DISALLOWED,CHAM PUNCTUATION SPIRAL..CHAM PUNCTUATION TRIPLE DANDA +AA60-AA76,PVALID,MYANMAR LETTER KHAMTI GA..MYANMAR LOGOGRAM KHAMTI HM +AA77-AA79,DISALLOWED,MYANMAR SYMBOL AITON EXCLAMATION..MYANMAR SYMBOL AITON TWO +AA7A-AA7B,PVALID,MYANMAR LETTER AITON RA..MYANMAR SIGN PAO KAREN TONE +AA7C-AA7F,UNASSIGNED,.. +AA80-AAC2,PVALID,TAI VIET LETTER LOW KO..TAI VIET TONE MAI SONG +AAC3-AADA,UNASSIGNED,.. +AADB-AADD,PVALID,TAI VIET SYMBOL KON..TAI VIET SYMBOL SAM +AADE-AADF,DISALLOWED,TAI VIET SYMBOL HO HOI..TAI VIET SYMBOL KOI KOI +AAE0-AAEF,PVALID,MEETEI MAYEK LETTER E..MEETEI MAYEK VOWEL SIGN AAU +AAF0-AAF1,DISALLOWED,MEETEI MAYEK CHEIKHAN..MEETEI MAYEK AHANG KHUDAM +AAF2-AAF6,PVALID,MEETEI MAYEK ANJI..MEETEI MAYEK VIRAMA +AAF7-AB00,UNASSIGNED,.. +AB01-AB06,PVALID,ETHIOPIC SYLLABLE TTHU..ETHIOPIC SYLLABLE TTHO +AB07-AB08,UNASSIGNED,.. +AB09-AB0E,PVALID,ETHIOPIC SYLLABLE DDHU..ETHIOPIC SYLLABLE DDHO +AB0F-AB10,UNASSIGNED,.. +AB11-AB16,PVALID,ETHIOPIC SYLLABLE DZU..ETHIOPIC SYLLABLE DZO +AB17-AB1F,UNASSIGNED,.. +AB20-AB26,PVALID,ETHIOPIC SYLLABLE CCHHA..ETHIOPIC SYLLABLE CCHHO +AB27,UNASSIGNED, +AB28-AB2E,PVALID,ETHIOPIC SYLLABLE BBA..ETHIOPIC SYLLABLE BBO +AB2F-ABBF,UNASSIGNED,.. +ABC0-ABEA,PVALID,MEETEI MAYEK LETTER KOK..MEETEI MAYEK VOWEL SIGN NUNG +ABEB,DISALLOWED,MEETEI MAYEK CHEIKHEI +ABEC-ABED,PVALID,MEETEI MAYEK LUM IYEK..MEETEI MAYEK APUN IYEK +ABEE-ABEF,UNASSIGNED,.. +ABF0-ABF9,PVALID,MEETEI MAYEK DIGIT ZERO..MEETEI MAYEK DIGIT NINE +ABFA-ABFF,UNASSIGNED,.. +AC00-D7A3,PVALID,".." +D7A4-D7AF,UNASSIGNED,.. +D7B0-D7C6,DISALLOWED,HANGUL JUNGSEONG O-YEO..HANGUL JUNGSEONG ARAEA-E +D7C7-D7CA,UNASSIGNED,.. +D7CB-D7FB,DISALLOWED,HANGUL JONGSEONG NIEUN-RIEUL..HANGUL JONGSEONG PHIEUPH-THIEUTH +D7FC-D7FF,UNASSIGNED,.. +D800-FA0D,DISALLOWED,"..CJK COMPATIBILITY IDEOGRAPH-FA0D" +FA0E-FA0F,PVALID,CJK COMPATIBILITY IDEOGRAPH-FA0E..CJK COMPATIBILITY IDEOGRAPH-FA0F +FA10,DISALLOWED,CJK COMPATIBILITY IDEOGRAPH-FA10 +FA11,PVALID,CJK COMPATIBILITY IDEOGRAPH-FA11 +FA12,DISALLOWED,CJK COMPATIBILITY IDEOGRAPH-FA12 +FA13-FA14,PVALID,CJK COMPATIBILITY IDEOGRAPH-FA13..CJK COMPATIBILITY IDEOGRAPH-FA14 +FA15-FA1E,DISALLOWED,CJK COMPATIBILITY IDEOGRAPH-FA15..CJK COMPATIBILITY IDEOGRAPH-FA1E +FA1F,PVALID,CJK COMPATIBILITY IDEOGRAPH-FA1F +FA20,DISALLOWED,CJK COMPATIBILITY IDEOGRAPH-FA20 +FA21,PVALID,CJK COMPATIBILITY IDEOGRAPH-FA21 +FA22,DISALLOWED,CJK COMPATIBILITY IDEOGRAPH-FA22 +FA23-FA24,PVALID,CJK COMPATIBILITY IDEOGRAPH-FA23..CJK COMPATIBILITY IDEOGRAPH-FA24 +FA25-FA26,DISALLOWED,CJK COMPATIBILITY IDEOGRAPH-FA25..CJK COMPATIBILITY IDEOGRAPH-FA26 +FA27-FA29,PVALID,CJK COMPATIBILITY IDEOGRAPH-FA27..CJK COMPATIBILITY IDEOGRAPH-FA29 +FA2A-FA6D,DISALLOWED,CJK COMPATIBILITY IDEOGRAPH-FA2A..CJK COMPATIBILITY IDEOGRAPH-FA6D +FA6E-FA6F,UNASSIGNED,.. +FA70-FAD9,DISALLOWED,CJK COMPATIBILITY IDEOGRAPH-FA70..CJK COMPATIBILITY IDEOGRAPH-FAD9 +FADA-FAFF,UNASSIGNED,.. +FB00-FB06,DISALLOWED,LATIN SMALL LIGATURE FF..LATIN SMALL LIGATURE ST +FB07-FB12,UNASSIGNED,.. +FB13-FB17,DISALLOWED,ARMENIAN SMALL LIGATURE MEN NOW..ARMENIAN SMALL LIGATURE MEN XEH +FB18-FB1C,UNASSIGNED,.. +FB1D,DISALLOWED,HEBREW LETTER YOD WITH HIRIQ +FB1E,PVALID,HEBREW POINT JUDEO-SPANISH VARIKA +FB1F-FB36,DISALLOWED,HEBREW LIGATURE YIDDISH YOD YOD PATAH..HEBREW LETTER ZAYIN WITH DAGESH +FB37,UNASSIGNED, +FB38-FB3C,DISALLOWED,HEBREW LETTER TET WITH DAGESH..HEBREW LETTER LAMED WITH DAGESH +FB3D,UNASSIGNED, +FB3E,DISALLOWED,HEBREW LETTER MEM WITH DAGESH +FB3F,UNASSIGNED, +FB40-FB41,DISALLOWED,HEBREW LETTER NUN WITH DAGESH..HEBREW LETTER SAMEKH WITH DAGESH +FB42,UNASSIGNED, +FB43-FB44,DISALLOWED,HEBREW LETTER FINAL PE WITH DAGESH..HEBREW LETTER PE WITH DAGESH +FB45,UNASSIGNED, +FB46-FBC1,DISALLOWED,HEBREW LETTER TSADI WITH DAGESH..ARABIC SYMBOL SMALL TAH BELOW +FBC2-FBD2,UNASSIGNED,.. +FBD3-FD3F,DISALLOWED,ARABIC LETTER NG ISOLATED FORM..ORNATE RIGHT PARENTHESIS +FD40-FD4F,UNASSIGNED,.. +FD50-FD8F,DISALLOWED,ARABIC LIGATURE TEH WITH JEEM WITH MEEM INITIAL FORM..ARABIC LIGATURE MEEM WITH KHAH WITH MEEM INITIAL FORM +FD90-FD91,UNASSIGNED,.. +FD92-FDC7,DISALLOWED,ARABIC LIGATURE MEEM WITH JEEM WITH KHAH INITIAL FORM..ARABIC LIGATURE NOON WITH JEEM WITH YEH FINAL FORM +FDC8-FDCF,UNASSIGNED,.. +FDD0-FDFD,DISALLOWED,..ARABIC LIGATURE BISMILLAH AR-RAHMAN AR-RAHEEM +FDFE-FDFF,UNASSIGNED,.. +FE00-FE19,DISALLOWED,VARIATION SELECTOR-1..PRESENTATION FORM FOR VERTICAL HORIZONTAL ELLIPSIS +FE1A-FE1F,UNASSIGNED,.. +FE20-FE26,PVALID,COMBINING LIGATURE LEFT HALF..COMBINING CONJOINING MACRON +FE27-FE2F,UNASSIGNED,.. +FE30-FE52,DISALLOWED,PRESENTATION FORM FOR VERTICAL TWO DOT LEADER..SMALL FULL STOP +FE53,UNASSIGNED, +FE54-FE66,DISALLOWED,SMALL SEMICOLON..SMALL EQUALS SIGN +FE67,UNASSIGNED, +FE68-FE6B,DISALLOWED,SMALL REVERSE SOLIDUS..SMALL COMMERCIAL AT +FE6C-FE6F,UNASSIGNED,.. +FE70-FE72,DISALLOWED,ARABIC FATHATAN ISOLATED FORM..ARABIC DAMMATAN ISOLATED FORM +FE73,PVALID,ARABIC TAIL FRAGMENT +FE74,DISALLOWED,ARABIC KASRATAN ISOLATED FORM +FE75,UNASSIGNED, +FE76-FEFC,DISALLOWED,ARABIC FATHA ISOLATED FORM..ARABIC LIGATURE LAM WITH ALEF FINAL FORM +FEFD-FEFE,UNASSIGNED,.. +FEFF,DISALLOWED,ZERO WIDTH NO-BREAK SPACE +FF00,UNASSIGNED, +FF01-FFBE,DISALLOWED,FULLWIDTH EXCLAMATION MARK..HALFWIDTH HANGUL LETTER HIEUH +FFBF-FFC1,UNASSIGNED,.. +FFC2-FFC7,DISALLOWED,HALFWIDTH HANGUL LETTER A..HALFWIDTH HANGUL LETTER E +FFC8-FFC9,UNASSIGNED,.. +FFCA-FFCF,DISALLOWED,HALFWIDTH HANGUL LETTER YEO..HALFWIDTH HANGUL LETTER OE +FFD0-FFD1,UNASSIGNED,.. +FFD2-FFD7,DISALLOWED,HALFWIDTH HANGUL LETTER YO..HALFWIDTH HANGUL LETTER YU +FFD8-FFD9,UNASSIGNED,.. +FFDA-FFDC,DISALLOWED,HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL LETTER I +FFDD-FFDF,UNASSIGNED,.. +FFE0-FFE6,DISALLOWED,FULLWIDTH CENT SIGN..FULLWIDTH WON SIGN +FFE7,UNASSIGNED, +FFE8-FFEE,DISALLOWED,HALFWIDTH FORMS LIGHT VERTICAL..HALFWIDTH WHITE CIRCLE +FFEF-FFF8,UNASSIGNED,.. +FFF9-FFFF,DISALLOWED,INTERLINEAR ANNOTATION ANCHOR.. +10000-1000B,PVALID,LINEAR B SYLLABLE B008 A..LINEAR B SYLLABLE B046 JE +1000C,UNASSIGNED, +1000D-10026,PVALID,LINEAR B SYLLABLE B036 JO..LINEAR B SYLLABLE B032 QO +10027,UNASSIGNED, +10028-1003A,PVALID,LINEAR B SYLLABLE B060 RA..LINEAR B SYLLABLE B042 WO +1003B,UNASSIGNED, +1003C-1003D,PVALID,LINEAR B SYLLABLE B017 ZA..LINEAR B SYLLABLE B074 ZE +1003E,UNASSIGNED, +1003F-1004D,PVALID,LINEAR B SYLLABLE B020 ZO..LINEAR B SYLLABLE B091 TWO +1004E-1004F,UNASSIGNED,.. +10050-1005D,PVALID,LINEAR B SYMBOL B018..LINEAR B SYMBOL B089 +1005E-1007F,UNASSIGNED,.. +10080-100FA,PVALID,LINEAR B IDEOGRAM B100 MAN..LINEAR B IDEOGRAM VESSEL B305 +100FB-100FF,UNASSIGNED,.. +10100-10102,DISALLOWED,AEGEAN WORD SEPARATOR LINE..AEGEAN CHECK MARK +10103-10106,UNASSIGNED,.. +10107-10133,DISALLOWED,AEGEAN NUMBER ONE..AEGEAN NUMBER NINETY THOUSAND +10134-10136,UNASSIGNED,.. +10137-1018A,DISALLOWED,AEGEAN WEIGHT BASE UNIT..GREEK ZERO SIGN +1018B-1018F,UNASSIGNED,.. +10190-1019B,DISALLOWED,ROMAN SEXTANS SIGN..ROMAN CENTURIAL SIGN +1019C-101CF,UNASSIGNED,.. +101D0-101FC,DISALLOWED,PHAISTOS DISC SIGN PEDESTRIAN..PHAISTOS DISC SIGN WAVY BAND +101FD,PVALID,PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE +101FE-1027F,UNASSIGNED,.. +10280-1029C,PVALID,LYCIAN LETTER A..LYCIAN LETTER X +1029D-1029F,UNASSIGNED,.. +102A0-102D0,PVALID,CARIAN LETTER A..CARIAN LETTER UUU3 +102D1-102FF,UNASSIGNED,.. +10300-1031E,PVALID,OLD ITALIC LETTER A..OLD ITALIC LETTER UU +1031F,UNASSIGNED, +10320-10323,DISALLOWED,OLD ITALIC NUMERAL ONE..OLD ITALIC NUMERAL FIFTY +10324-1032F,UNASSIGNED,.. +10330-10340,PVALID,GOTHIC LETTER AHSA..GOTHIC LETTER PAIRTHRA +10341,DISALLOWED,GOTHIC LETTER NINETY +10342-10349,PVALID,GOTHIC LETTER RAIDA..GOTHIC LETTER OTHAL +1034A,DISALLOWED,GOTHIC LETTER NINE HUNDRED +1034B-1037F,UNASSIGNED,.. +10380-1039D,PVALID,UGARITIC LETTER ALPA..UGARITIC LETTER SSU +1039E,UNASSIGNED, +1039F,DISALLOWED,UGARITIC WORD DIVIDER +103A0-103C3,PVALID,OLD PERSIAN SIGN A..OLD PERSIAN SIGN HA +103C4-103C7,UNASSIGNED,.. +103C8-103CF,PVALID,OLD PERSIAN SIGN AURAMAZDAA..OLD PERSIAN SIGN BUUMISH +103D0-103D5,DISALLOWED,OLD PERSIAN WORD DIVIDER..OLD PERSIAN NUMBER HUNDRED +103D6-103FF,UNASSIGNED,.. +10400-10427,DISALLOWED,DESERET CAPITAL LETTER LONG I..DESERET CAPITAL LETTER EW +10428-1049D,PVALID,DESERET SMALL LETTER LONG I..OSMANYA LETTER OO +1049E-1049F,UNASSIGNED,.. +104A0-104A9,PVALID,OSMANYA DIGIT ZERO..OSMANYA DIGIT NINE +104AA-107FF,UNASSIGNED,.. +10800-10805,PVALID,CYPRIOT SYLLABLE A..CYPRIOT SYLLABLE JA +10806-10807,UNASSIGNED,.. +10808,PVALID,CYPRIOT SYLLABLE JO +10809,UNASSIGNED, +1080A-10835,PVALID,CYPRIOT SYLLABLE KA..CYPRIOT SYLLABLE WO +10836,UNASSIGNED, +10837-10838,PVALID,CYPRIOT SYLLABLE XA..CYPRIOT SYLLABLE XE +10839-1083B,UNASSIGNED,.. +1083C,PVALID,CYPRIOT SYLLABLE ZA +1083D-1083E,UNASSIGNED,.. +1083F-10855,PVALID,CYPRIOT SYLLABLE ZO..IMPERIAL ARAMAIC LETTER TAW +10856,UNASSIGNED, +10857-1085F,DISALLOWED,IMPERIAL ARAMAIC SECTION SIGN..IMPERIAL ARAMAIC NUMBER TEN THOUSAND +10860-108FF,UNASSIGNED,.. +10900-10915,PVALID,PHOENICIAN LETTER ALF..PHOENICIAN LETTER TAU +10916-1091B,DISALLOWED,PHOENICIAN NUMBER ONE..PHOENICIAN NUMBER THREE +1091C-1091E,UNASSIGNED,.. +1091F,DISALLOWED,PHOENICIAN WORD SEPARATOR +10920-10939,PVALID,LYDIAN LETTER A..LYDIAN LETTER C +1093A-1093E,UNASSIGNED,.. +1093F,DISALLOWED,LYDIAN TRIANGULAR MARK +10940-1097F,UNASSIGNED,.. +10980-109B7,PVALID,MEROITIC HIEROGLYPHIC LETTER A..MEROITIC CURSIVE LETTER DA +109B8-109BD,UNASSIGNED,.. +109BE-109BF,PVALID,MEROITIC CURSIVE LOGOGRAM RMT..MEROITIC CURSIVE LOGOGRAM IMN +109C0-109FF,UNASSIGNED,.. +10A00-10A03,PVALID,KHAROSHTHI LETTER A..KHAROSHTHI VOWEL SIGN VOCALIC R +10A04,UNASSIGNED, +10A05-10A06,PVALID,KHAROSHTHI VOWEL SIGN E..KHAROSHTHI VOWEL SIGN O +10A07-10A0B,UNASSIGNED,.. +10A0C-10A13,PVALID,KHAROSHTHI VOWEL LENGTH MARK..KHAROSHTHI LETTER GHA +10A14,UNASSIGNED, +10A15-10A17,PVALID,KHAROSHTHI LETTER CA..KHAROSHTHI LETTER JA +10A18,UNASSIGNED, +10A19-10A33,PVALID,KHAROSHTHI LETTER NYA..KHAROSHTHI LETTER TTTHA +10A34-10A37,UNASSIGNED,.. +10A38-10A3A,PVALID,KHAROSHTHI SIGN BAR ABOVE..KHAROSHTHI SIGN DOT BELOW +10A3B-10A3E,UNASSIGNED,.. +10A3F,PVALID,KHAROSHTHI VIRAMA +10A40-10A47,DISALLOWED,KHAROSHTHI DIGIT ONE..KHAROSHTHI NUMBER ONE THOUSAND +10A48-10A4F,UNASSIGNED,.. +10A50-10A58,DISALLOWED,KHAROSHTHI PUNCTUATION DOT..KHAROSHTHI PUNCTUATION LINES +10A59-10A5F,UNASSIGNED,.. +10A60-10A7C,PVALID,OLD SOUTH ARABIAN LETTER HE..OLD SOUTH ARABIAN LETTER THETH +10A7D-10A7F,DISALLOWED,OLD SOUTH ARABIAN NUMBER ONE..OLD SOUTH ARABIAN NUMERIC INDICATOR +10A80-10AFF,UNASSIGNED,.. +10B00-10B35,PVALID,AVESTAN LETTER A..AVESTAN LETTER HE +10B36-10B38,UNASSIGNED,.. +10B39-10B3F,DISALLOWED,AVESTAN ABBREVIATION MARK..LARGE ONE RING OVER TWO RINGS PUNCTUATION +10B40-10B55,PVALID,INSCRIPTIONAL PARTHIAN LETTER ALEPH..INSCRIPTIONAL PARTHIAN LETTER TAW +10B56-10B57,UNASSIGNED,.. +10B58-10B5F,DISALLOWED,INSCRIPTIONAL PARTHIAN NUMBER ONE..INSCRIPTIONAL PARTHIAN NUMBER ONE THOUSAND +10B60-10B72,PVALID,INSCRIPTIONAL PAHLAVI LETTER ALEPH..INSCRIPTIONAL PAHLAVI LETTER TAW +10B73-10B77,UNASSIGNED,.. +10B78-10B7F,DISALLOWED,INSCRIPTIONAL PAHLAVI NUMBER ONE..INSCRIPTIONAL PAHLAVI NUMBER ONE THOUSAND +10B80-10BFF,UNASSIGNED,.. +10C00-10C48,PVALID,OLD TURKIC LETTER ORKHON A..OLD TURKIC LETTER ORKHON BASH +10C49-10E5F,UNASSIGNED,.. +10E60-10E7E,DISALLOWED,RUMI DIGIT ONE..RUMI FRACTION TWO THIRDS +10E7F-10FFF,UNASSIGNED,.. +11000-11046,PVALID,BRAHMI SIGN CANDRABINDU..BRAHMI VIRAMA +11047-1104D,DISALLOWED,BRAHMI DANDA..BRAHMI PUNCTUATION LOTUS +1104E-11051,UNASSIGNED,.. +11052-11065,DISALLOWED,BRAHMI NUMBER ONE..BRAHMI NUMBER ONE THOUSAND +11066-1106F,PVALID,BRAHMI DIGIT ZERO..BRAHMI DIGIT NINE +11070-1107F,UNASSIGNED,.. +11080-110BA,PVALID,KAITHI SIGN CANDRABINDU..KAITHI SIGN NUKTA +110BB-110C1,DISALLOWED,KAITHI ABBREVIATION SIGN..KAITHI DOUBLE DANDA +110C2-110CF,UNASSIGNED,.. +110D0-110E8,PVALID,SORA SOMPENG LETTER SAH..SORA SOMPENG LETTER MAE +110E9-110EF,UNASSIGNED,.. +110F0-110F9,PVALID,SORA SOMPENG DIGIT ZERO..SORA SOMPENG DIGIT NINE +110FA-110FF,UNASSIGNED,.. +11100-11134,PVALID,CHAKMA SIGN CANDRABINDU..CHAKMA MAAYYAA +11135,UNASSIGNED, +11136-1113F,PVALID,CHAKMA DIGIT ZERO..CHAKMA DIGIT NINE +11140-11143,DISALLOWED,CHAKMA SECTION MARK..CHAKMA QUESTION MARK +11144-1117F,UNASSIGNED,.. +11180-111C4,PVALID,SHARADA SIGN CANDRABINDU..SHARADA OM +111C5-111C8,DISALLOWED,SHARADA DANDA..SHARADA SEPARATOR +111C9-111CF,UNASSIGNED,.. +111D0-111D9,PVALID,SHARADA DIGIT ZERO..SHARADA DIGIT NINE +111DA-1167F,UNASSIGNED,.. +11680-116B7,PVALID,TAKRI LETTER A..TAKRI SIGN NUKTA +116B8-116BF,UNASSIGNED,.. +116C0-116C9,PVALID,TAKRI DIGIT ZERO..TAKRI DIGIT NINE +116CA-11FFF,UNASSIGNED,.. +12000-1236E,PVALID,CUNEIFORM SIGN A..CUNEIFORM SIGN ZUM +1236F-123FF,UNASSIGNED,.. +12400-12462,DISALLOWED,CUNEIFORM NUMERIC SIGN TWO ASH..CUNEIFORM NUMERIC SIGN OLD ASSYRIAN ONE QUARTER +12463-1246F,UNASSIGNED,.. +12470-12473,DISALLOWED,CUNEIFORM PUNCTUATION SIGN OLD ASSYRIAN WORD DIVIDER..CUNEIFORM PUNCTUATION SIGN DIAGONAL TRICOLON +12474-12FFF,UNASSIGNED,.. +13000-1342E,PVALID,EGYPTIAN HIEROGLYPH A001..EGYPTIAN HIEROGLYPH AA032 +1342F-167FF,UNASSIGNED,.. +16800-16A38,PVALID,BAMUM LETTER PHASE-A NGKUE MFON..BAMUM LETTER PHASE-F VUEQ +16A39-16EFF,UNASSIGNED,.. +16F00-16F44,PVALID,MIAO LETTER PA..MIAO LETTER HHA +16F45-16F4F,UNASSIGNED,.. +16F50-16F7E,PVALID,MIAO LETTER NASALIZATION..MIAO VOWEL SIGN NG +16F7F-16F8E,UNASSIGNED,.. +16F8F-16F9F,PVALID,MIAO TONE RIGHT..MIAO LETTER REFORMED TONE-8 +16FA0-1AFFF,UNASSIGNED,.. +1B000-1B001,PVALID,KATAKANA LETTER ARCHAIC E..HIRAGANA LETTER ARCHAIC YE +1B002-1CFFF,UNASSIGNED,.. +1D000-1D0F5,DISALLOWED,BYZANTINE MUSICAL SYMBOL PSILI..BYZANTINE MUSICAL SYMBOL GORGON NEO KATO +1D0F6-1D0FF,UNASSIGNED,.. +1D100-1D126,DISALLOWED,MUSICAL SYMBOL SINGLE BARLINE..MUSICAL SYMBOL DRUM CLEF-2 +1D127-1D128,UNASSIGNED,.. +1D129-1D1DD,DISALLOWED,MUSICAL SYMBOL MULTIPLE MEASURE REST..MUSICAL SYMBOL PES SUBPUNCTIS +1D1DE-1D1FF,UNASSIGNED,.. +1D200-1D245,DISALLOWED,GREEK VOCAL NOTATION SYMBOL-1..GREEK MUSICAL LEIMMA +1D246-1D2FF,UNASSIGNED,.. +1D300-1D356,DISALLOWED,MONOGRAM FOR EARTH..TETRAGRAM FOR FOSTERING +1D357-1D35F,UNASSIGNED,.. +1D360-1D371,DISALLOWED,COUNTING ROD UNIT DIGIT ONE..COUNTING ROD TENS DIGIT NINE +1D372-1D3FF,UNASSIGNED,.. +1D400-1D454,DISALLOWED,MATHEMATICAL BOLD CAPITAL A..MATHEMATICAL ITALIC SMALL G +1D455,UNASSIGNED, +1D456-1D49C,DISALLOWED,MATHEMATICAL ITALIC SMALL I..MATHEMATICAL SCRIPT CAPITAL A +1D49D,UNASSIGNED, +1D49E-1D49F,DISALLOWED,MATHEMATICAL SCRIPT CAPITAL C..MATHEMATICAL SCRIPT CAPITAL D +1D4A0-1D4A1,UNASSIGNED,.. +1D4A2,DISALLOWED,MATHEMATICAL SCRIPT CAPITAL G +1D4A3-1D4A4,UNASSIGNED,.. +1D4A5-1D4A6,DISALLOWED,MATHEMATICAL SCRIPT CAPITAL J..MATHEMATICAL SCRIPT CAPITAL K +1D4A7-1D4A8,UNASSIGNED,.. +1D4A9-1D4AC,DISALLOWED,MATHEMATICAL SCRIPT CAPITAL N..MATHEMATICAL SCRIPT CAPITAL Q +1D4AD,UNASSIGNED, +1D4AE-1D4B9,DISALLOWED,MATHEMATICAL SCRIPT CAPITAL S..MATHEMATICAL SCRIPT SMALL D +1D4BA,UNASSIGNED, +1D4BB,DISALLOWED,MATHEMATICAL SCRIPT SMALL F +1D4BC,UNASSIGNED, +1D4BD-1D4C3,DISALLOWED,MATHEMATICAL SCRIPT SMALL H..MATHEMATICAL SCRIPT SMALL N +1D4C4,UNASSIGNED, +1D4C5-1D505,DISALLOWED,MATHEMATICAL SCRIPT SMALL P..MATHEMATICAL FRAKTUR CAPITAL B +1D506,UNASSIGNED, +1D507-1D50A,DISALLOWED,MATHEMATICAL FRAKTUR CAPITAL D..MATHEMATICAL FRAKTUR CAPITAL G +1D50B-1D50C,UNASSIGNED,.. +1D50D-1D514,DISALLOWED,MATHEMATICAL FRAKTUR CAPITAL J..MATHEMATICAL FRAKTUR CAPITAL Q +1D515,UNASSIGNED, +1D516-1D51C,DISALLOWED,MATHEMATICAL FRAKTUR CAPITAL S..MATHEMATICAL FRAKTUR CAPITAL Y +1D51D,UNASSIGNED, +1D51E-1D539,DISALLOWED,MATHEMATICAL FRAKTUR SMALL A..MATHEMATICAL DOUBLE-STRUCK CAPITAL B +1D53A,UNASSIGNED, +1D53B-1D53E,DISALLOWED,MATHEMATICAL DOUBLE-STRUCK CAPITAL D..MATHEMATICAL DOUBLE-STRUCK CAPITAL G +1D53F,UNASSIGNED, +1D540-1D544,DISALLOWED,MATHEMATICAL DOUBLE-STRUCK CAPITAL I..MATHEMATICAL DOUBLE-STRUCK CAPITAL M +1D545,UNASSIGNED, +1D546,DISALLOWED,MATHEMATICAL DOUBLE-STRUCK CAPITAL O +1D547-1D549,UNASSIGNED,.. +1D54A-1D550,DISALLOWED,MATHEMATICAL DOUBLE-STRUCK CAPITAL S..MATHEMATICAL DOUBLE-STRUCK CAPITAL Y +1D551,UNASSIGNED, +1D552-1D6A5,DISALLOWED,MATHEMATICAL DOUBLE-STRUCK SMALL A..MATHEMATICAL ITALIC SMALL DOTLESS J +1D6A6-1D6A7,UNASSIGNED,.. +1D6A8-1D7CB,DISALLOWED,MATHEMATICAL BOLD CAPITAL ALPHA..MATHEMATICAL BOLD SMALL DIGAMMA +1D7CC-1D7CD,UNASSIGNED,.. +1D7CE-1D7FF,DISALLOWED,MATHEMATICAL BOLD DIGIT ZERO..MATHEMATICAL MONOSPACE DIGIT NINE +1D800-1EDFF,UNASSIGNED,.. +1EE00-1EE03,DISALLOWED,ARABIC MATHEMATICAL ALEF..ARABIC MATHEMATICAL DAL +1EE04,UNASSIGNED, +1EE05-1EE1F,DISALLOWED,ARABIC MATHEMATICAL WAW..ARABIC MATHEMATICAL DOTLESS QAF +1EE20,UNASSIGNED, +1EE21-1EE22,DISALLOWED,ARABIC MATHEMATICAL INITIAL BEH..ARABIC MATHEMATICAL INITIAL JEEM +1EE23,UNASSIGNED, +1EE24,DISALLOWED,ARABIC MATHEMATICAL INITIAL HEH +1EE25-1EE26,UNASSIGNED,.. +1EE27,DISALLOWED,ARABIC MATHEMATICAL INITIAL HAH +1EE28,UNASSIGNED, +1EE29-1EE32,DISALLOWED,ARABIC MATHEMATICAL INITIAL YEH..ARABIC MATHEMATICAL INITIAL QAF +1EE33,UNASSIGNED, +1EE34-1EE37,DISALLOWED,ARABIC MATHEMATICAL INITIAL SHEEN..ARABIC MATHEMATICAL INITIAL KHAH +1EE38,UNASSIGNED, +1EE39,DISALLOWED,ARABIC MATHEMATICAL INITIAL DAD +1EE3A,UNASSIGNED, +1EE3B,DISALLOWED,ARABIC MATHEMATICAL INITIAL GHAIN +1EE3C-1EE41,UNASSIGNED,.. +1EE42,DISALLOWED,ARABIC MATHEMATICAL TAILED JEEM +1EE43-1EE46,UNASSIGNED,.. +1EE47,DISALLOWED,ARABIC MATHEMATICAL TAILED HAH +1EE48,UNASSIGNED, +1EE49,DISALLOWED,ARABIC MATHEMATICAL TAILED YEH +1EE4A,UNASSIGNED, +1EE4B,DISALLOWED,ARABIC MATHEMATICAL TAILED LAM +1EE4C,UNASSIGNED, +1EE4D-1EE4F,DISALLOWED,ARABIC MATHEMATICAL TAILED NOON..ARABIC MATHEMATICAL TAILED AIN +1EE50,UNASSIGNED, +1EE51-1EE52,DISALLOWED,ARABIC MATHEMATICAL TAILED SAD..ARABIC MATHEMATICAL TAILED QAF +1EE53,UNASSIGNED, +1EE54,DISALLOWED,ARABIC MATHEMATICAL TAILED SHEEN +1EE55-1EE56,UNASSIGNED,.. +1EE57,DISALLOWED,ARABIC MATHEMATICAL TAILED KHAH +1EE58,UNASSIGNED, +1EE59,DISALLOWED,ARABIC MATHEMATICAL TAILED DAD +1EE5A,UNASSIGNED, +1EE5B,DISALLOWED,ARABIC MATHEMATICAL TAILED GHAIN +1EE5C,UNASSIGNED, +1EE5D,DISALLOWED,ARABIC MATHEMATICAL TAILED DOTLESS NOON +1EE5E,UNASSIGNED, +1EE5F,DISALLOWED,ARABIC MATHEMATICAL TAILED DOTLESS QAF +1EE60,UNASSIGNED, +1EE61-1EE62,DISALLOWED,ARABIC MATHEMATICAL STRETCHED BEH..ARABIC MATHEMATICAL STRETCHED JEEM +1EE63,UNASSIGNED, +1EE64,DISALLOWED,ARABIC MATHEMATICAL STRETCHED HEH +1EE65-1EE66,UNASSIGNED,.. +1EE67-1EE6A,DISALLOWED,ARABIC MATHEMATICAL STRETCHED HAH..ARABIC MATHEMATICAL STRETCHED KAF +1EE6B,UNASSIGNED, +1EE6C-1EE72,DISALLOWED,ARABIC MATHEMATICAL STRETCHED MEEM..ARABIC MATHEMATICAL STRETCHED QAF +1EE73,UNASSIGNED, +1EE74-1EE77,DISALLOWED,ARABIC MATHEMATICAL STRETCHED SHEEN..ARABIC MATHEMATICAL STRETCHED KHAH +1EE78,UNASSIGNED, +1EE79-1EE7C,DISALLOWED,ARABIC MATHEMATICAL STRETCHED DAD..ARABIC MATHEMATICAL STRETCHED DOTLESS BEH +1EE7D,UNASSIGNED, +1EE7E,DISALLOWED,ARABIC MATHEMATICAL STRETCHED DOTLESS FEH +1EE7F,UNASSIGNED, +1EE80-1EE89,DISALLOWED,ARABIC MATHEMATICAL LOOPED ALEF..ARABIC MATHEMATICAL LOOPED YEH +1EE8A,UNASSIGNED, +1EE8B-1EE9B,DISALLOWED,ARABIC MATHEMATICAL LOOPED LAM..ARABIC MATHEMATICAL LOOPED GHAIN +1EE9C-1EEA0,UNASSIGNED,.. +1EEA1-1EEA3,DISALLOWED,ARABIC MATHEMATICAL DOUBLE-STRUCK BEH..ARABIC MATHEMATICAL DOUBLE-STRUCK DAL +1EEA4,UNASSIGNED, +1EEA5-1EEA9,DISALLOWED,ARABIC MATHEMATICAL DOUBLE-STRUCK WAW..ARABIC MATHEMATICAL DOUBLE-STRUCK YEH +1EEAA,UNASSIGNED, +1EEAB-1EEBB,DISALLOWED,ARABIC MATHEMATICAL DOUBLE-STRUCK LAM..ARABIC MATHEMATICAL DOUBLE-STRUCK GHAIN +1EEBC-1EEEF,UNASSIGNED,.. +1EEF0-1EEF1,DISALLOWED,ARABIC MATHEMATICAL OPERATOR MEEM WITH HAH WITH TATWEEL..ARABIC MATHEMATICAL OPERATOR HAH WITH DAL +1EEF2-1EFFF,UNASSIGNED,.. +1F000-1F02B,DISALLOWED,MAHJONG TILE EAST WIND..MAHJONG TILE BACK +1F02C-1F02F,UNASSIGNED,.. +1F030-1F093,DISALLOWED,DOMINO TILE HORIZONTAL BACK..DOMINO TILE VERTICAL-06-06 +1F094-1F09F,UNASSIGNED,.. +1F0A0-1F0AE,DISALLOWED,PLAYING CARD BACK..PLAYING CARD KING OF SPADES +1F0AF-1F0B0,UNASSIGNED,.. +1F0B1-1F0BE,DISALLOWED,PLAYING CARD ACE OF HEARTS..PLAYING CARD KING OF HEARTS +1F0BF-1F0C0,UNASSIGNED,.. +1F0C1-1F0CF,DISALLOWED,PLAYING CARD ACE OF DIAMONDS..PLAYING CARD BLACK JOKER +1F0D0,UNASSIGNED, +1F0D1-1F0DF,DISALLOWED,PLAYING CARD ACE OF CLUBS..PLAYING CARD WHITE JOKER +1F0E0-1F0FF,UNASSIGNED,.. +1F100-1F10A,DISALLOWED,DIGIT ZERO FULL STOP..DIGIT NINE COMMA +1F10B-1F10F,UNASSIGNED,.. +1F110-1F12E,DISALLOWED,PARENTHESIZED LATIN CAPITAL LETTER A..CIRCLED WZ +1F12F,UNASSIGNED, +1F130-1F16B,DISALLOWED,SQUARED LATIN CAPITAL LETTER A..RAISED MD SIGN +1F16C-1F16F,UNASSIGNED,.. +1F170-1F19A,DISALLOWED,NEGATIVE SQUARED LATIN CAPITAL LETTER A..SQUARED VS +1F19B-1F1E5,UNASSIGNED,.. +1F1E6-1F202,DISALLOWED,REGIONAL INDICATOR SYMBOL LETTER A..SQUARED KATAKANA SA +1F203-1F20F,UNASSIGNED,.. +1F210-1F23A,DISALLOWED,SQUARED CJK UNIFIED IDEOGRAPH-624B..SQUARED CJK UNIFIED IDEOGRAPH-55B6 +1F23B-1F23F,UNASSIGNED,.. +1F240-1F248,DISALLOWED,TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-672C..TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-6557 +1F249-1F24F,UNASSIGNED,.. +1F250-1F251,DISALLOWED,CIRCLED IDEOGRAPH ADVANTAGE..CIRCLED IDEOGRAPH ACCEPT +1F252-1F2FF,UNASSIGNED,.. +1F300-1F320,DISALLOWED,CYCLONE..SHOOTING STAR +1F321-1F32F,UNASSIGNED,.. +1F330-1F335,DISALLOWED,CHESTNUT..CACTUS +1F336,UNASSIGNED, +1F337-1F37C,DISALLOWED,TULIP..BABY BOTTLE +1F37D-1F37F,UNASSIGNED,.. +1F380-1F393,DISALLOWED,RIBBON..GRADUATION CAP +1F394-1F39F,UNASSIGNED,.. +1F3A0-1F3C4,DISALLOWED,CAROUSEL HORSE..SURFER +1F3C5,UNASSIGNED, +1F3C6-1F3CA,DISALLOWED,TROPHY..SWIMMER +1F3CB-1F3DF,UNASSIGNED,.. +1F3E0-1F3F0,DISALLOWED,HOUSE BUILDING..EUROPEAN CASTLE +1F3F1-1F3FF,UNASSIGNED,.. +1F400-1F43E,DISALLOWED,RAT..PAW PRINTS +1F43F,UNASSIGNED, +1F440,DISALLOWED,EYES +1F441,UNASSIGNED, +1F442-1F4F7,DISALLOWED,EAR..CAMERA +1F4F8,UNASSIGNED, +1F4F9-1F4FC,DISALLOWED,VIDEO CAMERA..VIDEOCASSETTE +1F4FD-1F4FF,UNASSIGNED,.. +1F500-1F53D,DISALLOWED,TWISTED RIGHTWARDS ARROWS..DOWN-POINTING SMALL RED TRIANGLE +1F53E-1F53F,UNASSIGNED,.. +1F540-1F543,DISALLOWED,CIRCLED CROSS POMMEE..NOTCHED LEFT SEMICIRCLE WITH THREE DOTS +1F544-1F54F,UNASSIGNED,.. +1F550-1F567,DISALLOWED,CLOCK FACE ONE OCLOCK..CLOCK FACE TWELVE-THIRTY +1F568-1F5FA,UNASSIGNED,.. +1F5FB-1F640,DISALLOWED,MOUNT FUJI..WEARY CAT FACE +1F641-1F644,UNASSIGNED,.. +1F645-1F64F,DISALLOWED,FACE WITH NO GOOD GESTURE..PERSON WITH FOLDED HANDS +1F650-1F67F,UNASSIGNED,.. +1F680-1F6C5,DISALLOWED,ROCKET..LEFT LUGGAGE +1F6C6-1F6FF,UNASSIGNED,.. +1F700-1F773,DISALLOWED,ALCHEMICAL SYMBOL FOR QUINTESSENCE..ALCHEMICAL SYMBOL FOR HALF OUNCE +1F774-1FFFD,UNASSIGNED,.. +1FFFE-1FFFF,DISALLOWED,.. +20000-2A6D6,PVALID,".." +2A6D7-2A6FF,UNASSIGNED,.. +2A700-2B734,PVALID,".." +2B735-2B73F,UNASSIGNED,.. +2B740-2B81D,PVALID,".." +2B81E-2F7FF,UNASSIGNED,.. +2F800-2FA1D,DISALLOWED,CJK COMPATIBILITY IDEOGRAPH-2F800..CJK COMPATIBILITY IDEOGRAPH-2FA1D +2FA1E-2FFFD,UNASSIGNED,.. +2FFFE-2FFFF,DISALLOWED,.. +30000-3FFFD,UNASSIGNED,.. +3FFFE-3FFFF,DISALLOWED,.. +40000-4FFFD,UNASSIGNED,.. +4FFFE-4FFFF,DISALLOWED,.. +50000-5FFFD,UNASSIGNED,.. +5FFFE-5FFFF,DISALLOWED,.. +60000-6FFFD,UNASSIGNED,.. +6FFFE-6FFFF,DISALLOWED,.. +70000-7FFFD,UNASSIGNED,.. +7FFFE-7FFFF,DISALLOWED,.. +80000-8FFFD,UNASSIGNED,.. +8FFFE-8FFFF,DISALLOWED,.. +90000-9FFFD,UNASSIGNED,.. +9FFFE-9FFFF,DISALLOWED,.. +A0000-AFFFD,UNASSIGNED,.. +AFFFE-AFFFF,DISALLOWED,.. +B0000-BFFFD,UNASSIGNED,.. +BFFFE-BFFFF,DISALLOWED,.. +C0000-CFFFD,UNASSIGNED,.. +CFFFE-CFFFF,DISALLOWED,.. +D0000-DFFFD,UNASSIGNED,.. +DFFFE-DFFFF,DISALLOWED,.. +E0000,UNASSIGNED, +E0001,DISALLOWED,LANGUAGE TAG +E0002-E001F,UNASSIGNED,.. +E0020-E007F,DISALLOWED,TAG SPACE..CANCEL TAG +E0080-E00FF,UNASSIGNED,.. +E0100-E01EF,DISALLOWED,VARIATION SELECTOR-17..VARIATION SELECTOR-256 +E01F0-EFFFD,UNASSIGNED,.. +EFFFE-10FFFF,DISALLOWED,.. diff --git a/src/hyperlink/strategies.py b/src/hyperlink/strategies.py new file mode 100644 index 00000000..375b596c --- /dev/null +++ b/src/hyperlink/strategies.py @@ -0,0 +1,280 @@ +# -*- coding: utf-8 -*- +""" +Hypothesis strategies. +""" + +from csv import reader as csv_reader +from os.path import dirname, join +from string import ascii_letters, digits +from sys import maxunicode +from typing import Callable, Iterable, Optional, Sequence, Text, TypeVar, cast + +from . import DecodedURL, EncodedURL + +from hypothesis import assume +from hypothesis.strategies import ( + composite, integers, lists, sampled_from, text +) + +from idna import IDNAError, check_label, encode as idna_encode + + +__all__ = () + + +T = TypeVar('T') +DrawCallable = Callable[[Callable[..., T]], T] + + +try: + unichr +except NameError: # Py3 + unichr = chr # type: Callable[[int], Text] + + +def idna_characters(): + # type: () -> Text + """ + Returns a string containing IDNA characters. + """ + global _idnaCharacters + + if not _idnaCharacters: + result = [] + + # Data source "IDNA Derived Properties": + # https://www.iana.org/assignments/idna-tables-6.3.0/ + # idna-tables-6.3.0.xhtml#idna-tables-properties + dataFileName = join(dirname(__file__), "idna-tables-properties.csv") + with open(dataFileName) as dataFile: + reader = csv_reader(dataFile, delimiter=",") + next(reader) # Skip header row + for row in reader: + codes, prop, description = row + + if prop != "PVALID": + # CONTEXTO or CONTEXTJ are also allowed, but they come with + # rules, so we're punting on those here. + # See: https://tools.ietf.org/html/rfc5892 + continue + + startEnd = row[0].split("-", 1) + if len(startEnd) == 1: + # No end of range given; use start + startEnd.append(startEnd[0]) + start, end = (int(i, 16) for i in startEnd) + + for i in range(start, end + 1): + assert i <= maxunicode + result.append(unichr(i)) + + _idnaCharacters = u"".join(result) + + return _idnaCharacters + + +_idnaCharacters = "" # type: Text + + +@composite +def idna_text(draw, min_size=1, max_size=None): + # type: (DrawCallable, int, Optional[int]) -> Text + """ + A strategy which generates IDNA-encodable text. + + @param min_size: The minimum number of characters in the text. + C{None} is treated as C{0}. + + @param max_size: The maximum number of characters in the text. + Use C{None} for an unbounded size. + """ + alphabet = idna_characters() + + assert min_size >= 1 + + if max_size is not None: + assert max_size >= 1 + + result = cast( + Text, + draw(text( + min_size=min_size, max_size=max_size, alphabet=alphabet + )) + ) + + # FIXME: There should be a more efficient way to ensure we produce valid + # IDNA text. + try: + idna_encode(result) + except IDNAError: + assume(False) + + return result + + +@composite +def port_numbers(draw, allow_zero=False): + # type: (DrawCallable, bool) -> int + """ + A strategy which generates port numbers. + + @param allow_zero: Whether to allow port C{0} as a possible value. + """ + if allow_zero: + min_value = 0 + else: + min_value = 1 + + return cast( + int, draw(integers(min_value=min_value, max_value=65535)) + ) + + +@composite +def hostname_labels(draw, allow_idn=True): + # type: (DrawCallable, bool) -> Text + """ + A strategy which generates host name labels. + + @param allow_idn: Whether to allow non-ASCII characters as allowed by + internationalized domain names (IDNs). + """ + if allow_idn: + label = cast(Text, draw(idna_text(min_size=1, max_size=63))) + + try: + label.encode("ascii") + except UnicodeEncodeError: + # If the label doesn't encode to ASCII, then we need to check the + # length of the label after encoding to punycode and adding the + # xn-- prefix. + while ( + len(label.encode("punycode")) > 63 - len("xn--") + ): # pragma: no cover (not always drawn) + # Rather than bombing out, just trim from the end until it is + # short enough, so hypothesis doesn't have to generate new + # data. + label = label[:-1] + + else: + label = cast( + Text, + draw(text( + min_size=1, max_size=63, + alphabet=Text(ascii_letters + digits + u"-") + )) + ) + + # Filter invalid labels. + # It would be better to reliably avoid generation of bogus labels in the + # first place, but it's hard... + try: + check_label(label) + except UnicodeError: # pragma: no cover (not always drawn) + assume(False) + + return label + + +@composite +def hostnames(draw, allow_leading_digit=True, allow_idn=True): + # type: (DrawCallable, bool, bool) -> Text + """ + A strategy which generates host names. + + @param allow_leading_digit: Whether to allow a leading digit in host names; + they were not allowed prior to RFC 1123. + + @param allow_idn: Whether to allow non-ASCII characters as allowed by + internationalized domain names (IDNs). + """ + labels = cast( + Sequence[Text], + draw( + lists(hostname_labels(allow_idn=allow_idn), min_size=1, max_size=5) + .filter(lambda ls: sum(len(l) for l in ls) + len(ls) - 1 <= 252) + ) + ) + + if not allow_leading_digit: + assume(labels[0][0] not in digits) + + return u".".join(labels) + + +def path_characters(): + # type: () -> str + """ + Returns a string containing valid URL path characters. + """ + global _path_characters + + if _path_characters is None: + def chars(): + # type: () -> Iterable[Text] + for i in range(maxunicode): + c = unichr(i) + + # Exclude reserved characters + if c in "#/?": + continue + + # Exclude anything not UTF-8 compatible + try: + c.encode("utf-8") + except UnicodeEncodeError: + continue + + yield c + + _path_characters = "".join(chars()) + + return _path_characters + + +_path_characters = None # type: Optional[str] + + +@composite +def paths(draw): + # type: (DrawCallable) -> Sequence[Text] + return cast( + Sequence[Text], + draw( + lists(text(min_size=1, alphabet=path_characters()), max_size=10) + ) + ) + + +@composite +def encoded_urls(draw): + # type: (DrawCallable) -> EncodedURL + """ + A strategy which generates L{EncodedURL}s. + Call the L{EncodedURL.to_uri} method on each URL to get an HTTP + protocol-friendly URI. + """ + port = cast(Optional[int], draw(port_numbers(allow_zero=True))) + host = cast(Text, draw(hostnames())) + path = cast(Sequence[Text], draw(paths())) + + if port == 0: + port = None + + args = dict( + scheme=cast(Text, draw(sampled_from((u"http", u"https")))), + host=host, port=port, path=path, + ) + + return EncodedURL(**args) + + +@composite +def decoded_urls(draw): + # type: (DrawCallable) -> DecodedURL + """ + A strategy which generates L{DecodedURL}s. + Call the L{EncodedURL.to_uri} method on each URL to get an HTTP + protocol-friendly URI. + """ + return DecodedURL(draw(encoded_urls())) diff --git a/src/hyperlink/test/test_socket.py b/src/hyperlink/test/test_socket.py index a66e1b05..af7802a7 100644 --- a/src/hyperlink/test/test_socket.py +++ b/src/hyperlink/test/test_socket.py @@ -3,7 +3,7 @@ try: from socket import inet_pton except ImportError: - inet_pton = None # type: ignore[assignment] + inet_pton = None # type: ignore[assignment] not optional if not inet_pton: import socket @@ -11,7 +11,6 @@ from .common import HyperlinkTestCase from .._socket import inet_pton - class TestSocket(HyperlinkTestCase): def test_inet_pton_ipv4_valid(self): # type: () -> None diff --git a/src/hyperlink/test/test_strategies.py b/src/hyperlink/test/test_strategies.py new file mode 100644 index 00000000..3939c002 --- /dev/null +++ b/src/hyperlink/test/test_strategies.py @@ -0,0 +1,172 @@ +""" +Tests for hyperlink.strategies. +""" + +from string import digits +from typing import Any, Sequence, Text + +from hypothesis import given +from hypothesis.strategies import data + +from idna import IDNAError, check_label, encode as idna_encode + +from .common import HyperlinkTestCase +from .. import DecodedURL, EncodedURL +from ..strategies import ( + decoded_urls, encoded_urls, hostname_labels, hostnames, + idna_text, paths, port_numbers, +) + + +class TestHyperlink(HyperlinkTestCase): + """ + Tests for hyperlink.strategies. + """ + + @given(idna_text()) + def test_idna_text_valid(self, text): + # type: (Text) -> None + """ + idna_text() generates IDNA-encodable text. + """ + try: + idna_encode(text) + except IDNAError: # pragma: no cover + raise AssertionError( + "Invalid IDNA text: {!r}".format(text) + ) + + @given(data()) + def test_idna_text_min_max(self, data): + # type: (Any) -> None + """ + idna_text() raises AssertionError if min_size is < 1. + """ + self.assertRaises(AssertionError, data.draw, idna_text(min_size=0)) + self.assertRaises(AssertionError, data.draw, idna_text(max_size=0)) + + @given(port_numbers()) + def test_port_numbers_bounds(self, port): + # type: (int) -> None + """ + port_numbers() generates integers between 1 and 65535, inclusive. + """ + self.assertGreaterEqual(port, 1) + self.assertLessEqual(port, 65535) + + @given(port_numbers(allow_zero=True)) + def test_port_numbers_bounds_allow_zero(self, port): + # type: (int) -> None + """ + port_numbers(allow_zero=True) generates integers between 0 and 65535, + inclusive. + """ + self.assertGreaterEqual(port, 0) + self.assertLessEqual(port, 65535) + + @given(hostname_labels()) + def test_hostname_labels_valid_idn(self, label): + # type: (Text) -> None + """ + hostname_labels() generates IDN host name labels. + """ + try: + check_label(label) + idna_encode(label) + except UnicodeError: # pragma: no cover + raise AssertionError( + "Invalid IDN label: {!r}".format(label) + ) + + @given(hostname_labels(allow_idn=False)) + def test_hostname_labels_valid_ascii(self, label): + # type: (Text) -> None + """ + hostname_labels() generates a ASCII host name labels. + """ + try: + check_label(label) + label.encode("ascii") + except UnicodeError: # pragma: no cover + raise AssertionError( + "Invalid ASCII label: {!r}".format(label) + ) + + @given(hostnames()) + def test_hostnames_idn(self, hostname): + # type: (Text) -> None + """ + hostnames() generates a IDN host names. + """ + try: + for label in hostname.split(u"."): + check_label(label) + idna_encode(hostname) + except UnicodeError: # pragma: no cover + raise AssertionError( + "Invalid IDN host name: {!r}".format(hostname) + ) + + @given(hostnames(allow_leading_digit=False)) + def test_hostnames_idn_nolead(self, hostname): + # type: (Text) -> None + """ + hostnames(allow_leading_digit=False) generates a IDN host names without + leading digits. + """ + self.assertTrue(hostname == hostname.lstrip(digits)) + + @given(hostnames(allow_idn=False)) + def test_hostnames_ascii(self, hostname): + # type: (Text) -> None + """ + hostnames() generates a ASCII host names. + """ + try: + for label in hostname.split(u"."): + check_label(label) + hostname.encode("ascii") + except UnicodeError: # pragma: no cover + raise AssertionError( + "Invalid ASCII host name: {!r}".format(hostname) + ) + + @given(hostnames(allow_leading_digit=False, allow_idn=False)) + def test_hostnames_ascii_nolead(self, hostname): + # type: (Text) -> None + """ + hostnames(allow_leading_digit=False, allow_idn=False) generates a ASCII + host names without leading digits. + """ + self.assertTrue(hostname == hostname.lstrip(digits)) + + @given(paths()) + def test_paths(self, path): + # type: (Sequence[Text]) -> None + """ + paths() generates sequences of URL path components. + """ + text = u"/".join(path) + try: + text.encode("utf-8") + except UnicodeError: # pragma: no cover + raise AssertionError("Invalid URL path: {!r}".format(path)) + + for segment in path: + self.assertNotIn("#/?", segment) + + @given(encoded_urls()) + def test_encoded_urls(self, url): + # type: (EncodedURL) -> None + """ + encoded_urls() generates EncodedURLs. + """ + self.assertIsInstance(url, EncodedURL) + + @given(decoded_urls()) + def test_decoded_urls(self, url): + # type: (DecodedURL) -> None + """ + decoded_urls() generates DecodedURLs. + """ + self.assertIsInstance(url, DecodedURL) diff --git a/tox.ini b/tox.ini index bb56d322..62e6050d 100644 --- a/tox.ini +++ b/tox.ini @@ -31,6 +31,7 @@ basepython = deps = test: coverage==4.5.4 + test: hypothesis==4.43.3 test: idna==2.8 test: typing==3.7.4.1 test: {py26,py27,py34}: pytest==4.6.6 @@ -179,6 +180,16 @@ allow_untyped_defs = True [mypy-idna] ignore_missing_imports = True +# Don't complain about dependencies known to lack type hints + +[mypy-hypothesis] +ignore_missing_imports = True +[mypy-hypothesis.*] +ignore_missing_imports = True + +[mypy-twisted.*] +ignore_missing_imports = True + ## # Coverage report From e9c888cf296ee945426c573d460be9eb3d00b289 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Wed, 6 Nov 2019 12:55:41 -0800 Subject: [PATCH 070/419] exports --- src/hyperlink/strategies.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/hyperlink/strategies.py b/src/hyperlink/strategies.py index 375b596c..8f51cf0a 100644 --- a/src/hyperlink/strategies.py +++ b/src/hyperlink/strategies.py @@ -19,7 +19,15 @@ from idna import IDNAError, check_label, encode as idna_encode -__all__ = () +__all__ = ( + "decoded_urls", + "encoded_urls", + "hostname_labels", + "hostnames", + "idna_text", + "paths", + "port_numbers", +) T = TypeVar('T') From dce56a7f6d535f89853405c04462bfde82b3611d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Wed, 6 Nov 2019 16:31:11 -0800 Subject: [PATCH 071/419] Move .hypothesis to .tox/hypothesis --- src/hyperlink/test/test_strategies.py | 1 + tox.ini | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/hyperlink/test/test_strategies.py b/src/hyperlink/test/test_strategies.py index 3939c002..2631b9f4 100644 --- a/src/hyperlink/test/test_strategies.py +++ b/src/hyperlink/test/test_strategies.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ Tests for hyperlink.strategies. """ diff --git a/tox.ini b/tox.ini index 62e6050d..b0976ad8 100644 --- a/tox.ini +++ b/tox.ini @@ -55,6 +55,8 @@ passenv = setenv = PY_MODULE=hyperlink + test: HYPOTHESIS_STORAGE_DIRECTORY={toxworkdir}/hypothesis + test: COVERAGE_FILE={toxworkdir}/coverage.{envname} {coverage_report,codecov}: COVERAGE_FILE={toxworkdir}/coverage codecov: COVERAGE_XML={envlogdir}/coverage_report.xml From c7509785af58fa4d065faed2557e57943e31f53d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Wed, 6 Nov 2019 16:42:28 -0800 Subject: [PATCH 072/419] Disable HealthCheck.too_slow in CI. --- LICENSE | 1 + src/hyperlink/test/__init__.py | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/LICENSE b/LICENSE index 30953dde..a73f882f 100644 --- a/LICENSE +++ b/LICENSE @@ -5,6 +5,7 @@ Jean Paul Calderone Adi Roiban Amber Hawkie Brown Mahmoud Hashemi +Wilfredo Sanchez Vega and others that have contributed code to the public domain. diff --git a/src/hyperlink/test/__init__.py b/src/hyperlink/test/__init__.py index e69de29b..9d1a87ad 100644 --- a/src/hyperlink/test/__init__.py +++ b/src/hyperlink/test/__init__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +""" +Tests for hyperlink +""" + +__all = () + + +def _init_hypothesis(): + # type: () -> None + from os import environ + + if "CI" in environ: + from hypothesis import HealthCheck, settings + + settings.register_profile( + "patience", settings(suppress_health_check=[HealthCheck.too_slow]) + ) + settings.load_profile("patience") + + +_init_hypothesis() From 0e6c43aae2027da65834d60f9eccebc91856772e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Wed, 6 Nov 2019 18:35:01 -0800 Subject: [PATCH 073/419] hypothesis doesn't support Python 3.4. Jump through some hoops. --- src/hyperlink/strategies.py | 507 +++++++++++++------------- src/hyperlink/test/__init__.py | 5 +- src/hyperlink/test/test_strategies.py | 335 ++++++++--------- tox.ini | 8 +- 4 files changed, 434 insertions(+), 421 deletions(-) diff --git a/src/hyperlink/strategies.py b/src/hyperlink/strategies.py index 8f51cf0a..26eac5c0 100644 --- a/src/hyperlink/strategies.py +++ b/src/hyperlink/strategies.py @@ -3,286 +3,287 @@ Hypothesis strategies. """ -from csv import reader as csv_reader -from os.path import dirname, join -from string import ascii_letters, digits -from sys import maxunicode -from typing import Callable, Iterable, Optional, Sequence, Text, TypeVar, cast - -from . import DecodedURL, EncodedURL - -from hypothesis import assume -from hypothesis.strategies import ( - composite, integers, lists, sampled_from, text -) - -from idna import IDNAError, check_label, encode as idna_encode - - -__all__ = ( - "decoded_urls", - "encoded_urls", - "hostname_labels", - "hostnames", - "idna_text", - "paths", - "port_numbers", -) - - -T = TypeVar('T') -DrawCallable = Callable[[Callable[..., T]], T] - - try: - unichr -except NameError: # Py3 - unichr = chr # type: Callable[[int], Text] - - -def idna_characters(): - # type: () -> Text - """ - Returns a string containing IDNA characters. - """ - global _idnaCharacters - - if not _idnaCharacters: - result = [] - - # Data source "IDNA Derived Properties": - # https://www.iana.org/assignments/idna-tables-6.3.0/ - # idna-tables-6.3.0.xhtml#idna-tables-properties - dataFileName = join(dirname(__file__), "idna-tables-properties.csv") - with open(dataFileName) as dataFile: - reader = csv_reader(dataFile, delimiter=",") - next(reader) # Skip header row - for row in reader: - codes, prop, description = row - - if prop != "PVALID": - # CONTEXTO or CONTEXTJ are also allowed, but they come with - # rules, so we're punting on those here. - # See: https://tools.ietf.org/html/rfc5892 - continue - - startEnd = row[0].split("-", 1) - if len(startEnd) == 1: - # No end of range given; use start - startEnd.append(startEnd[0]) - start, end = (int(i, 16) for i in startEnd) - - for i in range(start, end + 1): - assert i <= maxunicode - result.append(unichr(i)) - - _idnaCharacters = u"".join(result) - - return _idnaCharacters - - -_idnaCharacters = "" # type: Text - - -@composite -def idna_text(draw, min_size=1, max_size=None): - # type: (DrawCallable, int, Optional[int]) -> Text - """ - A strategy which generates IDNA-encodable text. - - @param min_size: The minimum number of characters in the text. - C{None} is treated as C{0}. - - @param max_size: The maximum number of characters in the text. - Use C{None} for an unbounded size. - """ - alphabet = idna_characters() - - assert min_size >= 1 - - if max_size is not None: - assert max_size >= 1 - - result = cast( - Text, - draw(text( - min_size=min_size, max_size=max_size, alphabet=alphabet - )) + from hypothesis import assume +except ImportError: + from typing import Tuple + __all__ = () # type: Tuple[str, ...] +else: + from csv import reader as csv_reader + from os.path import dirname, join + from string import ascii_letters, digits + from sys import maxunicode + from typing import ( + Callable, Iterable, Optional, Sequence, Text, TypeVar, cast ) - # FIXME: There should be a more efficient way to ensure we produce valid - # IDNA text. - try: - idna_encode(result) - except IDNAError: - assume(False) - - return result - + from . import DecodedURL, EncodedURL -@composite -def port_numbers(draw, allow_zero=False): - # type: (DrawCallable, bool) -> int - """ - A strategy which generates port numbers. - - @param allow_zero: Whether to allow port C{0} as a possible value. - """ - if allow_zero: - min_value = 0 - else: - min_value = 1 - - return cast( - int, draw(integers(min_value=min_value, max_value=65535)) + from hypothesis.strategies import ( + composite, integers, lists, sampled_from, text ) + from idna import IDNAError, check_label, encode as idna_encode -@composite -def hostname_labels(draw, allow_idn=True): - # type: (DrawCallable, bool) -> Text - """ - A strategy which generates host name labels. + __all__ = ( + "decoded_urls", + "encoded_urls", + "hostname_labels", + "hostnames", + "idna_text", + "paths", + "port_numbers", + ) - @param allow_idn: Whether to allow non-ASCII characters as allowed by - internationalized domain names (IDNs). - """ - if allow_idn: - label = cast(Text, draw(idna_text(min_size=1, max_size=63))) + T = TypeVar('T') + DrawCallable = Callable[[Callable[..., T]], T] - try: - label.encode("ascii") - except UnicodeEncodeError: - # If the label doesn't encode to ASCII, then we need to check the - # length of the label after encoding to punycode and adding the - # xn-- prefix. - while ( - len(label.encode("punycode")) > 63 - len("xn--") - ): # pragma: no cover (not always drawn) - # Rather than bombing out, just trim from the end until it is - # short enough, so hypothesis doesn't have to generate new - # data. - label = label[:-1] - - else: - label = cast( + try: + unichr + except NameError: # Py3 + unichr = chr # type: Callable[[int], Text] + + def idna_characters(): + # type: () -> Text + """ + Returns a string containing IDNA characters. + """ + global _idnaCharacters + + if not _idnaCharacters: + result = [] + + # Data source "IDNA Derived Properties": + # https://www.iana.org/assignments/idna-tables-6.3.0/ + # idna-tables-6.3.0.xhtml#idna-tables-properties + dataFileName = join( + dirname(__file__), "idna-tables-properties.csv" + ) + with open(dataFileName) as dataFile: + reader = csv_reader(dataFile, delimiter=",") + next(reader) # Skip header row + for row in reader: + codes, prop, description = row + + if prop != "PVALID": + # CONTEXTO or CONTEXTJ are also allowed, but they come + # with rules, so we're punting on those here. + # See: https://tools.ietf.org/html/rfc5892 + continue + + startEnd = row[0].split("-", 1) + if len(startEnd) == 1: + # No end of range given; use start + startEnd.append(startEnd[0]) + start, end = (int(i, 16) for i in startEnd) + + for i in range(start, end + 1): + assert i <= maxunicode + result.append(unichr(i)) + + _idnaCharacters = u"".join(result) + + return _idnaCharacters + + _idnaCharacters = "" # type: Text + + @composite + def idna_text(draw, min_size=1, max_size=None): + # type: (DrawCallable, int, Optional[int]) -> Text + """ + A strategy which generates IDNA-encodable text. + + @param min_size: The minimum number of characters in the text. + C{None} is treated as C{0}. + + @param max_size: The maximum number of characters in the text. + Use C{None} for an unbounded size. + """ + alphabet = idna_characters() + + assert min_size >= 1 + + if max_size is not None: + assert max_size >= 1 + + result = cast( Text, draw(text( - min_size=1, max_size=63, - alphabet=Text(ascii_letters + digits + u"-") + min_size=min_size, max_size=max_size, alphabet=alphabet )) ) - # Filter invalid labels. - # It would be better to reliably avoid generation of bogus labels in the - # first place, but it's hard... - try: - check_label(label) - except UnicodeError: # pragma: no cover (not always drawn) - assume(False) - - return label - - -@composite -def hostnames(draw, allow_leading_digit=True, allow_idn=True): - # type: (DrawCallable, bool, bool) -> Text - """ - A strategy which generates host names. - - @param allow_leading_digit: Whether to allow a leading digit in host names; - they were not allowed prior to RFC 1123. - - @param allow_idn: Whether to allow non-ASCII characters as allowed by - internationalized domain names (IDNs). - """ - labels = cast( - Sequence[Text], - draw( - lists(hostname_labels(allow_idn=allow_idn), min_size=1, max_size=5) - .filter(lambda ls: sum(len(l) for l in ls) + len(ls) - 1 <= 252) + # FIXME: There should be a more efficient way to ensure we produce + # valid IDNA text. + try: + idna_encode(result) + except IDNAError: + assume(False) + + return result + + @composite + def port_numbers(draw, allow_zero=False): + # type: (DrawCallable, bool) -> int + """ + A strategy which generates port numbers. + + @param allow_zero: Whether to allow port C{0} as a possible value. + """ + if allow_zero: + min_value = 0 + else: + min_value = 1 + + return cast( + int, draw(integers(min_value=min_value, max_value=65535)) ) - ) - - if not allow_leading_digit: - assume(labels[0][0] not in digits) - - return u".".join(labels) + @composite + def hostname_labels(draw, allow_idn=True): + # type: (DrawCallable, bool) -> Text + """ + A strategy which generates host name labels. + + @param allow_idn: Whether to allow non-ASCII characters as allowed by + internationalized domain names (IDNs). + """ + if allow_idn: + label = cast(Text, draw(idna_text(min_size=1, max_size=63))) + + try: + label.encode("ascii") + except UnicodeEncodeError: + # If the label doesn't encode to ASCII, then we need to check + # the length of the label after encoding to punycode and adding + # the xn-- prefix. + while ( + len(label.encode("punycode")) > 63 - len("xn--") + ): # pragma: no cover (not always drawn) + # Rather than bombing out, just trim from the end until it + # is short enough, so hypothesis doesn't have to generate + # new data. + label = label[:-1] + + else: + label = cast( + Text, + draw(text( + min_size=1, max_size=63, + alphabet=Text(ascii_letters + digits + u"-") + )) + ) + + # Filter invalid labels. + # It would be better to reliably avoid generation of bogus labels in + # the first place, but it's hard... + try: + check_label(label) + except UnicodeError: # pragma: no cover (not always drawn) + assume(False) + + return label + + @composite + def hostnames(draw, allow_leading_digit=True, allow_idn=True): + # type: (DrawCallable, bool, bool) -> Text + """ + A strategy which generates host names. + + @param allow_leading_digit: Whether to allow a leading digit in host + names; they were not allowed prior to RFC 1123. + + @param allow_idn: Whether to allow non-ASCII characters as allowed by + internationalized domain names (IDNs). + """ + labels = cast( + Sequence[Text], + draw( + lists( + hostname_labels(allow_idn=allow_idn), + min_size=1, max_size=5, + ).filter( + lambda ls: sum(len(l) for l in ls) + len(ls) - 1 <= 252 + ) + ) + ) -def path_characters(): - # type: () -> str - """ - Returns a string containing valid URL path characters. - """ - global _path_characters + if not allow_leading_digit: + assume(labels[0][0] not in digits) - if _path_characters is None: - def chars(): - # type: () -> Iterable[Text] - for i in range(maxunicode): - c = unichr(i) + return u".".join(labels) - # Exclude reserved characters - if c in "#/?": - continue + def path_characters(): + # type: () -> str + """ + Returns a string containing valid URL path characters. + """ + global _path_characters - # Exclude anything not UTF-8 compatible - try: - c.encode("utf-8") - except UnicodeEncodeError: - continue + if _path_characters is None: + def chars(): + # type: () -> Iterable[Text] + for i in range(maxunicode): + c = unichr(i) - yield c + # Exclude reserved characters + if c in "#/?": + continue - _path_characters = "".join(chars()) + # Exclude anything not UTF-8 compatible + try: + c.encode("utf-8") + except UnicodeEncodeError: + continue - return _path_characters + yield c + _path_characters = "".join(chars()) -_path_characters = None # type: Optional[str] + return _path_characters + _path_characters = None # type: Optional[str] -@composite -def paths(draw): - # type: (DrawCallable) -> Sequence[Text] - return cast( - Sequence[Text], - draw( - lists(text(min_size=1, alphabet=path_characters()), max_size=10) + @composite + def paths(draw): + # type: (DrawCallable) -> Sequence[Text] + return cast( + Sequence[Text], + draw( + lists( + text(min_size=1, alphabet=path_characters()), max_size=10 + ) + ) ) - ) - - -@composite -def encoded_urls(draw): - # type: (DrawCallable) -> EncodedURL - """ - A strategy which generates L{EncodedURL}s. - Call the L{EncodedURL.to_uri} method on each URL to get an HTTP - protocol-friendly URI. - """ - port = cast(Optional[int], draw(port_numbers(allow_zero=True))) - host = cast(Text, draw(hostnames())) - path = cast(Sequence[Text], draw(paths())) - - if port == 0: - port = None - - args = dict( - scheme=cast(Text, draw(sampled_from((u"http", u"https")))), - host=host, port=port, path=path, - ) - - return EncodedURL(**args) + @composite + def encoded_urls(draw): + # type: (DrawCallable) -> EncodedURL + """ + A strategy which generates L{EncodedURL}s. + Call the L{EncodedURL.to_uri} method on each URL to get an HTTP + protocol-friendly URI. + """ + port = cast(Optional[int], draw(port_numbers(allow_zero=True))) + host = cast(Text, draw(hostnames())) + path = cast(Sequence[Text], draw(paths())) + + if port == 0: + port = None + + args = dict( + scheme=cast(Text, draw(sampled_from((u"http", u"https")))), + host=host, port=port, path=path, + ) -@composite -def decoded_urls(draw): - # type: (DrawCallable) -> DecodedURL - """ - A strategy which generates L{DecodedURL}s. - Call the L{EncodedURL.to_uri} method on each URL to get an HTTP - protocol-friendly URI. - """ - return DecodedURL(draw(encoded_urls())) + return EncodedURL(**args) + + @composite + def decoded_urls(draw): + # type: (DrawCallable) -> DecodedURL + """ + A strategy which generates L{DecodedURL}s. + Call the L{EncodedURL.to_uri} method on each URL to get an HTTP + protocol-friendly URI. + """ + return DecodedURL(draw(encoded_urls())) diff --git a/src/hyperlink/test/__init__.py b/src/hyperlink/test/__init__.py index 9d1a87ad..0a488d85 100644 --- a/src/hyperlink/test/__init__.py +++ b/src/hyperlink/test/__init__.py @@ -11,7 +11,10 @@ def _init_hypothesis(): from os import environ if "CI" in environ: - from hypothesis import HealthCheck, settings + try: + from hypothesis import HealthCheck, settings + except ImportError: + return settings.register_profile( "patience", settings(suppress_health_check=[HealthCheck.too_slow]) diff --git a/src/hyperlink/test/test_strategies.py b/src/hyperlink/test/test_strategies.py index 2631b9f4..15fde378 100644 --- a/src/hyperlink/test/test_strategies.py +++ b/src/hyperlink/test/test_strategies.py @@ -3,171 +3,174 @@ Tests for hyperlink.strategies. """ -from string import digits -from typing import Any, Sequence, Text - -from hypothesis import given -from hypothesis.strategies import data - -from idna import IDNAError, check_label, encode as idna_encode - -from .common import HyperlinkTestCase -from .. import DecodedURL, EncodedURL -from ..strategies import ( - decoded_urls, encoded_urls, hostname_labels, hostnames, - idna_text, paths, port_numbers, -) - - -class TestHyperlink(HyperlinkTestCase): - """ - Tests for hyperlink.strategies. - """ - - @given(idna_text()) - def test_idna_text_valid(self, text): - # type: (Text) -> None - """ - idna_text() generates IDNA-encodable text. - """ - try: - idna_encode(text) - except IDNAError: # pragma: no cover - raise AssertionError( - "Invalid IDNA text: {!r}".format(text) - ) - - @given(data()) - def test_idna_text_min_max(self, data): - # type: (Any) -> None - """ - idna_text() raises AssertionError if min_size is < 1. - """ - self.assertRaises(AssertionError, data.draw, idna_text(min_size=0)) - self.assertRaises(AssertionError, data.draw, idna_text(max_size=0)) - - @given(port_numbers()) - def test_port_numbers_bounds(self, port): - # type: (int) -> None - """ - port_numbers() generates integers between 1 and 65535, inclusive. - """ - self.assertGreaterEqual(port, 1) - self.assertLessEqual(port, 65535) - - @given(port_numbers(allow_zero=True)) - def test_port_numbers_bounds_allow_zero(self, port): - # type: (int) -> None - """ - port_numbers(allow_zero=True) generates integers between 0 and 65535, - inclusive. - """ - self.assertGreaterEqual(port, 0) - self.assertLessEqual(port, 65535) - - @given(hostname_labels()) - def test_hostname_labels_valid_idn(self, label): - # type: (Text) -> None - """ - hostname_labels() generates IDN host name labels. - """ - try: - check_label(label) - idna_encode(label) - except UnicodeError: # pragma: no cover - raise AssertionError( - "Invalid IDN label: {!r}".format(label) - ) - - @given(hostname_labels(allow_idn=False)) - def test_hostname_labels_valid_ascii(self, label): - # type: (Text) -> None - """ - hostname_labels() generates a ASCII host name labels. - """ - try: - check_label(label) - label.encode("ascii") - except UnicodeError: # pragma: no cover - raise AssertionError( - "Invalid ASCII label: {!r}".format(label) - ) - - @given(hostnames()) - def test_hostnames_idn(self, hostname): - # type: (Text) -> None - """ - hostnames() generates a IDN host names. - """ - try: - for label in hostname.split(u"."): +try: + from hypothesis import given +except ImportError: + pass +else: + from string import digits + from typing import Any, Sequence, Text + + from hypothesis.strategies import data + + from idna import IDNAError, check_label, encode as idna_encode + + from .common import HyperlinkTestCase + from .. import DecodedURL, EncodedURL + from ..strategies import ( + decoded_urls, encoded_urls, hostname_labels, hostnames, + idna_text, paths, port_numbers, + ) + + class TestHyperlink(HyperlinkTestCase): + """ + Tests for hyperlink.strategies. + """ + + @given(idna_text()) + def test_idna_text_valid(self, text): + # type: (Text) -> None + """ + idna_text() generates IDNA-encodable text. + """ + try: + idna_encode(text) + except IDNAError: # pragma: no cover + raise AssertionError( + "Invalid IDNA text: {!r}".format(text) + ) + + @given(data()) + def test_idna_text_min_max(self, data): + # type: (Any) -> None + """ + idna_text() raises AssertionError if min_size is < 1. + """ + self.assertRaises(AssertionError, data.draw, idna_text(min_size=0)) + self.assertRaises(AssertionError, data.draw, idna_text(max_size=0)) + + @given(port_numbers()) + def test_port_numbers_bounds(self, port): + # type: (int) -> None + """ + port_numbers() generates integers between 1 and 65535, inclusive. + """ + self.assertGreaterEqual(port, 1) + self.assertLessEqual(port, 65535) + + @given(port_numbers(allow_zero=True)) + def test_port_numbers_bounds_allow_zero(self, port): + # type: (int) -> None + """ + port_numbers(allow_zero=True) generates integers between 0 and + 65535, inclusive. + """ + self.assertGreaterEqual(port, 0) + self.assertLessEqual(port, 65535) + + @given(hostname_labels()) + def test_hostname_labels_valid_idn(self, label): + # type: (Text) -> None + """ + hostname_labels() generates IDN host name labels. + """ + try: check_label(label) - idna_encode(hostname) - except UnicodeError: # pragma: no cover - raise AssertionError( - "Invalid IDN host name: {!r}".format(hostname) - ) - - @given(hostnames(allow_leading_digit=False)) - def test_hostnames_idn_nolead(self, hostname): - # type: (Text) -> None - """ - hostnames(allow_leading_digit=False) generates a IDN host names without - leading digits. - """ - self.assertTrue(hostname == hostname.lstrip(digits)) - - @given(hostnames(allow_idn=False)) - def test_hostnames_ascii(self, hostname): - # type: (Text) -> None - """ - hostnames() generates a ASCII host names. - """ - try: - for label in hostname.split(u"."): + idna_encode(label) + except UnicodeError: # pragma: no cover + raise AssertionError( + "Invalid IDN label: {!r}".format(label) + ) + + @given(hostname_labels(allow_idn=False)) + def test_hostname_labels_valid_ascii(self, label): + # type: (Text) -> None + """ + hostname_labels() generates a ASCII host name labels. + """ + try: check_label(label) - hostname.encode("ascii") - except UnicodeError: # pragma: no cover - raise AssertionError( - "Invalid ASCII host name: {!r}".format(hostname) - ) - - @given(hostnames(allow_leading_digit=False, allow_idn=False)) - def test_hostnames_ascii_nolead(self, hostname): - # type: (Text) -> None - """ - hostnames(allow_leading_digit=False, allow_idn=False) generates a ASCII - host names without leading digits. - """ - self.assertTrue(hostname == hostname.lstrip(digits)) - - @given(paths()) - def test_paths(self, path): - # type: (Sequence[Text]) -> None - """ - paths() generates sequences of URL path components. - """ - text = u"/".join(path) - try: - text.encode("utf-8") - except UnicodeError: # pragma: no cover - raise AssertionError("Invalid URL path: {!r}".format(path)) - - for segment in path: - self.assertNotIn("#/?", segment) - - @given(encoded_urls()) - def test_encoded_urls(self, url): - # type: (EncodedURL) -> None - """ - encoded_urls() generates EncodedURLs. - """ - self.assertIsInstance(url, EncodedURL) - - @given(decoded_urls()) - def test_decoded_urls(self, url): - # type: (DecodedURL) -> None - """ - decoded_urls() generates DecodedURLs. - """ - self.assertIsInstance(url, DecodedURL) + label.encode("ascii") + except UnicodeError: # pragma: no cover + raise AssertionError( + "Invalid ASCII label: {!r}".format(label) + ) + + @given(hostnames()) + def test_hostnames_idn(self, hostname): + # type: (Text) -> None + """ + hostnames() generates a IDN host names. + """ + try: + for label in hostname.split(u"."): + check_label(label) + idna_encode(hostname) + except UnicodeError: # pragma: no cover + raise AssertionError( + "Invalid IDN host name: {!r}".format(hostname) + ) + + @given(hostnames(allow_leading_digit=False)) + def test_hostnames_idn_nolead(self, hostname): + # type: (Text) -> None + """ + hostnames(allow_leading_digit=False) generates a IDN host names + without leading digits. + """ + self.assertTrue(hostname == hostname.lstrip(digits)) + + @given(hostnames(allow_idn=False)) + def test_hostnames_ascii(self, hostname): + # type: (Text) -> None + """ + hostnames() generates a ASCII host names. + """ + try: + for label in hostname.split(u"."): + check_label(label) + hostname.encode("ascii") + except UnicodeError: # pragma: no cover + raise AssertionError( + "Invalid ASCII host name: {!r}".format(hostname) + ) + + @given(hostnames(allow_leading_digit=False, allow_idn=False)) + def test_hostnames_ascii_nolead(self, hostname): + # type: (Text) -> None + """ + hostnames(allow_leading_digit=False, allow_idn=False) generates + ASCII host names without leading digits. + """ + self.assertTrue(hostname == hostname.lstrip(digits)) + + @given(paths()) + def test_paths(self, path): + # type: (Sequence[Text]) -> None + """ + paths() generates sequences of URL path components. + """ + text = u"/".join(path) + try: + text.encode("utf-8") + except UnicodeError: # pragma: no cover + raise AssertionError("Invalid URL path: {!r}".format(path)) + + for segment in path: + self.assertNotIn("#/?", segment) + + @given(encoded_urls()) + def test_encoded_urls(self, url): + # type: (EncodedURL) -> None + """ + encoded_urls() generates EncodedURLs. + """ + self.assertIsInstance(url, EncodedURL) + + @given(decoded_urls()) + def test_decoded_urls(self, url): + # type: (DecodedURL) -> None + """ + decoded_urls() generates DecodedURLs. + """ + self.assertIsInstance(url, DecodedURL) diff --git a/tox.ini b/tox.ini index b0976ad8..a691fde2 100644 --- a/tox.ini +++ b/tox.ini @@ -31,7 +31,13 @@ basepython = deps = test: coverage==4.5.4 - test: hypothesis==4.43.3 + test-py27: hypothesis==4.43.3 + # py34 isn't supported by hypothesis + test-py35: hypothesis==4.43.3 + test-py36: hypothesis==4.43.3 + test-py37: hypothesis==4.43.3 + test-py38: hypothesis==4.43.3 + test-py39: hypothesis==4.43.3 test: idna==2.8 test: typing==3.7.4.1 test: {py26,py27,py34}: pytest==4.6.6 From 41675c12e2ed515000530b1641929a100557927f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Thu, 7 Nov 2019 09:05:54 -0800 Subject: [PATCH 074/419] Fix for Py2/Windows --- src/hyperlink/strategies.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/hyperlink/strategies.py b/src/hyperlink/strategies.py index 26eac5c0..278d18fa 100644 --- a/src/hyperlink/strategies.py +++ b/src/hyperlink/strategies.py @@ -78,7 +78,8 @@ def idna_characters(): start, end = (int(i, 16) for i in startEnd) for i in range(start, end + 1): - assert i <= maxunicode + if i > maxunicode: # Happens using Py2 on Windows + break result.append(unichr(i)) _idnaCharacters = u"".join(result) From e92163c8d03660be6d9a739df980046219754310 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Thu, 7 Nov 2019 09:43:59 -0800 Subject: [PATCH 075/419] Add test_hostname_labels_long_idn_punycode --- src/hyperlink/strategies.py | 6 ++-- src/hyperlink/test/test_strategies.py | 43 +++++++++++++++++++++++---- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/src/hyperlink/strategies.py b/src/hyperlink/strategies.py index 278d18fa..2f25da87 100644 --- a/src/hyperlink/strategies.py +++ b/src/hyperlink/strategies.py @@ -4,7 +4,8 @@ """ try: - from hypothesis import assume + import hypothesis + del(hypothesis) except ImportError: from typing import Tuple __all__ = () # type: Tuple[str, ...] @@ -19,6 +20,7 @@ from . import DecodedURL, EncodedURL + from hypothesis import assume from hypothesis.strategies import ( composite, integers, lists, sampled_from, text ) @@ -160,7 +162,7 @@ def hostname_labels(draw, allow_idn=True): # the xn-- prefix. while ( len(label.encode("punycode")) > 63 - len("xn--") - ): # pragma: no cover (not always drawn) + ): # Rather than bombing out, just trim from the end until it # is short enough, so hypothesis doesn't have to generate # new data. diff --git a/src/hyperlink/test/test_strategies.py b/src/hyperlink/test/test_strategies.py index 15fde378..7e100ee0 100644 --- a/src/hyperlink/test/test_strategies.py +++ b/src/hyperlink/test/test_strategies.py @@ -4,22 +4,25 @@ """ try: - from hypothesis import given + import hypothesis + del(hypothesis) except ImportError: pass else: from string import digits - from typing import Any, Sequence, Text + from typing import Sequence, Text + from unittest.mock import patch - from hypothesis.strategies import data + from hypothesis import given, settings + from hypothesis.strategies import SearchStrategy, data from idna import IDNAError, check_label, encode as idna_encode from .common import HyperlinkTestCase from .. import DecodedURL, EncodedURL from ..strategies import ( - decoded_urls, encoded_urls, hostname_labels, hostnames, - idna_text, paths, port_numbers, + DrawCallable, composite, decoded_urls, encoded_urls, + hostname_labels, hostnames, idna_text, paths, port_numbers, ) class TestHyperlink(HyperlinkTestCase): @@ -42,7 +45,7 @@ def test_idna_text_valid(self, text): @given(data()) def test_idna_text_min_max(self, data): - # type: (Any) -> None + # type: (SearchStrategy) -> None """ idna_text() raises AssertionError if min_size is < 1. """ @@ -82,6 +85,34 @@ def test_hostname_labels_valid_idn(self, label): "Invalid IDN label: {!r}".format(label) ) + @given(data()) + @settings(max_examples=10) + def test_hostname_labels_long_idn_punycode(self, data): + # type: (SearchStrategy) -> None + """ + hostname_labels() handles case where idna_text() generates text + that encoded to punycode ends up as longer than allowed. + """ + @composite + def mock_idna_text(draw, min_size, max_size): + # type: (DrawCallable, int, int) -> Text + # We want a string that does not exceed max_size, but when + # encoded to punycode, does exceed max_size. + # So use a unicode character that is larger when encoded, + # "á" being a great example, and use it max_size times, which + # will be max_size * 3 in size when encoded. + return u"\N{LATIN SMALL LETTER A WITH ACUTE}" * max_size + + with patch("hyperlink.strategies.idna_text", mock_idna_text): + label = data.draw(hostname_labels()) + try: + check_label(label) + idna_encode(label) + except UnicodeError: # pragma: no cover + raise AssertionError( + "Invalid IDN label: {!r}".format(label) + ) + @given(hostname_labels(allow_idn=False)) def test_hostname_labels_valid_ascii(self, label): # type: (Text) -> None From 710ff6e784b43dac5b43662c49911e58cc6c05ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Thu, 7 Nov 2019 10:08:25 -0800 Subject: [PATCH 076/419] Bring in mock for Py2. --- src/hyperlink/test/test_strategies.py | 5 ++++- tox.ini | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/hyperlink/test/test_strategies.py b/src/hyperlink/test/test_strategies.py index 7e100ee0..931953f2 100644 --- a/src/hyperlink/test/test_strategies.py +++ b/src/hyperlink/test/test_strategies.py @@ -11,7 +11,10 @@ else: from string import digits from typing import Sequence, Text - from unittest.mock import patch + try: + from unittest.mock import patch + except ImportError: + from mock import patch # type: ignore[misc] from hypothesis import given, settings from hypothesis.strategies import SearchStrategy, data diff --git a/tox.ini b/tox.ini index a691fde2..c4d5a1ed 100644 --- a/tox.ini +++ b/tox.ini @@ -39,6 +39,7 @@ deps = test-py38: hypothesis==4.43.3 test-py39: hypothesis==4.43.3 test: idna==2.8 + test-py27: mock==3.0.5 test: typing==3.7.4.1 test: {py26,py27,py34}: pytest==4.6.6 test: {py35,py36,py37,py38}: pytest==5.2.1 From ad2b6939e9dab73d56e0470e28d22f71373363a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Thu, 7 Nov 2019 10:21:31 -0800 Subject: [PATCH 077/419] Pass CI through for tests. --- tox.ini | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tox.ini b/tox.ini index c4d5a1ed..485816b3 100644 --- a/tox.ini +++ b/tox.ini @@ -46,6 +46,9 @@ deps = test: pytest-cov==2.8.1 passenv = + # For Hypothesis settings + test: CI + # See https://github.com/codecov/codecov-python/blob/master/README.md#using-tox # And CI-specific docs: # https://help.github.com/en/articles/virtual-environments-for-github-actions#default-environment-variables From 8faf500d61407139f13731b588f5aa1565db9cca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Thu, 7 Nov 2019 11:16:03 -0800 Subject: [PATCH 078/419] del is a statement, yo --- src/hyperlink/strategies.py | 2 +- src/hyperlink/test/test_strategies.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hyperlink/strategies.py b/src/hyperlink/strategies.py index 2f25da87..1e7dce07 100644 --- a/src/hyperlink/strategies.py +++ b/src/hyperlink/strategies.py @@ -5,7 +5,7 @@ try: import hypothesis - del(hypothesis) + del hypothesis except ImportError: from typing import Tuple __all__ = () # type: Tuple[str, ...] diff --git a/src/hyperlink/test/test_strategies.py b/src/hyperlink/test/test_strategies.py index 931953f2..cf67bc1b 100644 --- a/src/hyperlink/test/test_strategies.py +++ b/src/hyperlink/test/test_strategies.py @@ -5,7 +5,7 @@ try: import hypothesis - del(hypothesis) + del hypothesis except ImportError: pass else: From 9d4aaa2c48b402e206cf45c9ab9044a402e2a8b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Thu, 7 Nov 2019 16:22:08 -0800 Subject: [PATCH 079/419] Add docs environment to tox config. --- .travis.yml | 2 ++ tox.ini | 34 +++++++++++++++++++++++++++++----- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 028ed0b8..e17f3cd0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,6 +26,8 @@ matrix: env: TOXENV=test-pypy3,codecov - python: "2.7" env: TOXENV=packaging-py27 + - python: "3.8" + env: TOXENV=docs install: diff --git a/tox.ini b/tox.ini index bb56d322..ad43dff7 100644 --- a/tox.ini +++ b/tox.ini @@ -5,6 +5,7 @@ envlist = test-py{26,27,34,35,36,37,38,py,py3} coverage_report packaging + docs skip_missing_interpreters = {tty:True:False} @@ -220,11 +221,34 @@ deps = commands = coverage combine coverage xml -o "{env:COVERAGE_XML}" - codecov --file="{env:COVERAGE_XML}" --env \ - GITHUB_REF GITHUB_COMMIT GITHUB_USER GITHUB_WORKFLOW \ - TRAVIS_BRANCH TRAVIS_BUILD_WEB_URL TRAVIS_COMMIT TRAVIS_COMMIT_MESSAGE \ - APPVEYOR_REPO_BRANCH APPVEYOR_REPO_COMMIT \ - APPVEYOR_REPO_COMMIT_AUTHOR_EMAIL APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED + codecov --file="{env:COVERAGE_XML}" --env \ + GITHUB_REF GITHUB_COMMIT GITHUB_USER GITHUB_WORKFLOW \ + TRAVIS_BRANCH TRAVIS_BUILD_WEB_URL \ + TRAVIS_COMMIT TRAVIS_COMMIT_MESSAGE \ + APPVEYOR_REPO_BRANCH APPVEYOR_REPO_COMMIT \ + APPVEYOR_REPO_COMMIT_AUTHOR_EMAIL \ + APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED + + +## +# Documentation +## + +[testenv:docs] + +description = build documentation + +basepython = python3.8 + +deps = + Sphinx==2.2.1 + sphinx_rtd_theme + +commands = + sphinx-build \ + -b html -d "{envtmpdir}/doctrees" \ + "{toxinidir}/docs" \ + "{toxworkdir}/docs/html" ## From 497b26fd16361a2d917c5bf81f9ad9b7061983e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Thu, 7 Nov 2019 16:22:26 -0800 Subject: [PATCH 080/419] URL.family does not exist. --- docs/api.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/api.rst b/docs/api.rst index 47854540..c2498000 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -61,7 +61,6 @@ URLs have many parts, and URL objects have many attributes to represent them. .. autoattribute:: hyperlink.URL.userinfo .. autoattribute:: hyperlink.URL.user .. autoattribute:: hyperlink.URL.rooted -.. autoattribute:: hyperlink.URL.family Low-level functions ------------------- From 26cb7f8653001ad9ea72db63f59814fac6a23737 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Thu, 7 Nov 2019 16:23:38 -0800 Subject: [PATCH 081/419] parse_host is documented but not exported publicly. --- src/hyperlink/__init__.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/hyperlink/__init__.py b/src/hyperlink/__init__.py index a027d52d..349b2199 100644 --- a/src/hyperlink/__init__.py +++ b/src/hyperlink/__init__.py @@ -1,16 +1,19 @@ +from ._url import ( + parse, + parse_host, + register_scheme, + URL, + EncodedURL, + DecodedURL, + URLParseError, +) -from ._url import (URL, - parse, - EncodedURL, - DecodedURL, - URLParseError, - register_scheme) - -__all__ = [ - "URL", +__all__ = ( "parse", + "parse_host", + "register_scheme", + "URL", "EncodedURL", "DecodedURL", "URLParseError", - "register_scheme", -] +) From 088859a4ca44c5808ce63c866d5b5dd0aff64be4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Thu, 7 Nov 2019 16:23:53 -0800 Subject: [PATCH 082/419] parse is exported publicly but not documented. --- docs/api.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/api.rst b/docs/api.rst index c2498000..d2fd7ed7 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -70,5 +70,6 @@ A couple of notable helpers used by the :class:`~hyperlink.URL` type. .. autoclass:: hyperlink.URLParseError .. autofunction:: hyperlink.register_scheme .. autofunction:: hyperlink.parse_host +.. autofunction:: hyperlink.parse .. TODO: run doctests in docs? From 351034302bb55a0faa10f209deb8d81f09d596c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Thu, 7 Nov 2019 16:24:46 -0800 Subject: [PATCH 083/419] Add flake8 and mypy to Travis --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 028ed0b8..c8650969 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,10 @@ language: python matrix: include: + - python: "3.8" + env: TOXENV=flake8 + - python: "3.8" + env: TOXENV=mypy - python: "2.7" env: TOXENV=test-py27,codecov - python: "3.4" From 2c9cf879b6c877c34f71edfdb210e4ea321f4d9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Thu, 7 Nov 2019 16:35:47 -0800 Subject: [PATCH 084/419] Forgot to pin sphinx-rtd-theme --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index ad43dff7..8e353863 100644 --- a/tox.ini +++ b/tox.ini @@ -242,7 +242,7 @@ basepython = python3.8 deps = Sphinx==2.2.1 - sphinx_rtd_theme + sphinx-rtd-theme==0.4.3 commands = sphinx-build \ From f1cb4776f6c6d6961a195736c4b38a8e57552f8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Thu, 7 Nov 2019 16:38:08 -0800 Subject: [PATCH 085/419] Un-export parse_host and remove it from the docs. --- docs/api.rst | 1 - src/hyperlink/__init__.py | 2 -- 2 files changed, 3 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index d2fd7ed7..e5473f84 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -69,7 +69,6 @@ A couple of notable helpers used by the :class:`~hyperlink.URL` type. .. autoclass:: hyperlink.URLParseError .. autofunction:: hyperlink.register_scheme -.. autofunction:: hyperlink.parse_host .. autofunction:: hyperlink.parse .. TODO: run doctests in docs? diff --git a/src/hyperlink/__init__.py b/src/hyperlink/__init__.py index 349b2199..f680b01a 100644 --- a/src/hyperlink/__init__.py +++ b/src/hyperlink/__init__.py @@ -1,6 +1,5 @@ from ._url import ( parse, - parse_host, register_scheme, URL, EncodedURL, @@ -10,7 +9,6 @@ __all__ = ( "parse", - "parse_host", "register_scheme", "URL", "EncodedURL", From 22ac514d24111079f5238bc0f3e413547033a844 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Thu, 7 Nov 2019 16:42:10 -0800 Subject: [PATCH 086/419] Work around flake8 bug --- src/hyperlink/test/test_socket.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hyperlink/test/test_socket.py b/src/hyperlink/test/test_socket.py index a66e1b05..0af2ed07 100644 --- a/src/hyperlink/test/test_socket.py +++ b/src/hyperlink/test/test_socket.py @@ -3,7 +3,7 @@ try: from socket import inet_pton except ImportError: - inet_pton = None # type: ignore[assignment] + inet_pton = None # type: ignore[assignment] intentional if not inet_pton: import socket From 0bbf49a0bbc7321d4a97eb5bc8913998612ad961 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Thu, 7 Nov 2019 16:43:26 -0800 Subject: [PATCH 087/419] Work around flake8 bug --- src/hyperlink/test/test_socket.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hyperlink/test/test_socket.py b/src/hyperlink/test/test_socket.py index a66e1b05..0af2ed07 100644 --- a/src/hyperlink/test/test_socket.py +++ b/src/hyperlink/test/test_socket.py @@ -3,7 +3,7 @@ try: from socket import inet_pton except ImportError: - inet_pton = None # type: ignore[assignment] + inet_pton = None # type: ignore[assignment] intentional if not inet_pton: import socket From 2eae7f6401d3d05232cff9469d8b578c09aeebff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Thu, 7 Nov 2019 16:43:50 -0800 Subject: [PATCH 088/419] Revert "Work around flake8 bug" This reverts commit 22ac514d24111079f5238bc0f3e413547033a844. --- src/hyperlink/test/test_socket.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hyperlink/test/test_socket.py b/src/hyperlink/test/test_socket.py index 0af2ed07..a66e1b05 100644 --- a/src/hyperlink/test/test_socket.py +++ b/src/hyperlink/test/test_socket.py @@ -3,7 +3,7 @@ try: from socket import inet_pton except ImportError: - inet_pton = None # type: ignore[assignment] intentional + inet_pton = None # type: ignore[assignment] if not inet_pton: import socket From 627e16789e12e3d9692cadfe78cebe1d09ea96a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Thu, 7 Nov 2019 17:00:12 -0800 Subject: [PATCH 089/419] too many lines --- src/hyperlink/test/test_socket.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/hyperlink/test/test_socket.py b/src/hyperlink/test/test_socket.py index 0af2ed07..e900fa4a 100644 --- a/src/hyperlink/test/test_socket.py +++ b/src/hyperlink/test/test_socket.py @@ -11,7 +11,6 @@ from .common import HyperlinkTestCase from .._socket import inet_pton - class TestSocket(HyperlinkTestCase): def test_inet_pton_ipv4_valid(self): # type: () -> None From 769df58e2baeee30f3f631ddfebfc2cd34fdd0b1 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Thu, 7 Nov 2019 17:02:30 -0800 Subject: [PATCH 090/419] [requires.io] dependency update --- tox.ini | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tox.ini b/tox.ini index bb56d322..e63ea143 100644 --- a/tox.ini +++ b/tox.ini @@ -34,7 +34,7 @@ deps = test: idna==2.8 test: typing==3.7.4.1 test: {py26,py27,py34}: pytest==4.6.6 - test: {py35,py36,py37,py38}: pytest==5.2.1 + test: {py35,py36,py37,py38}: pytest==5.2.2 test: pytest-cov==2.8.1 passenv = @@ -80,9 +80,9 @@ deps = #flake8-docstrings==1.4.0 #flake8-import-order==0.18.1 #flake8-pep3101==1.2.1 - flake8==3.7.8 + flake8==3.7.9 mccabe==0.6.1 - pep8-naming==0.8.2 + pep8-naming==0.9.0 pydocstyle==4.0.1 commands = @@ -141,7 +141,7 @@ skip_install = True deps = - mypy==0.730 + mypy==0.740 commands = From 1c0f640d56ec897e9c3e55b9b5d668bcde526411 Mon Sep 17 00:00:00 2001 From: Glyph Date: Sun, 10 Nov 2019 00:54:36 -0800 Subject: [PATCH 091/419] add some tests and a fix --- src/hyperlink/_url.py | 14 +++++++++---- src/hyperlink/test/test_url.py | 36 ++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index 2d4e712a..0b665327 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -761,7 +761,9 @@ class URL(object): query (tuple): The query parameters, as a dictionary or as an iterable of key-value pairs. fragment (unicode): The fragment part of the URL. - rooted (bool): Whether or not the path begins with a slash. + rooted (bool): A rooted URL is one which indicates an absolute path. + This is True on any URL that includes a host, or any relative URL + that starts with a slash. userinfo (unicode): The username or colon-separated username:password pair. uses_netloc (bool): Indicates whether two slashes appear @@ -823,8 +825,12 @@ def __init__(self, scheme=None, host=None, path=(), query=(), fragment=u'', uses_netloc = scheme_uses_netloc(self._scheme, uses_netloc) self._uses_netloc = _typecheck("uses_netloc", uses_netloc, bool, NoneType) - - return + # fixup for rooted consistency + if self._host: + self._rooted = True + if (not self._rooted) and self._path and self._path[0] == '': + self._rooted = True + self._path = self._path[1:] def get_decoded_url(self, lazy=False): try: @@ -985,7 +991,7 @@ def __eq__(self, other): if not isinstance(other, self.__class__): return NotImplemented for attr in ['scheme', 'userinfo', 'host', 'query', - 'fragment', 'port', 'uses_netloc']: + 'fragment', 'port', 'uses_netloc', 'rooted']: if getattr(self, attr) != getattr(other, attr): return False if ( diff --git a/src/hyperlink/test/test_url.py b/src/hyperlink/test/test_url.py index 511515c8..5f10c387 100644 --- a/src/hyperlink/test/test_url.py +++ b/src/hyperlink/test/test_url.py @@ -1081,6 +1081,42 @@ def test_netloc_slashes(self): return + def test_rooted_to_relative(self): + # type: () -> None + """ + On host-relative URLs, the C{rooted} flag can be updated to indicate + that the path should no longer be treated as absolute. + """ + a = URL(path=['hello']) + self.assertEqual(a.to_text(), 'hello') + b = a.replace(rooted=True) + self.assertEqual(b.to_text(), '/hello') + self.assertNotEqual(a, b) + + def test_autorooted(self): + # type: () -> None + """ + The C{rooted} flag can be updated in some cases, but it cannot be made + to conflict with other facts surrounding the URL; for example, all URLs + involving an authority (host) are inherently rooted because it is not + syntactically possible to express otherwise; also, once an unrooted URL + gains a path that starts with an empty string, that empty string is + elided and it becomes rooted, because these cases are syntactically + indistinguisable in real URL text. + """ + relative_path_rooted = URL(path=['', 'foo'], rooted=False) + self.assertEqual(relative_path_rooted.rooted, True) + relative_flag_rooted = URL(path=['foo'], rooted=True) + self.assertEqual(relative_flag_rooted.rooted, True) + self.assertEqual(relative_path_rooted, relative_flag_rooted) + + attempt_unrooted_absolute = URL(host="foo", path=['bar'], rooted=False) + normal_absolute = URL(host="foo", path=["bar"]) + attempted_rooted_replacement = normal_absolute.replace(rooted=True) + self.assertEqual(attempt_unrooted_absolute, normal_absolute) + self.assertEqual(normal_absolute.rooted, True) + self.assertEqual(attempt_unrooted_absolute.rooted, True) + def test_wrong_constructor(self): # type: () -> None with self.assertRaises(ValueError): From 753a1ca65cdd7d2fe8dd90e94d5f1e7d65da3d23 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Thu, 14 Nov 2019 10:01:20 -0800 Subject: [PATCH 092/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index e63ea143..ba59c2f4 100644 --- a/tox.ini +++ b/tox.ini @@ -82,7 +82,7 @@ deps = #flake8-pep3101==1.2.1 flake8==3.7.9 mccabe==0.6.1 - pep8-naming==0.9.0 + pep8-naming==0.9.1 pydocstyle==4.0.1 commands = From f0b88bcbe7dd87af64eea29cf03e96f13b060118 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Thu, 14 Nov 2019 16:11:45 -0800 Subject: [PATCH 093/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index ba59c2f4..3d04dddb 100644 --- a/tox.ini +++ b/tox.ini @@ -34,7 +34,7 @@ deps = test: idna==2.8 test: typing==3.7.4.1 test: {py26,py27,py34}: pytest==4.6.6 - test: {py35,py36,py37,py38}: pytest==5.2.2 + test: {py35,py36,py37,py38}: pytest==5.2.3 test: pytest-cov==2.8.1 passenv = From cd12550dc96b2f8884889e46fe5f7e1760c90c40 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Fri, 15 Nov 2019 15:39:51 -0800 Subject: [PATCH 094/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 3d04dddb..d8098dd7 100644 --- a/tox.ini +++ b/tox.ini @@ -34,7 +34,7 @@ deps = test: idna==2.8 test: typing==3.7.4.1 test: {py26,py27,py34}: pytest==4.6.6 - test: {py35,py36,py37,py38}: pytest==5.2.3 + test: {py35,py36,py37,py38}: pytest==5.2.4 test: pytest-cov==2.8.1 passenv = From 7d33145a8dfa139048c81d727ebdf705c9b39c2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20S=C3=A1nchez=20Vega?= Date: Mon, 18 Nov 2019 12:41:13 -0800 Subject: [PATCH 095/419] =?UTF-8?q?MOAR=20BADGERS,=20er=E2=80=A6=20badges?= =?UTF-8?q?=20(#87)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a slew of new badges for lots of extra flair :) --- README.md | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index bca5d5f0..1d25ce86 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,24 @@ *Cool URLs that don't change.* - - - + + Documentation + + + PyPI + + + Calendar Versioning + + + Python Version Compatibility + + + Code Coverage + + + Requirements Status + Hyperlink provides a pure-Python implementation of immutable URLs. Based on [RFC 3986][rfc3986] and [3987][rfc3987], the Hyperlink URL From 29c3dd9cd72e33db0e2f9e35ca5be3502279ec93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Tue, 19 Nov 2019 10:39:29 -0800 Subject: [PATCH 096/419] pin pyflakes pending a release with https://github.com/PyCQA/pyflakes/pull/455 Clean up some type hints. --- src/hyperlink/_url.py | 32 +++++++++++++------------- src/hyperlink/test/common.py | 2 +- src/hyperlink/test/test_common.py | 2 +- src/hyperlink/test/test_decoded_url.py | 2 +- src/hyperlink/test/test_socket.py | 2 +- src/hyperlink/test/test_url.py | 2 +- tox.ini | 2 ++ 7 files changed, 23 insertions(+), 21 deletions(-) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index a7bb914b..81222374 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -23,7 +23,7 @@ try: from socket import AddressFamily except ImportError: - AddressFamily = int # type: ignore[assignment,misc] Python 2 + AddressFamily = int # type: ignore[assignment,misc] from typing import ( Any, Callable, Dict, Iterable, Iterator, List, Mapping, Optional, Sequence, Text, Tuple, Type, TypeVar, Union, cast, @@ -97,7 +97,7 @@ def __repr__(self): if var_name: # superclass type hints don't allow str return type, but it is # allowed in the docs, hence the ignore[override] below - def __reduce__(self): # type: ignore[override] intentional + def __reduce__(self): # type: ignore[override] # type: () -> str return self.var_name @@ -110,7 +110,7 @@ def __nonzero__(self): return Sentinel() -_unspecified = _UNSET = make_sentinel('_UNSET') +_unspecified = _UNSET = make_sentinel('_UNSET') # type: Any # RFC 3986 Section 2.3, Unreserved URI Characters @@ -466,7 +466,7 @@ def _textcheck(name, value, delims=frozenset(), nullable=False): if not isinstance(value, Text): if nullable and value is None: # used by query string values - return value # type: ignore[misc] unreachable + return value # type: ignore[misc] # unreachable else: str_name = "unicode" if PY2 else "str" exp = str_name + ' or NoneType' if nullable else str_name @@ -474,7 +474,7 @@ def _textcheck(name, value, delims=frozenset(), nullable=False): if delims and set(value) & set(delims): # TODO: test caching into regexes raise ValueError('one or more reserved delimiters %s present in %s: %r' % (''.join(delims), name, value)) - return value # type: ignore[return-value] T vs. Text + return value # type: ignore[return-value] # T vs. Text def iter_pairs(iterable): @@ -1089,7 +1089,7 @@ def absolute(self): """ return bool(self.scheme and self.host) - def replace( # type: ignore[assignment] _UNSET is private + def replace( self, scheme=_UNSET, # type: Optional[Text] host=_UNSET, # type: Optional[Text] @@ -1194,7 +1194,7 @@ def from_text(cls, text): port = au_gs['port'] if port is not None: try: - port = int(port) # type: ignore[assignment] FIXME, also below + port = int(port) # type: ignore[assignment] # FIXME, see below except ValueError: if not port: # TODO: excessive? raise URLParseError('port must not be empty: %r' % au_text) @@ -1227,7 +1227,7 @@ def from_text(cls, text): query = () return cls( scheme, host, path, query, fragment, - port, # type: ignore[arg-type] FIXME, also above + port, # type: ignore[arg-type] # FIXME, see above rooted, userinfo, uses_netloc, ) @@ -1323,7 +1323,7 @@ def child(self, *segments): if not segments: return self - segments = [ # type: ignore[assignment] variable is tuple + segments = [ # type: ignore[assignment] # variable is tuple _textcheck('path segment', s) for s in segments ] new_path = tuple(self.path) @@ -1679,7 +1679,7 @@ def get(self, name): """ return [value for (key, value) in self.query if name == key] - def remove( # type: ignore[assignment] _UNSET is private + def remove( self, name, # type: Text value=_UNSET, # type: Text @@ -1863,7 +1863,7 @@ def path(self): try: return cast( Tuple[Text, ...], - self._path # type: ignore[has-type] can't determine + self._path # type: ignore[has-type] # can't determine ) except AttributeError: pass @@ -1879,7 +1879,7 @@ def query(self): try: return cast( QueryPairs, - self._query # type: ignore[has-type] can't determine + self._query # type: ignore[has-type] # can't determine ) except AttributeError: pass @@ -1899,7 +1899,7 @@ def fragment(self): try: return cast( Text, - self._fragment # type: ignore[has-type] can't determine + self._fragment # type: ignore[has-type] # can't determine ) except AttributeError: pass @@ -1913,7 +1913,7 @@ def userinfo(self): try: return cast( Union[Tuple[str], Tuple[str, str]], - self._userinfo # type: ignore[has-type] can't determine + self._userinfo # type: ignore[has-type] # can't determine ) except AttributeError: pass @@ -1938,7 +1938,7 @@ def uses_netloc(self): # type: () -> bool return cast(bool, self._url.uses_netloc) - def replace( # type: ignore[assignment] _UNSET is private + def replace( self, scheme=_UNSET, # type: Optional[Text] host=_UNSET, # type: Optional[Text] @@ -2007,7 +2007,7 @@ def set(self, name, value=None): q[idx:idx] = [(name, value)] return self.replace(query=q) - def remove( # type: ignore[assignment] _UNSET is private + def remove( self, name, # type: Text value=_UNSET, # type: Text diff --git a/src/hyperlink/test/common.py b/src/hyperlink/test/common.py index c9696fcf..a2961f45 100644 --- a/src/hyperlink/test/common.py +++ b/src/hyperlink/test/common.py @@ -6,7 +6,7 @@ class HyperlinkTestCase(TestCase): """This type mostly exists to provide a backwards-compatible assertRaises method for Python 2.6 testing. """ - def assertRaises( # type: ignore[override] Doesn't match superclass, meh + def assertRaises( # type: ignore[override] self, expected_exception, callableObj=None, *args, **kwargs ): # type: (Type[BaseException], Optional[Callable], Any, Any) -> Any diff --git a/src/hyperlink/test/test_common.py b/src/hyperlink/test/test_common.py index ac8e9b3c..c16e12e2 100644 --- a/src/hyperlink/test/test_common.py +++ b/src/hyperlink/test/test_common.py @@ -88,7 +88,7 @@ def test_assertRaisesContextManager(self): with self.hyperlink_test.assertRaises(_ExpectedException) as cm: raise _ExpectedException - self.assertTrue( # type: ignore[misc] unreachable + self.assertTrue( # type: ignore[misc] # unreachable isinstance(cm.exception, _ExpectedException) ) diff --git a/src/hyperlink/test/test_decoded_url.py b/src/hyperlink/test/test_decoded_url.py index f00052ad..35491072 100644 --- a/src/hyperlink/test/test_decoded_url.py +++ b/src/hyperlink/test/test_decoded_url.py @@ -170,7 +170,7 @@ def test_replace_userinfo(self): durl = DecodedURL.from_text(TOTAL_URL) with self.assertRaises(ValueError): durl.replace( - userinfo=( # type: ignore[arg-type] intentional + userinfo=( # type: ignore[arg-type] 'user', 'pw', 'thiswillcauseafailure' ) ) diff --git a/src/hyperlink/test/test_socket.py b/src/hyperlink/test/test_socket.py index 8ec9a829..5f83d45b 100644 --- a/src/hyperlink/test/test_socket.py +++ b/src/hyperlink/test/test_socket.py @@ -3,7 +3,7 @@ try: from socket import inet_pton except ImportError: - inet_pton = None # type: ignore[assignment] optional + inet_pton = None # type: ignore[assignment] if not inet_pton: import socket diff --git a/src/hyperlink/test/test_url.py b/src/hyperlink/test/test_url.py index 6ee4d172..cbb23912 100644 --- a/src/hyperlink/test/test_url.py +++ b/src/hyperlink/test/test_url.py @@ -871,7 +871,7 @@ def assertRaised(raised, expectation, name): def check(param, expectation=defaultExpectation): # type: (Any, str) -> None with self.assertRaises(TypeError) as raised: - URL(**{param: Unexpected()}) # type: ignore[arg-type] ok + URL(**{param: Unexpected()}) # type: ignore[arg-type] assertRaised(raised, expectation, param) diff --git a/tox.ini b/tox.ini index af9f12c4..d8fe12e2 100644 --- a/tox.ini +++ b/tox.ini @@ -84,6 +84,8 @@ deps = mccabe==0.6.1 pep8-naming==0.9.1 pydocstyle==4.0.1 + # pin pyflakes pending a release with https://github.com/PyCQA/pyflakes/pull/455 + git+git://github.com/PyCQA/pyflakes@ffe9386#egg=pyflakes commands = flake8 {posargs:src/{env:PY_MODULE}} From 13fccdf725ec659a511e8933f4933066d9db94a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Tue, 19 Nov 2019 11:09:23 -0800 Subject: [PATCH 097/419] Reduce calls to cast() --- src/hyperlink/_url.py | 81 +++++++++++++++---------------------------- 1 file changed, 28 insertions(+), 53 deletions(-) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index 81222374..f98b5605 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -1217,9 +1217,8 @@ def from_text(cls, text): if gs['query']: query = tuple( ( - cast(Tuple[str, str], qe.split(u"=", 1)) - if u'=' in qe else - (qe, None) + qe.split(u"=", 1) # type: ignore[misc] + if u'=' in qe else (qe, None) ) for qe in gs['query'].split(u"&") ) # type: QueryPairs @@ -1860,72 +1859,48 @@ def rooted(self): @property def path(self): # type: () -> Sequence[Text] - try: - return cast( - Tuple[Text, ...], - self._path # type: ignore[has-type] # can't determine - ) - except AttributeError: - pass - self._path = tuple([ - _percent_decode(p, raise_subencoding_exc=True) - for p in self._url.path - ]) + if not hasattr(self, "_path"): + self._path = tuple([ + _percent_decode(p, raise_subencoding_exc=True) + for p in self._url.path + ]) return self._path @property def query(self): # type: () -> QueryPairs - try: - return cast( - QueryPairs, - self._query # type: ignore[has-type] # can't determine - ) - except AttributeError: - pass - self._query = cast(QueryPairs, tuple( - tuple( - _percent_decode(x, raise_subencoding_exc=True) - if x is not None else None - for x in (k, v) - ) - for k, v in self._url.query - )) + if not hasattr(self, "_query"): + self._query = cast(QueryPairs, tuple( + tuple( + _percent_decode(x, raise_subencoding_exc=True) + if x is not None else None + for x in (k, v) + ) + for k, v in self._url.query + )) return self._query @property def fragment(self): # type: () -> Text - try: - return cast( - Text, - self._fragment # type: ignore[has-type] # can't determine - ) - except AttributeError: - pass - frag = self._url.fragment - self._fragment = _percent_decode(frag, raise_subencoding_exc=True) + if not hasattr(self, "_fragment"): + frag = self._url.fragment + self._fragment = _percent_decode(frag, raise_subencoding_exc=True) return self._fragment @property def userinfo(self): # type: () -> Union[Tuple[str], Tuple[str, str]] - try: - return cast( + if not hasattr(self, "_userinfo"): + self._userinfo = cast( Union[Tuple[str], Tuple[str, str]], - self._userinfo # type: ignore[has-type] # can't determine - ) - except AttributeError: - pass - self._userinfo = cast( - Union[Tuple[str], Tuple[str, str]], - tuple( tuple( - _percent_decode(p, raise_subencoding_exc=True) - for p in self._url.userinfo.split(':', 1) + tuple( + _percent_decode(p, raise_subencoding_exc=True) + for p in self._url.userinfo.split(':', 1) + ) ) ) - ) return self._userinfo @property @@ -1935,8 +1910,8 @@ def user(self): @property def uses_netloc(self): - # type: () -> bool - return cast(bool, self._url.uses_netloc) + # type: () -> Optional[bool] + return self._url.uses_netloc def replace( self, @@ -1975,7 +1950,7 @@ def replace( ' ["user", "password"], got %r' % (userinfo,)) userinfo_text = u':'.join([_encode_reserved(p) for p in userinfo]) else: - userinfo_text = cast(Text, _UNSET) + userinfo_text = _UNSET new_url = self._url.replace(scheme=scheme, host=host, path=path, From 40f78a6bac99222cec1130d3c614680a75b484b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Tue, 19 Nov 2019 11:35:51 -0800 Subject: [PATCH 098/419] Add docs-auto Tox environment --- tox.ini | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tox.ini b/tox.ini index 648fa5fe..ed5a914a 100644 --- a/tox.ini +++ b/tox.ini @@ -251,6 +251,25 @@ commands = "{toxworkdir}/docs/html" +[testenv:docs-auto] + +description = build documentation + +basepython = python3.8 + +deps = + Sphinx==2.2.1 + sphinx-rtd-theme==0.4.3 + sphinx-autobuild==0.7.1 + +commands = + sphinx-autobuild \ + -b html -d "{envtmpdir}/doctrees" \ + --host=localhost \ + "{toxinidir}/docs" \ + "{toxworkdir}/docs/html" + + ## # Packaging ## From cd7115d8e48fc908d626269eedc5ef669082a4c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Tue, 19 Nov 2019 11:38:31 -0800 Subject: [PATCH 099/419] update decription --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index ed5a914a..4be3fb70 100644 --- a/tox.ini +++ b/tox.ini @@ -253,7 +253,7 @@ commands = [testenv:docs-auto] -description = build documentation +description = build documentation and rebuild automatically basepython = python3.8 From 387af6feb1902a13e368ea6366611d7d0742a084 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Wed, 20 Nov 2019 13:41:46 -0800 Subject: [PATCH 100/419] Add disallow_any_generics to the TODO list --- tox.ini | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/tox.ini b/tox.ini index d8fe12e2..e3f4b31a 100644 --- a/tox.ini +++ b/tox.ini @@ -159,6 +159,7 @@ commands = # Global settings check_untyped_defs = True +disallow_any_generics = True disallow_incomplete_defs = True disallow_untyped_defs = True no_implicit_optional = True @@ -224,6 +225,46 @@ commands = APPVEYOR_REPO_COMMIT_AUTHOR_EMAIL APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED +## +# Documentation +## + +[testenv:docs] + +description = build documentation + +basepython = python3.8 + +deps = + Sphinx==2.2.1 + sphinx-rtd-theme==0.4.3 + +commands = + sphinx-build \ + -b html -d "{envtmpdir}/doctrees" \ + "{toxinidir}/docs" \ + "{toxworkdir}/docs/html" + + +[testenv:docs-auto] + +description = build documentation and rebuild automatically + +basepython = python3.8 + +deps = + Sphinx==2.2.1 + sphinx-rtd-theme==0.4.3 + sphinx-autobuild==0.7.1 + +commands = + sphinx-autobuild \ + -b html -d "{envtmpdir}/doctrees" \ + --host=localhost \ + "{toxinidir}/docs" \ + "{toxworkdir}/docs/html" + + ## # Packaging ## From 02522e955bf4658eeced6e3760a6b163fa2717b8 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Fri, 29 Nov 2019 11:05:11 -0800 Subject: [PATCH 101/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 4be3fb70..aafc6ba7 100644 --- a/tox.ini +++ b/tox.ini @@ -142,7 +142,7 @@ skip_install = True deps = - mypy==0.740 + mypy==0.750 commands = From a6dfaa0fdcf11e712874aea148dec870e96daa53 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Mon, 2 Dec 2019 08:40:48 -0800 Subject: [PATCH 102/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index aafc6ba7..fabfd466 100644 --- a/tox.ini +++ b/tox.ini @@ -241,7 +241,7 @@ description = build documentation basepython = python3.8 deps = - Sphinx==2.2.1 + Sphinx==2.2.2 sphinx-rtd-theme==0.4.3 commands = From 4721020b96225f098030213171fc5097e61694cf Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Mon, 2 Dec 2019 08:43:39 -0800 Subject: [PATCH 103/419] [requires.io] dependency update From 53cd8a73f9424e4ad96c403cd4eda487935e5759 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Mon, 2 Dec 2019 11:34:45 -0600 Subject: [PATCH 104/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index fabfd466..f45d67f0 100644 --- a/tox.ini +++ b/tox.ini @@ -258,7 +258,7 @@ description = build documentation and rebuild automatically basepython = python3.8 deps = - Sphinx==2.2.1 + Sphinx==2.2.2 sphinx-rtd-theme==0.4.3 sphinx-autobuild==0.7.1 From 41d9efd59fe9a8327c67505b30865a7f92e32a22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Mon, 2 Dec 2019 10:01:09 -0800 Subject: [PATCH 105/419] Filter less generated data --- src/hyperlink/strategies.py | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/src/hyperlink/strategies.py b/src/hyperlink/strategies.py index 1e7dce07..7a7e62ef 100644 --- a/src/hyperlink/strategies.py +++ b/src/hyperlink/strategies.py @@ -15,7 +15,7 @@ from string import ascii_letters, digits from sys import maxunicode from typing import ( - Callable, Iterable, Optional, Sequence, Text, TypeVar, cast + Callable, Iterable, List, Optional, Sequence, Text, TypeVar, cast ) from . import DecodedURL, EncodedURL @@ -199,20 +199,30 @@ def hostnames(draw, allow_leading_digit=True, allow_idn=True): @param allow_idn: Whether to allow non-ASCII characters as allowed by internationalized domain names (IDNs). """ - labels = cast( - Sequence[Text], - draw( - lists( - hostname_labels(allow_idn=allow_idn), - min_size=1, max_size=5, - ).filter( - lambda ls: sum(len(l) for l in ls) + len(ls) - 1 <= 252 - ) + # Draw first label, filtering out labels with leading digits if needed + labels = [ + cast( + Text, + draw(hostname_labels(allow_idn=allow_idn).filter( + lambda l: ( + True if allow_leading_digit else l[0] not in digits + ) + )) ) + ] + # Draw remaining labels + labels += cast( + List[Text], + draw(lists( + hostname_labels(allow_idn=allow_idn), + min_size=1, max_size=4, + )) ) - if not allow_leading_digit: - assume(labels[0][0] not in digits) + # Trim off labels until the total host name length fits in 252 + # characters. This avoids having to filter the data. + while sum(len(label) for label in labels) + len(labels) - 1 > 252: + labels = labels[:-1] return u".".join(labels) @@ -251,7 +261,7 @@ def chars(): def paths(draw): # type: (DrawCallable) -> Sequence[Text] return cast( - Sequence[Text], + List[Text], draw( lists( text(min_size=1, alphabet=path_characters()), max_size=10 From 9930ced48f358c4bc626008835234c4fac270730 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Mon, 2 Dec 2019 10:05:45 -0800 Subject: [PATCH 106/419] =?UTF-8?q?Rename=20hyperlink.strategies=20?= =?UTF-8?q?=E2=9E=9C=20hyperlink.hypothesis.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hyperlink/{strategies.py => hypothesis.py} | 0 src/hyperlink/test/test_strategies.py | 8 ++++---- 2 files changed, 4 insertions(+), 4 deletions(-) rename src/hyperlink/{strategies.py => hypothesis.py} (100%) diff --git a/src/hyperlink/strategies.py b/src/hyperlink/hypothesis.py similarity index 100% rename from src/hyperlink/strategies.py rename to src/hyperlink/hypothesis.py diff --git a/src/hyperlink/test/test_strategies.py b/src/hyperlink/test/test_strategies.py index cf67bc1b..502eb602 100644 --- a/src/hyperlink/test/test_strategies.py +++ b/src/hyperlink/test/test_strategies.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- """ -Tests for hyperlink.strategies. +Tests for hyperlink.hypothesis. """ try: @@ -23,14 +23,14 @@ from .common import HyperlinkTestCase from .. import DecodedURL, EncodedURL - from ..strategies import ( + from ..hypothesis import ( DrawCallable, composite, decoded_urls, encoded_urls, hostname_labels, hostnames, idna_text, paths, port_numbers, ) class TestHyperlink(HyperlinkTestCase): """ - Tests for hyperlink.strategies. + Tests for hyperlink.hypothesis. """ @given(idna_text()) @@ -106,7 +106,7 @@ def mock_idna_text(draw, min_size, max_size): # will be max_size * 3 in size when encoded. return u"\N{LATIN SMALL LETTER A WITH ACUTE}" * max_size - with patch("hyperlink.strategies.idna_text", mock_idna_text): + with patch("hyperlink.hypothesis.idna_text", mock_idna_text): label = data.draw(hostname_labels()) try: check_label(label) From 17ee994a67ab2d6b66e76177fc1a2fe95ffb71da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Mon, 2 Dec 2019 10:21:56 -0800 Subject: [PATCH 107/419] Fix type syntax --- src/hyperlink/test/common.py | 2 +- src/hyperlink/test/test_common.py | 2 +- src/hyperlink/test/test_socket.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hyperlink/test/common.py b/src/hyperlink/test/common.py index c9696fcf..a2961f45 100644 --- a/src/hyperlink/test/common.py +++ b/src/hyperlink/test/common.py @@ -6,7 +6,7 @@ class HyperlinkTestCase(TestCase): """This type mostly exists to provide a backwards-compatible assertRaises method for Python 2.6 testing. """ - def assertRaises( # type: ignore[override] Doesn't match superclass, meh + def assertRaises( # type: ignore[override] self, expected_exception, callableObj=None, *args, **kwargs ): # type: (Type[BaseException], Optional[Callable], Any, Any) -> Any diff --git a/src/hyperlink/test/test_common.py b/src/hyperlink/test/test_common.py index ac8e9b3c..c16e12e2 100644 --- a/src/hyperlink/test/test_common.py +++ b/src/hyperlink/test/test_common.py @@ -88,7 +88,7 @@ def test_assertRaisesContextManager(self): with self.hyperlink_test.assertRaises(_ExpectedException) as cm: raise _ExpectedException - self.assertTrue( # type: ignore[misc] unreachable + self.assertTrue( # type: ignore[misc] # unreachable isinstance(cm.exception, _ExpectedException) ) diff --git a/src/hyperlink/test/test_socket.py b/src/hyperlink/test/test_socket.py index e900fa4a..5f83d45b 100644 --- a/src/hyperlink/test/test_socket.py +++ b/src/hyperlink/test/test_socket.py @@ -3,7 +3,7 @@ try: from socket import inet_pton except ImportError: - inet_pton = None # type: ignore[assignment] intentional + inet_pton = None # type: ignore[assignment] if not inet_pton: import socket From d6c070fde9b76517bb5655f161fe99d03867f77b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Mon, 2 Dec 2019 10:23:03 -0800 Subject: [PATCH 108/419] Update tox config --- tox.ini | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/tox.ini b/tox.ini index d8098dd7..fb32edee 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,7 @@ [tox] envlist = - flake8 + flake8, mypy test-py{26,27,34,35,36,37,38,py,py3} coverage_report packaging @@ -54,6 +54,8 @@ passenv = setenv = PY_MODULE=hyperlink + test: PYTHONPYCACHEPREFIX={envtmpdir}/pycache + test: COVERAGE_FILE={toxworkdir}/coverage.{envname} {coverage_report,codecov}: COVERAGE_FILE={toxworkdir}/coverage codecov: COVERAGE_XML={envlogdir}/coverage_report.xml @@ -74,16 +76,15 @@ basepython = python3.8 skip_install = True -# Pin pydocstyle to version 3: see https://gitlab.com/pycqa/flake8-docstrings/issues/36 deps = flake8-bugbear==19.8.0 - #flake8-docstrings==1.4.0 - #flake8-import-order==0.18.1 - #flake8-pep3101==1.2.1 + #flake8-docstrings==1.5.0 flake8==3.7.9 mccabe==0.6.1 pep8-naming==0.9.1 pydocstyle==4.0.1 + # pin pyflakes pending a release with https://github.com/PyCQA/pyflakes/pull/455 + git+git://github.com/PyCQA/pyflakes@ffe9386#egg=pyflakes commands = flake8 {posargs:src/{env:PY_MODULE}} @@ -139,11 +140,9 @@ basepython = python3.8 skip_install = True - deps = mypy==0.740 - commands = mypy \ --config-file="{toxinidir}/tox.ini" \ @@ -171,7 +170,7 @@ warn_unused_ignores = True # Enable these over time check_untyped_defs = False -# Disable some checks until effected files fully adopt mypy +# Don't complain about dependencies known to lack type hints [mypy-hyperlink._url] allow_untyped_defs = True @@ -197,8 +196,8 @@ deps = commands = coverage combine - coverage report - coverage html + - coverage report + - coverage html ## @@ -218,13 +217,17 @@ deps = codecov==2.0.15 commands = + # Note documentation for CI variables in default environment's passenv + coverage combine coverage xml -o "{env:COVERAGE_XML}" - codecov --file="{env:COVERAGE_XML}" --env \ - GITHUB_REF GITHUB_COMMIT GITHUB_USER GITHUB_WORKFLOW \ - TRAVIS_BRANCH TRAVIS_BUILD_WEB_URL TRAVIS_COMMIT TRAVIS_COMMIT_MESSAGE \ - APPVEYOR_REPO_BRANCH APPVEYOR_REPO_COMMIT \ - APPVEYOR_REPO_COMMIT_AUTHOR_EMAIL APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED + codecov --file="{env:COVERAGE_XML}" --env \ + GITHUB_REF GITHUB_COMMIT GITHUB_USER GITHUB_WORKFLOW \ + TRAVIS_BRANCH TRAVIS_BUILD_WEB_URL \ + TRAVIS_COMMIT TRAVIS_COMMIT_MESSAGE \ + APPVEYOR_REPO_BRANCH APPVEYOR_REPO_COMMIT \ + APPVEYOR_REPO_COMMIT_AUTHOR_EMAIL \ + APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED ## @@ -240,7 +243,9 @@ basepython = python deps = check-manifest==0.40 readme_renderer==24.0 + twine==2.0.0 commands = check-manifest - python setup.py check --metadata --restructuredtext --strict + pip wheel --wheel-dir "{envtmpdir}/dist" --no-deps {toxinidir} + twine check "{envtmpdir}/dist/"* From d7f2e738e0559e338a12d3f2d5e4fbd5ad0828ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Mon, 2 Dec 2019 12:31:38 -0800 Subject: [PATCH 109/419] Rename tests to match tested module --- src/hyperlink/test/{test_strategies.py => test_hypothesis.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/hyperlink/test/{test_strategies.py => test_hypothesis.py} (99%) diff --git a/src/hyperlink/test/test_strategies.py b/src/hyperlink/test/test_hypothesis.py similarity index 99% rename from src/hyperlink/test/test_strategies.py rename to src/hyperlink/test/test_hypothesis.py index 502eb602..5039ff91 100644 --- a/src/hyperlink/test/test_strategies.py +++ b/src/hyperlink/test/test_hypothesis.py @@ -28,7 +28,7 @@ hostname_labels, hostnames, idna_text, paths, port_numbers, ) - class TestHyperlink(HyperlinkTestCase): + class TestHypothesisStrategies(HyperlinkTestCase): """ Tests for hyperlink.hypothesis. """ From 5ea07e25e8522de2fd3855abc3529d591beba613 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Mon, 2 Dec 2019 12:34:54 -0800 Subject: [PATCH 110/419] Don't fail health check.filter_too_much in CI --- src/hyperlink/test/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/hyperlink/test/__init__.py b/src/hyperlink/test/__init__.py index 0a488d85..2a651514 100644 --- a/src/hyperlink/test/__init__.py +++ b/src/hyperlink/test/__init__.py @@ -17,7 +17,11 @@ def _init_hypothesis(): return settings.register_profile( - "patience", settings(suppress_health_check=[HealthCheck.too_slow]) + "patience", settings( + suppress_health_check=[ + HealthCheck.too_slow, HealthCheck.filter_too_much + ] + ) ) settings.load_profile("patience") From c1d0de27ed770f761e1606c22559e5ce57156f92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Mon, 2 Dec 2019 12:40:15 -0800 Subject: [PATCH 111/419] Remove unused variable --- src/hyperlink/test/test_url.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/hyperlink/test/test_url.py b/src/hyperlink/test/test_url.py index 5f10c387..77f459a9 100644 --- a/src/hyperlink/test/test_url.py +++ b/src/hyperlink/test/test_url.py @@ -1112,7 +1112,6 @@ def test_autorooted(self): attempt_unrooted_absolute = URL(host="foo", path=['bar'], rooted=False) normal_absolute = URL(host="foo", path=["bar"]) - attempted_rooted_replacement = normal_absolute.replace(rooted=True) self.assertEqual(attempt_unrooted_absolute, normal_absolute) self.assertEqual(normal_absolute.rooted, True) self.assertEqual(attempt_unrooted_absolute.rooted, True) From e66e3fdf92806a8a2280b22c33352da821c9f459 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Mon, 2 Dec 2019 12:43:05 -0800 Subject: [PATCH 112/419] Need absolute_import now. --- src/hyperlink/hypothesis.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/hyperlink/hypothesis.py b/src/hyperlink/hypothesis.py index 7a7e62ef..a6ae7c26 100644 --- a/src/hyperlink/hypothesis.py +++ b/src/hyperlink/hypothesis.py @@ -2,6 +2,7 @@ """ Hypothesis strategies. """ +from __future__ import absolute_import try: import hypothesis From b1b7b80366a7d62deed73031c1b539471a98dd1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Mon, 2 Dec 2019 12:44:42 -0800 Subject: [PATCH 113/419] Revert "Remove unused variable" This reverts commit c1d0de27ed770f761e1606c22559e5ce57156f92. --- src/hyperlink/test/test_url.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/hyperlink/test/test_url.py b/src/hyperlink/test/test_url.py index 77f459a9..5f10c387 100644 --- a/src/hyperlink/test/test_url.py +++ b/src/hyperlink/test/test_url.py @@ -1112,6 +1112,7 @@ def test_autorooted(self): attempt_unrooted_absolute = URL(host="foo", path=['bar'], rooted=False) normal_absolute = URL(host="foo", path=["bar"]) + attempted_rooted_replacement = normal_absolute.replace(rooted=True) self.assertEqual(attempt_unrooted_absolute, normal_absolute) self.assertEqual(normal_absolute.rooted, True) self.assertEqual(attempt_unrooted_absolute.rooted, True) From 11ea3ff5a00f1ccd525c12469bdadabcd0f0bba2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Mon, 2 Dec 2019 12:49:09 -0800 Subject: [PATCH 114/419] Fix packaging --- MANIFEST.in | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index c4b6e32f..2fac6ee9 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,8 @@ -include README.md LICENSE CHANGELOG.md tox.ini requirements-test.txt .coveragerc Makefile pytest.ini .tox-coveragerc +include README.md LICENSE CHANGELOG.md +include tox.ini pytest.ini .coveragerc exclude TODO.md appveyor.yml +include src/hyperlink/idna-tables-properties.csv + graft docs prune docs/_build From 8315e3009ebc2b10ed3f53dbc9f26bae0cd5d04e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Mon, 2 Dec 2019 13:03:54 -0800 Subject: [PATCH 115/419] Remove unused variable for now --- src/hyperlink/test/test_url.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/hyperlink/test/test_url.py b/src/hyperlink/test/test_url.py index 5f10c387..77f459a9 100644 --- a/src/hyperlink/test/test_url.py +++ b/src/hyperlink/test/test_url.py @@ -1112,7 +1112,6 @@ def test_autorooted(self): attempt_unrooted_absolute = URL(host="foo", path=['bar'], rooted=False) normal_absolute = URL(host="foo", path=["bar"]) - attempted_rooted_replacement = normal_absolute.replace(rooted=True) self.assertEqual(attempt_unrooted_absolute, normal_absolute) self.assertEqual(normal_absolute.rooted, True) self.assertEqual(attempt_unrooted_absolute.rooted, True) From 39bf87a2183f0860a2c318e2e114bbb1e6bbd9c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Mon, 2 Dec 2019 13:40:19 -0800 Subject: [PATCH 116/419] Remove duplicate section --- tox.ini | 40 ---------------------------------------- 1 file changed, 40 deletions(-) diff --git a/tox.ini b/tox.ini index 2fa2184c..5e258436 100644 --- a/tox.ini +++ b/tox.ini @@ -268,46 +268,6 @@ commands = "{toxworkdir}/docs/html" -## -# Documentation -## - -[testenv:docs] - -description = build documentation - -basepython = python3.8 - -deps = - Sphinx==2.2.1 - sphinx-rtd-theme==0.4.3 - -commands = - sphinx-build \ - -b html -d "{envtmpdir}/doctrees" \ - "{toxinidir}/docs" \ - "{toxworkdir}/docs/html" - - -[testenv:docs-auto] - -description = build documentation and rebuild automatically - -basepython = python3.8 - -deps = - Sphinx==2.2.1 - sphinx-rtd-theme==0.4.3 - sphinx-autobuild==0.7.1 - -commands = - sphinx-autobuild \ - -b html -d "{envtmpdir}/doctrees" \ - --host=localhost \ - "{toxinidir}/docs" \ - "{toxworkdir}/docs/html" - - ## # Packaging ## From 7e5d944a2a13566af85acaf5add6c93046997821 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Mon, 2 Dec 2019 13:57:54 -0800 Subject: [PATCH 117/419] Add types back to doc strings --- src/hyperlink/_url.py | 123 ++++++++++++++++++++++-------------------- 1 file changed, 66 insertions(+), 57 deletions(-) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index cfa8b126..0d8896dd 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -369,12 +369,13 @@ def register_scheme(text, uses_netloc=True, default_port=None): `file an issue`_! Args: - text: Text representing the scheme. + text (Text): A string representation of the scheme. (the 'http' in 'http://hatnote.com') - uses_netloc: Does the scheme support specifying a network host? + uses_netloc (bool): Does the scheme support specifying a network host? For instance, "http" does, "mailto" does not. Defaults to True. - default_port: The default port, if any, for netloc-using schemes. + default_port (Optional[int]): The default port, if any, for + netloc-using schemes. .. _file an issue: https://github.com/mahmoud/hyperlink/issues """ @@ -797,24 +798,24 @@ class URL(object): constructor arguments is below. Args: - scheme: The text name of the scheme. - host: The host portion of the network location - port: The port part of the network location. If + scheme (Optional[Text]): The text name of the scheme. + host (Optional[Text]): The host portion of the network location + port (Optional[int]): The port part of the network location. If ``None`` or no port is passed, the port will default to the default port of the scheme, if it is known. See the ``SCHEME_PORT_MAP`` and :func:`register_default_port` for more info. - path: A tuple of strings representing the + path (Iterable[Text]): A tuple of strings representing the slash-separated parts of the path. - query: The query parameters, as a dictionary or - as an iterable of key-value pairs. - fragment: The fragment part of the URL. - rooted: A rooted URL is one which indicates an absolute path. + query (Sequence[Tuple[Text, Optional[Text]]]): The query parameters, as + a dictionary or as an iterable of key-value pairs. + fragment (Text): The fragment part of the URL. + rooted (bool): A rooted URL is one which indicates an absolute path. This is True on any URL that includes a host, or any relative URL that starts with a slash. - userinfo: The username or colon-separated + userinfo (Text): The username or colon-separated username:password pair. - uses_netloc: Indicates whether two slashes appear + uses_netloc (bool): Indicates whether two slashes appear between the scheme and the host (``http://eg.com`` vs ``mailto:e@g.com``). Set automatically based on scheme. @@ -1027,12 +1028,13 @@ def authority(self, with_password=False, **kw): u'user:pass@localhost:8080' Args: - with_password: Whether the return value of this method include the - password in the URL, if it is set. Defaults to False. + with_password (bool): Whether the return value of this method + include the password in the URL, if it is set. + Defaults to False. Returns: - The authority (network location and user information) portion of the - URL. + The authority (network location and user information) portion of + the URL. """ # first, a bit of twisted compat with_password = kw.pop('includeSecrets', with_password) @@ -1119,18 +1121,22 @@ def replace( the value on the current URL. Args: - scheme: The text name of the scheme. - host: The host portion of the network location - path: A tuple of strings representing the slash-separated parts of - the path. - query: The query parameters, as a dictionary or as an iterable of - key-value pairs. - fragment: The fragment part of the URL. - port: The port part of the network location. - rooted: Whether or not the path begins with a slash. - userinfo: The username or colon-separated username:password pair. - uses_netloc: Indicates whether two slashes appear between the - scheme and the host (``http://eg.com`` vs ``mailto:e@g.com``) + scheme (Optional[Text]): The text name of the scheme. + host (Optional[Text]): The host portion of the network location. + path (Iterable[Text]): A tuple of strings representing the + slash-separated parts of the path. + query (Sequence[Tuple[Text, Optional[Text]]]): The query + parameters, as a dictionary or as an iterable of key-value + pairs. + fragment (Text): The fragment part of the URL. + port (Optional[int]): The port part of the network location. + rooted (Optional[bool]): Whether or not the path begins with a + slash. + userinfo (Text): The username or colon-separated username:password + pair. + uses_netloc (bool): Indicates whether two slashes appear between + the scheme and the host + (``http://eg.com`` vs ``mailto:e@g.com``) Returns: A copy of the current :class:`URL`, with new values for @@ -1166,7 +1172,7 @@ def from_text(cls, text): sure to decode those bytestrings. Args: - text: A valid URL string. + text (Text): A valid URL string. Returns: The structured object version of the parsed string. @@ -1257,14 +1263,15 @@ def normalize(self, scheme=True, host=True, path=True, query=True, name. Args: - scheme: Convert the scheme to lowercase - host: Convert the host to lowercase - path: Normalize the path (see above for details) - query: Normalize the query string - fragment: Normalize the fragment - userinfo: Normalize the userinfo - percents: Encode isolated percent signs for any percent-encoded - fields which are being normalized (defaults to True). + scheme (bool): Convert the scheme to lowercase + host (bool): Convert the host to lowercase + path (bool): Normalize the path (see above for details) + query (bool): Normalize the query string + fragment (bool): Normalize the fragment + userinfo (bool): Normalize the userinfo + percents (bool): Encode isolated percent signs for any + percent-encoded fields which are being normalized + (defaults to True). >>> url = URL.from_text(u'Http://example.COM/a/../b/./c%2f?%61%') >>> print(url.normalize().to_text()) @@ -1320,7 +1327,7 @@ def child(self, *segments): u'http://localhost/a/b/c/d?x=y' Args: - segments: Additional parts to be joined and added to + segments (Text): Additional parts to be joined and added to the path, like :func:`os.path.join`. Special characters in segments will be percent encoded. @@ -1345,7 +1352,7 @@ def sibling(self, segment): sibling of this URL path. Args: - segment: A single path segment. + segment (Text): A single path segment. Returns: A copy of the current URL with the last path segment @@ -1370,7 +1377,7 @@ def click(self, href=u''): >>> url.click(u'../d/./e').to_text() u'http://localhost/a/b/d/e' - Args: + Args (Text): href: A string representing a clicked URL. Return: @@ -1510,7 +1517,7 @@ def to_text(self, with_password=False): unless the data after the colon is the empty string (indicating no password)." - Args: + Args (bool): with_password: Whether or not to include the password in the URL text. Defaults to False. @@ -1626,9 +1633,9 @@ def add(self, name, value=None): URL.from_text(u'https://example.com/?x=y&x=z') Args: - name: The name of the query parameter to add. + name (Text): The name of the query parameter to add. The part before the ``=``. - value: The value of the query parameter to add. + value (Optional[Text]): The value of the query parameter to add. The part after the ``=``. Defaults to ``None``, meaning no value. @@ -1649,9 +1656,9 @@ def set(self, name, value=None): URL.from_text(u'https://example.com/?x=z') Args: - name: The name of the query parameter to set. + name (Text): The name of the query parameter to set. The part before the ``=``. - value: The value of the query parameter to set. + value (Optional[Text]): The value of the query parameter to set. The part after the ``=``. Defaults to ``None``, meaning no value. @@ -1679,7 +1686,7 @@ def get(self, name): list is always returned, and this method raises no exceptions. Args: - name: The name of the query parameter to get. + name (Text): The name of the query parameter to get. Returns: A list of all the values associated with the key, in string form. @@ -1699,11 +1706,12 @@ def remove( parameter is not already set. Args: - name: The name of the query parameter to remove. - value: Optional value to additionally filter on. + name (Text): The name of the query parameter to remove. + value (Text): Optional value to additionally filter on. Setting this removes query parameters which match both name and value. - limit: Optional maximum number of parameters to remove. + limit (Optional[int]): Optional maximum number of parameters to + remove. Returns: URL: A new :class:`URL` instance with the parameter removed. @@ -1750,9 +1758,9 @@ class DecodedURL(object): special characters encoded with codecs other than UTF-8. Args: - url: A :class:`URL` object to wrap. - lazy: Set to True to avoid pre-decode all parts of the URL to check for - validity. Defaults to False. + url (URL): A :class:`URL` object to wrap. + lazy (bool): Set to True to avoid pre-decode all parts of the URL to + check for validity. Defaults to False. """ def __init__(self, url, lazy=False): @@ -1771,8 +1779,8 @@ def from_text(cls, text, lazy=False): Make a `DecodedURL` instance from any text string containing a URL. Args: - text: Text containing the URL - lazy: Whether to pre-decode all parts of the URL to check for + text (Text): Text containing the URL + lazy (bool): Whether to pre-decode all parts of the URL to check for validity. Defaults to True. """ _url = URL.from_text(text) @@ -2084,15 +2092,16 @@ def parse(url, decoded=True, lazy=False): """Automatically turn text into a structured URL object. Args: + url (Text): A string representation of a URL. - decoded: Whether or not to return a :class:`DecodedURL`, + decoded (bool): Whether or not to return a :class:`DecodedURL`, which automatically handles all encoding/decoding/quoting/unquoting for all the various accessors of parts of the URL, or an :class:`EncodedURL`, which has the same API, but requires handling of special characters for different parts of the URL. - lazy: In the case of `decoded=True`, this controls + lazy (bool): In the case of `decoded=True`, this controls whether the URL is decoded immediately or as accessed. The default, `lazy=False`, checks all encoded parts of the URL for decodability. From 9661999935acef38e67c90cc97eaf0f6bbd43f64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Mon, 2 Dec 2019 14:14:00 -0800 Subject: [PATCH 118/419] Add return ntypes to doc strings --- src/hyperlink/_url.py | 47 ++++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index 0d8896dd..15f6cc9d 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -371,9 +371,9 @@ def register_scheme(text, uses_netloc=True, default_port=None): Args: text (Text): A string representation of the scheme. (the 'http' in 'http://hatnote.com') - uses_netloc (bool): Does the scheme support specifying a network host? - For instance, "http" does, "mailto" does not. - Defaults to True. + uses_netloc (bool): Does the scheme support specifying a + network host? For instance, "http" does, "mailto" does + not. Defaults to True. default_port (Optional[int]): The default port, if any, for netloc-using schemes. @@ -587,7 +587,7 @@ def _percent_decode( underlying the percent-decoding should be raised. Returns: - The percent-decoded version of *text*, decoded by *subencoding*. + Text: The percent-decoded version of *text*, decoded by *subencoding*. """ try: quoted_bytes = text.encode(subencoding) @@ -736,7 +736,7 @@ def parse_host(host): Will raise :class:`URLParseError` on invalid IPv6 constants. Returns: - tuple: family (socket constant or None), host (string) + family (socket constant or None), host (string) >>> import socket >>> parse_host('googlewebsite.com') == (None, 'googlewebsite.com') @@ -808,7 +808,7 @@ class URL(object): path (Iterable[Text]): A tuple of strings representing the slash-separated parts of the path. query (Sequence[Tuple[Text, Optional[Text]]]): The query parameters, as - a dictionary or as an iterable of key-value pairs. + a dictionary or as an sequence of key-value pairs. fragment (Text): The fragment part of the URL. rooted (bool): A rooted URL is one which indicates an absolute path. This is True on any URL that includes a host, or any relative URL @@ -1028,13 +1028,13 @@ def authority(self, with_password=False, **kw): u'user:pass@localhost:8080' Args: - with_password (bool): Whether the return value of this method - include the password in the URL, if it is set. - Defaults to False. + with_password (bool): Whether the return value of this + method include the password in the URL, if it is + set. Defaults to False. Returns: - The authority (network location and user information) portion of - the URL. + Text: The authority (network location and user information) portion + of the URL. """ # first, a bit of twisted compat with_password = kw.pop('includeSecrets', with_password) @@ -1126,7 +1126,7 @@ def replace( path (Iterable[Text]): A tuple of strings representing the slash-separated parts of the path. query (Sequence[Tuple[Text, Optional[Text]]]): The query - parameters, as a dictionary or as an iterable of key-value + parameters, as a dictionary or as an sequence of key-value pairs. fragment (Text): The fragment part of the URL. port (Optional[int]): The port part of the network location. @@ -1139,7 +1139,7 @@ def replace( (``http://eg.com`` vs ``mailto:e@g.com``) Returns: - A copy of the current :class:`URL`, with new values for + URL: A copy of the current :class:`URL`, with new values for parameters passed. """ return self.__class__( @@ -1175,7 +1175,7 @@ def from_text(cls, text): text (Text): A valid URL string. Returns: - The structured object version of the parsed string. + URL: The structured object version of the parsed string. .. note:: @@ -1332,7 +1332,7 @@ def child(self, *segments): in segments will be percent encoded. Returns: - A copy of the current URL with the extra path segments. + URL: A copy of the current URL with the extra path segments. """ if not segments: return self @@ -1355,7 +1355,7 @@ def sibling(self, segment): segment (Text): A single path segment. Returns: - A copy of the current URL with the last path segment + URL: A copy of the current URL with the last path segment replaced by *segment*. Special characters such as ``/?#`` will be percent encoded. """ @@ -1437,8 +1437,8 @@ def to_uri(self): Returns: URL: A new instance with its path segments, query parameters, and - hostname encoded, so that they are all in the standard - US-ASCII range. + hostname encoded, so that they are all in the standard + US-ASCII range. """ new_userinfo = u':'.join([_encode_userinfo_part(p) for p in self.userinfo.split(':', 1)]) @@ -1482,7 +1482,7 @@ def to_iri(self): Returns: URL: A new instance with its path segments, query parameters, and - hostname decoded for display purposes. + hostname decoded for display purposes. """ # noqa: E501 new_userinfo = u':'.join([ _decode_userinfo_part(p) for p in self.userinfo.split(':', 1) @@ -1522,7 +1522,7 @@ def to_text(self, with_password=False): text. Defaults to False. Returns: - The serialized textual representation of this URL, such as + Text: The serialized textual representation of this URL, such as ``u"http://example.com/some/path?some=query"``. The natural counterpart to :class:`URL.from_text()`. @@ -1640,7 +1640,7 @@ def add(self, name, value=None): value. Returns: - A new :class:`URL` instance with the parameter added. + URL: A new :class:`URL` instance with the parameter added. """ return self.replace(query=self.query + ((name, value),)) @@ -1663,7 +1663,7 @@ def set(self, name, value=None): value. Returns: - A new :class:`URL` instance with the parameter set. + URL: A new :class:`URL` instance with the parameter set. """ # Preserve the original position of the query key in the list q = [(k, v) for (k, v) in self.query if k != name] @@ -1689,7 +1689,8 @@ def get(self, name): name (Text): The name of the query parameter to get. Returns: - A list of all the values associated with the key, in string form. + List[Optional[Text]]: A list of all the values associated with the + key, in string form. """ return [value for (key, value) in self.query if name == key] From c6b26a5f6a0d3e1f7746fa90669ba0b7b38c6138 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Thu, 5 Dec 2019 19:16:09 -0800 Subject: [PATCH 119/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index f45d67f0..d558dbf0 100644 --- a/tox.ini +++ b/tox.ini @@ -34,7 +34,7 @@ deps = test: coverage==4.5.4 test: idna==2.8 test: typing==3.7.4.1 - test: {py26,py27,py34}: pytest==4.6.6 + test: {py26,py27,py34}: pytest==4.6.7 test: {py35,py36,py37,py38}: pytest==5.2.4 test: pytest-cov==2.8.1 From 70b0d67a7c8d4ffa19c4f4d0917e4f1da0b22b22 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Thu, 5 Dec 2019 19:43:47 -0800 Subject: [PATCH 120/419] [requires.io] dependency update From af8e5ef8ac5be22b71c22d1c3fc6054d24ed7c4b Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Mon, 9 Dec 2019 00:54:49 -0800 Subject: [PATCH 121/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index d558dbf0..c30ce557 100644 --- a/tox.ini +++ b/tox.ini @@ -84,7 +84,7 @@ deps = flake8==3.7.9 mccabe==0.6.1 pep8-naming==0.9.1 - pydocstyle==4.0.1 + pydocstyle==5.0.0 commands = flake8 {posargs:src/{env:PY_MODULE}} From 4e5cab4fc76443e9b568b9f08437fac359c34ed0 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Mon, 9 Dec 2019 12:08:31 -0800 Subject: [PATCH 122/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index c30ce557..d9bab94c 100644 --- a/tox.ini +++ b/tox.ini @@ -84,7 +84,7 @@ deps = flake8==3.7.9 mccabe==0.6.1 pep8-naming==0.9.1 - pydocstyle==5.0.0 + pydocstyle==5.0.1 commands = flake8 {posargs:src/{env:PY_MODULE}} From 1c60d3d5114cbac26187c2d83c8f928e17dd0f5f Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sat, 14 Dec 2019 08:38:27 -0800 Subject: [PATCH 123/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index d9bab94c..f6cec912 100644 --- a/tox.ini +++ b/tox.ini @@ -31,7 +31,7 @@ basepython = pypy3: pypy3 deps = - test: coverage==4.5.4 + test: coverage==5.0 test: idna==2.8 test: typing==3.7.4.1 test: {py26,py27,py34}: pytest==4.6.7 From e42a5dd603cfde7c6ebab37778a6a6adfde266bb Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sun, 15 Dec 2019 07:22:11 -0800 Subject: [PATCH 124/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index f6cec912..6aa47886 100644 --- a/tox.ini +++ b/tox.ini @@ -241,7 +241,7 @@ description = build documentation basepython = python3.8 deps = - Sphinx==2.2.2 + Sphinx==2.3.0 sphinx-rtd-theme==0.4.3 commands = From 4261d09c536d9aeb8519c3c66d5003f01a2401d6 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Tue, 17 Dec 2019 11:49:43 -0800 Subject: [PATCH 125/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 6aa47886..038e17a5 100644 --- a/tox.ini +++ b/tox.ini @@ -142,7 +142,7 @@ skip_install = True deps = - mypy==0.750 + mypy==0.760 commands = From a5b4f9c1dc289c202e83b55aa9c8af1d06ba195a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20S=C3=A1nchez=20Vega?= Date: Wed, 18 Dec 2019 16:05:31 -0800 Subject: [PATCH 126/419] Update tox.ini fix coverage version mismatch --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 038e17a5..0afe760a 100644 --- a/tox.ini +++ b/tox.ini @@ -194,7 +194,7 @@ basepython = python skip_install = True deps = - coverage==4.5.4 + coverage==5.0 commands = coverage combine From 1dad682384c4666bfb0b92085ce97abd9f13eb6b Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Thu, 19 Dec 2019 11:53:43 -0800 Subject: [PATCH 127/419] [requires.io] dependency update --- tox.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 0afe760a..7cfc1d94 100644 --- a/tox.ini +++ b/tox.ini @@ -142,7 +142,7 @@ skip_install = True deps = - mypy==0.760 + mypy==0.761 commands = @@ -194,7 +194,7 @@ basepython = python skip_install = True deps = - coverage==5.0 + coverage==4.5.4 commands = coverage combine From c79f6e1afdfeb37bc754faefb4de91c0e013b8b8 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Thu, 19 Dec 2019 16:29:08 -0800 Subject: [PATCH 128/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 7cfc1d94..d72a94f1 100644 --- a/tox.ini +++ b/tox.ini @@ -34,7 +34,7 @@ deps = test: coverage==5.0 test: idna==2.8 test: typing==3.7.4.1 - test: {py26,py27,py34}: pytest==4.6.7 + test: {py26,py27,py34}: pytest==4.6.8 test: {py35,py36,py37,py38}: pytest==5.2.4 test: pytest-cov==2.8.1 From 5423e7bef7883948b936c11e5635904517ab816d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20S=C3=A1nchez=20Vega?= Date: Thu, 19 Dec 2019 16:33:33 -0800 Subject: [PATCH 129/419] Update tox.ini --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index d72a94f1..dc7d56b4 100644 --- a/tox.ini +++ b/tox.ini @@ -194,7 +194,7 @@ basepython = python skip_install = True deps = - coverage==4.5.4 + coverage==5.0 commands = coverage combine From 3ce394efd73e288b47297fc9b6616bdfbc98d7ef Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sun, 22 Dec 2019 06:18:48 -0800 Subject: [PATCH 130/419] [requires.io] dependency update --- tox.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index dc7d56b4..89f6f03b 100644 --- a/tox.ini +++ b/tox.ini @@ -194,7 +194,7 @@ basepython = python skip_install = True deps = - coverage==5.0 + coverage==4.5.4 commands = coverage combine @@ -241,7 +241,7 @@ description = build documentation basepython = python3.8 deps = - Sphinx==2.3.0 + Sphinx==2.3.1 sphinx-rtd-theme==0.4.3 commands = From 70f6f572c644583baf740de8f7428ca89a355f02 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sun, 22 Dec 2019 10:50:55 -0800 Subject: [PATCH 131/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 89f6f03b..981491c7 100644 --- a/tox.ini +++ b/tox.ini @@ -31,7 +31,7 @@ basepython = pypy3: pypy3 deps = - test: coverage==5.0 + test: coverage==5.0.1 test: idna==2.8 test: typing==3.7.4.1 test: {py26,py27,py34}: pytest==4.6.8 From c6985786e6867e3ad7b4d6ddb8e3e0648db4df57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Thu, 26 Dec 2019 14:47:43 -0800 Subject: [PATCH 132/419] match other use of this module --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 0afe760a..b112f6ff 100644 --- a/tox.ini +++ b/tox.ini @@ -215,7 +215,7 @@ basepython = python skip_install = True deps = - coverage==4.5.4 + coverage==5.0 codecov==2.0.15 commands = From 2fc2b0cd185276d5b08951a712df5c279124c131 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 29 Dec 2019 19:29:48 +0200 Subject: [PATCH 133/419] Update supported Python versions --- setup.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/setup.py b/setup.py index db3b4214..e902a40e 100644 --- a/setup.py +++ b/setup.py @@ -39,11 +39,15 @@ 'Intended Audience :: Developers', 'Topic :: Software Development :: Libraries', 'Development Status :: 5 - Production/Stable', + 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 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', 'Programming Language :: Python :: Implementation :: PyPy', 'License :: OSI Approved :: MIT License', ] ) From b2d38f008dfbb9ba9fec4bc8caddb49d8921fa24 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 29 Dec 2019 19:30:02 +0200 Subject: [PATCH 134/419] Update for tested Python versions --- README.md | 2 +- docs/index.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1d25ce86..e1936ca1 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Hyperlink provides a pure-Python implementation of immutable URLs. Based on [RFC 3986][rfc3986] and [3987][rfc3987], the Hyperlink URL makes working with both URIs and IRIs easy. -Hyperlink is tested against Python 2.7, 3.4, 3.5, 3.6, and PyPy. +Hyperlink is tested against Python 2.7, 3.4, 3.5, 3.6, 3.7, 3.8, and PyPy. Full documentation is available on [Read the Docs][docs]. diff --git a/docs/index.rst b/docs/index.rst index 2e65635d..57c78a87 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -10,7 +10,7 @@ hyperlink URLs. Based on `RFC 3986`_ and `RFC 3987`_, the Hyperlink URL balances simplicity and correctness for both :ref:`URIs and IRIs `. -Hyperlink is tested against Python 2.7, 3.4, 3.5, 3.6, and PyPy. +Hyperlink is tested against Python 2.7, 3.4, 3.5, 3.6, 3.7, 3.8, and PyPy. For an introduction to the hyperlink library, its background, and URLs in general, see `this talk from PyConWeb 2017`_ (and `the accompanying From 97fcf1e68cebaf60daf4a5645896d012798adb6f Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 29 Dec 2019 19:31:02 +0200 Subject: [PATCH 135/419] Add python_requires to help pip --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index e902a40e..ef009aee 100644 --- a/setup.py +++ b/setup.py @@ -34,6 +34,7 @@ 'idna>=2.5', 'typing ; python_version<"3.5"', ], + python_requires='>=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', classifiers=[ 'Topic :: Utilities', 'Intended Audience :: Developers', From ce7a513a7c99529211ceb5d151a4548875fa9c04 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 29 Dec 2019 19:34:30 +0200 Subject: [PATCH 136/419] Fix Travis caching --- .travis.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index e17f3cd0..226f3221 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,5 @@ sudo: false -cache: -directories: -- $HOME/.cache/pip +cache: pip language: python From 5e0ba47e559d564f474c35be1c9a59f1e4186191 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 29 Dec 2019 19:34:52 +0200 Subject: [PATCH 137/419] sudo no longer needed https://blog.travis-ci.com/2018-11-19-required-linux-infrastructure-migration --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 226f3221..fe7acec0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,3 @@ -sudo: false cache: pip language: python From fc8843427b678f6697dcd22987b0a35daa546f6d Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 29 Dec 2019 19:42:32 +0200 Subject: [PATCH 138/419] Hide with other dotfiles --- appveyor.yml => .appveyor.yml | 0 MANIFEST.in | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename appveyor.yml => .appveyor.yml (100%) diff --git a/appveyor.yml b/.appveyor.yml similarity index 100% rename from appveyor.yml rename to .appveyor.yml diff --git a/MANIFEST.in b/MANIFEST.in index c4b6e32f..3e8a8eb5 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,5 @@ include README.md LICENSE CHANGELOG.md tox.ini requirements-test.txt .coveragerc Makefile pytest.ini .tox-coveragerc -exclude TODO.md appveyor.yml +exclude TODO.md .appveyor.yml graft docs prune docs/_build From 15c40fabc053f39d88b1814ad2ad6da8e3268f5a Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 29 Dec 2019 19:43:54 +0200 Subject: [PATCH 139/419] Test Python 3.8 on Windows --- .appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 3c3a63cf..4a09c1ec 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -18,8 +18,8 @@ environment: - PYTHON: "C:\\Python37-x64" TOX_ENV: "test-py37,codecov" - #- PYTHON: "C:\\Python38-x64" - # TOX_ENV: "test-py38,codecov" + - PYTHON: "C:\\Python38-x64" + TOX_ENV: "test-py38,codecov" init: From 68a0047cdf8147a4a3cf055629db020157fdcbf1 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sun, 29 Dec 2019 14:22:47 -0800 Subject: [PATCH 140/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 5929e441..981491c7 100644 --- a/tox.ini +++ b/tox.ini @@ -215,7 +215,7 @@ basepython = python skip_install = True deps = - coverage==5.0 + coverage==4.5.4 codecov==2.0.15 commands = From c924dd8a59054c750ff51ebe57230d423468c27f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sun, 29 Dec 2019 14:35:46 -0800 Subject: [PATCH 141/419] Make coverage==5.0.1 in all places --- tox.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 981491c7..0c07ee00 100644 --- a/tox.ini +++ b/tox.ini @@ -194,7 +194,7 @@ basepython = python skip_install = True deps = - coverage==4.5.4 + coverage==5.0.1 commands = coverage combine @@ -215,7 +215,7 @@ basepython = python skip_install = True deps = - coverage==4.5.4 + coverage==5.0.1 codecov==2.0.15 commands = From 2b418d7268c9c4248c98a1187452d99a9534d199 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Wed, 1 Jan 2020 16:27:50 -0800 Subject: [PATCH 142/419] [requires.io] dependency update --- tox.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tox.ini b/tox.ini index 0c07ee00..54b88a82 100644 --- a/tox.ini +++ b/tox.ini @@ -77,7 +77,7 @@ skip_install = True # Pin pydocstyle to version 3: see https://gitlab.com/pycqa/flake8-docstrings/issues/36 deps = - flake8-bugbear==19.8.0 + flake8-bugbear==20.1.0 #flake8-docstrings==1.4.0 #flake8-import-order==0.18.1 #flake8-pep3101==1.2.1 @@ -194,7 +194,7 @@ basepython = python skip_install = True deps = - coverage==5.0.1 + coverage==4.5.4 commands = coverage combine @@ -215,7 +215,7 @@ basepython = python skip_install = True deps = - coverage==5.0.1 + coverage==4.5.4 codecov==2.0.15 commands = From ca2f7722e7812affc0340d97b34ce7a3f775ed04 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sat, 4 Jan 2020 13:07:39 -0800 Subject: [PATCH 143/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 54b88a82..a061f0ac 100644 --- a/tox.ini +++ b/tox.ini @@ -34,7 +34,7 @@ deps = test: coverage==5.0.1 test: idna==2.8 test: typing==3.7.4.1 - test: {py26,py27,py34}: pytest==4.6.8 + test: {py26,py27,py34}: pytest==4.6.9 test: {py35,py36,py37,py38}: pytest==5.2.4 test: pytest-cov==2.8.1 From 5cc077ed159afbfcc8839b1bbda8c01516fe4998 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sat, 4 Jan 2020 18:04:26 -0800 Subject: [PATCH 144/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index a061f0ac..2ff126e7 100644 --- a/tox.ini +++ b/tox.ini @@ -77,7 +77,7 @@ skip_install = True # Pin pydocstyle to version 3: see https://gitlab.com/pycqa/flake8-docstrings/issues/36 deps = - flake8-bugbear==20.1.0 + flake8-bugbear==20.1.1 #flake8-docstrings==1.4.0 #flake8-import-order==0.18.1 #flake8-pep3101==1.2.1 From b8cae173a6185ecc0f29092f4daccef4c613723e Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sun, 5 Jan 2020 16:09:30 -0800 Subject: [PATCH 145/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 2ff126e7..21a96c75 100644 --- a/tox.ini +++ b/tox.ini @@ -31,7 +31,7 @@ basepython = pypy3: pypy3 deps = - test: coverage==5.0.1 + test: coverage==5.0.2 test: idna==2.8 test: typing==3.7.4.1 test: {py26,py27,py34}: pytest==4.6.9 From 82fce3cb5aa0d23d01d999f43eece0d471a0f029 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sun, 5 Jan 2020 19:52:50 -0800 Subject: [PATCH 146/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 21a96c75..061d024f 100644 --- a/tox.ini +++ b/tox.ini @@ -77,7 +77,7 @@ skip_install = True # Pin pydocstyle to version 3: see https://gitlab.com/pycqa/flake8-docstrings/issues/36 deps = - flake8-bugbear==20.1.1 + flake8-bugbear==20.1.2 #flake8-docstrings==1.4.0 #flake8-import-order==0.18.1 #flake8-pep3101==1.2.1 From f6c28234dd89e69849174494701ac2075031c683 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Mon, 6 Jan 2020 14:09:24 -0800 Subject: [PATCH 147/419] requires.io keeps wanting to update only one usage of a module --- tox.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 061d024f..6bda3363 100644 --- a/tox.ini +++ b/tox.ini @@ -194,7 +194,7 @@ basepython = python skip_install = True deps = - coverage==4.5.4 + coverage==5.0.2 commands = coverage combine @@ -215,7 +215,7 @@ basepython = python skip_install = True deps = - coverage==4.5.4 + coverage==5.0.2 codecov==2.0.15 commands = From 290f39a0e643beaa87e25c60afd1a8a57c54fb0b Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Fri, 10 Jan 2020 00:31:23 -0800 Subject: [PATCH 148/419] [requires.io] dependency update --- tox.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tox.ini b/tox.ini index 6bda3363..3f2b45b6 100644 --- a/tox.ini +++ b/tox.ini @@ -84,7 +84,7 @@ deps = flake8==3.7.9 mccabe==0.6.1 pep8-naming==0.9.1 - pydocstyle==5.0.1 + pydocstyle==5.0.2 commands = flake8 {posargs:src/{env:PY_MODULE}} @@ -194,7 +194,7 @@ basepython = python skip_install = True deps = - coverage==5.0.2 + coverage==4.5.4 commands = coverage combine @@ -215,7 +215,7 @@ basepython = python skip_install = True deps = - coverage==5.0.2 + coverage==4.5.4 codecov==2.0.15 commands = From 33263b6f1e90918134f6c8f74d45b3dfbc5cb518 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sun, 12 Jan 2020 04:47:53 -0800 Subject: [PATCH 149/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 3f2b45b6..ad5514d7 100644 --- a/tox.ini +++ b/tox.ini @@ -31,7 +31,7 @@ basepython = pypy3: pypy3 deps = - test: coverage==5.0.2 + test: coverage==5.0.3 test: idna==2.8 test: typing==3.7.4.1 test: {py26,py27,py34}: pytest==4.6.9 From 2ac3b20c19616984689e0dfa9dcf77570034d152 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Mon, 20 Jan 2020 16:16:49 -0800 Subject: [PATCH 150/419] [requires.io] dependency update --- setup.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/setup.py b/setup.py index db3b4214..ef009aee 100644 --- a/setup.py +++ b/setup.py @@ -34,16 +34,21 @@ 'idna>=2.5', 'typing ; python_version<"3.5"', ], + python_requires='>=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', classifiers=[ 'Topic :: Utilities', 'Intended Audience :: Developers', 'Topic :: Software Development :: Libraries', 'Development Status :: 5 - Production/Stable', + 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 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', 'Programming Language :: Python :: Implementation :: PyPy', 'License :: OSI Approved :: MIT License', ] ) From 86923ca454c2962c41478c8293deb5f5d8024f51 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Mon, 20 Jan 2020 16:43:02 -0800 Subject: [PATCH 151/419] [requires.io] dependency update --- tox.ini | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/tox.ini b/tox.ini index ad5514d7..6e691a60 100644 --- a/tox.ini +++ b/tox.ini @@ -85,6 +85,8 @@ deps = mccabe==0.6.1 pep8-naming==0.9.1 pydocstyle==5.0.2 + # pin pyflakes pending a release with https://github.com/PyCQA/pyflakes/pull/455 + git+git://github.com/PyCQA/pyflakes@ffe9386#egg=pyflakes commands = flake8 {posargs:src/{env:PY_MODULE}} @@ -157,6 +159,8 @@ commands = # Global settings +check_untyped_defs = True +disallow_any_generics = True disallow_incomplete_defs = True disallow_untyped_defs = True no_implicit_optional = True @@ -169,13 +173,7 @@ warn_return_any = True warn_unreachable = True warn_unused_ignores = True -# Enable these over time -check_untyped_defs = False - -# Disable some checks until effected files fully adopt mypy - -[mypy-hyperlink._url] -allow_untyped_defs = True +# Don't complain about dependencies known to lack type hints [mypy-idna] ignore_missing_imports = True From 86a4736a29556faac5eb072aa52c7571ec5e3cfd Mon Sep 17 00:00:00 2001 From: Mahmoud Hashemi Date: Mon, 20 Jan 2020 17:44:00 -0800 Subject: [PATCH 152/419] fix long line --- src/hyperlink/test/common.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/hyperlink/test/common.py b/src/hyperlink/test/common.py index 7625c85e..f489266e 100644 --- a/src/hyperlink/test/common.py +++ b/src/hyperlink/test/common.py @@ -7,9 +7,13 @@ class HyperlinkTestCase(TestCase): assertRaises method for Python 2.6 testing. """ def assertRaises( # type: ignore[override] - self, expected_exception, callableObj=None, *args, **kwargs + self, + expected_exception, # type: Type[BaseException] + callableObj=None, # type: Optional[Callable[..., Any]] + *args, # type: Any + **kwargs # type: Any ): - # type: (Type[BaseException], Optional[Callable[..., Any]], Any, Any) -> Any + # type: (...) -> Any """Fail unless an exception of class expected_exception is raised by callableObj when invoked with arguments args and keyword arguments kwargs. If a different type of exception is From c798fd0a76c3eb59233bb5b53ca7b1561b31e43c Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Mon, 20 Jan 2020 18:11:09 -0800 Subject: [PATCH 153/419] [requires.io] dependency update --- tox.ini | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/tox.ini b/tox.ini index 6e691a60..8da62a2c 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,7 @@ [tox] envlist = - flake8 + flake8, mypy test-py{26,27,34,35,36,37,38,py,py3} coverage_report packaging @@ -55,6 +55,8 @@ passenv = setenv = PY_MODULE=hyperlink + test: PYTHONPYCACHEPREFIX={envtmpdir}/pycache + test: COVERAGE_FILE={toxworkdir}/coverage.{envname} {coverage_report,codecov}: COVERAGE_FILE={toxworkdir}/coverage codecov: COVERAGE_XML={envlogdir}/coverage_report.xml @@ -75,12 +77,9 @@ basepython = python3.8 skip_install = True -# Pin pydocstyle to version 3: see https://gitlab.com/pycqa/flake8-docstrings/issues/36 deps = flake8-bugbear==20.1.2 - #flake8-docstrings==1.4.0 - #flake8-import-order==0.18.1 - #flake8-pep3101==1.2.1 + #flake8-docstrings==1.5.0 flake8==3.7.9 mccabe==0.6.1 pep8-naming==0.9.1 @@ -142,11 +141,9 @@ basepython = python3.8 skip_install = True - deps = mypy==0.761 - commands = mypy \ --config-file="{toxinidir}/tox.ini" \ @@ -173,7 +170,11 @@ warn_return_any = True warn_unreachable = True warn_unused_ignores = True +[mypy-hyperlink._url] # Don't complain about dependencies known to lack type hints +# 4 at time of writing (2020-20-01), so maybe disable this soon +allow_untyped_defs = True + [mypy-idna] ignore_missing_imports = True @@ -196,8 +197,8 @@ deps = commands = coverage combine - coverage report - coverage html + - coverage report + - coverage html ## @@ -217,6 +218,8 @@ deps = codecov==2.0.15 commands = + # Note documentation for CI variables in default environment's passenv + coverage combine coverage xml -o "{env:COVERAGE_XML}" codecov --file="{env:COVERAGE_XML}" --env \ @@ -281,7 +284,9 @@ basepython = python deps = check-manifest==0.40 readme_renderer==24.0 + twine==3.1.1 commands = check-manifest - python setup.py check --metadata --restructuredtext --strict + pip wheel --wheel-dir "{envtmpdir}/dist" --no-deps {toxinidir} + twine check "{envtmpdir}/dist/"* From cf0ff4ceee9da3c6c58a517afe3e9b41e48c68d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20S=C3=A1nchez=20Vega?= Date: Wed, 22 Jan 2020 07:44:04 -0800 Subject: [PATCH 154/419] fix other refs to coverage --- tox.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 8da62a2c..0d57fcf9 100644 --- a/tox.ini +++ b/tox.ini @@ -193,7 +193,7 @@ basepython = python skip_install = True deps = - coverage==4.5.4 + coverage==5.0.3 commands = coverage combine @@ -214,7 +214,7 @@ basepython = python skip_install = True deps = - coverage==4.5.4 + coverage==5.0.3 codecov==2.0.15 commands = From f33b77267272b7d17228a8da14e81a527bb89378 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sat, 25 Jan 2020 15:25:47 -0800 Subject: [PATCH 155/419] Check in black config --- .gitignore | 7 +-- MANIFEST.in | 2 +- pyproject.toml | 10 ++++ tox.ini | 139 ++++++++++++++++++++++++++++++++++--------------- 4 files changed, 111 insertions(+), 47 deletions(-) create mode 100644 pyproject.toml diff --git a/.gitignore b/.gitignore index 35a65f26..d5ee9c7c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ -docs/_build +/docs/_build tmp.py -htmlcov/ +/htmlcov/ +/htmldocs/ .coverage.* *.py[cod] .mypy_cache @@ -34,7 +35,7 @@ pip-log.txt # Unit test / coverage reports .coverage -.tox/ +/.tox/ nosetests.xml # Translations diff --git a/MANIFEST.in b/MANIFEST.in index 3e8a8eb5..18ba8682 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ -include README.md LICENSE CHANGELOG.md tox.ini requirements-test.txt .coveragerc Makefile pytest.ini .tox-coveragerc +include README.md LICENSE CHANGELOG.md tox.ini pyproject.toml .coveragerc Makefile pytest.ini .tox-coveragerc exclude TODO.md .appveyor.yml graft docs diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..e7efe6ae --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,10 @@ +[build-system] + +requires = ["setuptools", "wheel"] +build-backend = "setuptools.build_meta" + + +[tool.black] + +line-length = 80 +target-version = ["py27"] diff --git a/tox.ini b/tox.ini index bfd3c7b4..f7c27ad7 100644 --- a/tox.ini +++ b/tox.ini @@ -1,17 +1,27 @@ [tox] envlist = - flake8, mypy + flake8, mypy # black test-py{26,27,34,35,36,37,38,py,py3} coverage_report - packaging docs + packaging skip_missing_interpreters = {tty:True:False} +[default] + +basepython = python3.8 + +setenv = + PY_MODULE=hyperlink + + PYTHONPYCACHEPREFIX={envtmpdir}/pycache + + ## -# Build (default environment) +# Default environment: unit tests ## [testenv] @@ -31,40 +41,56 @@ basepython = pypy3: pypy3 deps = - test: coverage==4.5.4 test: idna==2.8 test: typing==3.7.4.1 test: {py26,py27,py34}: pytest==4.6.7 test: {py35,py36,py37,py38}: pytest==5.2.4 test: pytest-cov==2.8.1 + test: {[testenv:coverage_report]deps} passenv = - # See https://github.com/codecov/codecov-python/blob/master/README.md#using-tox - # And CI-specific docs: - # https://help.github.com/en/articles/virtual-environments-for-github-actions#default-environment-variables - # https://docs.travis-ci.com/user/environment-variables#default-environment-variables - # https://www.appveyor.com/docs/environment-variables/ - codecov: TOXENV CODECOV_* CI - codecov: GITHUB_* - codecov: TRAVIS TRAVIS_* - codecov: APPVEYOR APPVEYOR_* - - # Used in our AppVeyor config - codecov: OS + test: CI setenv = - PY_MODULE=hyperlink - - test: PYTHONPYCACHEPREFIX={envtmpdir}/pycache + {[default]setenv} test: COVERAGE_FILE={toxworkdir}/coverage.{envname} - {coverage_report,codecov}: COVERAGE_FILE={toxworkdir}/coverage - codecov: COVERAGE_XML={envlogdir}/coverage_report.xml commands = test: pytest --cov={env:PY_MODULE} --cov-report=term-missing:skip-covered --doctest-modules {posargs:src/{env:PY_MODULE}} +## +# Black code formatting +## + +[testenv:black] + +description = run Black (linter) + +basepython = {[default]basepython} + +skip_install = True + +deps = + black==19.10b0 + +setenv = + BLACK_LINT_ARGS=--check + +commands = + black {env:BLACK_LINT_ARGS:} src + + +[testenv:black-reformat] + +description = {[testenv:black]description} and reformat +basepython = {[testenv:black]basepython} +skip_install = {[testenv:black]skip_install} +deps = {[testenv:black]deps} +commands = {[testenv:black]commands} + + ## # Flake8 linting ## @@ -73,16 +99,16 @@ commands = description = run Flake8 (linter) -basepython = python3.8 +basepython = {[default]basepython} skip_install = True deps = flake8-bugbear==19.8.0 - #flake8-docstrings==1.5.0 flake8==3.7.9 mccabe==0.6.1 pep8-naming==0.9.1 + pycodestyle==2.5.0 pydocstyle==5.0.1 # pin pyflakes pending a release with https://github.com/PyCQA/pyflakes/pull/455 git+git://github.com/PyCQA/pyflakes@ffe9386#egg=pyflakes @@ -99,6 +125,8 @@ select = A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z show-source = True doctests = True +max-line-length = 80 + # Codes: http://flake8.pycqa.org/en/latest/user/error-codes.html ignore = # syntax error in type comment @@ -119,8 +147,8 @@ ignore = # variable in global scope should not be mixedCase N816, - # line break after binary operator - W504, + # line break before binary operator + W503, # End of list (allows last item to end with trailing ',') EOL @@ -137,12 +165,10 @@ application-import-names = deploy description = run Mypy (static type checker) -basepython = python3.8 - -skip_install = True +basepython = {[default]basepython} deps = - mypy==0.750 + mypy==0.760 commands = mypy \ @@ -188,12 +214,19 @@ ignore_missing_imports = True description = generate coverage report -basepython = python +depends = test-py{36,37,38,39,py3} + +basepython = {[default]basepython} skip_install = True deps = - coverage==4.5.4 + coverage==5.0.3 + +setenv = + {[default]setenv} + + COVERAGE_FILE={toxworkdir}/coverage commands = coverage combine @@ -209,17 +242,35 @@ commands = description = upload coverage to Codecov +depends = {[coverage_report]depends} + basepython = python skip_install = True deps = - coverage==4.5.4 + {[testenv:coverage_report]deps} + codecov==2.0.15 -commands = - # Note documentation for CI variables in default environment's passenv +passenv = + # See https://github.com/codecov/codecov-python/blob/master/README.md#using-tox + # And CI-specific docs: + # https://help.github.com/en/articles/virtual-environments-for-github-actions#default-environment-variables + # https://docs.travis-ci.com/user/environment-variables#default-environment-variables + # https://www.appveyor.com/docs/environment-variables/ + TOXENV CODECOV_* CI + GITHUB_* + TRAVIS TRAVIS_* + APPVEYOR APPVEYOR_* + +setenv = + {[testenv:coverage_report]setenv} + + COVERAGE_XML={envlogdir}/coverage_report.xml +commands = + # Note documentation for CI variables in passenv above coverage combine coverage xml -o "{env:COVERAGE_XML}" codecov --file="{env:COVERAGE_XML}" --env \ @@ -239,28 +290,28 @@ commands = description = build documentation -basepython = python3.8 +basepython = {[default]basepython} deps = - Sphinx==2.2.2 + Sphinx==2.3.0 sphinx-rtd-theme==0.4.3 commands = sphinx-build \ -b html -d "{envtmpdir}/doctrees" \ "{toxinidir}/docs" \ - "{toxworkdir}/docs/html" + "{toxinidir}/htmldocs" [testenv:docs-auto] description = build documentation and rebuild automatically -basepython = python3.8 +basepython = {[default]basepython} deps = - Sphinx==2.2.2 - sphinx-rtd-theme==0.4.3 + {[testenv:docs]deps} + sphinx-autobuild==0.7.1 commands = @@ -268,7 +319,7 @@ commands = -b html -d "{envtmpdir}/doctrees" \ --host=localhost \ "{toxinidir}/docs" \ - "{toxworkdir}/docs/html" + "{toxinidir}/htmldocs" ## @@ -279,12 +330,14 @@ commands = description = check for potential packaging problems -basepython = python +basepython = {[default]basepython} + +skip_install = True deps = check-manifest==0.40 readme_renderer==24.0 - twine==2.0.0 + twine==3.1.1 commands = check-manifest From 9f80c1c6765b12b67f35d3a3c80380fa06dd1500 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sat, 25 Jan 2020 15:32:16 -0800 Subject: [PATCH 156/419] Re-add W504 so CI passes, until we reformat --- tox.ini | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tox.ini b/tox.ini index f7c27ad7..dd4ec549 100644 --- a/tox.ini +++ b/tox.ini @@ -150,6 +150,9 @@ ignore = # line break before binary operator W503, + # line break after binary operator + W504, + # End of list (allows last item to end with trailing ',') EOL From d1f3a6800a7cc2caac5c85dcb10bb43bce17cb75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sat, 25 Jan 2020 18:20:00 -0800 Subject: [PATCH 157/419] coverage 5.0 drops support for Python 3.4 --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index dd4ec549..0c196cca 100644 --- a/tox.ini +++ b/tox.ini @@ -224,7 +224,7 @@ basepython = {[default]basepython} skip_install = True deps = - coverage==5.0.3 + coverage==4.5.4 # coverage 5.0 drops support for Python 3.4 setenv = {[default]setenv} From ad7f2715294798b8749eae3ce8542da637716f16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sat, 25 Jan 2020 18:22:04 -0800 Subject: [PATCH 158/419] coverage 5.x drops python 3.4 --- tox.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tox.ini b/tox.ini index 0d57fcf9..c438c717 100644 --- a/tox.ini +++ b/tox.ini @@ -31,7 +31,7 @@ basepython = pypy3: pypy3 deps = - test: coverage==5.0.3 + test: coverage==4.5.4 test: idna==2.8 test: typing==3.7.4.1 test: {py26,py27,py34}: pytest==4.6.9 @@ -193,7 +193,7 @@ basepython = python skip_install = True deps = - coverage==5.0.3 + coverage==4.5.4 commands = coverage combine @@ -214,7 +214,7 @@ basepython = python skip_install = True deps = - coverage==5.0.3 + coverage==4.5.4 codecov==2.0.15 commands = From d30c568aaeefd92a3734b6ed8a16b7457dfa7b39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sat, 25 Jan 2020 18:24:17 -0800 Subject: [PATCH 159/419] Add requires.io filter --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 0c196cca..0e6e371d 100644 --- a/tox.ini +++ b/tox.ini @@ -224,7 +224,7 @@ basepython = {[default]basepython} skip_install = True deps = - coverage==4.5.4 # coverage 5.0 drops support for Python 3.4 + coverage==4.5.4 # rq.filter: <5 # coverage 5.0 drops Python 3.4 support setenv = {[default]setenv} From 62b520e502f7f9a6bfc69d1fa8ca8d19ef824fe7 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sun, 26 Jan 2020 10:55:24 -0800 Subject: [PATCH 160/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index c438c717..8da62a2c 100644 --- a/tox.ini +++ b/tox.ini @@ -31,7 +31,7 @@ basepython = pypy3: pypy3 deps = - test: coverage==4.5.4 + test: coverage==5.0.3 test: idna==2.8 test: typing==3.7.4.1 test: {py26,py27,py34}: pytest==4.6.9 From 5529635349608d26ee8af09b6d7e641b1f0556a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sun, 26 Jan 2020 12:28:27 -0800 Subject: [PATCH 161/419] Tell requires.io to stop trying to update to coverage>4 --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 8da62a2c..12f3a331 100644 --- a/tox.ini +++ b/tox.ini @@ -31,7 +31,7 @@ basepython = pypy3: pypy3 deps = - test: coverage==5.0.3 + test: coverage==4.5.4 # rq.filter: <5 test: idna==2.8 test: typing==3.7.4.1 test: {py26,py27,py34}: pytest==4.6.9 From 63138666fb3d9ed0bb4258bd47756fb929e66236 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sun, 26 Jan 2020 12:58:57 -0800 Subject: [PATCH 162/419] Add py.typed --- src/hyperlink/py.typed | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/hyperlink/py.typed diff --git a/src/hyperlink/py.typed b/src/hyperlink/py.typed new file mode 100644 index 00000000..e69de29b From f31c3586fa289c269432447ff63ba45fd967fd15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sun, 26 Jan 2020 13:03:48 -0800 Subject: [PATCH 163/419] Add some text in case someone wonders what this file is --- src/hyperlink/py.typed | 1 + 1 file changed, 1 insertion(+) diff --git a/src/hyperlink/py.typed b/src/hyperlink/py.typed index e69de29b..d2dfd5e4 100644 --- a/src/hyperlink/py.typed +++ b/src/hyperlink/py.typed @@ -0,0 +1 @@ +# See: https://www.python.org/dev/peps/pep-0561/ From 983b3162b12f9902babf289e9390dea6234a981a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sun, 26 Jan 2020 13:05:18 -0800 Subject: [PATCH 164/419] Nix stray commit --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 12f3a331..c438c717 100644 --- a/tox.ini +++ b/tox.ini @@ -31,7 +31,7 @@ basepython = pypy3: pypy3 deps = - test: coverage==4.5.4 # rq.filter: <5 + test: coverage==4.5.4 test: idna==2.8 test: typing==3.7.4.1 test: {py26,py27,py34}: pytest==4.6.9 From f69a685ccf95fa0c9f0fdb88c61c41aa7004f9d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sun, 26 Jan 2020 13:41:35 -0800 Subject: [PATCH 165/419] include_package_data only works when you have a VCS checkout, and not from an sdist --- setup.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index ef009aee..fceb4fac 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,11 @@ url=__url__, packages=find_packages(where="src"), package_dir={"": "src"}, - include_package_data=True, + package_data=dict( + hyperlink=[ + "py.typed", + ], + ), zip_safe=False, license=__license__, platforms='any', From a2873922404eebfdb56e5284869036feb2011d46 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Tue, 28 Jan 2020 19:12:18 -0800 Subject: [PATCH 166/419] [requires.io] dependency update --- tox.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 12f3a331..1698505a 100644 --- a/tox.ini +++ b/tox.ini @@ -31,7 +31,7 @@ basepython = pypy3: pypy3 deps = - test: coverage==4.5.4 # rq.filter: <5 + test: coverage==5.0.3 test: idna==2.8 test: typing==3.7.4.1 test: {py26,py27,py34}: pytest==4.6.9 @@ -78,7 +78,7 @@ basepython = python3.8 skip_install = True deps = - flake8-bugbear==20.1.2 + flake8-bugbear==20.1.3 #flake8-docstrings==1.5.0 flake8==3.7.9 mccabe==0.6.1 From 2cf1470fb01b55e6f723e8f66d3f787da65f30a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Wed, 29 Jan 2020 10:16:06 -0800 Subject: [PATCH 167/419] No to coverage 5 --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 1698505a..adaa5869 100644 --- a/tox.ini +++ b/tox.ini @@ -31,7 +31,7 @@ basepython = pypy3: pypy3 deps = - test: coverage==5.0.3 + test: coverage==4.5.4 # rq.filter: <5 test: idna==2.8 test: typing==3.7.4.1 test: {py26,py27,py34}: pytest==4.6.9 From fa9312792558e987376c6780b74062461ad8bd9e Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Fri, 31 Jan 2020 20:12:02 -0800 Subject: [PATCH 168/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index adaa5869..02e34153 100644 --- a/tox.ini +++ b/tox.ini @@ -78,7 +78,7 @@ basepython = python3.8 skip_install = True deps = - flake8-bugbear==20.1.3 + flake8-bugbear==20.1.4 #flake8-docstrings==1.5.0 flake8==3.7.9 mccabe==0.6.1 From 32b078bf8ba080498508eed6316aced957613448 Mon Sep 17 00:00:00 2001 From: Glyph Date: Sun, 16 Feb 2020 01:35:08 -0800 Subject: [PATCH 169/419] fix up inconsistencies in parsing & textual representation of 'rooted' and 'uses_netloc' --- src/hyperlink/_url.py | 38 +++++++++++++++++++++++++--------- src/hyperlink/test/test_url.py | 34 ++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 10 deletions(-) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index f39525d3..e20edc4f 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -815,9 +815,9 @@ class URL(object): that starts with a slash. userinfo (Text): The username or colon-separated username:password pair. - uses_netloc (bool): Indicates whether two slashes appear - between the scheme and the host (``http://eg.com`` vs - ``mailto:e@g.com``). Set automatically based on scheme. + uses_netloc (bool): Indicates whether ``://`` will appear to separate + the scheme from the path, even in cases where no host is present. + May be implied by scheme, or set explictly. All of these parts are also exposed as read-only attributes of URL instances, along with several useful methods. @@ -882,15 +882,28 @@ def __init__( self._rooted = _typecheck("rooted", rooted, bool) self._userinfo = _textcheck("userinfo", userinfo, '/?#@') - uses_netloc = scheme_uses_netloc(self._scheme, uses_netloc) + if uses_netloc is None: + uses_netloc = scheme_uses_netloc(self._scheme, uses_netloc) self._uses_netloc = _typecheck("uses_netloc", uses_netloc, bool, NoneType) - # fixup for rooted consistency - if self._host: + will_have_authority = ( + self._host or + (self._port and self._port != SCHEME_PORT_MAP.get(scheme)) + ) + if will_have_authority: + # fixup for rooted consistency; if there's any 'authority' + # represented in the textual URL, then the path must be rooted, and + # we're definitely using a netloc (there must be a ://). self._rooted = True - if (not self._rooted) and self._path and self._path[0] == '': + self._uses_netloc = True + if (not self._rooted) and self.path[:1] == (u'',): self._rooted = True self._path = self._path[1:] + if not will_have_authority and self._path and not self._rooted: + # If, after fixing up the path, there *is* a path and it *isn't* + # rooted, then we are definitely not using a netloc; if we did, it + # would make the path (erroneously) look like a hostname. + self._uses_netloc = False def get_decoded_url(self, lazy=False): # type: (bool) -> DecodedURL @@ -1006,6 +1019,8 @@ def userinfo(self): def uses_netloc(self): # type: () -> Optional[bool] """ + Whether the textual URL representation will contain a ``://`` netloc + separator. """ return self._uses_netloc @@ -1134,14 +1149,17 @@ def replace( slash. userinfo (Text): The username or colon-separated username:password pair. - uses_netloc (bool): Indicates whether two slashes appear between - the scheme and the host - (``http://eg.com`` vs ``mailto:e@g.com``) + uses_netloc (bool): Indicates whether rooted paths should include a + scheme-separator by default. Returns: URL: A copy of the current :class:`URL`, with new values for parameters passed. """ + if scheme is not _UNSET and scheme != self.scheme: + # when changing schemes, reset the explicit uses_netloc preference + # to honor the new scheme. + uses_netloc = None return self.__class__( scheme=_optional(scheme, self.scheme), host=_optional(host, self.host), diff --git a/src/hyperlink/test/test_url.py b/src/hyperlink/test/test_url.py index fa5c7bf1..356def9c 100644 --- a/src/hyperlink/test/test_url.py +++ b/src/hyperlink/test/test_url.py @@ -817,6 +817,18 @@ def test_mailto(self): self.assertEqual(URL.from_text(u"mailto:user@example.com").to_text(), u"mailto:user@example.com") + def test_httpWithoutHost(self): + # type: () -> None + """ + An HTTP URL without a hostname, but with a path, should also round-trip + cleanly. + """ + without_host = URL.from_text(u"http:relative-path") + self.assertEqual(without_host.host, u'') + self.assertEqual(without_host.path, (u'relative-path',)) + self.assertEqual(without_host.uses_netloc, False) + self.assertEqual(without_host.to_text(), u"http:relative-path") + def test_queryIterable(self): # type: () -> None """ @@ -938,15 +950,25 @@ def test_netloc(self): # type: () -> None url = URL(scheme='https') self.assertEqual(url.uses_netloc, True) + self.assertEqual(url.to_text(), u'https://') + self.assertEqual(URL.from_text('https:').uses_netloc, False) + self.assertEqual(URL.from_text('https://').uses_netloc, True) + + url = URL(scheme='https', uses_netloc=False) + self.assertEqual(url.uses_netloc, False) + self.assertEqual(url.to_text(), u'https:') url = URL(scheme='git+https') self.assertEqual(url.uses_netloc, True) + self.assertEqual(url.to_text(), u'git+https://') url = URL(scheme='mailto') self.assertEqual(url.uses_netloc, False) + self.assertEqual(url.to_text(), u'mailto:') url = URL(scheme='ztp') self.assertEqual(url.uses_netloc, None) + self.assertEqual(url.to_text(), u'ztp:') url = URL.from_text('ztp://test.com') self.assertEqual(url.uses_netloc, True) @@ -1116,6 +1138,18 @@ def test_autorooted(self): self.assertEqual(normal_absolute.rooted, True) self.assertEqual(attempt_unrooted_absolute.rooted, True) + def test_rooted_with_empty_non_none_host(self): + # type: () -> None + """ + The C{rooted} constructor argument will be ignored on URLs that include + a scheme. + """ + directly_constructed = URL(scheme='udp', port=4900) + parsed = URL.from_text('udp://:4900') + self.assertEqual(str(directly_constructed), str(parsed)) + self.assertEqual(directly_constructed.asText(), parsed.asText()) + self.assertEqual(directly_constructed, parsed) + def test_wrong_constructor(self): # type: () -> None with self.assertRaises(ValueError): From 82f096ba9ec018ff746204a634bd27f6468eb1ce Mon Sep 17 00:00:00 2001 From: Glyph Date: Sun, 16 Feb 2020 14:15:53 -0800 Subject: [PATCH 170/419] per CR: make the test a little more thorough, improve docstring --- src/hyperlink/test/test_url.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/hyperlink/test/test_url.py b/src/hyperlink/test/test_url.py index 356def9c..4b96b2eb 100644 --- a/src/hyperlink/test/test_url.py +++ b/src/hyperlink/test/test_url.py @@ -1138,17 +1138,29 @@ def test_autorooted(self): self.assertEqual(normal_absolute.rooted, True) self.assertEqual(attempt_unrooted_absolute.rooted, True) - def test_rooted_with_empty_non_none_host(self): + def test_rooted_with_port_but_no_host(self): # type: () -> None """ - The C{rooted} constructor argument will be ignored on URLs that include - a scheme. + URLs which include a netloc separator are inherently rooted, regardless + of whether they include one because they specify an explicit host or + port, whether they are parsed or directly constructed, and whether the + ``rooted`` constructor argument is supplied or not. """ - directly_constructed = URL(scheme='udp', port=4900) + directly_constructed = URL(scheme='udp', port=4900, rooted=False) + directly_constructed_implict = URL(scheme='udp', port=4900) + directly_constructed_rooted = URL(scheme=u'udp', port=4900, rooted=True) + self.assertEqual(directly_constructed.rooted, True) + self.assertEqual(directly_constructed_implict.rooted, True) + self.assertEqual(directly_constructed_rooted.rooted, True) parsed = URL.from_text('udp://:4900') self.assertEqual(str(directly_constructed), str(parsed)) + self.assertEqual(str(directly_constructed_implict), str(parsed)) self.assertEqual(directly_constructed.asText(), parsed.asText()) self.assertEqual(directly_constructed, parsed) + self.assertEqual(directly_constructed, directly_constructed_implict) + self.assertEqual(directly_constructed, directly_constructed_rooted) + self.assertEqual(directly_constructed_implict, parsed) + self.assertEqual(directly_constructed_rooted, parsed) def test_wrong_constructor(self): # type: () -> None From b3d03b48c736bb026879d598ef8c380d78c711bc Mon Sep 17 00:00:00 2001 From: Glyph Date: Sun, 16 Feb 2020 14:20:41 -0800 Subject: [PATCH 171/419] <79 --- src/hyperlink/test/test_url.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/hyperlink/test/test_url.py b/src/hyperlink/test/test_url.py index 4b96b2eb..668ddd84 100644 --- a/src/hyperlink/test/test_url.py +++ b/src/hyperlink/test/test_url.py @@ -1148,7 +1148,8 @@ def test_rooted_with_port_but_no_host(self): """ directly_constructed = URL(scheme='udp', port=4900, rooted=False) directly_constructed_implict = URL(scheme='udp', port=4900) - directly_constructed_rooted = URL(scheme=u'udp', port=4900, rooted=True) + directly_constructed_rooted = URL(scheme=u'udp', port=4900, + rooted=True) self.assertEqual(directly_constructed.rooted, True) self.assertEqual(directly_constructed_implict.rooted, True) self.assertEqual(directly_constructed_rooted.rooted, True) From 4550849e3f1958faa680c7ec8a3660b9ad8ae75e Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sun, 16 Feb 2020 23:22:30 -0800 Subject: [PATCH 172/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 02e34153..618f3b22 100644 --- a/tox.ini +++ b/tox.ini @@ -32,7 +32,7 @@ basepython = deps = test: coverage==4.5.4 # rq.filter: <5 - test: idna==2.8 + test: idna==2.9 test: typing==3.7.4.1 test: {py26,py27,py34}: pytest==4.6.9 test: {py35,py36,py37,py38}: pytest==5.2.4 From 575a437c4f899b77dc95e1d6dffa35cb5bf8157e Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sun, 16 Feb 2020 23:41:57 -0800 Subject: [PATCH 173/419] [requires.io] dependency update From 2cc9796e4a0a19e77b0ca2a0c41fdadbf703e89e Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Mon, 24 Feb 2020 10:26:12 -0800 Subject: [PATCH 174/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 618f3b22..003fdbc7 100644 --- a/tox.ini +++ b/tox.ini @@ -215,7 +215,7 @@ skip_install = True deps = coverage==4.5.4 - codecov==2.0.15 + codecov==2.0.16 commands = # Note documentation for CI variables in default environment's passenv From 0012b38faf3f32b86919d809d53c08179d47652b Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Mon, 24 Feb 2020 10:28:40 -0800 Subject: [PATCH 175/419] [requires.io] dependency update From 634fc09adf5f47be18f5d56893627255e63fce5f Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Mon, 24 Feb 2020 23:13:31 -0800 Subject: [PATCH 176/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 003fdbc7..bc8a5df4 100644 --- a/tox.ini +++ b/tox.ini @@ -282,7 +282,7 @@ description = check for potential packaging problems basepython = python deps = - check-manifest==0.40 + check-manifest==0.41 readme_renderer==24.0 twine==3.1.1 From b512a7988a6a4d7fdaa4cd8f304eaa6fc6151549 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Tue, 10 Mar 2020 18:19:22 -0700 Subject: [PATCH 177/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index bc8a5df4..b4117d37 100644 --- a/tox.ini +++ b/tox.ini @@ -142,7 +142,7 @@ basepython = python3.8 skip_install = True deps = - mypy==0.761 + mypy==0.770 commands = mypy \ From f36b6b81f8c9643d5a43e7478d895e02bf3b62fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Tue, 10 Mar 2020 19:23:53 -0700 Subject: [PATCH 178/419] mypy added a more specific "unreachable" error code. --- src/hyperlink/_url.py | 2 +- src/hyperlink/test/test_common.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index f39525d3..7b2b27c0 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -467,7 +467,7 @@ def _textcheck(name, value, delims=frozenset(), nullable=False): if not isinstance(value, Text): if nullable and value is None: # used by query string values - return value # type: ignore[misc] # unreachable + return value # type: ignore[unreachable] else: str_name = "unicode" if PY2 else "str" exp = str_name + ' or NoneType' if nullable else str_name diff --git a/src/hyperlink/test/test_common.py b/src/hyperlink/test/test_common.py index c16e12e2..af495d8b 100644 --- a/src/hyperlink/test/test_common.py +++ b/src/hyperlink/test/test_common.py @@ -88,7 +88,7 @@ def test_assertRaisesContextManager(self): with self.hyperlink_test.assertRaises(_ExpectedException) as cm: raise _ExpectedException - self.assertTrue( # type: ignore[misc] # unreachable + self.assertTrue( # type: ignore[unreachable] isinstance(cm.exception, _ExpectedException) ) From ec66e1206bee853360537c9184c823dbfc4a1eb4 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sat, 14 Mar 2020 10:53:04 -0700 Subject: [PATCH 179/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index b4117d37..2b622395 100644 --- a/tox.ini +++ b/tox.ini @@ -283,7 +283,7 @@ basepython = python deps = check-manifest==0.41 - readme_renderer==24.0 + readme-renderer==25.0 twine==3.1.1 commands = From 4f7cb264e80f08e8cc60aded876658999133e3e6 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sat, 14 Mar 2020 10:53:51 -0700 Subject: [PATCH 180/419] [requires.io] dependency update From ddd9e8e3ac5c65afbc710b734945e7604938b5c9 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Tue, 17 Mar 2020 07:26:04 -0700 Subject: [PATCH 181/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 2b622395..fe4d2d34 100644 --- a/tox.ini +++ b/tox.ini @@ -215,7 +215,7 @@ skip_install = True deps = coverage==4.5.4 - codecov==2.0.16 + codecov==2.0.17 commands = # Note documentation for CI variables in default environment's passenv From d9be1e42c521b1244682ab633a9d6aebc7452c55 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Tue, 17 Mar 2020 11:20:58 -0700 Subject: [PATCH 182/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index fe4d2d34..d402d835 100644 --- a/tox.ini +++ b/tox.ini @@ -215,7 +215,7 @@ skip_install = True deps = coverage==4.5.4 - codecov==2.0.17 + codecov==2.0.18 commands = # Note documentation for CI variables in default environment's passenv From 2b55b8fbfb1bd224308cf721335aaf619e6d8da2 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Tue, 17 Mar 2020 12:07:00 -0700 Subject: [PATCH 183/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index d402d835..569b8fe1 100644 --- a/tox.ini +++ b/tox.ini @@ -215,7 +215,7 @@ skip_install = True deps = coverage==4.5.4 - codecov==2.0.18 + codecov==2.0.19 commands = # Note documentation for CI variables in default environment's passenv From ee7c6303d6cd8940c49c815f8d08bf392c8722df Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Tue, 17 Mar 2020 12:19:54 -0700 Subject: [PATCH 184/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 569b8fe1..c42850ff 100644 --- a/tox.ini +++ b/tox.ini @@ -215,7 +215,7 @@ skip_install = True deps = coverage==4.5.4 - codecov==2.0.19 + codecov==2.0.20 commands = # Note documentation for CI variables in default environment's passenv From e109dc9a3a6cdaed00b4c9de3e6efa311aedbe57 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Tue, 17 Mar 2020 14:57:40 -0700 Subject: [PATCH 185/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index c42850ff..2e29c208 100644 --- a/tox.ini +++ b/tox.ini @@ -215,7 +215,7 @@ skip_install = True deps = coverage==4.5.4 - codecov==2.0.20 + codecov==2.0.21 commands = # Note documentation for CI variables in default environment's passenv From 4de842cf1816f24f0472816e80157aeaac863ca6 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Wed, 18 Mar 2020 05:52:28 -0700 Subject: [PATCH 186/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 2e29c208..7a8b3ce6 100644 --- a/tox.ini +++ b/tox.ini @@ -215,7 +215,7 @@ skip_install = True deps = coverage==4.5.4 - codecov==2.0.21 + codecov==2.0.22 commands = # Note documentation for CI variables in default environment's passenv From ccdb45e68527d8f7b8ae74cb7a385ae816aa7247 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Fri, 20 Mar 2020 14:54:38 -0700 Subject: [PATCH 187/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 7a8b3ce6..8c15db59 100644 --- a/tox.ini +++ b/tox.ini @@ -82,7 +82,7 @@ deps = #flake8-docstrings==1.5.0 flake8==3.7.9 mccabe==0.6.1 - pep8-naming==0.9.1 + pep8-naming==0.10.0 pydocstyle==5.0.2 # pin pyflakes pending a release with https://github.com/PyCQA/pyflakes/pull/455 git+git://github.com/PyCQA/pyflakes@ffe9386#egg=pyflakes From a16d5d634070beb099097adc5998cf17ba29bd8e Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Tue, 24 Mar 2020 10:24:27 -0700 Subject: [PATCH 188/419] [requires.io] dependency update --- setup.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index ef009aee..fceb4fac 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,11 @@ url=__url__, packages=find_packages(where="src"), package_dir={"": "src"}, - include_package_data=True, + package_data=dict( + hyperlink=[ + "py.typed", + ], + ), zip_safe=False, license=__license__, platforms='any', From 83fe2acb71b937b11d363fb6fb1a3a60224f0a01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Fri, 27 Mar 2020 18:06:13 -0700 Subject: [PATCH 189/419] Fix new mypy errors. --- src/hyperlink/hypothesis.py | 8 ++++---- src/hyperlink/test/test_socket.py | 2 +- tox.ini | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/hyperlink/hypothesis.py b/src/hyperlink/hypothesis.py index a6ae7c26..db9659b1 100644 --- a/src/hyperlink/hypothesis.py +++ b/src/hyperlink/hypothesis.py @@ -285,13 +285,13 @@ def encoded_urls(draw): if port == 0: port = None - args = dict( + return EncodedURL( scheme=cast(Text, draw(sampled_from((u"http", u"https")))), - host=host, port=port, path=path, + host=host, + port=port, + path=path, ) - return EncodedURL(**args) - @composite def decoded_urls(draw): # type: (DrawCallable) -> DecodedURL diff --git a/src/hyperlink/test/test_socket.py b/src/hyperlink/test/test_socket.py index af7802a7..5f83d45b 100644 --- a/src/hyperlink/test/test_socket.py +++ b/src/hyperlink/test/test_socket.py @@ -3,7 +3,7 @@ try: from socket import inet_pton except ImportError: - inet_pton = None # type: ignore[assignment] not optional + inet_pton = None # type: ignore[assignment] if not inet_pton: import socket diff --git a/tox.ini b/tox.ini index f404ff6d..bedef7f3 100644 --- a/tox.ini +++ b/tox.ini @@ -169,7 +169,7 @@ commands = # Global settings check_untyped_defs = True -disallow_any_generics = True +disallow_any_generics = False disallow_incomplete_defs = True disallow_untyped_defs = True no_implicit_optional = True From 67f2ef60a3fdc2c3d4f29b03779c7fb98b061b32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Mon, 30 Mar 2020 15:42:18 -0700 Subject: [PATCH 190/419] Compress the IDNA table --- MANIFEST.in | 5 +- src/hyperlink/hypothesis.py | 5 +- src/hyperlink/idna-tables-properties.csv | 2471 ------------------- src/hyperlink/idna-tables-properties.csv.gz | Bin 0 -> 25555 bytes 4 files changed, 6 insertions(+), 2475 deletions(-) delete mode 100644 src/hyperlink/idna-tables-properties.csv create mode 100644 src/hyperlink/idna-tables-properties.csv.gz diff --git a/MANIFEST.in b/MANIFEST.in index 2fac6ee9..5869a052 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,8 +1,9 @@ include README.md LICENSE CHANGELOG.md include tox.ini pytest.ini .coveragerc -exclude TODO.md appveyor.yml +exclude TODO.md +exclude .appveyor.yml -include src/hyperlink/idna-tables-properties.csv +include src/hyperlink/idna-tables-properties.csv.gz graft docs prune docs/_build diff --git a/src/hyperlink/hypothesis.py b/src/hyperlink/hypothesis.py index db9659b1..157f8aa6 100644 --- a/src/hyperlink/hypothesis.py +++ b/src/hyperlink/hypothesis.py @@ -18,6 +18,7 @@ from typing import ( Callable, Iterable, List, Optional, Sequence, Text, TypeVar, cast ) + from gzip import open as open_gzip from . import DecodedURL, EncodedURL @@ -60,9 +61,9 @@ def idna_characters(): # https://www.iana.org/assignments/idna-tables-6.3.0/ # idna-tables-6.3.0.xhtml#idna-tables-properties dataFileName = join( - dirname(__file__), "idna-tables-properties.csv" + dirname(__file__), "idna-tables-properties.csv.gz" ) - with open(dataFileName) as dataFile: + with open_gzip(dataFileName, "rt") as dataFile: reader = csv_reader(dataFile, delimiter=",") next(reader) # Skip header row for row in reader: diff --git a/src/hyperlink/idna-tables-properties.csv b/src/hyperlink/idna-tables-properties.csv deleted file mode 100644 index 062ee6bf..00000000 --- a/src/hyperlink/idna-tables-properties.csv +++ /dev/null @@ -1,2471 +0,0 @@ -Codepoint,Property,Description -0000-002C,DISALLOWED,NULL..COMMA -002D,PVALID,HYPHEN-MINUS -002E-002F,DISALLOWED,FULL STOP..SOLIDUS -0030-0039,PVALID,DIGIT ZERO..DIGIT NINE -003A-0060,DISALLOWED,COLON..GRAVE ACCENT -0061-007A,PVALID,LATIN SMALL LETTER A..LATIN SMALL LETTER Z -007B-00B6,DISALLOWED,LEFT CURLY BRACKET..PILCROW SIGN -00B7,CONTEXTO,MIDDLE DOT -00B8-00DE,DISALLOWED,CEDILLA..LATIN CAPITAL LETTER THORN -00DF-00F6,PVALID,LATIN SMALL LETTER SHARP S..LATIN SMALL LETTER O WITH DIAERESIS -00F7,DISALLOWED,DIVISION SIGN -00F8-00FF,PVALID,LATIN SMALL LETTER O WITH STROKE..LATIN SMALL LETTER Y WITH DIAERESIS -0100,DISALLOWED,LATIN CAPITAL LETTER A WITH MACRON -0101,PVALID,LATIN SMALL LETTER A WITH MACRON -0102,DISALLOWED,LATIN CAPITAL LETTER A WITH BREVE -0103,PVALID,LATIN SMALL LETTER A WITH BREVE -0104,DISALLOWED,LATIN CAPITAL LETTER A WITH OGONEK -0105,PVALID,LATIN SMALL LETTER A WITH OGONEK -0106,DISALLOWED,LATIN CAPITAL LETTER C WITH ACUTE -0107,PVALID,LATIN SMALL LETTER C WITH ACUTE -0108,DISALLOWED,LATIN CAPITAL LETTER C WITH CIRCUMFLEX -0109,PVALID,LATIN SMALL LETTER C WITH CIRCUMFLEX -010A,DISALLOWED,LATIN CAPITAL LETTER C WITH DOT ABOVE -010B,PVALID,LATIN SMALL LETTER C WITH DOT ABOVE -010C,DISALLOWED,LATIN CAPITAL LETTER C WITH CARON -010D,PVALID,LATIN SMALL LETTER C WITH CARON -010E,DISALLOWED,LATIN CAPITAL LETTER D WITH CARON -010F,PVALID,LATIN SMALL LETTER D WITH CARON -0110,DISALLOWED,LATIN CAPITAL LETTER D WITH STROKE -0111,PVALID,LATIN SMALL LETTER D WITH STROKE -0112,DISALLOWED,LATIN CAPITAL LETTER E WITH MACRON -0113,PVALID,LATIN SMALL LETTER E WITH MACRON -0114,DISALLOWED,LATIN CAPITAL LETTER E WITH BREVE -0115,PVALID,LATIN SMALL LETTER E WITH BREVE -0116,DISALLOWED,LATIN CAPITAL LETTER E WITH DOT ABOVE -0117,PVALID,LATIN SMALL LETTER E WITH DOT ABOVE -0118,DISALLOWED,LATIN CAPITAL LETTER E WITH OGONEK -0119,PVALID,LATIN SMALL LETTER E WITH OGONEK -011A,DISALLOWED,LATIN CAPITAL LETTER E WITH CARON -011B,PVALID,LATIN SMALL LETTER E WITH CARON -011C,DISALLOWED,LATIN CAPITAL LETTER G WITH CIRCUMFLEX -011D,PVALID,LATIN SMALL LETTER G WITH CIRCUMFLEX -011E,DISALLOWED,LATIN CAPITAL LETTER G WITH BREVE -011F,PVALID,LATIN SMALL LETTER G WITH BREVE -0120,DISALLOWED,LATIN CAPITAL LETTER G WITH DOT ABOVE -0121,PVALID,LATIN SMALL LETTER G WITH DOT ABOVE -0122,DISALLOWED,LATIN CAPITAL LETTER G WITH CEDILLA -0123,PVALID,LATIN SMALL LETTER G WITH CEDILLA -0124,DISALLOWED,LATIN CAPITAL LETTER H WITH CIRCUMFLEX -0125,PVALID,LATIN SMALL LETTER H WITH CIRCUMFLEX -0126,DISALLOWED,LATIN CAPITAL LETTER H WITH STROKE -0127,PVALID,LATIN SMALL LETTER H WITH STROKE -0128,DISALLOWED,LATIN CAPITAL LETTER I WITH TILDE -0129,PVALID,LATIN SMALL LETTER I WITH TILDE -012A,DISALLOWED,LATIN CAPITAL LETTER I WITH MACRON -012B,PVALID,LATIN SMALL LETTER I WITH MACRON -012C,DISALLOWED,LATIN CAPITAL LETTER I WITH BREVE -012D,PVALID,LATIN SMALL LETTER I WITH BREVE -012E,DISALLOWED,LATIN CAPITAL LETTER I WITH OGONEK -012F,PVALID,LATIN SMALL LETTER I WITH OGONEK -0130,DISALLOWED,LATIN CAPITAL LETTER I WITH DOT ABOVE -0131,PVALID,LATIN SMALL LETTER DOTLESS I -0132-0134,DISALLOWED,LATIN CAPITAL LIGATURE IJ..LATIN CAPITAL LETTER J WITH CIRCUMFLEX -0135,PVALID,LATIN SMALL LETTER J WITH CIRCUMFLEX -0136,DISALLOWED,LATIN CAPITAL LETTER K WITH CEDILLA -0137-0138,PVALID,LATIN SMALL LETTER K WITH CEDILLA..LATIN SMALL LETTER KRA -0139,DISALLOWED,LATIN CAPITAL LETTER L WITH ACUTE -013A,PVALID,LATIN SMALL LETTER L WITH ACUTE -013B,DISALLOWED,LATIN CAPITAL LETTER L WITH CEDILLA -013C,PVALID,LATIN SMALL LETTER L WITH CEDILLA -013D,DISALLOWED,LATIN CAPITAL LETTER L WITH CARON -013E,PVALID,LATIN SMALL LETTER L WITH CARON -013F-0141,DISALLOWED,LATIN CAPITAL LETTER L WITH MIDDLE DOT..LATIN CAPITAL LETTER L WITH STROKE -0142,PVALID,LATIN SMALL LETTER L WITH STROKE -0143,DISALLOWED,LATIN CAPITAL LETTER N WITH ACUTE -0144,PVALID,LATIN SMALL LETTER N WITH ACUTE -0145,DISALLOWED,LATIN CAPITAL LETTER N WITH CEDILLA -0146,PVALID,LATIN SMALL LETTER N WITH CEDILLA -0147,DISALLOWED,LATIN CAPITAL LETTER N WITH CARON -0148,PVALID,LATIN SMALL LETTER N WITH CARON -0149-014A,DISALLOWED,LATIN SMALL LETTER N PRECEDED BY APOSTROPHE..LATIN CAPITAL LETTER ENG -014B,PVALID,LATIN SMALL LETTER ENG -014C,DISALLOWED,LATIN CAPITAL LETTER O WITH MACRON -014D,PVALID,LATIN SMALL LETTER O WITH MACRON -014E,DISALLOWED,LATIN CAPITAL LETTER O WITH BREVE -014F,PVALID,LATIN SMALL LETTER O WITH BREVE -0150,DISALLOWED,LATIN CAPITAL LETTER O WITH DOUBLE ACUTE -0151,PVALID,LATIN SMALL LETTER O WITH DOUBLE ACUTE -0152,DISALLOWED,LATIN CAPITAL LIGATURE OE -0153,PVALID,LATIN SMALL LIGATURE OE -0154,DISALLOWED,LATIN CAPITAL LETTER R WITH ACUTE -0155,PVALID,LATIN SMALL LETTER R WITH ACUTE -0156,DISALLOWED,LATIN CAPITAL LETTER R WITH CEDILLA -0157,PVALID,LATIN SMALL LETTER R WITH CEDILLA -0158,DISALLOWED,LATIN CAPITAL LETTER R WITH CARON -0159,PVALID,LATIN SMALL LETTER R WITH CARON -015A,DISALLOWED,LATIN CAPITAL LETTER S WITH ACUTE -015B,PVALID,LATIN SMALL LETTER S WITH ACUTE -015C,DISALLOWED,LATIN CAPITAL LETTER S WITH CIRCUMFLEX -015D,PVALID,LATIN SMALL LETTER S WITH CIRCUMFLEX -015E,DISALLOWED,LATIN CAPITAL LETTER S WITH CEDILLA -015F,PVALID,LATIN SMALL LETTER S WITH CEDILLA -0160,DISALLOWED,LATIN CAPITAL LETTER S WITH CARON -0161,PVALID,LATIN SMALL LETTER S WITH CARON -0162,DISALLOWED,LATIN CAPITAL LETTER T WITH CEDILLA -0163,PVALID,LATIN SMALL LETTER T WITH CEDILLA -0164,DISALLOWED,LATIN CAPITAL LETTER T WITH CARON -0165,PVALID,LATIN SMALL LETTER T WITH CARON -0166,DISALLOWED,LATIN CAPITAL LETTER T WITH STROKE -0167,PVALID,LATIN SMALL LETTER T WITH STROKE -0168,DISALLOWED,LATIN CAPITAL LETTER U WITH TILDE -0169,PVALID,LATIN SMALL LETTER U WITH TILDE -016A,DISALLOWED,LATIN CAPITAL LETTER U WITH MACRON -016B,PVALID,LATIN SMALL LETTER U WITH MACRON -016C,DISALLOWED,LATIN CAPITAL LETTER U WITH BREVE -016D,PVALID,LATIN SMALL LETTER U WITH BREVE -016E,DISALLOWED,LATIN CAPITAL LETTER U WITH RING ABOVE -016F,PVALID,LATIN SMALL LETTER U WITH RING ABOVE -0170,DISALLOWED,LATIN CAPITAL LETTER U WITH DOUBLE ACUTE -0171,PVALID,LATIN SMALL LETTER U WITH DOUBLE ACUTE -0172,DISALLOWED,LATIN CAPITAL LETTER U WITH OGONEK -0173,PVALID,LATIN SMALL LETTER U WITH OGONEK -0174,DISALLOWED,LATIN CAPITAL LETTER W WITH CIRCUMFLEX -0175,PVALID,LATIN SMALL LETTER W WITH CIRCUMFLEX -0176,DISALLOWED,LATIN CAPITAL LETTER Y WITH CIRCUMFLEX -0177,PVALID,LATIN SMALL LETTER Y WITH CIRCUMFLEX -0178-0179,DISALLOWED,LATIN CAPITAL LETTER Y WITH DIAERESIS..LATIN CAPITAL LETTER Z WITH ACUTE -017A,PVALID,LATIN SMALL LETTER Z WITH ACUTE -017B,DISALLOWED,LATIN CAPITAL LETTER Z WITH DOT ABOVE -017C,PVALID,LATIN SMALL LETTER Z WITH DOT ABOVE -017D,DISALLOWED,LATIN CAPITAL LETTER Z WITH CARON -017E,PVALID,LATIN SMALL LETTER Z WITH CARON -017F,DISALLOWED,LATIN SMALL LETTER LONG S -0180,PVALID,LATIN SMALL LETTER B WITH STROKE -0181-0182,DISALLOWED,LATIN CAPITAL LETTER B WITH HOOK..LATIN CAPITAL LETTER B WITH TOPBAR -0183,PVALID,LATIN SMALL LETTER B WITH TOPBAR -0184,DISALLOWED,LATIN CAPITAL LETTER TONE SIX -0185,PVALID,LATIN SMALL LETTER TONE SIX -0186-0187,DISALLOWED,LATIN CAPITAL LETTER OPEN O..LATIN CAPITAL LETTER C WITH HOOK -0188,PVALID,LATIN SMALL LETTER C WITH HOOK -0189-018B,DISALLOWED,LATIN CAPITAL LETTER AFRICAN D..LATIN CAPITAL LETTER D WITH TOPBAR -018C-018D,PVALID,LATIN SMALL LETTER D WITH TOPBAR..LATIN SMALL LETTER TURNED DELTA -018E-0191,DISALLOWED,LATIN CAPITAL LETTER REVERSED E..LATIN CAPITAL LETTER F WITH HOOK -0192,PVALID,LATIN SMALL LETTER F WITH HOOK -0193-0194,DISALLOWED,LATIN CAPITAL LETTER G WITH HOOK..LATIN CAPITAL LETTER GAMMA -0195,PVALID,LATIN SMALL LETTER HV -0196-0198,DISALLOWED,LATIN CAPITAL LETTER IOTA..LATIN CAPITAL LETTER K WITH HOOK -0199-019B,PVALID,LATIN SMALL LETTER K WITH HOOK..LATIN SMALL LETTER LAMBDA WITH STROKE -019C-019D,DISALLOWED,LATIN CAPITAL LETTER TURNED M..LATIN CAPITAL LETTER N WITH LEFT HOOK -019E,PVALID,LATIN SMALL LETTER N WITH LONG RIGHT LEG -019F-01A0,DISALLOWED,LATIN CAPITAL LETTER O WITH MIDDLE TILDE..LATIN CAPITAL LETTER O WITH HORN -01A1,PVALID,LATIN SMALL LETTER O WITH HORN -01A2,DISALLOWED,LATIN CAPITAL LETTER OI -01A3,PVALID,LATIN SMALL LETTER OI -01A4,DISALLOWED,LATIN CAPITAL LETTER P WITH HOOK -01A5,PVALID,LATIN SMALL LETTER P WITH HOOK -01A6-01A7,DISALLOWED,LATIN LETTER YR..LATIN CAPITAL LETTER TONE TWO -01A8,PVALID,LATIN SMALL LETTER TONE TWO -01A9,DISALLOWED,LATIN CAPITAL LETTER ESH -01AA-01AB,PVALID,LATIN LETTER REVERSED ESH LOOP..LATIN SMALL LETTER T WITH PALATAL HOOK -01AC,DISALLOWED,LATIN CAPITAL LETTER T WITH HOOK -01AD,PVALID,LATIN SMALL LETTER T WITH HOOK -01AE-01AF,DISALLOWED,LATIN CAPITAL LETTER T WITH RETROFLEX HOOK..LATIN CAPITAL LETTER U WITH HORN -01B0,PVALID,LATIN SMALL LETTER U WITH HORN -01B1-01B3,DISALLOWED,LATIN CAPITAL LETTER UPSILON..LATIN CAPITAL LETTER Y WITH HOOK -01B4,PVALID,LATIN SMALL LETTER Y WITH HOOK -01B5,DISALLOWED,LATIN CAPITAL LETTER Z WITH STROKE -01B6,PVALID,LATIN SMALL LETTER Z WITH STROKE -01B7-01B8,DISALLOWED,LATIN CAPITAL LETTER EZH..LATIN CAPITAL LETTER EZH REVERSED -01B9-01BB,PVALID,LATIN SMALL LETTER EZH REVERSED..LATIN LETTER TWO WITH STROKE -01BC,DISALLOWED,LATIN CAPITAL LETTER TONE FIVE -01BD-01C3,PVALID,LATIN SMALL LETTER TONE FIVE..LATIN LETTER RETROFLEX CLICK -01C4-01CD,DISALLOWED,LATIN CAPITAL LETTER DZ WITH CARON..LATIN CAPITAL LETTER A WITH CARON -01CE,PVALID,LATIN SMALL LETTER A WITH CARON -01CF,DISALLOWED,LATIN CAPITAL LETTER I WITH CARON -01D0,PVALID,LATIN SMALL LETTER I WITH CARON -01D1,DISALLOWED,LATIN CAPITAL LETTER O WITH CARON -01D2,PVALID,LATIN SMALL LETTER O WITH CARON -01D3,DISALLOWED,LATIN CAPITAL LETTER U WITH CARON -01D4,PVALID,LATIN SMALL LETTER U WITH CARON -01D5,DISALLOWED,LATIN CAPITAL LETTER U WITH DIAERESIS AND MACRON -01D6,PVALID,LATIN SMALL LETTER U WITH DIAERESIS AND MACRON -01D7,DISALLOWED,LATIN CAPITAL LETTER U WITH DIAERESIS AND ACUTE -01D8,PVALID,LATIN SMALL LETTER U WITH DIAERESIS AND ACUTE -01D9,DISALLOWED,LATIN CAPITAL LETTER U WITH DIAERESIS AND CARON -01DA,PVALID,LATIN SMALL LETTER U WITH DIAERESIS AND CARON -01DB,DISALLOWED,LATIN CAPITAL LETTER U WITH DIAERESIS AND GRAVE -01DC-01DD,PVALID,LATIN SMALL LETTER U WITH DIAERESIS AND GRAVE..LATIN SMALL LETTER TURNED E -01DE,DISALLOWED,LATIN CAPITAL LETTER A WITH DIAERESIS AND MACRON -01DF,PVALID,LATIN SMALL LETTER A WITH DIAERESIS AND MACRON -01E0,DISALLOWED,LATIN CAPITAL LETTER A WITH DOT ABOVE AND MACRON -01E1,PVALID,LATIN SMALL LETTER A WITH DOT ABOVE AND MACRON -01E2,DISALLOWED,LATIN CAPITAL LETTER AE WITH MACRON -01E3,PVALID,LATIN SMALL LETTER AE WITH MACRON -01E4,DISALLOWED,LATIN CAPITAL LETTER G WITH STROKE -01E5,PVALID,LATIN SMALL LETTER G WITH STROKE -01E6,DISALLOWED,LATIN CAPITAL LETTER G WITH CARON -01E7,PVALID,LATIN SMALL LETTER G WITH CARON -01E8,DISALLOWED,LATIN CAPITAL LETTER K WITH CARON -01E9,PVALID,LATIN SMALL LETTER K WITH CARON -01EA,DISALLOWED,LATIN CAPITAL LETTER O WITH OGONEK -01EB,PVALID,LATIN SMALL LETTER O WITH OGONEK -01EC,DISALLOWED,LATIN CAPITAL LETTER O WITH OGONEK AND MACRON -01ED,PVALID,LATIN SMALL LETTER O WITH OGONEK AND MACRON -01EE,DISALLOWED,LATIN CAPITAL LETTER EZH WITH CARON -01EF-01F0,PVALID,LATIN SMALL LETTER EZH WITH CARON..LATIN SMALL LETTER J WITH CARON -01F1-01F4,DISALLOWED,LATIN CAPITAL LETTER DZ..LATIN CAPITAL LETTER G WITH ACUTE -01F5,PVALID,LATIN SMALL LETTER G WITH ACUTE -01F6-01F8,DISALLOWED,LATIN CAPITAL LETTER HWAIR..LATIN CAPITAL LETTER N WITH GRAVE -01F9,PVALID,LATIN SMALL LETTER N WITH GRAVE -01FA,DISALLOWED,LATIN CAPITAL LETTER A WITH RING ABOVE AND ACUTE -01FB,PVALID,LATIN SMALL LETTER A WITH RING ABOVE AND ACUTE -01FC,DISALLOWED,LATIN CAPITAL LETTER AE WITH ACUTE -01FD,PVALID,LATIN SMALL LETTER AE WITH ACUTE -01FE,DISALLOWED,LATIN CAPITAL LETTER O WITH STROKE AND ACUTE -01FF,PVALID,LATIN SMALL LETTER O WITH STROKE AND ACUTE -0200,DISALLOWED,LATIN CAPITAL LETTER A WITH DOUBLE GRAVE -0201,PVALID,LATIN SMALL LETTER A WITH DOUBLE GRAVE -0202,DISALLOWED,LATIN CAPITAL LETTER A WITH INVERTED BREVE -0203,PVALID,LATIN SMALL LETTER A WITH INVERTED BREVE -0204,DISALLOWED,LATIN CAPITAL LETTER E WITH DOUBLE GRAVE -0205,PVALID,LATIN SMALL LETTER E WITH DOUBLE GRAVE -0206,DISALLOWED,LATIN CAPITAL LETTER E WITH INVERTED BREVE -0207,PVALID,LATIN SMALL LETTER E WITH INVERTED BREVE -0208,DISALLOWED,LATIN CAPITAL LETTER I WITH DOUBLE GRAVE -0209,PVALID,LATIN SMALL LETTER I WITH DOUBLE GRAVE -020A,DISALLOWED,LATIN CAPITAL LETTER I WITH INVERTED BREVE -020B,PVALID,LATIN SMALL LETTER I WITH INVERTED BREVE -020C,DISALLOWED,LATIN CAPITAL LETTER O WITH DOUBLE GRAVE -020D,PVALID,LATIN SMALL LETTER O WITH DOUBLE GRAVE -020E,DISALLOWED,LATIN CAPITAL LETTER O WITH INVERTED BREVE -020F,PVALID,LATIN SMALL LETTER O WITH INVERTED BREVE -0210,DISALLOWED,LATIN CAPITAL LETTER R WITH DOUBLE GRAVE -0211,PVALID,LATIN SMALL LETTER R WITH DOUBLE GRAVE -0212,DISALLOWED,LATIN CAPITAL LETTER R WITH INVERTED BREVE -0213,PVALID,LATIN SMALL LETTER R WITH INVERTED BREVE -0214,DISALLOWED,LATIN CAPITAL LETTER U WITH DOUBLE GRAVE -0215,PVALID,LATIN SMALL LETTER U WITH DOUBLE GRAVE -0216,DISALLOWED,LATIN CAPITAL LETTER U WITH INVERTED BREVE -0217,PVALID,LATIN SMALL LETTER U WITH INVERTED BREVE -0218,DISALLOWED,LATIN CAPITAL LETTER S WITH COMMA BELOW -0219,PVALID,LATIN SMALL LETTER S WITH COMMA BELOW -021A,DISALLOWED,LATIN CAPITAL LETTER T WITH COMMA BELOW -021B,PVALID,LATIN SMALL LETTER T WITH COMMA BELOW -021C,DISALLOWED,LATIN CAPITAL LETTER YOGH -021D,PVALID,LATIN SMALL LETTER YOGH -021E,DISALLOWED,LATIN CAPITAL LETTER H WITH CARON -021F,PVALID,LATIN SMALL LETTER H WITH CARON -0220,DISALLOWED,LATIN CAPITAL LETTER N WITH LONG RIGHT LEG -0221,PVALID,LATIN SMALL LETTER D WITH CURL -0222,DISALLOWED,LATIN CAPITAL LETTER OU -0223,PVALID,LATIN SMALL LETTER OU -0224,DISALLOWED,LATIN CAPITAL LETTER Z WITH HOOK -0225,PVALID,LATIN SMALL LETTER Z WITH HOOK -0226,DISALLOWED,LATIN CAPITAL LETTER A WITH DOT ABOVE -0227,PVALID,LATIN SMALL LETTER A WITH DOT ABOVE -0228,DISALLOWED,LATIN CAPITAL LETTER E WITH CEDILLA -0229,PVALID,LATIN SMALL LETTER E WITH CEDILLA -022A,DISALLOWED,LATIN CAPITAL LETTER O WITH DIAERESIS AND MACRON -022B,PVALID,LATIN SMALL LETTER O WITH DIAERESIS AND MACRON -022C,DISALLOWED,LATIN CAPITAL LETTER O WITH TILDE AND MACRON -022D,PVALID,LATIN SMALL LETTER O WITH TILDE AND MACRON -022E,DISALLOWED,LATIN CAPITAL LETTER O WITH DOT ABOVE -022F,PVALID,LATIN SMALL LETTER O WITH DOT ABOVE -0230,DISALLOWED,LATIN CAPITAL LETTER O WITH DOT ABOVE AND MACRON -0231,PVALID,LATIN SMALL LETTER O WITH DOT ABOVE AND MACRON -0232,DISALLOWED,LATIN CAPITAL LETTER Y WITH MACRON -0233-0239,PVALID,LATIN SMALL LETTER Y WITH MACRON..LATIN SMALL LETTER QP DIGRAPH -023A-023B,DISALLOWED,LATIN CAPITAL LETTER A WITH STROKE..LATIN CAPITAL LETTER C WITH STROKE -023C,PVALID,LATIN SMALL LETTER C WITH STROKE -023D-023E,DISALLOWED,LATIN CAPITAL LETTER L WITH BAR..LATIN CAPITAL LETTER T WITH DIAGONAL STROKE -023F-0240,PVALID,LATIN SMALL LETTER S WITH SWASH TAIL..LATIN SMALL LETTER Z WITH SWASH TAIL -0241,DISALLOWED,LATIN CAPITAL LETTER GLOTTAL STOP -0242,PVALID,LATIN SMALL LETTER GLOTTAL STOP -0243-0246,DISALLOWED,LATIN CAPITAL LETTER B WITH STROKE..LATIN CAPITAL LETTER E WITH STROKE -0247,PVALID,LATIN SMALL LETTER E WITH STROKE -0248,DISALLOWED,LATIN CAPITAL LETTER J WITH STROKE -0249,PVALID,LATIN SMALL LETTER J WITH STROKE -024A,DISALLOWED,LATIN CAPITAL LETTER SMALL Q WITH HOOK TAIL -024B,PVALID,LATIN SMALL LETTER Q WITH HOOK TAIL -024C,DISALLOWED,LATIN CAPITAL LETTER R WITH STROKE -024D,PVALID,LATIN SMALL LETTER R WITH STROKE -024E,DISALLOWED,LATIN CAPITAL LETTER Y WITH STROKE -024F-02AF,PVALID,LATIN SMALL LETTER Y WITH STROKE..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL -02B0-02B8,DISALLOWED,MODIFIER LETTER SMALL H..MODIFIER LETTER SMALL Y -02B9-02C1,PVALID,MODIFIER LETTER PRIME..MODIFIER LETTER REVERSED GLOTTAL STOP -02C2-02C5,DISALLOWED,MODIFIER LETTER LEFT ARROWHEAD..MODIFIER LETTER DOWN ARROWHEAD -02C6-02D1,PVALID,MODIFIER LETTER CIRCUMFLEX ACCENT..MODIFIER LETTER HALF TRIANGULAR COLON -02D2-02EB,DISALLOWED,MODIFIER LETTER CENTRED RIGHT HALF RING..MODIFIER LETTER YANG DEPARTING TONE MARK -02EC,PVALID,MODIFIER LETTER VOICING -02ED,DISALLOWED,MODIFIER LETTER UNASPIRATED -02EE,PVALID,MODIFIER LETTER DOUBLE APOSTROPHE -02EF-02FF,DISALLOWED,MODIFIER LETTER LOW DOWN ARROWHEAD..MODIFIER LETTER LOW LEFT ARROW -0300-033F,PVALID,COMBINING GRAVE ACCENT..COMBINING DOUBLE OVERLINE -0340-0341,DISALLOWED,COMBINING GRAVE TONE MARK..COMBINING ACUTE TONE MARK -0342,PVALID,COMBINING GREEK PERISPOMENI -0343-0345,DISALLOWED,COMBINING GREEK KORONIS..COMBINING GREEK YPOGEGRAMMENI -0346-034E,PVALID,COMBINING BRIDGE ABOVE..COMBINING UPWARDS ARROW BELOW -034F,DISALLOWED,COMBINING GRAPHEME JOINER -0350-036F,PVALID,COMBINING RIGHT ARROWHEAD ABOVE..COMBINING LATIN SMALL LETTER X -0370,DISALLOWED,GREEK CAPITAL LETTER HETA -0371,PVALID,GREEK SMALL LETTER HETA -0372,DISALLOWED,GREEK CAPITAL LETTER ARCHAIC SAMPI -0373,PVALID,GREEK SMALL LETTER ARCHAIC SAMPI -0374,DISALLOWED,GREEK NUMERAL SIGN -0375,CONTEXTO,GREEK LOWER NUMERAL SIGN -0376,DISALLOWED,GREEK CAPITAL LETTER PAMPHYLIAN DIGAMMA -0377,PVALID,GREEK SMALL LETTER PAMPHYLIAN DIGAMMA -0378-0379,UNASSIGNED,.. -037A,DISALLOWED,GREEK YPOGEGRAMMENI -037B-037D,PVALID,GREEK SMALL REVERSED LUNATE SIGMA SYMBOL..GREEK SMALL REVERSED DOTTED LUNATE SIGMA SYMBOL -037E,DISALLOWED,GREEK QUESTION MARK -037F-0383,UNASSIGNED,.. -0384-038A,DISALLOWED,GREEK TONOS..GREEK CAPITAL LETTER IOTA WITH TONOS -038B,UNASSIGNED, -038C,DISALLOWED,GREEK CAPITAL LETTER OMICRON WITH TONOS -038D,UNASSIGNED, -038E-038F,DISALLOWED,GREEK CAPITAL LETTER UPSILON WITH TONOS..GREEK CAPITAL LETTER OMEGA WITH TONOS -0390,PVALID,GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS -0391-03A1,DISALLOWED,GREEK CAPITAL LETTER ALPHA..GREEK CAPITAL LETTER RHO -03A2,UNASSIGNED, -03A3-03AB,DISALLOWED,GREEK CAPITAL LETTER SIGMA..GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA -03AC-03CE,PVALID,GREEK SMALL LETTER ALPHA WITH TONOS..GREEK SMALL LETTER OMEGA WITH TONOS -03CF-03D6,DISALLOWED,GREEK CAPITAL KAI SYMBOL..GREEK PI SYMBOL -03D7,PVALID,GREEK KAI SYMBOL -03D8,DISALLOWED,GREEK LETTER ARCHAIC KOPPA -03D9,PVALID,GREEK SMALL LETTER ARCHAIC KOPPA -03DA,DISALLOWED,GREEK LETTER STIGMA -03DB,PVALID,GREEK SMALL LETTER STIGMA -03DC,DISALLOWED,GREEK LETTER DIGAMMA -03DD,PVALID,GREEK SMALL LETTER DIGAMMA -03DE,DISALLOWED,GREEK LETTER KOPPA -03DF,PVALID,GREEK SMALL LETTER KOPPA -03E0,DISALLOWED,GREEK LETTER SAMPI -03E1,PVALID,GREEK SMALL LETTER SAMPI -03E2,DISALLOWED,COPTIC CAPITAL LETTER SHEI -03E3,PVALID,COPTIC SMALL LETTER SHEI -03E4,DISALLOWED,COPTIC CAPITAL LETTER FEI -03E5,PVALID,COPTIC SMALL LETTER FEI -03E6,DISALLOWED,COPTIC CAPITAL LETTER KHEI -03E7,PVALID,COPTIC SMALL LETTER KHEI -03E8,DISALLOWED,COPTIC CAPITAL LETTER HORI -03E9,PVALID,COPTIC SMALL LETTER HORI -03EA,DISALLOWED,COPTIC CAPITAL LETTER GANGIA -03EB,PVALID,COPTIC SMALL LETTER GANGIA -03EC,DISALLOWED,COPTIC CAPITAL LETTER SHIMA -03ED,PVALID,COPTIC SMALL LETTER SHIMA -03EE,DISALLOWED,COPTIC CAPITAL LETTER DEI -03EF,PVALID,COPTIC SMALL LETTER DEI -03F0-03F2,DISALLOWED,GREEK KAPPA SYMBOL..GREEK LUNATE SIGMA SYMBOL -03F3,PVALID,GREEK LETTER YOT -03F4-03F7,DISALLOWED,GREEK CAPITAL THETA SYMBOL..GREEK CAPITAL LETTER SHO -03F8,PVALID,GREEK SMALL LETTER SHO -03F9-03FA,DISALLOWED,GREEK CAPITAL LUNATE SIGMA SYMBOL..GREEK CAPITAL LETTER SAN -03FB-03FC,PVALID,GREEK SMALL LETTER SAN..GREEK RHO WITH STROKE SYMBOL -03FD-042F,DISALLOWED,GREEK CAPITAL REVERSED LUNATE SIGMA SYMBOL..CYRILLIC CAPITAL LETTER YA -0430-045F,PVALID,CYRILLIC SMALL LETTER A..CYRILLIC SMALL LETTER DZHE -0460,DISALLOWED,CYRILLIC CAPITAL LETTER OMEGA -0461,PVALID,CYRILLIC SMALL LETTER OMEGA -0462,DISALLOWED,CYRILLIC CAPITAL LETTER YAT -0463,PVALID,CYRILLIC SMALL LETTER YAT -0464,DISALLOWED,CYRILLIC CAPITAL LETTER IOTIFIED E -0465,PVALID,CYRILLIC SMALL LETTER IOTIFIED E -0466,DISALLOWED,CYRILLIC CAPITAL LETTER LITTLE YUS -0467,PVALID,CYRILLIC SMALL LETTER LITTLE YUS -0468,DISALLOWED,CYRILLIC CAPITAL LETTER IOTIFIED LITTLE YUS -0469,PVALID,CYRILLIC SMALL LETTER IOTIFIED LITTLE YUS -046A,DISALLOWED,CYRILLIC CAPITAL LETTER BIG YUS -046B,PVALID,CYRILLIC SMALL LETTER BIG YUS -046C,DISALLOWED,CYRILLIC CAPITAL LETTER IOTIFIED BIG YUS -046D,PVALID,CYRILLIC SMALL LETTER IOTIFIED BIG YUS -046E,DISALLOWED,CYRILLIC CAPITAL LETTER KSI -046F,PVALID,CYRILLIC SMALL LETTER KSI -0470,DISALLOWED,CYRILLIC CAPITAL LETTER PSI -0471,PVALID,CYRILLIC SMALL LETTER PSI -0472,DISALLOWED,CYRILLIC CAPITAL LETTER FITA -0473,PVALID,CYRILLIC SMALL LETTER FITA -0474,DISALLOWED,CYRILLIC CAPITAL LETTER IZHITSA -0475,PVALID,CYRILLIC SMALL LETTER IZHITSA -0476,DISALLOWED,CYRILLIC CAPITAL LETTER IZHITSA WITH DOUBLE GRAVE ACCENT -0477,PVALID,CYRILLIC SMALL LETTER IZHITSA WITH DOUBLE GRAVE ACCENT -0478,DISALLOWED,CYRILLIC CAPITAL LETTER UK -0479,PVALID,CYRILLIC SMALL LETTER UK -047A,DISALLOWED,CYRILLIC CAPITAL LETTER ROUND OMEGA -047B,PVALID,CYRILLIC SMALL LETTER ROUND OMEGA -047C,DISALLOWED,CYRILLIC CAPITAL LETTER OMEGA WITH TITLO -047D,PVALID,CYRILLIC SMALL LETTER OMEGA WITH TITLO -047E,DISALLOWED,CYRILLIC CAPITAL LETTER OT -047F,PVALID,CYRILLIC SMALL LETTER OT -0480,DISALLOWED,CYRILLIC CAPITAL LETTER KOPPA -0481,PVALID,CYRILLIC SMALL LETTER KOPPA -0482,DISALLOWED,CYRILLIC THOUSANDS SIGN -0483-0487,PVALID,COMBINING CYRILLIC TITLO..COMBINING CYRILLIC POKRYTIE -0488-048A,DISALLOWED,COMBINING CYRILLIC HUNDRED THOUSANDS SIGN..CYRILLIC CAPITAL LETTER SHORT I WITH TAIL -048B,PVALID,CYRILLIC SMALL LETTER SHORT I WITH TAIL -048C,DISALLOWED,CYRILLIC CAPITAL LETTER SEMISOFT SIGN -048D,PVALID,CYRILLIC SMALL LETTER SEMISOFT SIGN -048E,DISALLOWED,CYRILLIC CAPITAL LETTER ER WITH TICK -048F,PVALID,CYRILLIC SMALL LETTER ER WITH TICK -0490,DISALLOWED,CYRILLIC CAPITAL LETTER GHE WITH UPTURN -0491,PVALID,CYRILLIC SMALL LETTER GHE WITH UPTURN -0492,DISALLOWED,CYRILLIC CAPITAL LETTER GHE WITH STROKE -0493,PVALID,CYRILLIC SMALL LETTER GHE WITH STROKE -0494,DISALLOWED,CYRILLIC CAPITAL LETTER GHE WITH MIDDLE HOOK -0495,PVALID,CYRILLIC SMALL LETTER GHE WITH MIDDLE HOOK -0496,DISALLOWED,CYRILLIC CAPITAL LETTER ZHE WITH DESCENDER -0497,PVALID,CYRILLIC SMALL LETTER ZHE WITH DESCENDER -0498,DISALLOWED,CYRILLIC CAPITAL LETTER ZE WITH DESCENDER -0499,PVALID,CYRILLIC SMALL LETTER ZE WITH DESCENDER -049A,DISALLOWED,CYRILLIC CAPITAL LETTER KA WITH DESCENDER -049B,PVALID,CYRILLIC SMALL LETTER KA WITH DESCENDER -049C,DISALLOWED,CYRILLIC CAPITAL LETTER KA WITH VERTICAL STROKE -049D,PVALID,CYRILLIC SMALL LETTER KA WITH VERTICAL STROKE -049E,DISALLOWED,CYRILLIC CAPITAL LETTER KA WITH STROKE -049F,PVALID,CYRILLIC SMALL LETTER KA WITH STROKE -04A0,DISALLOWED,CYRILLIC CAPITAL LETTER BASHKIR KA -04A1,PVALID,CYRILLIC SMALL LETTER BASHKIR KA -04A2,DISALLOWED,CYRILLIC CAPITAL LETTER EN WITH DESCENDER -04A3,PVALID,CYRILLIC SMALL LETTER EN WITH DESCENDER -04A4,DISALLOWED,CYRILLIC CAPITAL LIGATURE EN GHE -04A5,PVALID,CYRILLIC SMALL LIGATURE EN GHE -04A6,DISALLOWED,CYRILLIC CAPITAL LETTER PE WITH MIDDLE HOOK -04A7,PVALID,CYRILLIC SMALL LETTER PE WITH MIDDLE HOOK -04A8,DISALLOWED,CYRILLIC CAPITAL LETTER ABKHASIAN HA -04A9,PVALID,CYRILLIC SMALL LETTER ABKHASIAN HA -04AA,DISALLOWED,CYRILLIC CAPITAL LETTER ES WITH DESCENDER -04AB,PVALID,CYRILLIC SMALL LETTER ES WITH DESCENDER -04AC,DISALLOWED,CYRILLIC CAPITAL LETTER TE WITH DESCENDER -04AD,PVALID,CYRILLIC SMALL LETTER TE WITH DESCENDER -04AE,DISALLOWED,CYRILLIC CAPITAL LETTER STRAIGHT U -04AF,PVALID,CYRILLIC SMALL LETTER STRAIGHT U -04B0,DISALLOWED,CYRILLIC CAPITAL LETTER STRAIGHT U WITH STROKE -04B1,PVALID,CYRILLIC SMALL LETTER STRAIGHT U WITH STROKE -04B2,DISALLOWED,CYRILLIC CAPITAL LETTER HA WITH DESCENDER -04B3,PVALID,CYRILLIC SMALL LETTER HA WITH DESCENDER -04B4,DISALLOWED,CYRILLIC CAPITAL LIGATURE TE TSE -04B5,PVALID,CYRILLIC SMALL LIGATURE TE TSE -04B6,DISALLOWED,CYRILLIC CAPITAL LETTER CHE WITH DESCENDER -04B7,PVALID,CYRILLIC SMALL LETTER CHE WITH DESCENDER -04B8,DISALLOWED,CYRILLIC CAPITAL LETTER CHE WITH VERTICAL STROKE -04B9,PVALID,CYRILLIC SMALL LETTER CHE WITH VERTICAL STROKE -04BA,DISALLOWED,CYRILLIC CAPITAL LETTER SHHA -04BB,PVALID,CYRILLIC SMALL LETTER SHHA -04BC,DISALLOWED,CYRILLIC CAPITAL LETTER ABKHASIAN CHE -04BD,PVALID,CYRILLIC SMALL LETTER ABKHASIAN CHE -04BE,DISALLOWED,CYRILLIC CAPITAL LETTER ABKHASIAN CHE WITH DESCENDER -04BF,PVALID,CYRILLIC SMALL LETTER ABKHASIAN CHE WITH DESCENDER -04C0-04C1,DISALLOWED,CYRILLIC LETTER PALOCHKA..CYRILLIC CAPITAL LETTER ZHE WITH BREVE -04C2,PVALID,CYRILLIC SMALL LETTER ZHE WITH BREVE -04C3,DISALLOWED,CYRILLIC CAPITAL LETTER KA WITH HOOK -04C4,PVALID,CYRILLIC SMALL LETTER KA WITH HOOK -04C5,DISALLOWED,CYRILLIC CAPITAL LETTER EL WITH TAIL -04C6,PVALID,CYRILLIC SMALL LETTER EL WITH TAIL -04C7,DISALLOWED,CYRILLIC CAPITAL LETTER EN WITH HOOK -04C8,PVALID,CYRILLIC SMALL LETTER EN WITH HOOK -04C9,DISALLOWED,CYRILLIC CAPITAL LETTER EN WITH TAIL -04CA,PVALID,CYRILLIC SMALL LETTER EN WITH TAIL -04CB,DISALLOWED,CYRILLIC CAPITAL LETTER KHAKASSIAN CHE -04CC,PVALID,CYRILLIC SMALL LETTER KHAKASSIAN CHE -04CD,DISALLOWED,CYRILLIC CAPITAL LETTER EM WITH TAIL -04CE-04CF,PVALID,CYRILLIC SMALL LETTER EM WITH TAIL..CYRILLIC SMALL LETTER PALOCHKA -04D0,DISALLOWED,CYRILLIC CAPITAL LETTER A WITH BREVE -04D1,PVALID,CYRILLIC SMALL LETTER A WITH BREVE -04D2,DISALLOWED,CYRILLIC CAPITAL LETTER A WITH DIAERESIS -04D3,PVALID,CYRILLIC SMALL LETTER A WITH DIAERESIS -04D4,DISALLOWED,CYRILLIC CAPITAL LIGATURE A IE -04D5,PVALID,CYRILLIC SMALL LIGATURE A IE -04D6,DISALLOWED,CYRILLIC CAPITAL LETTER IE WITH BREVE -04D7,PVALID,CYRILLIC SMALL LETTER IE WITH BREVE -04D8,DISALLOWED,CYRILLIC CAPITAL LETTER SCHWA -04D9,PVALID,CYRILLIC SMALL LETTER SCHWA -04DA,DISALLOWED,CYRILLIC CAPITAL LETTER SCHWA WITH DIAERESIS -04DB,PVALID,CYRILLIC SMALL LETTER SCHWA WITH DIAERESIS -04DC,DISALLOWED,CYRILLIC CAPITAL LETTER ZHE WITH DIAERESIS -04DD,PVALID,CYRILLIC SMALL LETTER ZHE WITH DIAERESIS -04DE,DISALLOWED,CYRILLIC CAPITAL LETTER ZE WITH DIAERESIS -04DF,PVALID,CYRILLIC SMALL LETTER ZE WITH DIAERESIS -04E0,DISALLOWED,CYRILLIC CAPITAL LETTER ABKHASIAN DZE -04E1,PVALID,CYRILLIC SMALL LETTER ABKHASIAN DZE -04E2,DISALLOWED,CYRILLIC CAPITAL LETTER I WITH MACRON -04E3,PVALID,CYRILLIC SMALL LETTER I WITH MACRON -04E4,DISALLOWED,CYRILLIC CAPITAL LETTER I WITH DIAERESIS -04E5,PVALID,CYRILLIC SMALL LETTER I WITH DIAERESIS -04E6,DISALLOWED,CYRILLIC CAPITAL LETTER O WITH DIAERESIS -04E7,PVALID,CYRILLIC SMALL LETTER O WITH DIAERESIS -04E8,DISALLOWED,CYRILLIC CAPITAL LETTER BARRED O -04E9,PVALID,CYRILLIC SMALL LETTER BARRED O -04EA,DISALLOWED,CYRILLIC CAPITAL LETTER BARRED O WITH DIAERESIS -04EB,PVALID,CYRILLIC SMALL LETTER BARRED O WITH DIAERESIS -04EC,DISALLOWED,CYRILLIC CAPITAL LETTER E WITH DIAERESIS -04ED,PVALID,CYRILLIC SMALL LETTER E WITH DIAERESIS -04EE,DISALLOWED,CYRILLIC CAPITAL LETTER U WITH MACRON -04EF,PVALID,CYRILLIC SMALL LETTER U WITH MACRON -04F0,DISALLOWED,CYRILLIC CAPITAL LETTER U WITH DIAERESIS -04F1,PVALID,CYRILLIC SMALL LETTER U WITH DIAERESIS -04F2,DISALLOWED,CYRILLIC CAPITAL LETTER U WITH DOUBLE ACUTE -04F3,PVALID,CYRILLIC SMALL LETTER U WITH DOUBLE ACUTE -04F4,DISALLOWED,CYRILLIC CAPITAL LETTER CHE WITH DIAERESIS -04F5,PVALID,CYRILLIC SMALL LETTER CHE WITH DIAERESIS -04F6,DISALLOWED,CYRILLIC CAPITAL LETTER GHE WITH DESCENDER -04F7,PVALID,CYRILLIC SMALL LETTER GHE WITH DESCENDER -04F8,DISALLOWED,CYRILLIC CAPITAL LETTER YERU WITH DIAERESIS -04F9,PVALID,CYRILLIC SMALL LETTER YERU WITH DIAERESIS -04FA,DISALLOWED,CYRILLIC CAPITAL LETTER GHE WITH STROKE AND HOOK -04FB,PVALID,CYRILLIC SMALL LETTER GHE WITH STROKE AND HOOK -04FC,DISALLOWED,CYRILLIC CAPITAL LETTER HA WITH HOOK -04FD,PVALID,CYRILLIC SMALL LETTER HA WITH HOOK -04FE,DISALLOWED,CYRILLIC CAPITAL LETTER HA WITH STROKE -04FF,PVALID,CYRILLIC SMALL LETTER HA WITH STROKE -0500,DISALLOWED,CYRILLIC CAPITAL LETTER KOMI DE -0501,PVALID,CYRILLIC SMALL LETTER KOMI DE -0502,DISALLOWED,CYRILLIC CAPITAL LETTER KOMI DJE -0503,PVALID,CYRILLIC SMALL LETTER KOMI DJE -0504,DISALLOWED,CYRILLIC CAPITAL LETTER KOMI ZJE -0505,PVALID,CYRILLIC SMALL LETTER KOMI ZJE -0506,DISALLOWED,CYRILLIC CAPITAL LETTER KOMI DZJE -0507,PVALID,CYRILLIC SMALL LETTER KOMI DZJE -0508,DISALLOWED,CYRILLIC CAPITAL LETTER KOMI LJE -0509,PVALID,CYRILLIC SMALL LETTER KOMI LJE -050A,DISALLOWED,CYRILLIC CAPITAL LETTER KOMI NJE -050B,PVALID,CYRILLIC SMALL LETTER KOMI NJE -050C,DISALLOWED,CYRILLIC CAPITAL LETTER KOMI SJE -050D,PVALID,CYRILLIC SMALL LETTER KOMI SJE -050E,DISALLOWED,CYRILLIC CAPITAL LETTER KOMI TJE -050F,PVALID,CYRILLIC SMALL LETTER KOMI TJE -0510,DISALLOWED,CYRILLIC CAPITAL LETTER REVERSED ZE -0511,PVALID,CYRILLIC SMALL LETTER REVERSED ZE -0512,DISALLOWED,CYRILLIC CAPITAL LETTER EL WITH HOOK -0513,PVALID,CYRILLIC SMALL LETTER EL WITH HOOK -0514,DISALLOWED,CYRILLIC CAPITAL LETTER LHA -0515,PVALID,CYRILLIC SMALL LETTER LHA -0516,DISALLOWED,CYRILLIC CAPITAL LETTER RHA -0517,PVALID,CYRILLIC SMALL LETTER RHA -0518,DISALLOWED,CYRILLIC CAPITAL LETTER YAE -0519,PVALID,CYRILLIC SMALL LETTER YAE -051A,DISALLOWED,CYRILLIC CAPITAL LETTER QA -051B,PVALID,CYRILLIC SMALL LETTER QA -051C,DISALLOWED,CYRILLIC CAPITAL LETTER WE -051D,PVALID,CYRILLIC SMALL LETTER WE -051E,DISALLOWED,CYRILLIC CAPITAL LETTER ALEUT KA -051F,PVALID,CYRILLIC SMALL LETTER ALEUT KA -0520,DISALLOWED,CYRILLIC CAPITAL LETTER EL WITH MIDDLE HOOK -0521,PVALID,CYRILLIC SMALL LETTER EL WITH MIDDLE HOOK -0522,DISALLOWED,CYRILLIC CAPITAL LETTER EN WITH MIDDLE HOOK -0523,PVALID,CYRILLIC SMALL LETTER EN WITH MIDDLE HOOK -0524,DISALLOWED,CYRILLIC CAPITAL LETTER PE WITH DESCENDER -0525,PVALID,CYRILLIC SMALL LETTER PE WITH DESCENDER -0526,DISALLOWED,CYRILLIC CAPITAL LETTER SHHA WITH DESCENDER -0527,PVALID,CYRILLIC SMALL LETTER SHHA WITH DESCENDER -0528-0530,UNASSIGNED,.. -0531-0556,DISALLOWED,ARMENIAN CAPITAL LETTER AYB..ARMENIAN CAPITAL LETTER FEH -0557-0558,UNASSIGNED,.. -0559,PVALID,ARMENIAN MODIFIER LETTER LEFT HALF RING -055A-055F,DISALLOWED,ARMENIAN APOSTROPHE..ARMENIAN ABBREVIATION MARK -0560,UNASSIGNED, -0561-0586,PVALID,ARMENIAN SMALL LETTER AYB..ARMENIAN SMALL LETTER FEH -0587,DISALLOWED,ARMENIAN SMALL LIGATURE ECH YIWN -0588,UNASSIGNED, -0589-058A,DISALLOWED,ARMENIAN FULL STOP..ARMENIAN HYPHEN -058B-058E,UNASSIGNED,.. -058F,DISALLOWED,ARMENIAN DRAM SIGN -0590,UNASSIGNED, -0591-05BD,PVALID,HEBREW ACCENT ETNAHTA..HEBREW POINT METEG -05BE,DISALLOWED,HEBREW PUNCTUATION MAQAF -05BF,PVALID,HEBREW POINT RAFE -05C0,DISALLOWED,HEBREW PUNCTUATION PASEQ -05C1-05C2,PVALID,HEBREW POINT SHIN DOT..HEBREW POINT SIN DOT -05C3,DISALLOWED,HEBREW PUNCTUATION SOF PASUQ -05C4-05C5,PVALID,HEBREW MARK UPPER DOT..HEBREW MARK LOWER DOT -05C6,DISALLOWED,HEBREW PUNCTUATION NUN HAFUKHA -05C7,PVALID,HEBREW POINT QAMATS QATAN -05C8-05CF,UNASSIGNED,.. -05D0-05EA,PVALID,HEBREW LETTER ALEF..HEBREW LETTER TAV -05EB-05EF,UNASSIGNED,.. -05F0-05F2,PVALID,HEBREW LIGATURE YIDDISH DOUBLE VAV..HEBREW LIGATURE YIDDISH DOUBLE YOD -05F3-05F4,CONTEXTO,HEBREW PUNCTUATION GERESH..HEBREW PUNCTUATION GERSHAYIM -05F5-05FF,UNASSIGNED,.. -0600-0604,DISALLOWED,ARABIC NUMBER SIGN..ARABIC SIGN SAMVAT -0605,UNASSIGNED, -0606-060F,DISALLOWED,ARABIC-INDIC CUBE ROOT..ARABIC SIGN MISRA -0610-061A,PVALID,ARABIC SIGN SALLALLAHOU ALAYHE WASSALLAM..ARABIC SMALL KASRA -061B-061C,DISALLOWED,ARABIC SEMICOLON..ARABIC LETTER MARK -061D,UNASSIGNED, -061E-061F,DISALLOWED,ARABIC TRIPLE DOT PUNCTUATION MARK..ARABIC QUESTION MARK -0620-063F,PVALID,ARABIC LETTER KASHMIRI YEH..ARABIC LETTER FARSI YEH WITH THREE DOTS ABOVE -0640,DISALLOWED,ARABIC TATWEEL -0641-065F,PVALID,ARABIC LETTER FEH..ARABIC WAVY HAMZA BELOW -0660-0669,CONTEXTO,ARABIC-INDIC DIGIT ZERO..ARABIC-INDIC DIGIT NINE -066A-066D,DISALLOWED,ARABIC PERCENT SIGN..ARABIC FIVE POINTED STAR -066E-0674,PVALID,ARABIC LETTER DOTLESS BEH..ARABIC LETTER HIGH HAMZA -0675-0678,DISALLOWED,ARABIC LETTER HIGH HAMZA ALEF..ARABIC LETTER HIGH HAMZA YEH -0679-06D3,PVALID,ARABIC LETTER TTEH..ARABIC LETTER YEH BARREE WITH HAMZA ABOVE -06D4,DISALLOWED,ARABIC FULL STOP -06D5-06DC,PVALID,ARABIC LETTER AE..ARABIC SMALL HIGH SEEN -06DD-06DE,DISALLOWED,ARABIC END OF AYAH..ARABIC START OF RUB EL HIZB -06DF-06E8,PVALID,ARABIC SMALL HIGH ROUNDED ZERO..ARABIC SMALL HIGH NOON -06E9,DISALLOWED,ARABIC PLACE OF SAJDAH -06EA-06EF,PVALID,ARABIC EMPTY CENTRE LOW STOP..ARABIC LETTER REH WITH INVERTED V -06F0-06F9,CONTEXTO,EXTENDED ARABIC-INDIC DIGIT ZERO..EXTENDED ARABIC-INDIC DIGIT NINE -06FA-06FF,PVALID,ARABIC LETTER SHEEN WITH DOT BELOW..ARABIC LETTER HEH WITH INVERTED V -0700-070D,DISALLOWED,SYRIAC END OF PARAGRAPH..SYRIAC HARKLEAN ASTERISCUS -070E,UNASSIGNED, -070F,DISALLOWED,SYRIAC ABBREVIATION MARK -0710-074A,PVALID,SYRIAC LETTER ALAPH..SYRIAC BARREKH -074B-074C,UNASSIGNED,.. -074D-07B1,PVALID,SYRIAC LETTER SOGDIAN ZHAIN..THAANA LETTER NAA -07B2-07BF,UNASSIGNED,.. -07C0-07F5,PVALID,NKO DIGIT ZERO..NKO LOW TONE APOSTROPHE -07F6-07FA,DISALLOWED,NKO SYMBOL OO DENNEN..NKO LAJANYALAN -07FB-07FF,UNASSIGNED,.. -0800-082D,PVALID,SAMARITAN LETTER ALAF..SAMARITAN MARK NEQUDAA -082E-082F,UNASSIGNED,.. -0830-083E,DISALLOWED,SAMARITAN PUNCTUATION NEQUDAA..SAMARITAN PUNCTUATION ANNAAU -083F,UNASSIGNED, -0840-085B,PVALID,MANDAIC LETTER HALQA..MANDAIC GEMINATION MARK -085C-085D,UNASSIGNED,.. -085E,DISALLOWED,MANDAIC PUNCTUATION -085F-089F,UNASSIGNED,.. -08A0,PVALID,ARABIC LETTER BEH WITH SMALL V BELOW -08A1,UNASSIGNED, -08A2-08AC,PVALID,ARABIC LETTER JEEM WITH TWO DOTS ABOVE..ARABIC LETTER ROHINGYA YEH -08AD-08E3,UNASSIGNED,.. -08E4-08FE,PVALID,ARABIC CURLY FATHA..ARABIC DAMMA WITH DOT -08FF,UNASSIGNED, -0900-0957,PVALID,DEVANAGARI SIGN INVERTED CANDRABINDU..DEVANAGARI VOWEL SIGN UUE -0958-095F,DISALLOWED,DEVANAGARI LETTER QA..DEVANAGARI LETTER YYA -0960-0963,PVALID,DEVANAGARI LETTER VOCALIC RR..DEVANAGARI VOWEL SIGN VOCALIC LL -0964-0965,DISALLOWED,DEVANAGARI DANDA..DEVANAGARI DOUBLE DANDA -0966-096F,PVALID,DEVANAGARI DIGIT ZERO..DEVANAGARI DIGIT NINE -0970,DISALLOWED,DEVANAGARI ABBREVIATION SIGN -0971-0977,PVALID,DEVANAGARI SIGN HIGH SPACING DOT..DEVANAGARI LETTER UUE -0978,UNASSIGNED, -0979-097F,PVALID,DEVANAGARI LETTER ZHA..DEVANAGARI LETTER BBA -0980,UNASSIGNED, -0981-0983,PVALID,BENGALI SIGN CANDRABINDU..BENGALI SIGN VISARGA -0984,UNASSIGNED, -0985-098C,PVALID,BENGALI LETTER A..BENGALI LETTER VOCALIC L -098D-098E,UNASSIGNED,.. -098F-0990,PVALID,BENGALI LETTER E..BENGALI LETTER AI -0991-0992,UNASSIGNED,.. -0993-09A8,PVALID,BENGALI LETTER O..BENGALI LETTER NA -09A9,UNASSIGNED, -09AA-09B0,PVALID,BENGALI LETTER PA..BENGALI LETTER RA -09B1,UNASSIGNED, -09B2,PVALID,BENGALI LETTER LA -09B3-09B5,UNASSIGNED,.. -09B6-09B9,PVALID,BENGALI LETTER SHA..BENGALI LETTER HA -09BA-09BB,UNASSIGNED,.. -09BC-09C4,PVALID,BENGALI SIGN NUKTA..BENGALI VOWEL SIGN VOCALIC RR -09C5-09C6,UNASSIGNED,.. -09C7-09C8,PVALID,BENGALI VOWEL SIGN E..BENGALI VOWEL SIGN AI -09C9-09CA,UNASSIGNED,.. -09CB-09CE,PVALID,BENGALI VOWEL SIGN O..BENGALI LETTER KHANDA TA -09CF-09D6,UNASSIGNED,.. -09D7,PVALID,BENGALI AU LENGTH MARK -09D8-09DB,UNASSIGNED,.. -09DC-09DD,DISALLOWED,BENGALI LETTER RRA..BENGALI LETTER RHA -09DE,UNASSIGNED, -09DF,DISALLOWED,BENGALI LETTER YYA -09E0-09E3,PVALID,BENGALI LETTER VOCALIC RR..BENGALI VOWEL SIGN VOCALIC LL -09E4-09E5,UNASSIGNED,.. -09E6-09F1,PVALID,BENGALI DIGIT ZERO..BENGALI LETTER RA WITH LOWER DIAGONAL -09F2-09FB,DISALLOWED,BENGALI RUPEE MARK..BENGALI GANDA MARK -09FC-0A00,UNASSIGNED,.. -0A01-0A03,PVALID,GURMUKHI SIGN ADAK BINDI..GURMUKHI SIGN VISARGA -0A04,UNASSIGNED, -0A05-0A0A,PVALID,GURMUKHI LETTER A..GURMUKHI LETTER UU -0A0B-0A0E,UNASSIGNED,.. -0A0F-0A10,PVALID,GURMUKHI LETTER EE..GURMUKHI LETTER AI -0A11-0A12,UNASSIGNED,.. -0A13-0A28,PVALID,GURMUKHI LETTER OO..GURMUKHI LETTER NA -0A29,UNASSIGNED, -0A2A-0A30,PVALID,GURMUKHI LETTER PA..GURMUKHI LETTER RA -0A31,UNASSIGNED, -0A32,PVALID,GURMUKHI LETTER LA -0A33,DISALLOWED,GURMUKHI LETTER LLA -0A34,UNASSIGNED, -0A35,PVALID,GURMUKHI LETTER VA -0A36,DISALLOWED,GURMUKHI LETTER SHA -0A37,UNASSIGNED, -0A38-0A39,PVALID,GURMUKHI LETTER SA..GURMUKHI LETTER HA -0A3A-0A3B,UNASSIGNED,.. -0A3C,PVALID,GURMUKHI SIGN NUKTA -0A3D,UNASSIGNED, -0A3E-0A42,PVALID,GURMUKHI VOWEL SIGN AA..GURMUKHI VOWEL SIGN UU -0A43-0A46,UNASSIGNED,.. -0A47-0A48,PVALID,GURMUKHI VOWEL SIGN EE..GURMUKHI VOWEL SIGN AI -0A49-0A4A,UNASSIGNED,.. -0A4B-0A4D,PVALID,GURMUKHI VOWEL SIGN OO..GURMUKHI SIGN VIRAMA -0A4E-0A50,UNASSIGNED,.. -0A51,PVALID,GURMUKHI SIGN UDAAT -0A52-0A58,UNASSIGNED,.. -0A59-0A5B,DISALLOWED,GURMUKHI LETTER KHHA..GURMUKHI LETTER ZA -0A5C,PVALID,GURMUKHI LETTER RRA -0A5D,UNASSIGNED, -0A5E,DISALLOWED,GURMUKHI LETTER FA -0A5F-0A65,UNASSIGNED,.. -0A66-0A75,PVALID,GURMUKHI DIGIT ZERO..GURMUKHI SIGN YAKASH -0A76-0A80,UNASSIGNED,.. -0A81-0A83,PVALID,GUJARATI SIGN CANDRABINDU..GUJARATI SIGN VISARGA -0A84,UNASSIGNED, -0A85-0A8D,PVALID,GUJARATI LETTER A..GUJARATI VOWEL CANDRA E -0A8E,UNASSIGNED, -0A8F-0A91,PVALID,GUJARATI LETTER E..GUJARATI VOWEL CANDRA O -0A92,UNASSIGNED, -0A93-0AA8,PVALID,GUJARATI LETTER O..GUJARATI LETTER NA -0AA9,UNASSIGNED, -0AAA-0AB0,PVALID,GUJARATI LETTER PA..GUJARATI LETTER RA -0AB1,UNASSIGNED, -0AB2-0AB3,PVALID,GUJARATI LETTER LA..GUJARATI LETTER LLA -0AB4,UNASSIGNED, -0AB5-0AB9,PVALID,GUJARATI LETTER VA..GUJARATI LETTER HA -0ABA-0ABB,UNASSIGNED,.. -0ABC-0AC5,PVALID,GUJARATI SIGN NUKTA..GUJARATI VOWEL SIGN CANDRA E -0AC6,UNASSIGNED, -0AC7-0AC9,PVALID,GUJARATI VOWEL SIGN E..GUJARATI VOWEL SIGN CANDRA O -0ACA,UNASSIGNED, -0ACB-0ACD,PVALID,GUJARATI VOWEL SIGN O..GUJARATI SIGN VIRAMA -0ACE-0ACF,UNASSIGNED,.. -0AD0,PVALID,GUJARATI OM -0AD1-0ADF,UNASSIGNED,.. -0AE0-0AE3,PVALID,GUJARATI LETTER VOCALIC RR..GUJARATI VOWEL SIGN VOCALIC LL -0AE4-0AE5,UNASSIGNED,.. -0AE6-0AEF,PVALID,GUJARATI DIGIT ZERO..GUJARATI DIGIT NINE -0AF0-0AF1,DISALLOWED,GUJARATI ABBREVIATION SIGN..GUJARATI RUPEE SIGN -0AF2-0B00,UNASSIGNED,.. -0B01-0B03,PVALID,ORIYA SIGN CANDRABINDU..ORIYA SIGN VISARGA -0B04,UNASSIGNED, -0B05-0B0C,PVALID,ORIYA LETTER A..ORIYA LETTER VOCALIC L -0B0D-0B0E,UNASSIGNED,.. -0B0F-0B10,PVALID,ORIYA LETTER E..ORIYA LETTER AI -0B11-0B12,UNASSIGNED,.. -0B13-0B28,PVALID,ORIYA LETTER O..ORIYA LETTER NA -0B29,UNASSIGNED, -0B2A-0B30,PVALID,ORIYA LETTER PA..ORIYA LETTER RA -0B31,UNASSIGNED, -0B32-0B33,PVALID,ORIYA LETTER LA..ORIYA LETTER LLA -0B34,UNASSIGNED, -0B35-0B39,PVALID,ORIYA LETTER VA..ORIYA LETTER HA -0B3A-0B3B,UNASSIGNED,.. -0B3C-0B44,PVALID,ORIYA SIGN NUKTA..ORIYA VOWEL SIGN VOCALIC RR -0B45-0B46,UNASSIGNED,.. -0B47-0B48,PVALID,ORIYA VOWEL SIGN E..ORIYA VOWEL SIGN AI -0B49-0B4A,UNASSIGNED,.. -0B4B-0B4D,PVALID,ORIYA VOWEL SIGN O..ORIYA SIGN VIRAMA -0B4E-0B55,UNASSIGNED,.. -0B56-0B57,PVALID,ORIYA AI LENGTH MARK..ORIYA AU LENGTH MARK -0B58-0B5B,UNASSIGNED,.. -0B5C-0B5D,DISALLOWED,ORIYA LETTER RRA..ORIYA LETTER RHA -0B5E,UNASSIGNED, -0B5F-0B63,PVALID,ORIYA LETTER YYA..ORIYA VOWEL SIGN VOCALIC LL -0B64-0B65,UNASSIGNED,.. -0B66-0B6F,PVALID,ORIYA DIGIT ZERO..ORIYA DIGIT NINE -0B70,DISALLOWED,ORIYA ISSHAR -0B71,PVALID,ORIYA LETTER WA -0B72-0B77,DISALLOWED,ORIYA FRACTION ONE QUARTER..ORIYA FRACTION THREE SIXTEENTHS -0B78-0B81,UNASSIGNED,.. -0B82-0B83,PVALID,TAMIL SIGN ANUSVARA..TAMIL SIGN VISARGA -0B84,UNASSIGNED, -0B85-0B8A,PVALID,TAMIL LETTER A..TAMIL LETTER UU -0B8B-0B8D,UNASSIGNED,.. -0B8E-0B90,PVALID,TAMIL LETTER E..TAMIL LETTER AI -0B91,UNASSIGNED, -0B92-0B95,PVALID,TAMIL LETTER O..TAMIL LETTER KA -0B96-0B98,UNASSIGNED,.. -0B99-0B9A,PVALID,TAMIL LETTER NGA..TAMIL LETTER CA -0B9B,UNASSIGNED, -0B9C,PVALID,TAMIL LETTER JA -0B9D,UNASSIGNED, -0B9E-0B9F,PVALID,TAMIL LETTER NYA..TAMIL LETTER TTA -0BA0-0BA2,UNASSIGNED,.. -0BA3-0BA4,PVALID,TAMIL LETTER NNA..TAMIL LETTER TA -0BA5-0BA7,UNASSIGNED,.. -0BA8-0BAA,PVALID,TAMIL LETTER NA..TAMIL LETTER PA -0BAB-0BAD,UNASSIGNED,.. -0BAE-0BB9,PVALID,TAMIL LETTER MA..TAMIL LETTER HA -0BBA-0BBD,UNASSIGNED,.. -0BBE-0BC2,PVALID,TAMIL VOWEL SIGN AA..TAMIL VOWEL SIGN UU -0BC3-0BC5,UNASSIGNED,.. -0BC6-0BC8,PVALID,TAMIL VOWEL SIGN E..TAMIL VOWEL SIGN AI -0BC9,UNASSIGNED, -0BCA-0BCD,PVALID,TAMIL VOWEL SIGN O..TAMIL SIGN VIRAMA -0BCE-0BCF,UNASSIGNED,.. -0BD0,PVALID,TAMIL OM -0BD1-0BD6,UNASSIGNED,.. -0BD7,PVALID,TAMIL AU LENGTH MARK -0BD8-0BE5,UNASSIGNED,.. -0BE6-0BEF,PVALID,TAMIL DIGIT ZERO..TAMIL DIGIT NINE -0BF0-0BFA,DISALLOWED,TAMIL NUMBER TEN..TAMIL NUMBER SIGN -0BFB-0C00,UNASSIGNED,.. -0C01-0C03,PVALID,TELUGU SIGN CANDRABINDU..TELUGU SIGN VISARGA -0C04,UNASSIGNED, -0C05-0C0C,PVALID,TELUGU LETTER A..TELUGU LETTER VOCALIC L -0C0D,UNASSIGNED, -0C0E-0C10,PVALID,TELUGU LETTER E..TELUGU LETTER AI -0C11,UNASSIGNED, -0C12-0C28,PVALID,TELUGU LETTER O..TELUGU LETTER NA -0C29,UNASSIGNED, -0C2A-0C33,PVALID,TELUGU LETTER PA..TELUGU LETTER LLA -0C34,UNASSIGNED, -0C35-0C39,PVALID,TELUGU LETTER VA..TELUGU LETTER HA -0C3A-0C3C,UNASSIGNED,.. -0C3D-0C44,PVALID,TELUGU SIGN AVAGRAHA..TELUGU VOWEL SIGN VOCALIC RR -0C45,UNASSIGNED, -0C46-0C48,PVALID,TELUGU VOWEL SIGN E..TELUGU VOWEL SIGN AI -0C49,UNASSIGNED, -0C4A-0C4D,PVALID,TELUGU VOWEL SIGN O..TELUGU SIGN VIRAMA -0C4E-0C54,UNASSIGNED,.. -0C55-0C56,PVALID,TELUGU LENGTH MARK..TELUGU AI LENGTH MARK -0C57,UNASSIGNED, -0C58-0C59,PVALID,TELUGU LETTER TSA..TELUGU LETTER DZA -0C5A-0C5F,UNASSIGNED,.. -0C60-0C63,PVALID,TELUGU LETTER VOCALIC RR..TELUGU VOWEL SIGN VOCALIC LL -0C64-0C65,UNASSIGNED,.. -0C66-0C6F,PVALID,TELUGU DIGIT ZERO..TELUGU DIGIT NINE -0C70-0C77,UNASSIGNED,.. -0C78-0C7F,DISALLOWED,TELUGU FRACTION DIGIT ZERO FOR ODD POWERS OF FOUR..TELUGU SIGN TUUMU -0C80-0C81,UNASSIGNED,.. -0C82-0C83,PVALID,KANNADA SIGN ANUSVARA..KANNADA SIGN VISARGA -0C84,UNASSIGNED, -0C85-0C8C,PVALID,KANNADA LETTER A..KANNADA LETTER VOCALIC L -0C8D,UNASSIGNED, -0C8E-0C90,PVALID,KANNADA LETTER E..KANNADA LETTER AI -0C91,UNASSIGNED, -0C92-0CA8,PVALID,KANNADA LETTER O..KANNADA LETTER NA -0CA9,UNASSIGNED, -0CAA-0CB3,PVALID,KANNADA LETTER PA..KANNADA LETTER LLA -0CB4,UNASSIGNED, -0CB5-0CB9,PVALID,KANNADA LETTER VA..KANNADA LETTER HA -0CBA-0CBB,UNASSIGNED,.. -0CBC-0CC4,PVALID,KANNADA SIGN NUKTA..KANNADA VOWEL SIGN VOCALIC RR -0CC5,UNASSIGNED, -0CC6-0CC8,PVALID,KANNADA VOWEL SIGN E..KANNADA VOWEL SIGN AI -0CC9,UNASSIGNED, -0CCA-0CCD,PVALID,KANNADA VOWEL SIGN O..KANNADA SIGN VIRAMA -0CCE-0CD4,UNASSIGNED,.. -0CD5-0CD6,PVALID,KANNADA LENGTH MARK..KANNADA AI LENGTH MARK -0CD7-0CDD,UNASSIGNED,.. -0CDE,PVALID,KANNADA LETTER FA -0CDF,UNASSIGNED, -0CE0-0CE3,PVALID,KANNADA LETTER VOCALIC RR..KANNADA VOWEL SIGN VOCALIC LL -0CE4-0CE5,UNASSIGNED,.. -0CE6-0CEF,PVALID,KANNADA DIGIT ZERO..KANNADA DIGIT NINE -0CF0,UNASSIGNED, -0CF1-0CF2,PVALID,KANNADA SIGN JIHVAMULIYA..KANNADA SIGN UPADHMANIYA -0CF3-0D01,UNASSIGNED,.. -0D02-0D03,PVALID,MALAYALAM SIGN ANUSVARA..MALAYALAM SIGN VISARGA -0D04,UNASSIGNED, -0D05-0D0C,PVALID,MALAYALAM LETTER A..MALAYALAM LETTER VOCALIC L -0D0D,UNASSIGNED, -0D0E-0D10,PVALID,MALAYALAM LETTER E..MALAYALAM LETTER AI -0D11,UNASSIGNED, -0D12-0D3A,PVALID,MALAYALAM LETTER O..MALAYALAM LETTER TTTA -0D3B-0D3C,UNASSIGNED,.. -0D3D-0D44,PVALID,MALAYALAM SIGN AVAGRAHA..MALAYALAM VOWEL SIGN VOCALIC RR -0D45,UNASSIGNED, -0D46-0D48,PVALID,MALAYALAM VOWEL SIGN E..MALAYALAM VOWEL SIGN AI -0D49,UNASSIGNED, -0D4A-0D4E,PVALID,MALAYALAM VOWEL SIGN O..MALAYALAM LETTER DOT REPH -0D4F-0D56,UNASSIGNED,.. -0D57,PVALID,MALAYALAM AU LENGTH MARK -0D58-0D5F,UNASSIGNED,.. -0D60-0D63,PVALID,MALAYALAM LETTER VOCALIC RR..MALAYALAM VOWEL SIGN VOCALIC LL -0D64-0D65,UNASSIGNED,.. -0D66-0D6F,PVALID,MALAYALAM DIGIT ZERO..MALAYALAM DIGIT NINE -0D70-0D75,DISALLOWED,MALAYALAM NUMBER TEN..MALAYALAM FRACTION THREE QUARTERS -0D76-0D78,UNASSIGNED,.. -0D79,DISALLOWED,MALAYALAM DATE MARK -0D7A-0D7F,PVALID,MALAYALAM LETTER CHILLU NN..MALAYALAM LETTER CHILLU K -0D80-0D81,UNASSIGNED,.. -0D82-0D83,PVALID,SINHALA SIGN ANUSVARAYA..SINHALA SIGN VISARGAYA -0D84,UNASSIGNED, -0D85-0D96,PVALID,SINHALA LETTER AYANNA..SINHALA LETTER AUYANNA -0D97-0D99,UNASSIGNED,.. -0D9A-0DB1,PVALID,SINHALA LETTER ALPAPRAANA KAYANNA..SINHALA LETTER DANTAJA NAYANNA -0DB2,UNASSIGNED, -0DB3-0DBB,PVALID,SINHALA LETTER SANYAKA DAYANNA..SINHALA LETTER RAYANNA -0DBC,UNASSIGNED, -0DBD,PVALID,SINHALA LETTER DANTAJA LAYANNA -0DBE-0DBF,UNASSIGNED,.. -0DC0-0DC6,PVALID,SINHALA LETTER VAYANNA..SINHALA LETTER FAYANNA -0DC7-0DC9,UNASSIGNED,.. -0DCA,PVALID,SINHALA SIGN AL-LAKUNA -0DCB-0DCE,UNASSIGNED,.. -0DCF-0DD4,PVALID,SINHALA VOWEL SIGN AELA-PILLA..SINHALA VOWEL SIGN KETTI PAA-PILLA -0DD5,UNASSIGNED, -0DD6,PVALID,SINHALA VOWEL SIGN DIGA PAA-PILLA -0DD7,UNASSIGNED, -0DD8-0DDF,PVALID,SINHALA VOWEL SIGN GAETTA-PILLA..SINHALA VOWEL SIGN GAYANUKITTA -0DE0-0DF1,UNASSIGNED,.. -0DF2-0DF3,PVALID,SINHALA VOWEL SIGN DIGA GAETTA-PILLA..SINHALA VOWEL SIGN DIGA GAYANUKITTA -0DF4,DISALLOWED,SINHALA PUNCTUATION KUNDDALIYA -0DF5-0E00,UNASSIGNED,.. -0E01-0E32,PVALID,THAI CHARACTER KO KAI..THAI CHARACTER SARA AA -0E33,DISALLOWED,THAI CHARACTER SARA AM -0E34-0E3A,PVALID,THAI CHARACTER SARA I..THAI CHARACTER PHINTHU -0E3B-0E3E,UNASSIGNED,.. -0E3F,DISALLOWED,THAI CURRENCY SYMBOL BAHT -0E40-0E4E,PVALID,THAI CHARACTER SARA E..THAI CHARACTER YAMAKKAN -0E4F,DISALLOWED,THAI CHARACTER FONGMAN -0E50-0E59,PVALID,THAI DIGIT ZERO..THAI DIGIT NINE -0E5A-0E5B,DISALLOWED,THAI CHARACTER ANGKHANKHU..THAI CHARACTER KHOMUT -0E5C-0E80,UNASSIGNED,.. -0E81-0E82,PVALID,LAO LETTER KO..LAO LETTER KHO SUNG -0E83,UNASSIGNED, -0E84,PVALID,LAO LETTER KHO TAM -0E85-0E86,UNASSIGNED,.. -0E87-0E88,PVALID,LAO LETTER NGO..LAO LETTER CO -0E89,UNASSIGNED, -0E8A,PVALID,LAO LETTER SO TAM -0E8B-0E8C,UNASSIGNED,.. -0E8D,PVALID,LAO LETTER NYO -0E8E-0E93,UNASSIGNED,.. -0E94-0E97,PVALID,LAO LETTER DO..LAO LETTER THO TAM -0E98,UNASSIGNED, -0E99-0E9F,PVALID,LAO LETTER NO..LAO LETTER FO SUNG -0EA0,UNASSIGNED, -0EA1-0EA3,PVALID,LAO LETTER MO..LAO LETTER LO LING -0EA4,UNASSIGNED, -0EA5,PVALID,LAO LETTER LO LOOT -0EA6,UNASSIGNED, -0EA7,PVALID,LAO LETTER WO -0EA8-0EA9,UNASSIGNED,.. -0EAA-0EAB,PVALID,LAO LETTER SO SUNG..LAO LETTER HO SUNG -0EAC,UNASSIGNED, -0EAD-0EB2,PVALID,LAO LETTER O..LAO VOWEL SIGN AA -0EB3,DISALLOWED,LAO VOWEL SIGN AM -0EB4-0EB9,PVALID,LAO VOWEL SIGN I..LAO VOWEL SIGN UU -0EBA,UNASSIGNED, -0EBB-0EBD,PVALID,LAO VOWEL SIGN MAI KON..LAO SEMIVOWEL SIGN NYO -0EBE-0EBF,UNASSIGNED,.. -0EC0-0EC4,PVALID,LAO VOWEL SIGN E..LAO VOWEL SIGN AI -0EC5,UNASSIGNED, -0EC6,PVALID,LAO KO LA -0EC7,UNASSIGNED, -0EC8-0ECD,PVALID,LAO TONE MAI EK..LAO NIGGAHITA -0ECE-0ECF,UNASSIGNED,.. -0ED0-0ED9,PVALID,LAO DIGIT ZERO..LAO DIGIT NINE -0EDA-0EDB,UNASSIGNED,.. -0EDC-0EDD,DISALLOWED,LAO HO NO..LAO HO MO -0EDE-0EDF,PVALID,LAO LETTER KHMU GO..LAO LETTER KHMU NYO -0EE0-0EFF,UNASSIGNED,.. -0F00,PVALID,TIBETAN SYLLABLE OM -0F01-0F0A,DISALLOWED,TIBETAN MARK GTER YIG MGO TRUNCATED A..TIBETAN MARK BKA- SHOG YIG MGO -0F0B,PVALID,TIBETAN MARK INTERSYLLABIC TSHEG -0F0C-0F17,DISALLOWED,TIBETAN MARK DELIMITER TSHEG BSTAR..TIBETAN ASTROLOGICAL SIGN SGRA GCAN -CHAR RTAGS -0F18-0F19,PVALID,TIBETAN ASTROLOGICAL SIGN -KHYUD PA..TIBETAN ASTROLOGICAL SIGN SDONG TSHUGS -0F1A-0F1F,DISALLOWED,TIBETAN SIGN RDEL DKAR GCIG..TIBETAN SIGN RDEL DKAR RDEL NAG -0F20-0F29,PVALID,TIBETAN DIGIT ZERO..TIBETAN DIGIT NINE -0F2A-0F34,DISALLOWED,TIBETAN DIGIT HALF ONE..TIBETAN MARK BSDUS RTAGS -0F35,PVALID,TIBETAN MARK NGAS BZUNG NYI ZLA -0F36,DISALLOWED,TIBETAN MARK CARET -DZUD RTAGS BZHI MIG CAN -0F37,PVALID,TIBETAN MARK NGAS BZUNG SGOR RTAGS -0F38,DISALLOWED,TIBETAN MARK CHE MGO -0F39,PVALID,TIBETAN MARK TSA -PHRU -0F3A-0F3D,DISALLOWED,TIBETAN MARK GUG RTAGS GYON..TIBETAN MARK ANG KHANG GYAS -0F3E-0F42,PVALID,TIBETAN SIGN YAR TSHES..TIBETAN LETTER GA -0F43,DISALLOWED,TIBETAN LETTER GHA -0F44-0F47,PVALID,TIBETAN LETTER NGA..TIBETAN LETTER JA -0F48,UNASSIGNED, -0F49-0F4C,PVALID,TIBETAN LETTER NYA..TIBETAN LETTER DDA -0F4D,DISALLOWED,TIBETAN LETTER DDHA -0F4E-0F51,PVALID,TIBETAN LETTER NNA..TIBETAN LETTER DA -0F52,DISALLOWED,TIBETAN LETTER DHA -0F53-0F56,PVALID,TIBETAN LETTER NA..TIBETAN LETTER BA -0F57,DISALLOWED,TIBETAN LETTER BHA -0F58-0F5B,PVALID,TIBETAN LETTER MA..TIBETAN LETTER DZA -0F5C,DISALLOWED,TIBETAN LETTER DZHA -0F5D-0F68,PVALID,TIBETAN LETTER WA..TIBETAN LETTER A -0F69,DISALLOWED,TIBETAN LETTER KSSA -0F6A-0F6C,PVALID,TIBETAN LETTER FIXED-FORM RA..TIBETAN LETTER RRA -0F6D-0F70,UNASSIGNED,.. -0F71-0F72,PVALID,TIBETAN VOWEL SIGN AA..TIBETAN VOWEL SIGN I -0F73,DISALLOWED,TIBETAN VOWEL SIGN II -0F74,PVALID,TIBETAN VOWEL SIGN U -0F75-0F79,DISALLOWED,TIBETAN VOWEL SIGN UU..TIBETAN VOWEL SIGN VOCALIC LL -0F7A-0F80,PVALID,TIBETAN VOWEL SIGN E..TIBETAN VOWEL SIGN REVERSED I -0F81,DISALLOWED,TIBETAN VOWEL SIGN REVERSED II -0F82-0F84,PVALID,TIBETAN SIGN NYI ZLA NAA DA..TIBETAN MARK HALANTA -0F85,DISALLOWED,TIBETAN MARK PALUTA -0F86-0F92,PVALID,TIBETAN SIGN LCI RTAGS..TIBETAN SUBJOINED LETTER GA -0F93,DISALLOWED,TIBETAN SUBJOINED LETTER GHA -0F94-0F97,PVALID,TIBETAN SUBJOINED LETTER NGA..TIBETAN SUBJOINED LETTER JA -0F98,UNASSIGNED, -0F99-0F9C,PVALID,TIBETAN SUBJOINED LETTER NYA..TIBETAN SUBJOINED LETTER DDA -0F9D,DISALLOWED,TIBETAN SUBJOINED LETTER DDHA -0F9E-0FA1,PVALID,TIBETAN SUBJOINED LETTER NNA..TIBETAN SUBJOINED LETTER DA -0FA2,DISALLOWED,TIBETAN SUBJOINED LETTER DHA -0FA3-0FA6,PVALID,TIBETAN SUBJOINED LETTER NA..TIBETAN SUBJOINED LETTER BA -0FA7,DISALLOWED,TIBETAN SUBJOINED LETTER BHA -0FA8-0FAB,PVALID,TIBETAN SUBJOINED LETTER MA..TIBETAN SUBJOINED LETTER DZA -0FAC,DISALLOWED,TIBETAN SUBJOINED LETTER DZHA -0FAD-0FB8,PVALID,TIBETAN SUBJOINED LETTER WA..TIBETAN SUBJOINED LETTER A -0FB9,DISALLOWED,TIBETAN SUBJOINED LETTER KSSA -0FBA-0FBC,PVALID,TIBETAN SUBJOINED LETTER FIXED-FORM WA..TIBETAN SUBJOINED LETTER FIXED-FORM RA -0FBD,UNASSIGNED, -0FBE-0FC5,DISALLOWED,TIBETAN KU RU KHA..TIBETAN SYMBOL RDO RJE -0FC6,PVALID,TIBETAN SYMBOL PADMA GDAN -0FC7-0FCC,DISALLOWED,TIBETAN SYMBOL RDO RJE RGYA GRAM..TIBETAN SYMBOL NOR BU BZHI -KHYIL -0FCD,UNASSIGNED, -0FCE-0FDA,DISALLOWED,TIBETAN SIGN RDEL NAG RDEL DKAR..TIBETAN MARK TRAILING MCHAN RTAGS -0FDB-0FFF,UNASSIGNED,.. -1000-1049,PVALID,MYANMAR LETTER KA..MYANMAR DIGIT NINE -104A-104F,DISALLOWED,MYANMAR SIGN LITTLE SECTION..MYANMAR SYMBOL GENITIVE -1050-109D,PVALID,MYANMAR LETTER SHA..MYANMAR VOWEL SIGN AITON AI -109E-10C5,DISALLOWED,MYANMAR SYMBOL SHAN ONE..GEORGIAN CAPITAL LETTER HOE -10C6,UNASSIGNED, -10C7,DISALLOWED,GEORGIAN CAPITAL LETTER YN -10C8-10CC,UNASSIGNED,.. -10CD,DISALLOWED,GEORGIAN CAPITAL LETTER AEN -10CE-10CF,UNASSIGNED,.. -10D0-10FA,PVALID,GEORGIAN LETTER AN..GEORGIAN LETTER AIN -10FB-10FC,DISALLOWED,GEORGIAN PARAGRAPH SEPARATOR..MODIFIER LETTER GEORGIAN NAR -10FD-10FF,PVALID,GEORGIAN LETTER AEN..GEORGIAN LETTER LABIAL SIGN -1100-11FF,DISALLOWED,HANGUL CHOSEONG KIYEOK..HANGUL JONGSEONG SSANGNIEUN -1200-1248,PVALID,ETHIOPIC SYLLABLE HA..ETHIOPIC SYLLABLE QWA -1249,UNASSIGNED, -124A-124D,PVALID,ETHIOPIC SYLLABLE QWI..ETHIOPIC SYLLABLE QWE -124E-124F,UNASSIGNED,.. -1250-1256,PVALID,ETHIOPIC SYLLABLE QHA..ETHIOPIC SYLLABLE QHO -1257,UNASSIGNED, -1258,PVALID,ETHIOPIC SYLLABLE QHWA -1259,UNASSIGNED, -125A-125D,PVALID,ETHIOPIC SYLLABLE QHWI..ETHIOPIC SYLLABLE QHWE -125E-125F,UNASSIGNED,.. -1260-1288,PVALID,ETHIOPIC SYLLABLE BA..ETHIOPIC SYLLABLE XWA -1289,UNASSIGNED, -128A-128D,PVALID,ETHIOPIC SYLLABLE XWI..ETHIOPIC SYLLABLE XWE -128E-128F,UNASSIGNED,.. -1290-12B0,PVALID,ETHIOPIC SYLLABLE NA..ETHIOPIC SYLLABLE KWA -12B1,UNASSIGNED, -12B2-12B5,PVALID,ETHIOPIC SYLLABLE KWI..ETHIOPIC SYLLABLE KWE -12B6-12B7,UNASSIGNED,.. -12B8-12BE,PVALID,ETHIOPIC SYLLABLE KXA..ETHIOPIC SYLLABLE KXO -12BF,UNASSIGNED, -12C0,PVALID,ETHIOPIC SYLLABLE KXWA -12C1,UNASSIGNED, -12C2-12C5,PVALID,ETHIOPIC SYLLABLE KXWI..ETHIOPIC SYLLABLE KXWE -12C6-12C7,UNASSIGNED,.. -12C8-12D6,PVALID,ETHIOPIC SYLLABLE WA..ETHIOPIC SYLLABLE PHARYNGEAL O -12D7,UNASSIGNED, -12D8-1310,PVALID,ETHIOPIC SYLLABLE ZA..ETHIOPIC SYLLABLE GWA -1311,UNASSIGNED, -1312-1315,PVALID,ETHIOPIC SYLLABLE GWI..ETHIOPIC SYLLABLE GWE -1316-1317,UNASSIGNED,.. -1318-135A,PVALID,ETHIOPIC SYLLABLE GGA..ETHIOPIC SYLLABLE FYA -135B-135C,UNASSIGNED,.. -135D-135F,PVALID,ETHIOPIC COMBINING GEMINATION AND VOWEL LENGTH MARK..ETHIOPIC COMBINING GEMINATION MARK -1360-137C,DISALLOWED,ETHIOPIC SECTION MARK..ETHIOPIC NUMBER TEN THOUSAND -137D-137F,UNASSIGNED,.. -1380-138F,PVALID,ETHIOPIC SYLLABLE SEBATBEIT MWA..ETHIOPIC SYLLABLE PWE -1390-1399,DISALLOWED,ETHIOPIC TONAL MARK YIZET..ETHIOPIC TONAL MARK KURT -139A-139F,UNASSIGNED,.. -13A0-13F4,PVALID,CHEROKEE LETTER A..CHEROKEE LETTER YV -13F5-13FF,UNASSIGNED,.. -1400,DISALLOWED,CANADIAN SYLLABICS HYPHEN -1401-166C,PVALID,CANADIAN SYLLABICS E..CANADIAN SYLLABICS CARRIER TTSA -166D-166E,DISALLOWED,CANADIAN SYLLABICS CHI SIGN..CANADIAN SYLLABICS FULL STOP -166F-167F,PVALID,CANADIAN SYLLABICS QAI..CANADIAN SYLLABICS BLACKFOOT W -1680,DISALLOWED,OGHAM SPACE MARK -1681-169A,PVALID,OGHAM LETTER BEITH..OGHAM LETTER PEITH -169B-169C,DISALLOWED,OGHAM FEATHER MARK..OGHAM REVERSED FEATHER MARK -169D-169F,UNASSIGNED,.. -16A0-16EA,PVALID,RUNIC LETTER FEHU FEOH FE F..RUNIC LETTER X -16EB-16F0,DISALLOWED,RUNIC SINGLE PUNCTUATION..RUNIC BELGTHOR SYMBOL -16F1-16FF,UNASSIGNED,.. -1700-170C,PVALID,TAGALOG LETTER A..TAGALOG LETTER YA -170D,UNASSIGNED, -170E-1714,PVALID,TAGALOG LETTER LA..TAGALOG SIGN VIRAMA -1715-171F,UNASSIGNED,.. -1720-1734,PVALID,HANUNOO LETTER A..HANUNOO SIGN PAMUDPOD -1735-1736,DISALLOWED,PHILIPPINE SINGLE PUNCTUATION..PHILIPPINE DOUBLE PUNCTUATION -1737-173F,UNASSIGNED,.. -1740-1753,PVALID,BUHID LETTER A..BUHID VOWEL SIGN U -1754-175F,UNASSIGNED,.. -1760-176C,PVALID,TAGBANWA LETTER A..TAGBANWA LETTER YA -176D,UNASSIGNED, -176E-1770,PVALID,TAGBANWA LETTER LA..TAGBANWA LETTER SA -1771,UNASSIGNED, -1772-1773,PVALID,TAGBANWA VOWEL SIGN I..TAGBANWA VOWEL SIGN U -1774-177F,UNASSIGNED,.. -1780-17B3,PVALID,KHMER LETTER KA..KHMER INDEPENDENT VOWEL QAU -17B4-17B5,DISALLOWED,KHMER VOWEL INHERENT AQ..KHMER VOWEL INHERENT AA -17B6-17D3,PVALID,KHMER VOWEL SIGN AA..KHMER SIGN BATHAMASAT -17D4-17D6,DISALLOWED,KHMER SIGN KHAN..KHMER SIGN CAMNUC PII KUUH -17D7,PVALID,KHMER SIGN LEK TOO -17D8-17DB,DISALLOWED,KHMER SIGN BEYYAL..KHMER CURRENCY SYMBOL RIEL -17DC-17DD,PVALID,KHMER SIGN AVAKRAHASANYA..KHMER SIGN ATTHACAN -17DE-17DF,UNASSIGNED,.. -17E0-17E9,PVALID,KHMER DIGIT ZERO..KHMER DIGIT NINE -17EA-17EF,UNASSIGNED,.. -17F0-17F9,DISALLOWED,KHMER SYMBOL LEK ATTAK SON..KHMER SYMBOL LEK ATTAK PRAM-BUON -17FA-17FF,UNASSIGNED,.. -1800-180E,DISALLOWED,MONGOLIAN BIRGA..MONGOLIAN VOWEL SEPARATOR -180F,UNASSIGNED, -1810-1819,PVALID,MONGOLIAN DIGIT ZERO..MONGOLIAN DIGIT NINE -181A-181F,UNASSIGNED,.. -1820-1877,PVALID,MONGOLIAN LETTER A..MONGOLIAN LETTER MANCHU ZHA -1878-187F,UNASSIGNED,.. -1880-18AA,PVALID,MONGOLIAN LETTER ALI GALI ANUSVARA ONE..MONGOLIAN LETTER MANCHU ALI GALI LHA -18AB-18AF,UNASSIGNED,.. -18B0-18F5,PVALID,CANADIAN SYLLABICS OY..CANADIAN SYLLABICS CARRIER DENTAL S -18F6-18FF,UNASSIGNED,.. -1900-191C,PVALID,LIMBU VOWEL-CARRIER LETTER..LIMBU LETTER HA -191D-191F,UNASSIGNED,.. -1920-192B,PVALID,LIMBU VOWEL SIGN A..LIMBU SUBJOINED LETTER WA -192C-192F,UNASSIGNED,.. -1930-193B,PVALID,LIMBU SMALL LETTER KA..LIMBU SIGN SA-I -193C-193F,UNASSIGNED,.. -1940,DISALLOWED,LIMBU SIGN LOO -1941-1943,UNASSIGNED,.. -1944-1945,DISALLOWED,LIMBU EXCLAMATION MARK..LIMBU QUESTION MARK -1946-196D,PVALID,LIMBU DIGIT ZERO..TAI LE LETTER AI -196E-196F,UNASSIGNED,.. -1970-1974,PVALID,TAI LE LETTER TONE-2..TAI LE LETTER TONE-6 -1975-197F,UNASSIGNED,.. -1980-19AB,PVALID,NEW TAI LUE LETTER HIGH QA..NEW TAI LUE LETTER LOW SUA -19AC-19AF,UNASSIGNED,.. -19B0-19C9,PVALID,NEW TAI LUE VOWEL SIGN VOWEL SHORTENER..NEW TAI LUE TONE MARK-2 -19CA-19CF,UNASSIGNED,.. -19D0-19D9,PVALID,NEW TAI LUE DIGIT ZERO..NEW TAI LUE DIGIT NINE -19DA,DISALLOWED,NEW TAI LUE THAM DIGIT ONE -19DB-19DD,UNASSIGNED,.. -19DE-19FF,DISALLOWED,NEW TAI LUE SIGN LAE..KHMER SYMBOL DAP-PRAM ROC -1A00-1A1B,PVALID,BUGINESE LETTER KA..BUGINESE VOWEL SIGN AE -1A1C-1A1D,UNASSIGNED,.. -1A1E-1A1F,DISALLOWED,BUGINESE PALLAWA..BUGINESE END OF SECTION -1A20-1A5E,PVALID,TAI THAM LETTER HIGH KA..TAI THAM CONSONANT SIGN SA -1A5F,UNASSIGNED, -1A60-1A7C,PVALID,TAI THAM SIGN SAKOT..TAI THAM SIGN KHUEN-LUE KARAN -1A7D-1A7E,UNASSIGNED,.. -1A7F-1A89,PVALID,TAI THAM COMBINING CRYPTOGRAMMIC DOT..TAI THAM HORA DIGIT NINE -1A8A-1A8F,UNASSIGNED,.. -1A90-1A99,PVALID,TAI THAM THAM DIGIT ZERO..TAI THAM THAM DIGIT NINE -1A9A-1A9F,UNASSIGNED,.. -1AA0-1AA6,DISALLOWED,TAI THAM SIGN WIANG..TAI THAM SIGN REVERSED ROTATED RANA -1AA7,PVALID,TAI THAM SIGN MAI YAMOK -1AA8-1AAD,DISALLOWED,TAI THAM SIGN KAAN..TAI THAM SIGN CAANG -1AAE-1AFF,UNASSIGNED,.. -1B00-1B4B,PVALID,BALINESE SIGN ULU RICEM..BALINESE LETTER ASYURA SASAK -1B4C-1B4F,UNASSIGNED,.. -1B50-1B59,PVALID,BALINESE DIGIT ZERO..BALINESE DIGIT NINE -1B5A-1B6A,DISALLOWED,BALINESE PANTI..BALINESE MUSICAL SYMBOL DANG GEDE -1B6B-1B73,PVALID,BALINESE MUSICAL SYMBOL COMBINING TEGEH..BALINESE MUSICAL SYMBOL COMBINING GONG -1B74-1B7C,DISALLOWED,BALINESE MUSICAL SYMBOL RIGHT-HAND OPEN DUG..BALINESE MUSICAL SYMBOL LEFT-HAND OPEN PING -1B7D-1B7F,UNASSIGNED,.. -1B80-1BF3,PVALID,SUNDANESE SIGN PANYECEK..BATAK PANONGONAN -1BF4-1BFB,UNASSIGNED,.. -1BFC-1BFF,DISALLOWED,BATAK SYMBOL BINDU NA METEK..BATAK SYMBOL BINDU PANGOLAT -1C00-1C37,PVALID,LEPCHA LETTER KA..LEPCHA SIGN NUKTA -1C38-1C3A,UNASSIGNED,.. -1C3B-1C3F,DISALLOWED,LEPCHA PUNCTUATION TA-ROL..LEPCHA PUNCTUATION TSHOOK -1C40-1C49,PVALID,LEPCHA DIGIT ZERO..LEPCHA DIGIT NINE -1C4A-1C4C,UNASSIGNED,.. -1C4D-1C7D,PVALID,LEPCHA LETTER TTA..OL CHIKI AHAD -1C7E-1C7F,DISALLOWED,OL CHIKI PUNCTUATION MUCAAD..OL CHIKI PUNCTUATION DOUBLE MUCAAD -1C80-1CBF,UNASSIGNED,.. -1CC0-1CC7,DISALLOWED,SUNDANESE PUNCTUATION BINDU SURYA..SUNDANESE PUNCTUATION BINDU BA SATANGA -1CC8-1CCF,UNASSIGNED,.. -1CD0-1CD2,PVALID,VEDIC TONE KARSHANA..VEDIC TONE PRENKHA -1CD3,DISALLOWED,VEDIC SIGN NIHSHVASA -1CD4-1CF6,PVALID,VEDIC SIGN YAJURVEDIC MIDLINE SVARITA..VEDIC SIGN UPADHMANIYA -1CF7-1CFF,UNASSIGNED,.. -1D00-1D2B,PVALID,LATIN LETTER SMALL CAPITAL A..CYRILLIC LETTER SMALL CAPITAL EL -1D2C-1D2E,DISALLOWED,MODIFIER LETTER CAPITAL A..MODIFIER LETTER CAPITAL B -1D2F,PVALID,MODIFIER LETTER CAPITAL BARRED B -1D30-1D3A,DISALLOWED,MODIFIER LETTER CAPITAL D..MODIFIER LETTER CAPITAL N -1D3B,PVALID,MODIFIER LETTER CAPITAL REVERSED N -1D3C-1D4D,DISALLOWED,MODIFIER LETTER CAPITAL O..MODIFIER LETTER SMALL G -1D4E,PVALID,MODIFIER LETTER SMALL TURNED I -1D4F-1D6A,DISALLOWED,MODIFIER LETTER SMALL K..GREEK SUBSCRIPT SMALL LETTER CHI -1D6B-1D77,PVALID,LATIN SMALL LETTER UE..LATIN SMALL LETTER TURNED G -1D78,DISALLOWED,MODIFIER LETTER CYRILLIC EN -1D79-1D9A,PVALID,LATIN SMALL LETTER INSULAR G..LATIN SMALL LETTER EZH WITH RETROFLEX HOOK -1D9B-1DBF,DISALLOWED,MODIFIER LETTER SMALL TURNED ALPHA..MODIFIER LETTER SMALL THETA -1DC0-1DE6,PVALID,COMBINING DOTTED GRAVE ACCENT..COMBINING LATIN SMALL LETTER Z -1DE7-1DFB,UNASSIGNED,.. -1DFC-1DFF,PVALID,COMBINING DOUBLE INVERTED BREVE BELOW..COMBINING RIGHT ARROWHEAD AND DOWN ARROWHEAD BELOW -1E00,DISALLOWED,LATIN CAPITAL LETTER A WITH RING BELOW -1E01,PVALID,LATIN SMALL LETTER A WITH RING BELOW -1E02,DISALLOWED,LATIN CAPITAL LETTER B WITH DOT ABOVE -1E03,PVALID,LATIN SMALL LETTER B WITH DOT ABOVE -1E04,DISALLOWED,LATIN CAPITAL LETTER B WITH DOT BELOW -1E05,PVALID,LATIN SMALL LETTER B WITH DOT BELOW -1E06,DISALLOWED,LATIN CAPITAL LETTER B WITH LINE BELOW -1E07,PVALID,LATIN SMALL LETTER B WITH LINE BELOW -1E08,DISALLOWED,LATIN CAPITAL LETTER C WITH CEDILLA AND ACUTE -1E09,PVALID,LATIN SMALL LETTER C WITH CEDILLA AND ACUTE -1E0A,DISALLOWED,LATIN CAPITAL LETTER D WITH DOT ABOVE -1E0B,PVALID,LATIN SMALL LETTER D WITH DOT ABOVE -1E0C,DISALLOWED,LATIN CAPITAL LETTER D WITH DOT BELOW -1E0D,PVALID,LATIN SMALL LETTER D WITH DOT BELOW -1E0E,DISALLOWED,LATIN CAPITAL LETTER D WITH LINE BELOW -1E0F,PVALID,LATIN SMALL LETTER D WITH LINE BELOW -1E10,DISALLOWED,LATIN CAPITAL LETTER D WITH CEDILLA -1E11,PVALID,LATIN SMALL LETTER D WITH CEDILLA -1E12,DISALLOWED,LATIN CAPITAL LETTER D WITH CIRCUMFLEX BELOW -1E13,PVALID,LATIN SMALL LETTER D WITH CIRCUMFLEX BELOW -1E14,DISALLOWED,LATIN CAPITAL LETTER E WITH MACRON AND GRAVE -1E15,PVALID,LATIN SMALL LETTER E WITH MACRON AND GRAVE -1E16,DISALLOWED,LATIN CAPITAL LETTER E WITH MACRON AND ACUTE -1E17,PVALID,LATIN SMALL LETTER E WITH MACRON AND ACUTE -1E18,DISALLOWED,LATIN CAPITAL LETTER E WITH CIRCUMFLEX BELOW -1E19,PVALID,LATIN SMALL LETTER E WITH CIRCUMFLEX BELOW -1E1A,DISALLOWED,LATIN CAPITAL LETTER E WITH TILDE BELOW -1E1B,PVALID,LATIN SMALL LETTER E WITH TILDE BELOW -1E1C,DISALLOWED,LATIN CAPITAL LETTER E WITH CEDILLA AND BREVE -1E1D,PVALID,LATIN SMALL LETTER E WITH CEDILLA AND BREVE -1E1E,DISALLOWED,LATIN CAPITAL LETTER F WITH DOT ABOVE -1E1F,PVALID,LATIN SMALL LETTER F WITH DOT ABOVE -1E20,DISALLOWED,LATIN CAPITAL LETTER G WITH MACRON -1E21,PVALID,LATIN SMALL LETTER G WITH MACRON -1E22,DISALLOWED,LATIN CAPITAL LETTER H WITH DOT ABOVE -1E23,PVALID,LATIN SMALL LETTER H WITH DOT ABOVE -1E24,DISALLOWED,LATIN CAPITAL LETTER H WITH DOT BELOW -1E25,PVALID,LATIN SMALL LETTER H WITH DOT BELOW -1E26,DISALLOWED,LATIN CAPITAL LETTER H WITH DIAERESIS -1E27,PVALID,LATIN SMALL LETTER H WITH DIAERESIS -1E28,DISALLOWED,LATIN CAPITAL LETTER H WITH CEDILLA -1E29,PVALID,LATIN SMALL LETTER H WITH CEDILLA -1E2A,DISALLOWED,LATIN CAPITAL LETTER H WITH BREVE BELOW -1E2B,PVALID,LATIN SMALL LETTER H WITH BREVE BELOW -1E2C,DISALLOWED,LATIN CAPITAL LETTER I WITH TILDE BELOW -1E2D,PVALID,LATIN SMALL LETTER I WITH TILDE BELOW -1E2E,DISALLOWED,LATIN CAPITAL LETTER I WITH DIAERESIS AND ACUTE -1E2F,PVALID,LATIN SMALL LETTER I WITH DIAERESIS AND ACUTE -1E30,DISALLOWED,LATIN CAPITAL LETTER K WITH ACUTE -1E31,PVALID,LATIN SMALL LETTER K WITH ACUTE -1E32,DISALLOWED,LATIN CAPITAL LETTER K WITH DOT BELOW -1E33,PVALID,LATIN SMALL LETTER K WITH DOT BELOW -1E34,DISALLOWED,LATIN CAPITAL LETTER K WITH LINE BELOW -1E35,PVALID,LATIN SMALL LETTER K WITH LINE BELOW -1E36,DISALLOWED,LATIN CAPITAL LETTER L WITH DOT BELOW -1E37,PVALID,LATIN SMALL LETTER L WITH DOT BELOW -1E38,DISALLOWED,LATIN CAPITAL LETTER L WITH DOT BELOW AND MACRON -1E39,PVALID,LATIN SMALL LETTER L WITH DOT BELOW AND MACRON -1E3A,DISALLOWED,LATIN CAPITAL LETTER L WITH LINE BELOW -1E3B,PVALID,LATIN SMALL LETTER L WITH LINE BELOW -1E3C,DISALLOWED,LATIN CAPITAL LETTER L WITH CIRCUMFLEX BELOW -1E3D,PVALID,LATIN SMALL LETTER L WITH CIRCUMFLEX BELOW -1E3E,DISALLOWED,LATIN CAPITAL LETTER M WITH ACUTE -1E3F,PVALID,LATIN SMALL LETTER M WITH ACUTE -1E40,DISALLOWED,LATIN CAPITAL LETTER M WITH DOT ABOVE -1E41,PVALID,LATIN SMALL LETTER M WITH DOT ABOVE -1E42,DISALLOWED,LATIN CAPITAL LETTER M WITH DOT BELOW -1E43,PVALID,LATIN SMALL LETTER M WITH DOT BELOW -1E44,DISALLOWED,LATIN CAPITAL LETTER N WITH DOT ABOVE -1E45,PVALID,LATIN SMALL LETTER N WITH DOT ABOVE -1E46,DISALLOWED,LATIN CAPITAL LETTER N WITH DOT BELOW -1E47,PVALID,LATIN SMALL LETTER N WITH DOT BELOW -1E48,DISALLOWED,LATIN CAPITAL LETTER N WITH LINE BELOW -1E49,PVALID,LATIN SMALL LETTER N WITH LINE BELOW -1E4A,DISALLOWED,LATIN CAPITAL LETTER N WITH CIRCUMFLEX BELOW -1E4B,PVALID,LATIN SMALL LETTER N WITH CIRCUMFLEX BELOW -1E4C,DISALLOWED,LATIN CAPITAL LETTER O WITH TILDE AND ACUTE -1E4D,PVALID,LATIN SMALL LETTER O WITH TILDE AND ACUTE -1E4E,DISALLOWED,LATIN CAPITAL LETTER O WITH TILDE AND DIAERESIS -1E4F,PVALID,LATIN SMALL LETTER O WITH TILDE AND DIAERESIS -1E50,DISALLOWED,LATIN CAPITAL LETTER O WITH MACRON AND GRAVE -1E51,PVALID,LATIN SMALL LETTER O WITH MACRON AND GRAVE -1E52,DISALLOWED,LATIN CAPITAL LETTER O WITH MACRON AND ACUTE -1E53,PVALID,LATIN SMALL LETTER O WITH MACRON AND ACUTE -1E54,DISALLOWED,LATIN CAPITAL LETTER P WITH ACUTE -1E55,PVALID,LATIN SMALL LETTER P WITH ACUTE -1E56,DISALLOWED,LATIN CAPITAL LETTER P WITH DOT ABOVE -1E57,PVALID,LATIN SMALL LETTER P WITH DOT ABOVE -1E58,DISALLOWED,LATIN CAPITAL LETTER R WITH DOT ABOVE -1E59,PVALID,LATIN SMALL LETTER R WITH DOT ABOVE -1E5A,DISALLOWED,LATIN CAPITAL LETTER R WITH DOT BELOW -1E5B,PVALID,LATIN SMALL LETTER R WITH DOT BELOW -1E5C,DISALLOWED,LATIN CAPITAL LETTER R WITH DOT BELOW AND MACRON -1E5D,PVALID,LATIN SMALL LETTER R WITH DOT BELOW AND MACRON -1E5E,DISALLOWED,LATIN CAPITAL LETTER R WITH LINE BELOW -1E5F,PVALID,LATIN SMALL LETTER R WITH LINE BELOW -1E60,DISALLOWED,LATIN CAPITAL LETTER S WITH DOT ABOVE -1E61,PVALID,LATIN SMALL LETTER S WITH DOT ABOVE -1E62,DISALLOWED,LATIN CAPITAL LETTER S WITH DOT BELOW -1E63,PVALID,LATIN SMALL LETTER S WITH DOT BELOW -1E64,DISALLOWED,LATIN CAPITAL LETTER S WITH ACUTE AND DOT ABOVE -1E65,PVALID,LATIN SMALL LETTER S WITH ACUTE AND DOT ABOVE -1E66,DISALLOWED,LATIN CAPITAL LETTER S WITH CARON AND DOT ABOVE -1E67,PVALID,LATIN SMALL LETTER S WITH CARON AND DOT ABOVE -1E68,DISALLOWED,LATIN CAPITAL LETTER S WITH DOT BELOW AND DOT ABOVE -1E69,PVALID,LATIN SMALL LETTER S WITH DOT BELOW AND DOT ABOVE -1E6A,DISALLOWED,LATIN CAPITAL LETTER T WITH DOT ABOVE -1E6B,PVALID,LATIN SMALL LETTER T WITH DOT ABOVE -1E6C,DISALLOWED,LATIN CAPITAL LETTER T WITH DOT BELOW -1E6D,PVALID,LATIN SMALL LETTER T WITH DOT BELOW -1E6E,DISALLOWED,LATIN CAPITAL LETTER T WITH LINE BELOW -1E6F,PVALID,LATIN SMALL LETTER T WITH LINE BELOW -1E70,DISALLOWED,LATIN CAPITAL LETTER T WITH CIRCUMFLEX BELOW -1E71,PVALID,LATIN SMALL LETTER T WITH CIRCUMFLEX BELOW -1E72,DISALLOWED,LATIN CAPITAL LETTER U WITH DIAERESIS BELOW -1E73,PVALID,LATIN SMALL LETTER U WITH DIAERESIS BELOW -1E74,DISALLOWED,LATIN CAPITAL LETTER U WITH TILDE BELOW -1E75,PVALID,LATIN SMALL LETTER U WITH TILDE BELOW -1E76,DISALLOWED,LATIN CAPITAL LETTER U WITH CIRCUMFLEX BELOW -1E77,PVALID,LATIN SMALL LETTER U WITH CIRCUMFLEX BELOW -1E78,DISALLOWED,LATIN CAPITAL LETTER U WITH TILDE AND ACUTE -1E79,PVALID,LATIN SMALL LETTER U WITH TILDE AND ACUTE -1E7A,DISALLOWED,LATIN CAPITAL LETTER U WITH MACRON AND DIAERESIS -1E7B,PVALID,LATIN SMALL LETTER U WITH MACRON AND DIAERESIS -1E7C,DISALLOWED,LATIN CAPITAL LETTER V WITH TILDE -1E7D,PVALID,LATIN SMALL LETTER V WITH TILDE -1E7E,DISALLOWED,LATIN CAPITAL LETTER V WITH DOT BELOW -1E7F,PVALID,LATIN SMALL LETTER V WITH DOT BELOW -1E80,DISALLOWED,LATIN CAPITAL LETTER W WITH GRAVE -1E81,PVALID,LATIN SMALL LETTER W WITH GRAVE -1E82,DISALLOWED,LATIN CAPITAL LETTER W WITH ACUTE -1E83,PVALID,LATIN SMALL LETTER W WITH ACUTE -1E84,DISALLOWED,LATIN CAPITAL LETTER W WITH DIAERESIS -1E85,PVALID,LATIN SMALL LETTER W WITH DIAERESIS -1E86,DISALLOWED,LATIN CAPITAL LETTER W WITH DOT ABOVE -1E87,PVALID,LATIN SMALL LETTER W WITH DOT ABOVE -1E88,DISALLOWED,LATIN CAPITAL LETTER W WITH DOT BELOW -1E89,PVALID,LATIN SMALL LETTER W WITH DOT BELOW -1E8A,DISALLOWED,LATIN CAPITAL LETTER X WITH DOT ABOVE -1E8B,PVALID,LATIN SMALL LETTER X WITH DOT ABOVE -1E8C,DISALLOWED,LATIN CAPITAL LETTER X WITH DIAERESIS -1E8D,PVALID,LATIN SMALL LETTER X WITH DIAERESIS -1E8E,DISALLOWED,LATIN CAPITAL LETTER Y WITH DOT ABOVE -1E8F,PVALID,LATIN SMALL LETTER Y WITH DOT ABOVE -1E90,DISALLOWED,LATIN CAPITAL LETTER Z WITH CIRCUMFLEX -1E91,PVALID,LATIN SMALL LETTER Z WITH CIRCUMFLEX -1E92,DISALLOWED,LATIN CAPITAL LETTER Z WITH DOT BELOW -1E93,PVALID,LATIN SMALL LETTER Z WITH DOT BELOW -1E94,DISALLOWED,LATIN CAPITAL LETTER Z WITH LINE BELOW -1E95-1E99,PVALID,LATIN SMALL LETTER Z WITH LINE BELOW..LATIN SMALL LETTER Y WITH RING ABOVE -1E9A-1E9B,DISALLOWED,LATIN SMALL LETTER A WITH RIGHT HALF RING..LATIN SMALL LETTER LONG S WITH DOT ABOVE -1E9C-1E9D,PVALID,LATIN SMALL LETTER LONG S WITH DIAGONAL STROKE..LATIN SMALL LETTER LONG S WITH HIGH STROKE -1E9E,DISALLOWED,LATIN CAPITAL LETTER SHARP S -1E9F,PVALID,LATIN SMALL LETTER DELTA -1EA0,DISALLOWED,LATIN CAPITAL LETTER A WITH DOT BELOW -1EA1,PVALID,LATIN SMALL LETTER A WITH DOT BELOW -1EA2,DISALLOWED,LATIN CAPITAL LETTER A WITH HOOK ABOVE -1EA3,PVALID,LATIN SMALL LETTER A WITH HOOK ABOVE -1EA4,DISALLOWED,LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND ACUTE -1EA5,PVALID,LATIN SMALL LETTER A WITH CIRCUMFLEX AND ACUTE -1EA6,DISALLOWED,LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND GRAVE -1EA7,PVALID,LATIN SMALL LETTER A WITH CIRCUMFLEX AND GRAVE -1EA8,DISALLOWED,LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE -1EA9,PVALID,LATIN SMALL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE -1EAA,DISALLOWED,LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND TILDE -1EAB,PVALID,LATIN SMALL LETTER A WITH CIRCUMFLEX AND TILDE -1EAC,DISALLOWED,LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND DOT BELOW -1EAD,PVALID,LATIN SMALL LETTER A WITH CIRCUMFLEX AND DOT BELOW -1EAE,DISALLOWED,LATIN CAPITAL LETTER A WITH BREVE AND ACUTE -1EAF,PVALID,LATIN SMALL LETTER A WITH BREVE AND ACUTE -1EB0,DISALLOWED,LATIN CAPITAL LETTER A WITH BREVE AND GRAVE -1EB1,PVALID,LATIN SMALL LETTER A WITH BREVE AND GRAVE -1EB2,DISALLOWED,LATIN CAPITAL LETTER A WITH BREVE AND HOOK ABOVE -1EB3,PVALID,LATIN SMALL LETTER A WITH BREVE AND HOOK ABOVE -1EB4,DISALLOWED,LATIN CAPITAL LETTER A WITH BREVE AND TILDE -1EB5,PVALID,LATIN SMALL LETTER A WITH BREVE AND TILDE -1EB6,DISALLOWED,LATIN CAPITAL LETTER A WITH BREVE AND DOT BELOW -1EB7,PVALID,LATIN SMALL LETTER A WITH BREVE AND DOT BELOW -1EB8,DISALLOWED,LATIN CAPITAL LETTER E WITH DOT BELOW -1EB9,PVALID,LATIN SMALL LETTER E WITH DOT BELOW -1EBA,DISALLOWED,LATIN CAPITAL LETTER E WITH HOOK ABOVE -1EBB,PVALID,LATIN SMALL LETTER E WITH HOOK ABOVE -1EBC,DISALLOWED,LATIN CAPITAL LETTER E WITH TILDE -1EBD,PVALID,LATIN SMALL LETTER E WITH TILDE -1EBE,DISALLOWED,LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND ACUTE -1EBF,PVALID,LATIN SMALL LETTER E WITH CIRCUMFLEX AND ACUTE -1EC0,DISALLOWED,LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND GRAVE -1EC1,PVALID,LATIN SMALL LETTER E WITH CIRCUMFLEX AND GRAVE -1EC2,DISALLOWED,LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE -1EC3,PVALID,LATIN SMALL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE -1EC4,DISALLOWED,LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND TILDE -1EC5,PVALID,LATIN SMALL LETTER E WITH CIRCUMFLEX AND TILDE -1EC6,DISALLOWED,LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND DOT BELOW -1EC7,PVALID,LATIN SMALL LETTER E WITH CIRCUMFLEX AND DOT BELOW -1EC8,DISALLOWED,LATIN CAPITAL LETTER I WITH HOOK ABOVE -1EC9,PVALID,LATIN SMALL LETTER I WITH HOOK ABOVE -1ECA,DISALLOWED,LATIN CAPITAL LETTER I WITH DOT BELOW -1ECB,PVALID,LATIN SMALL LETTER I WITH DOT BELOW -1ECC,DISALLOWED,LATIN CAPITAL LETTER O WITH DOT BELOW -1ECD,PVALID,LATIN SMALL LETTER O WITH DOT BELOW -1ECE,DISALLOWED,LATIN CAPITAL LETTER O WITH HOOK ABOVE -1ECF,PVALID,LATIN SMALL LETTER O WITH HOOK ABOVE -1ED0,DISALLOWED,LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND ACUTE -1ED1,PVALID,LATIN SMALL LETTER O WITH CIRCUMFLEX AND ACUTE -1ED2,DISALLOWED,LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND GRAVE -1ED3,PVALID,LATIN SMALL LETTER O WITH CIRCUMFLEX AND GRAVE -1ED4,DISALLOWED,LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE -1ED5,PVALID,LATIN SMALL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE -1ED6,DISALLOWED,LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND TILDE -1ED7,PVALID,LATIN SMALL LETTER O WITH CIRCUMFLEX AND TILDE -1ED8,DISALLOWED,LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND DOT BELOW -1ED9,PVALID,LATIN SMALL LETTER O WITH CIRCUMFLEX AND DOT BELOW -1EDA,DISALLOWED,LATIN CAPITAL LETTER O WITH HORN AND ACUTE -1EDB,PVALID,LATIN SMALL LETTER O WITH HORN AND ACUTE -1EDC,DISALLOWED,LATIN CAPITAL LETTER O WITH HORN AND GRAVE -1EDD,PVALID,LATIN SMALL LETTER O WITH HORN AND GRAVE -1EDE,DISALLOWED,LATIN CAPITAL LETTER O WITH HORN AND HOOK ABOVE -1EDF,PVALID,LATIN SMALL LETTER O WITH HORN AND HOOK ABOVE -1EE0,DISALLOWED,LATIN CAPITAL LETTER O WITH HORN AND TILDE -1EE1,PVALID,LATIN SMALL LETTER O WITH HORN AND TILDE -1EE2,DISALLOWED,LATIN CAPITAL LETTER O WITH HORN AND DOT BELOW -1EE3,PVALID,LATIN SMALL LETTER O WITH HORN AND DOT BELOW -1EE4,DISALLOWED,LATIN CAPITAL LETTER U WITH DOT BELOW -1EE5,PVALID,LATIN SMALL LETTER U WITH DOT BELOW -1EE6,DISALLOWED,LATIN CAPITAL LETTER U WITH HOOK ABOVE -1EE7,PVALID,LATIN SMALL LETTER U WITH HOOK ABOVE -1EE8,DISALLOWED,LATIN CAPITAL LETTER U WITH HORN AND ACUTE -1EE9,PVALID,LATIN SMALL LETTER U WITH HORN AND ACUTE -1EEA,DISALLOWED,LATIN CAPITAL LETTER U WITH HORN AND GRAVE -1EEB,PVALID,LATIN SMALL LETTER U WITH HORN AND GRAVE -1EEC,DISALLOWED,LATIN CAPITAL LETTER U WITH HORN AND HOOK ABOVE -1EED,PVALID,LATIN SMALL LETTER U WITH HORN AND HOOK ABOVE -1EEE,DISALLOWED,LATIN CAPITAL LETTER U WITH HORN AND TILDE -1EEF,PVALID,LATIN SMALL LETTER U WITH HORN AND TILDE -1EF0,DISALLOWED,LATIN CAPITAL LETTER U WITH HORN AND DOT BELOW -1EF1,PVALID,LATIN SMALL LETTER U WITH HORN AND DOT BELOW -1EF2,DISALLOWED,LATIN CAPITAL LETTER Y WITH GRAVE -1EF3,PVALID,LATIN SMALL LETTER Y WITH GRAVE -1EF4,DISALLOWED,LATIN CAPITAL LETTER Y WITH DOT BELOW -1EF5,PVALID,LATIN SMALL LETTER Y WITH DOT BELOW -1EF6,DISALLOWED,LATIN CAPITAL LETTER Y WITH HOOK ABOVE -1EF7,PVALID,LATIN SMALL LETTER Y WITH HOOK ABOVE -1EF8,DISALLOWED,LATIN CAPITAL LETTER Y WITH TILDE -1EF9,PVALID,LATIN SMALL LETTER Y WITH TILDE -1EFA,DISALLOWED,LATIN CAPITAL LETTER MIDDLE-WELSH LL -1EFB,PVALID,LATIN SMALL LETTER MIDDLE-WELSH LL -1EFC,DISALLOWED,LATIN CAPITAL LETTER MIDDLE-WELSH V -1EFD,PVALID,LATIN SMALL LETTER MIDDLE-WELSH V -1EFE,DISALLOWED,LATIN CAPITAL LETTER Y WITH LOOP -1EFF-1F07,PVALID,LATIN SMALL LETTER Y WITH LOOP..GREEK SMALL LETTER ALPHA WITH DASIA AND PERISPOMENI -1F08-1F0F,DISALLOWED,GREEK CAPITAL LETTER ALPHA WITH PSILI..GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI -1F10-1F15,PVALID,GREEK SMALL LETTER EPSILON WITH PSILI..GREEK SMALL LETTER EPSILON WITH DASIA AND OXIA -1F16-1F17,UNASSIGNED,.. -1F18-1F1D,DISALLOWED,GREEK CAPITAL LETTER EPSILON WITH PSILI..GREEK CAPITAL LETTER EPSILON WITH DASIA AND OXIA -1F1E-1F1F,UNASSIGNED,.. -1F20-1F27,PVALID,GREEK SMALL LETTER ETA WITH PSILI..GREEK SMALL LETTER ETA WITH DASIA AND PERISPOMENI -1F28-1F2F,DISALLOWED,GREEK CAPITAL LETTER ETA WITH PSILI..GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI -1F30-1F37,PVALID,GREEK SMALL LETTER IOTA WITH PSILI..GREEK SMALL LETTER IOTA WITH DASIA AND PERISPOMENI -1F38-1F3F,DISALLOWED,GREEK CAPITAL LETTER IOTA WITH PSILI..GREEK CAPITAL LETTER IOTA WITH DASIA AND PERISPOMENI -1F40-1F45,PVALID,GREEK SMALL LETTER OMICRON WITH PSILI..GREEK SMALL LETTER OMICRON WITH DASIA AND OXIA -1F46-1F47,UNASSIGNED,.. -1F48-1F4D,DISALLOWED,GREEK CAPITAL LETTER OMICRON WITH PSILI..GREEK CAPITAL LETTER OMICRON WITH DASIA AND OXIA -1F4E-1F4F,UNASSIGNED,.. -1F50-1F57,PVALID,GREEK SMALL LETTER UPSILON WITH PSILI..GREEK SMALL LETTER UPSILON WITH DASIA AND PERISPOMENI -1F58,UNASSIGNED, -1F59,DISALLOWED,GREEK CAPITAL LETTER UPSILON WITH DASIA -1F5A,UNASSIGNED, -1F5B,DISALLOWED,GREEK CAPITAL LETTER UPSILON WITH DASIA AND VARIA -1F5C,UNASSIGNED, -1F5D,DISALLOWED,GREEK CAPITAL LETTER UPSILON WITH DASIA AND OXIA -1F5E,UNASSIGNED, -1F5F,DISALLOWED,GREEK CAPITAL LETTER UPSILON WITH DASIA AND PERISPOMENI -1F60-1F67,PVALID,GREEK SMALL LETTER OMEGA WITH PSILI..GREEK SMALL LETTER OMEGA WITH DASIA AND PERISPOMENI -1F68-1F6F,DISALLOWED,GREEK CAPITAL LETTER OMEGA WITH PSILI..GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI -1F70,PVALID,GREEK SMALL LETTER ALPHA WITH VARIA -1F71,DISALLOWED,GREEK SMALL LETTER ALPHA WITH OXIA -1F72,PVALID,GREEK SMALL LETTER EPSILON WITH VARIA -1F73,DISALLOWED,GREEK SMALL LETTER EPSILON WITH OXIA -1F74,PVALID,GREEK SMALL LETTER ETA WITH VARIA -1F75,DISALLOWED,GREEK SMALL LETTER ETA WITH OXIA -1F76,PVALID,GREEK SMALL LETTER IOTA WITH VARIA -1F77,DISALLOWED,GREEK SMALL LETTER IOTA WITH OXIA -1F78,PVALID,GREEK SMALL LETTER OMICRON WITH VARIA -1F79,DISALLOWED,GREEK SMALL LETTER OMICRON WITH OXIA -1F7A,PVALID,GREEK SMALL LETTER UPSILON WITH VARIA -1F7B,DISALLOWED,GREEK SMALL LETTER UPSILON WITH OXIA -1F7C,PVALID,GREEK SMALL LETTER OMEGA WITH VARIA -1F7D,DISALLOWED,GREEK SMALL LETTER OMEGA WITH OXIA -1F7E-1F7F,UNASSIGNED,.. -1F80-1FAF,DISALLOWED,GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI..GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI -1FB0-1FB1,PVALID,GREEK SMALL LETTER ALPHA WITH VRACHY..GREEK SMALL LETTER ALPHA WITH MACRON -1FB2-1FB4,DISALLOWED,GREEK SMALL LETTER ALPHA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER ALPHA WITH OXIA AND YPOGEGRAMMENI -1FB5,UNASSIGNED, -1FB6,PVALID,GREEK SMALL LETTER ALPHA WITH PERISPOMENI -1FB7-1FC4,DISALLOWED,GREEK SMALL LETTER ALPHA WITH PERISPOMENI AND YPOGEGRAMMENI..GREEK SMALL LETTER ETA WITH OXIA AND YPOGEGRAMMENI -1FC5,UNASSIGNED, -1FC6,PVALID,GREEK SMALL LETTER ETA WITH PERISPOMENI -1FC7-1FCF,DISALLOWED,GREEK SMALL LETTER ETA WITH PERISPOMENI AND YPOGEGRAMMENI..GREEK PSILI AND PERISPOMENI -1FD0-1FD2,PVALID,GREEK SMALL LETTER IOTA WITH VRACHY..GREEK SMALL LETTER IOTA WITH DIALYTIKA AND VARIA -1FD3,DISALLOWED,GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA -1FD4-1FD5,UNASSIGNED,.. -1FD6-1FD7,PVALID,GREEK SMALL LETTER IOTA WITH PERISPOMENI..GREEK SMALL LETTER IOTA WITH DIALYTIKA AND PERISPOMENI -1FD8-1FDB,DISALLOWED,GREEK CAPITAL LETTER IOTA WITH VRACHY..GREEK CAPITAL LETTER IOTA WITH OXIA -1FDC,UNASSIGNED, -1FDD-1FDF,DISALLOWED,GREEK DASIA AND VARIA..GREEK DASIA AND PERISPOMENI -1FE0-1FE2,PVALID,GREEK SMALL LETTER UPSILON WITH VRACHY..GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND VARIA -1FE3,DISALLOWED,GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND OXIA -1FE4-1FE7,PVALID,GREEK SMALL LETTER RHO WITH PSILI..GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND PERISPOMENI -1FE8-1FEF,DISALLOWED,GREEK CAPITAL LETTER UPSILON WITH VRACHY..GREEK VARIA -1FF0-1FF1,UNASSIGNED,.. -1FF2-1FF4,DISALLOWED,GREEK SMALL LETTER OMEGA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI -1FF5,UNASSIGNED, -1FF6,PVALID,GREEK SMALL LETTER OMEGA WITH PERISPOMENI -1FF7-1FFE,DISALLOWED,GREEK SMALL LETTER OMEGA WITH PERISPOMENI AND YPOGEGRAMMENI..GREEK DASIA -1FFF,UNASSIGNED, -2000-200B,DISALLOWED,EN QUAD..ZERO WIDTH SPACE -200C-200D,CONTEXTJ,ZERO WIDTH NON-JOINER..ZERO WIDTH JOINER -200E-2064,DISALLOWED,LEFT-TO-RIGHT MARK..INVISIBLE PLUS -2065,UNASSIGNED, -2066-2071,DISALLOWED,LEFT-TO-RIGHT ISOLATE..SUPERSCRIPT LATIN SMALL LETTER I -2072-2073,UNASSIGNED,.. -2074-208E,DISALLOWED,SUPERSCRIPT FOUR..SUBSCRIPT RIGHT PARENTHESIS -208F,UNASSIGNED, -2090-209C,DISALLOWED,LATIN SUBSCRIPT SMALL LETTER A..LATIN SUBSCRIPT SMALL LETTER T -209D-209F,UNASSIGNED,.. -20A0-20BA,DISALLOWED,EURO-CURRENCY SIGN..TURKISH LIRA SIGN -20BB-20CF,UNASSIGNED,.. -20D0-20F0,DISALLOWED,COMBINING LEFT HARPOON ABOVE..COMBINING ASTERISK ABOVE -20F1-20FF,UNASSIGNED,.. -2100-214D,DISALLOWED,ACCOUNT OF..AKTIESELSKAB -214E,PVALID,TURNED SMALL F -214F-2183,DISALLOWED,SYMBOL FOR SAMARITAN SOURCE..ROMAN NUMERAL REVERSED ONE HUNDRED -2184,PVALID,LATIN SMALL LETTER REVERSED C -2185-2189,DISALLOWED,ROMAN NUMERAL SIX LATE FORM..VULGAR FRACTION ZERO THIRDS -218A-218F,UNASSIGNED,.. -2190-23F3,DISALLOWED,LEFTWARDS ARROW..HOURGLASS WITH FLOWING SAND -23F4-23FF,UNASSIGNED,.. -2400-2426,DISALLOWED,SYMBOL FOR NULL..SYMBOL FOR SUBSTITUTE FORM TWO -2427-243F,UNASSIGNED,.. -2440-244A,DISALLOWED,OCR HOOK..OCR DOUBLE BACKSLASH -244B-245F,UNASSIGNED,.. -2460-26FF,DISALLOWED,CIRCLED DIGIT ONE..WHITE FLAG WITH HORIZONTAL MIDDLE BLACK STRIPE -2700,UNASSIGNED, -2701-2B4C,DISALLOWED,UPPER BLADE SCISSORS..RIGHTWARDS ARROW ABOVE REVERSE TILDE OPERATOR -2B4D-2B4F,UNASSIGNED,.. -2B50-2B59,DISALLOWED,WHITE MEDIUM STAR..HEAVY CIRCLED SALTIRE -2B5A-2BFF,UNASSIGNED,.. -2C00-2C2E,DISALLOWED,GLAGOLITIC CAPITAL LETTER AZU..GLAGOLITIC CAPITAL LETTER LATINATE MYSLITE -2C2F,UNASSIGNED, -2C30-2C5E,PVALID,GLAGOLITIC SMALL LETTER AZU..GLAGOLITIC SMALL LETTER LATINATE MYSLITE -2C5F,UNASSIGNED, -2C60,DISALLOWED,LATIN CAPITAL LETTER L WITH DOUBLE BAR -2C61,PVALID,LATIN SMALL LETTER L WITH DOUBLE BAR -2C62-2C64,DISALLOWED,LATIN CAPITAL LETTER L WITH MIDDLE TILDE..LATIN CAPITAL LETTER R WITH TAIL -2C65-2C66,PVALID,LATIN SMALL LETTER A WITH STROKE..LATIN SMALL LETTER T WITH DIAGONAL STROKE -2C67,DISALLOWED,LATIN CAPITAL LETTER H WITH DESCENDER -2C68,PVALID,LATIN SMALL LETTER H WITH DESCENDER -2C69,DISALLOWED,LATIN CAPITAL LETTER K WITH DESCENDER -2C6A,PVALID,LATIN SMALL LETTER K WITH DESCENDER -2C6B,DISALLOWED,LATIN CAPITAL LETTER Z WITH DESCENDER -2C6C,PVALID,LATIN SMALL LETTER Z WITH DESCENDER -2C6D-2C70,DISALLOWED,LATIN CAPITAL LETTER ALPHA..LATIN CAPITAL LETTER TURNED ALPHA -2C71,PVALID,LATIN SMALL LETTER V WITH RIGHT HOOK -2C72,DISALLOWED,LATIN CAPITAL LETTER W WITH HOOK -2C73-2C74,PVALID,LATIN SMALL LETTER W WITH HOOK..LATIN SMALL LETTER V WITH CURL -2C75,DISALLOWED,LATIN CAPITAL LETTER HALF H -2C76-2C7B,PVALID,LATIN SMALL LETTER HALF H..LATIN LETTER SMALL CAPITAL TURNED E -2C7C-2C80,DISALLOWED,LATIN SUBSCRIPT SMALL LETTER J..COPTIC CAPITAL LETTER ALFA -2C81,PVALID,COPTIC SMALL LETTER ALFA -2C82,DISALLOWED,COPTIC CAPITAL LETTER VIDA -2C83,PVALID,COPTIC SMALL LETTER VIDA -2C84,DISALLOWED,COPTIC CAPITAL LETTER GAMMA -2C85,PVALID,COPTIC SMALL LETTER GAMMA -2C86,DISALLOWED,COPTIC CAPITAL LETTER DALDA -2C87,PVALID,COPTIC SMALL LETTER DALDA -2C88,DISALLOWED,COPTIC CAPITAL LETTER EIE -2C89,PVALID,COPTIC SMALL LETTER EIE -2C8A,DISALLOWED,COPTIC CAPITAL LETTER SOU -2C8B,PVALID,COPTIC SMALL LETTER SOU -2C8C,DISALLOWED,COPTIC CAPITAL LETTER ZATA -2C8D,PVALID,COPTIC SMALL LETTER ZATA -2C8E,DISALLOWED,COPTIC CAPITAL LETTER HATE -2C8F,PVALID,COPTIC SMALL LETTER HATE -2C90,DISALLOWED,COPTIC CAPITAL LETTER THETHE -2C91,PVALID,COPTIC SMALL LETTER THETHE -2C92,DISALLOWED,COPTIC CAPITAL LETTER IAUDA -2C93,PVALID,COPTIC SMALL LETTER IAUDA -2C94,DISALLOWED,COPTIC CAPITAL LETTER KAPA -2C95,PVALID,COPTIC SMALL LETTER KAPA -2C96,DISALLOWED,COPTIC CAPITAL LETTER LAULA -2C97,PVALID,COPTIC SMALL LETTER LAULA -2C98,DISALLOWED,COPTIC CAPITAL LETTER MI -2C99,PVALID,COPTIC SMALL LETTER MI -2C9A,DISALLOWED,COPTIC CAPITAL LETTER NI -2C9B,PVALID,COPTIC SMALL LETTER NI -2C9C,DISALLOWED,COPTIC CAPITAL LETTER KSI -2C9D,PVALID,COPTIC SMALL LETTER KSI -2C9E,DISALLOWED,COPTIC CAPITAL LETTER O -2C9F,PVALID,COPTIC SMALL LETTER O -2CA0,DISALLOWED,COPTIC CAPITAL LETTER PI -2CA1,PVALID,COPTIC SMALL LETTER PI -2CA2,DISALLOWED,COPTIC CAPITAL LETTER RO -2CA3,PVALID,COPTIC SMALL LETTER RO -2CA4,DISALLOWED,COPTIC CAPITAL LETTER SIMA -2CA5,PVALID,COPTIC SMALL LETTER SIMA -2CA6,DISALLOWED,COPTIC CAPITAL LETTER TAU -2CA7,PVALID,COPTIC SMALL LETTER TAU -2CA8,DISALLOWED,COPTIC CAPITAL LETTER UA -2CA9,PVALID,COPTIC SMALL LETTER UA -2CAA,DISALLOWED,COPTIC CAPITAL LETTER FI -2CAB,PVALID,COPTIC SMALL LETTER FI -2CAC,DISALLOWED,COPTIC CAPITAL LETTER KHI -2CAD,PVALID,COPTIC SMALL LETTER KHI -2CAE,DISALLOWED,COPTIC CAPITAL LETTER PSI -2CAF,PVALID,COPTIC SMALL LETTER PSI -2CB0,DISALLOWED,COPTIC CAPITAL LETTER OOU -2CB1,PVALID,COPTIC SMALL LETTER OOU -2CB2,DISALLOWED,COPTIC CAPITAL LETTER DIALECT-P ALEF -2CB3,PVALID,COPTIC SMALL LETTER DIALECT-P ALEF -2CB4,DISALLOWED,COPTIC CAPITAL LETTER OLD COPTIC AIN -2CB5,PVALID,COPTIC SMALL LETTER OLD COPTIC AIN -2CB6,DISALLOWED,COPTIC CAPITAL LETTER CRYPTOGRAMMIC EIE -2CB7,PVALID,COPTIC SMALL LETTER CRYPTOGRAMMIC EIE -2CB8,DISALLOWED,COPTIC CAPITAL LETTER DIALECT-P KAPA -2CB9,PVALID,COPTIC SMALL LETTER DIALECT-P KAPA -2CBA,DISALLOWED,COPTIC CAPITAL LETTER DIALECT-P NI -2CBB,PVALID,COPTIC SMALL LETTER DIALECT-P NI -2CBC,DISALLOWED,COPTIC CAPITAL LETTER CRYPTOGRAMMIC NI -2CBD,PVALID,COPTIC SMALL LETTER CRYPTOGRAMMIC NI -2CBE,DISALLOWED,COPTIC CAPITAL LETTER OLD COPTIC OOU -2CBF,PVALID,COPTIC SMALL LETTER OLD COPTIC OOU -2CC0,DISALLOWED,COPTIC CAPITAL LETTER SAMPI -2CC1,PVALID,COPTIC SMALL LETTER SAMPI -2CC2,DISALLOWED,COPTIC CAPITAL LETTER CROSSED SHEI -2CC3,PVALID,COPTIC SMALL LETTER CROSSED SHEI -2CC4,DISALLOWED,COPTIC CAPITAL LETTER OLD COPTIC SHEI -2CC5,PVALID,COPTIC SMALL LETTER OLD COPTIC SHEI -2CC6,DISALLOWED,COPTIC CAPITAL LETTER OLD COPTIC ESH -2CC7,PVALID,COPTIC SMALL LETTER OLD COPTIC ESH -2CC8,DISALLOWED,COPTIC CAPITAL LETTER AKHMIMIC KHEI -2CC9,PVALID,COPTIC SMALL LETTER AKHMIMIC KHEI -2CCA,DISALLOWED,COPTIC CAPITAL LETTER DIALECT-P HORI -2CCB,PVALID,COPTIC SMALL LETTER DIALECT-P HORI -2CCC,DISALLOWED,COPTIC CAPITAL LETTER OLD COPTIC HORI -2CCD,PVALID,COPTIC SMALL LETTER OLD COPTIC HORI -2CCE,DISALLOWED,COPTIC CAPITAL LETTER OLD COPTIC HA -2CCF,PVALID,COPTIC SMALL LETTER OLD COPTIC HA -2CD0,DISALLOWED,COPTIC CAPITAL LETTER L-SHAPED HA -2CD1,PVALID,COPTIC SMALL LETTER L-SHAPED HA -2CD2,DISALLOWED,COPTIC CAPITAL LETTER OLD COPTIC HEI -2CD3,PVALID,COPTIC SMALL LETTER OLD COPTIC HEI -2CD4,DISALLOWED,COPTIC CAPITAL LETTER OLD COPTIC HAT -2CD5,PVALID,COPTIC SMALL LETTER OLD COPTIC HAT -2CD6,DISALLOWED,COPTIC CAPITAL LETTER OLD COPTIC GANGIA -2CD7,PVALID,COPTIC SMALL LETTER OLD COPTIC GANGIA -2CD8,DISALLOWED,COPTIC CAPITAL LETTER OLD COPTIC DJA -2CD9,PVALID,COPTIC SMALL LETTER OLD COPTIC DJA -2CDA,DISALLOWED,COPTIC CAPITAL LETTER OLD COPTIC SHIMA -2CDB,PVALID,COPTIC SMALL LETTER OLD COPTIC SHIMA -2CDC,DISALLOWED,COPTIC CAPITAL LETTER OLD NUBIAN SHIMA -2CDD,PVALID,COPTIC SMALL LETTER OLD NUBIAN SHIMA -2CDE,DISALLOWED,COPTIC CAPITAL LETTER OLD NUBIAN NGI -2CDF,PVALID,COPTIC SMALL LETTER OLD NUBIAN NGI -2CE0,DISALLOWED,COPTIC CAPITAL LETTER OLD NUBIAN NYI -2CE1,PVALID,COPTIC SMALL LETTER OLD NUBIAN NYI -2CE2,DISALLOWED,COPTIC CAPITAL LETTER OLD NUBIAN WAU -2CE3-2CE4,PVALID,COPTIC SMALL LETTER OLD NUBIAN WAU..COPTIC SYMBOL KAI -2CE5-2CEB,DISALLOWED,COPTIC SYMBOL MI RO..COPTIC CAPITAL LETTER CRYPTOGRAMMIC SHEI -2CEC,PVALID,COPTIC SMALL LETTER CRYPTOGRAMMIC SHEI -2CED,DISALLOWED,COPTIC CAPITAL LETTER CRYPTOGRAMMIC GANGIA -2CEE-2CF1,PVALID,COPTIC SMALL LETTER CRYPTOGRAMMIC GANGIA..COPTIC COMBINING SPIRITUS LENIS -2CF2,DISALLOWED,COPTIC CAPITAL LETTER BOHAIRIC KHEI -2CF3,PVALID,COPTIC SMALL LETTER BOHAIRIC KHEI -2CF4-2CF8,UNASSIGNED,.. -2CF9-2CFF,DISALLOWED,COPTIC OLD NUBIAN FULL STOP..COPTIC MORPHOLOGICAL DIVIDER -2D00-2D25,PVALID,GEORGIAN SMALL LETTER AN..GEORGIAN SMALL LETTER HOE -2D26,UNASSIGNED, -2D27,PVALID,GEORGIAN SMALL LETTER YN -2D28-2D2C,UNASSIGNED,.. -2D2D,PVALID,GEORGIAN SMALL LETTER AEN -2D2E-2D2F,UNASSIGNED,.. -2D30-2D67,PVALID,TIFINAGH LETTER YA..TIFINAGH LETTER YO -2D68-2D6E,UNASSIGNED,.. -2D6F-2D70,DISALLOWED,TIFINAGH MODIFIER LETTER LABIALIZATION MARK..TIFINAGH SEPARATOR MARK -2D71-2D7E,UNASSIGNED,.. -2D7F-2D96,PVALID,TIFINAGH CONSONANT JOINER..ETHIOPIC SYLLABLE GGWE -2D97-2D9F,UNASSIGNED,.. -2DA0-2DA6,PVALID,ETHIOPIC SYLLABLE SSA..ETHIOPIC SYLLABLE SSO -2DA7,UNASSIGNED, -2DA8-2DAE,PVALID,ETHIOPIC SYLLABLE CCA..ETHIOPIC SYLLABLE CCO -2DAF,UNASSIGNED, -2DB0-2DB6,PVALID,ETHIOPIC SYLLABLE ZZA..ETHIOPIC SYLLABLE ZZO -2DB7,UNASSIGNED, -2DB8-2DBE,PVALID,ETHIOPIC SYLLABLE CCHA..ETHIOPIC SYLLABLE CCHO -2DBF,UNASSIGNED, -2DC0-2DC6,PVALID,ETHIOPIC SYLLABLE QYA..ETHIOPIC SYLLABLE QYO -2DC7,UNASSIGNED, -2DC8-2DCE,PVALID,ETHIOPIC SYLLABLE KYA..ETHIOPIC SYLLABLE KYO -2DCF,UNASSIGNED, -2DD0-2DD6,PVALID,ETHIOPIC SYLLABLE XYA..ETHIOPIC SYLLABLE XYO -2DD7,UNASSIGNED, -2DD8-2DDE,PVALID,ETHIOPIC SYLLABLE GYA..ETHIOPIC SYLLABLE GYO -2DDF,UNASSIGNED, -2DE0-2DFF,PVALID,COMBINING CYRILLIC LETTER BE..COMBINING CYRILLIC LETTER IOTIFIED BIG YUS -2E00-2E2E,DISALLOWED,RIGHT ANGLE SUBSTITUTION MARKER..REVERSED QUESTION MARK -2E2F,PVALID,VERTICAL TILDE -2E30-2E3B,DISALLOWED,RING POINT..THREE-EM DASH -2E3C-2E7F,UNASSIGNED,.. -2E80-2E99,DISALLOWED,CJK RADICAL REPEAT..CJK RADICAL RAP -2E9A,UNASSIGNED, -2E9B-2EF3,DISALLOWED,CJK RADICAL CHOKE..CJK RADICAL C-SIMPLIFIED TURTLE -2EF4-2EFF,UNASSIGNED,.. -2F00-2FD5,DISALLOWED,KANGXI RADICAL ONE..KANGXI RADICAL FLUTE -2FD6-2FEF,UNASSIGNED,.. -2FF0-2FFB,DISALLOWED,IDEOGRAPHIC DESCRIPTION CHARACTER LEFT TO RIGHT..IDEOGRAPHIC DESCRIPTION CHARACTER OVERLAID -2FFC-2FFF,UNASSIGNED,.. -3000-3004,DISALLOWED,IDEOGRAPHIC SPACE..JAPANESE INDUSTRIAL STANDARD SYMBOL -3005-3007,PVALID,IDEOGRAPHIC ITERATION MARK..IDEOGRAPHIC NUMBER ZERO -3008-3029,DISALLOWED,LEFT ANGLE BRACKET..HANGZHOU NUMERAL NINE -302A-302D,PVALID,IDEOGRAPHIC LEVEL TONE MARK..IDEOGRAPHIC ENTERING TONE MARK -302E-303B,DISALLOWED,HANGUL SINGLE DOT TONE MARK..VERTICAL IDEOGRAPHIC ITERATION MARK -303C,PVALID,MASU MARK -303D-303F,DISALLOWED,PART ALTERNATION MARK..IDEOGRAPHIC HALF FILL SPACE -3040,UNASSIGNED, -3041-3096,PVALID,HIRAGANA LETTER SMALL A..HIRAGANA LETTER SMALL KE -3097-3098,UNASSIGNED,.. -3099-309A,PVALID,COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK..COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK -309B-309C,DISALLOWED,KATAKANA-HIRAGANA VOICED SOUND MARK..KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK -309D-309E,PVALID,HIRAGANA ITERATION MARK..HIRAGANA VOICED ITERATION MARK -309F-30A0,DISALLOWED,HIRAGANA DIGRAPH YORI..KATAKANA-HIRAGANA DOUBLE HYPHEN -30A1-30FA,PVALID,KATAKANA LETTER SMALL A..KATAKANA LETTER VO -30FB,CONTEXTO,KATAKANA MIDDLE DOT -30FC-30FE,PVALID,KATAKANA-HIRAGANA PROLONGED SOUND MARK..KATAKANA VOICED ITERATION MARK -30FF,DISALLOWED,KATAKANA DIGRAPH KOTO -3100-3104,UNASSIGNED,.. -3105-312D,PVALID,BOPOMOFO LETTER B..BOPOMOFO LETTER IH -312E-3130,UNASSIGNED,.. -3131-318E,DISALLOWED,HANGUL LETTER KIYEOK..HANGUL LETTER ARAEAE -318F,UNASSIGNED, -3190-319F,DISALLOWED,IDEOGRAPHIC ANNOTATION LINKING MARK..IDEOGRAPHIC ANNOTATION MAN MARK -31A0-31BA,PVALID,BOPOMOFO LETTER BU..BOPOMOFO LETTER ZY -31BB-31BF,UNASSIGNED,.. -31C0-31E3,DISALLOWED,CJK STROKE T..CJK STROKE Q -31E4-31EF,UNASSIGNED,.. -31F0-31FF,PVALID,KATAKANA LETTER SMALL KU..KATAKANA LETTER SMALL RO -3200-321E,DISALLOWED,PARENTHESIZED HANGUL KIYEOK..PARENTHESIZED KOREAN CHARACTER O HU -321F,UNASSIGNED, -3220-32FE,DISALLOWED,PARENTHESIZED IDEOGRAPH ONE..CIRCLED KATAKANA WO -32FF,UNASSIGNED, -3300-33FF,DISALLOWED,SQUARE APAATO..SQUARE GAL -3400-4DB5,PVALID,".." -4DB6-4DBF,UNASSIGNED,.. -4DC0-4DFF,DISALLOWED,HEXAGRAM FOR THE CREATIVE HEAVEN..HEXAGRAM FOR BEFORE COMPLETION -4E00-9FCC,PVALID,".." -9FCD-9FFF,UNASSIGNED,.. -A000-A48C,PVALID,YI SYLLABLE IT..YI SYLLABLE YYR -A48D-A48F,UNASSIGNED,.. -A490-A4C6,DISALLOWED,YI RADICAL QOT..YI RADICAL KE -A4C7-A4CF,UNASSIGNED,.. -A4D0-A4FD,PVALID,LISU LETTER BA..LISU LETTER TONE MYA JEU -A4FE-A4FF,DISALLOWED,LISU PUNCTUATION COMMA..LISU PUNCTUATION FULL STOP -A500-A60C,PVALID,VAI SYLLABLE EE..VAI SYLLABLE LENGTHENER -A60D-A60F,DISALLOWED,VAI COMMA..VAI QUESTION MARK -A610-A62B,PVALID,VAI SYLLABLE NDOLE FA..VAI SYLLABLE NDOLE DO -A62C-A63F,UNASSIGNED,.. -A640,DISALLOWED,CYRILLIC CAPITAL LETTER ZEMLYA -A641,PVALID,CYRILLIC SMALL LETTER ZEMLYA -A642,DISALLOWED,CYRILLIC CAPITAL LETTER DZELO -A643,PVALID,CYRILLIC SMALL LETTER DZELO -A644,DISALLOWED,CYRILLIC CAPITAL LETTER REVERSED DZE -A645,PVALID,CYRILLIC SMALL LETTER REVERSED DZE -A646,DISALLOWED,CYRILLIC CAPITAL LETTER IOTA -A647,PVALID,CYRILLIC SMALL LETTER IOTA -A648,DISALLOWED,CYRILLIC CAPITAL LETTER DJERV -A649,PVALID,CYRILLIC SMALL LETTER DJERV -A64A,DISALLOWED,CYRILLIC CAPITAL LETTER MONOGRAPH UK -A64B,PVALID,CYRILLIC SMALL LETTER MONOGRAPH UK -A64C,DISALLOWED,CYRILLIC CAPITAL LETTER BROAD OMEGA -A64D,PVALID,CYRILLIC SMALL LETTER BROAD OMEGA -A64E,DISALLOWED,CYRILLIC CAPITAL LETTER NEUTRAL YER -A64F,PVALID,CYRILLIC SMALL LETTER NEUTRAL YER -A650,DISALLOWED,CYRILLIC CAPITAL LETTER YERU WITH BACK YER -A651,PVALID,CYRILLIC SMALL LETTER YERU WITH BACK YER -A652,DISALLOWED,CYRILLIC CAPITAL LETTER IOTIFIED YAT -A653,PVALID,CYRILLIC SMALL LETTER IOTIFIED YAT -A654,DISALLOWED,CYRILLIC CAPITAL LETTER REVERSED YU -A655,PVALID,CYRILLIC SMALL LETTER REVERSED YU -A656,DISALLOWED,CYRILLIC CAPITAL LETTER IOTIFIED A -A657,PVALID,CYRILLIC SMALL LETTER IOTIFIED A -A658,DISALLOWED,CYRILLIC CAPITAL LETTER CLOSED LITTLE YUS -A659,PVALID,CYRILLIC SMALL LETTER CLOSED LITTLE YUS -A65A,DISALLOWED,CYRILLIC CAPITAL LETTER BLENDED YUS -A65B,PVALID,CYRILLIC SMALL LETTER BLENDED YUS -A65C,DISALLOWED,CYRILLIC CAPITAL LETTER IOTIFIED CLOSED LITTLE YUS -A65D,PVALID,CYRILLIC SMALL LETTER IOTIFIED CLOSED LITTLE YUS -A65E,DISALLOWED,CYRILLIC CAPITAL LETTER YN -A65F,PVALID,CYRILLIC SMALL LETTER YN -A660,DISALLOWED,CYRILLIC CAPITAL LETTER REVERSED TSE -A661,PVALID,CYRILLIC SMALL LETTER REVERSED TSE -A662,DISALLOWED,CYRILLIC CAPITAL LETTER SOFT DE -A663,PVALID,CYRILLIC SMALL LETTER SOFT DE -A664,DISALLOWED,CYRILLIC CAPITAL LETTER SOFT EL -A665,PVALID,CYRILLIC SMALL LETTER SOFT EL -A666,DISALLOWED,CYRILLIC CAPITAL LETTER SOFT EM -A667,PVALID,CYRILLIC SMALL LETTER SOFT EM -A668,DISALLOWED,CYRILLIC CAPITAL LETTER MONOCULAR O -A669,PVALID,CYRILLIC SMALL LETTER MONOCULAR O -A66A,DISALLOWED,CYRILLIC CAPITAL LETTER BINOCULAR O -A66B,PVALID,CYRILLIC SMALL LETTER BINOCULAR O -A66C,DISALLOWED,CYRILLIC CAPITAL LETTER DOUBLE MONOCULAR O -A66D-A66F,PVALID,CYRILLIC SMALL LETTER DOUBLE MONOCULAR O..COMBINING CYRILLIC VZMET -A670-A673,DISALLOWED,COMBINING CYRILLIC TEN MILLIONS SIGN..SLAVONIC ASTERISK -A674-A67D,PVALID,COMBINING CYRILLIC LETTER UKRAINIAN IE..COMBINING CYRILLIC PAYEROK -A67E,DISALLOWED,CYRILLIC KAVYKA -A67F,PVALID,CYRILLIC PAYEROK -A680,DISALLOWED,CYRILLIC CAPITAL LETTER DWE -A681,PVALID,CYRILLIC SMALL LETTER DWE -A682,DISALLOWED,CYRILLIC CAPITAL LETTER DZWE -A683,PVALID,CYRILLIC SMALL LETTER DZWE -A684,DISALLOWED,CYRILLIC CAPITAL LETTER ZHWE -A685,PVALID,CYRILLIC SMALL LETTER ZHWE -A686,DISALLOWED,CYRILLIC CAPITAL LETTER CCHE -A687,PVALID,CYRILLIC SMALL LETTER CCHE -A688,DISALLOWED,CYRILLIC CAPITAL LETTER DZZE -A689,PVALID,CYRILLIC SMALL LETTER DZZE -A68A,DISALLOWED,CYRILLIC CAPITAL LETTER TE WITH MIDDLE HOOK -A68B,PVALID,CYRILLIC SMALL LETTER TE WITH MIDDLE HOOK -A68C,DISALLOWED,CYRILLIC CAPITAL LETTER TWE -A68D,PVALID,CYRILLIC SMALL LETTER TWE -A68E,DISALLOWED,CYRILLIC CAPITAL LETTER TSWE -A68F,PVALID,CYRILLIC SMALL LETTER TSWE -A690,DISALLOWED,CYRILLIC CAPITAL LETTER TSSE -A691,PVALID,CYRILLIC SMALL LETTER TSSE -A692,DISALLOWED,CYRILLIC CAPITAL LETTER TCHE -A693,PVALID,CYRILLIC SMALL LETTER TCHE -A694,DISALLOWED,CYRILLIC CAPITAL LETTER HWE -A695,PVALID,CYRILLIC SMALL LETTER HWE -A696,DISALLOWED,CYRILLIC CAPITAL LETTER SHWE -A697,PVALID,CYRILLIC SMALL LETTER SHWE -A698-A69E,UNASSIGNED,.. -A69F-A6E5,PVALID,COMBINING CYRILLIC LETTER IOTIFIED E..BAMUM LETTER KI -A6E6-A6EF,DISALLOWED,BAMUM LETTER MO..BAMUM LETTER KOGHOM -A6F0-A6F1,PVALID,BAMUM COMBINING MARK KOQNDON..BAMUM COMBINING MARK TUKWENTIS -A6F2-A6F7,DISALLOWED,BAMUM NJAEMLI..BAMUM QUESTION MARK -A6F8-A6FF,UNASSIGNED,.. -A700-A716,DISALLOWED,MODIFIER LETTER CHINESE TONE YIN PING..MODIFIER LETTER EXTRA-LOW LEFT-STEM TONE BAR -A717-A71F,PVALID,MODIFIER LETTER DOT VERTICAL BAR..MODIFIER LETTER LOW INVERTED EXCLAMATION MARK -A720-A722,DISALLOWED,MODIFIER LETTER STRESS AND HIGH TONE..LATIN CAPITAL LETTER EGYPTOLOGICAL ALEF -A723,PVALID,LATIN SMALL LETTER EGYPTOLOGICAL ALEF -A724,DISALLOWED,LATIN CAPITAL LETTER EGYPTOLOGICAL AIN -A725,PVALID,LATIN SMALL LETTER EGYPTOLOGICAL AIN -A726,DISALLOWED,LATIN CAPITAL LETTER HENG -A727,PVALID,LATIN SMALL LETTER HENG -A728,DISALLOWED,LATIN CAPITAL LETTER TZ -A729,PVALID,LATIN SMALL LETTER TZ -A72A,DISALLOWED,LATIN CAPITAL LETTER TRESILLO -A72B,PVALID,LATIN SMALL LETTER TRESILLO -A72C,DISALLOWED,LATIN CAPITAL LETTER CUATRILLO -A72D,PVALID,LATIN SMALL LETTER CUATRILLO -A72E,DISALLOWED,LATIN CAPITAL LETTER CUATRILLO WITH COMMA -A72F-A731,PVALID,LATIN SMALL LETTER CUATRILLO WITH COMMA..LATIN LETTER SMALL CAPITAL S -A732,DISALLOWED,LATIN CAPITAL LETTER AA -A733,PVALID,LATIN SMALL LETTER AA -A734,DISALLOWED,LATIN CAPITAL LETTER AO -A735,PVALID,LATIN SMALL LETTER AO -A736,DISALLOWED,LATIN CAPITAL LETTER AU -A737,PVALID,LATIN SMALL LETTER AU -A738,DISALLOWED,LATIN CAPITAL LETTER AV -A739,PVALID,LATIN SMALL LETTER AV -A73A,DISALLOWED,LATIN CAPITAL LETTER AV WITH HORIZONTAL BAR -A73B,PVALID,LATIN SMALL LETTER AV WITH HORIZONTAL BAR -A73C,DISALLOWED,LATIN CAPITAL LETTER AY -A73D,PVALID,LATIN SMALL LETTER AY -A73E,DISALLOWED,LATIN CAPITAL LETTER REVERSED C WITH DOT -A73F,PVALID,LATIN SMALL LETTER REVERSED C WITH DOT -A740,DISALLOWED,LATIN CAPITAL LETTER K WITH STROKE -A741,PVALID,LATIN SMALL LETTER K WITH STROKE -A742,DISALLOWED,LATIN CAPITAL LETTER K WITH DIAGONAL STROKE -A743,PVALID,LATIN SMALL LETTER K WITH DIAGONAL STROKE -A744,DISALLOWED,LATIN CAPITAL LETTER K WITH STROKE AND DIAGONAL STROKE -A745,PVALID,LATIN SMALL LETTER K WITH STROKE AND DIAGONAL STROKE -A746,DISALLOWED,LATIN CAPITAL LETTER BROKEN L -A747,PVALID,LATIN SMALL LETTER BROKEN L -A748,DISALLOWED,LATIN CAPITAL LETTER L WITH HIGH STROKE -A749,PVALID,LATIN SMALL LETTER L WITH HIGH STROKE -A74A,DISALLOWED,LATIN CAPITAL LETTER O WITH LONG STROKE OVERLAY -A74B,PVALID,LATIN SMALL LETTER O WITH LONG STROKE OVERLAY -A74C,DISALLOWED,LATIN CAPITAL LETTER O WITH LOOP -A74D,PVALID,LATIN SMALL LETTER O WITH LOOP -A74E,DISALLOWED,LATIN CAPITAL LETTER OO -A74F,PVALID,LATIN SMALL LETTER OO -A750,DISALLOWED,LATIN CAPITAL LETTER P WITH STROKE THROUGH DESCENDER -A751,PVALID,LATIN SMALL LETTER P WITH STROKE THROUGH DESCENDER -A752,DISALLOWED,LATIN CAPITAL LETTER P WITH FLOURISH -A753,PVALID,LATIN SMALL LETTER P WITH FLOURISH -A754,DISALLOWED,LATIN CAPITAL LETTER P WITH SQUIRREL TAIL -A755,PVALID,LATIN SMALL LETTER P WITH SQUIRREL TAIL -A756,DISALLOWED,LATIN CAPITAL LETTER Q WITH STROKE THROUGH DESCENDER -A757,PVALID,LATIN SMALL LETTER Q WITH STROKE THROUGH DESCENDER -A758,DISALLOWED,LATIN CAPITAL LETTER Q WITH DIAGONAL STROKE -A759,PVALID,LATIN SMALL LETTER Q WITH DIAGONAL STROKE -A75A,DISALLOWED,LATIN CAPITAL LETTER R ROTUNDA -A75B,PVALID,LATIN SMALL LETTER R ROTUNDA -A75C,DISALLOWED,LATIN CAPITAL LETTER RUM ROTUNDA -A75D,PVALID,LATIN SMALL LETTER RUM ROTUNDA -A75E,DISALLOWED,LATIN CAPITAL LETTER V WITH DIAGONAL STROKE -A75F,PVALID,LATIN SMALL LETTER V WITH DIAGONAL STROKE -A760,DISALLOWED,LATIN CAPITAL LETTER VY -A761,PVALID,LATIN SMALL LETTER VY -A762,DISALLOWED,LATIN CAPITAL LETTER VISIGOTHIC Z -A763,PVALID,LATIN SMALL LETTER VISIGOTHIC Z -A764,DISALLOWED,LATIN CAPITAL LETTER THORN WITH STROKE -A765,PVALID,LATIN SMALL LETTER THORN WITH STROKE -A766,DISALLOWED,LATIN CAPITAL LETTER THORN WITH STROKE THROUGH DESCENDER -A767,PVALID,LATIN SMALL LETTER THORN WITH STROKE THROUGH DESCENDER -A768,DISALLOWED,LATIN CAPITAL LETTER VEND -A769,PVALID,LATIN SMALL LETTER VEND -A76A,DISALLOWED,LATIN CAPITAL LETTER ET -A76B,PVALID,LATIN SMALL LETTER ET -A76C,DISALLOWED,LATIN CAPITAL LETTER IS -A76D,PVALID,LATIN SMALL LETTER IS -A76E,DISALLOWED,LATIN CAPITAL LETTER CON -A76F,PVALID,LATIN SMALL LETTER CON -A770,DISALLOWED,MODIFIER LETTER US -A771-A778,PVALID,LATIN SMALL LETTER DUM..LATIN SMALL LETTER UM -A779,DISALLOWED,LATIN CAPITAL LETTER INSULAR D -A77A,PVALID,LATIN SMALL LETTER INSULAR D -A77B,DISALLOWED,LATIN CAPITAL LETTER INSULAR F -A77C,PVALID,LATIN SMALL LETTER INSULAR F -A77D-A77E,DISALLOWED,LATIN CAPITAL LETTER INSULAR G..LATIN CAPITAL LETTER TURNED INSULAR G -A77F,PVALID,LATIN SMALL LETTER TURNED INSULAR G -A780,DISALLOWED,LATIN CAPITAL LETTER TURNED L -A781,PVALID,LATIN SMALL LETTER TURNED L -A782,DISALLOWED,LATIN CAPITAL LETTER INSULAR R -A783,PVALID,LATIN SMALL LETTER INSULAR R -A784,DISALLOWED,LATIN CAPITAL LETTER INSULAR S -A785,PVALID,LATIN SMALL LETTER INSULAR S -A786,DISALLOWED,LATIN CAPITAL LETTER INSULAR T -A787-A788,PVALID,LATIN SMALL LETTER INSULAR T..MODIFIER LETTER LOW CIRCUMFLEX ACCENT -A789-A78B,DISALLOWED,MODIFIER LETTER COLON..LATIN CAPITAL LETTER SALTILLO -A78C,PVALID,LATIN SMALL LETTER SALTILLO -A78D,DISALLOWED,LATIN CAPITAL LETTER TURNED H -A78E,PVALID,LATIN SMALL LETTER L WITH RETROFLEX HOOK AND BELT -A78F,UNASSIGNED, -A790,DISALLOWED,LATIN CAPITAL LETTER N WITH DESCENDER -A791,PVALID,LATIN SMALL LETTER N WITH DESCENDER -A792,DISALLOWED,LATIN CAPITAL LETTER C WITH BAR -A793,PVALID,LATIN SMALL LETTER C WITH BAR -A794-A79F,UNASSIGNED,.. -A7A0,DISALLOWED,LATIN CAPITAL LETTER G WITH OBLIQUE STROKE -A7A1,PVALID,LATIN SMALL LETTER G WITH OBLIQUE STROKE -A7A2,DISALLOWED,LATIN CAPITAL LETTER K WITH OBLIQUE STROKE -A7A3,PVALID,LATIN SMALL LETTER K WITH OBLIQUE STROKE -A7A4,DISALLOWED,LATIN CAPITAL LETTER N WITH OBLIQUE STROKE -A7A5,PVALID,LATIN SMALL LETTER N WITH OBLIQUE STROKE -A7A6,DISALLOWED,LATIN CAPITAL LETTER R WITH OBLIQUE STROKE -A7A7,PVALID,LATIN SMALL LETTER R WITH OBLIQUE STROKE -A7A8,DISALLOWED,LATIN CAPITAL LETTER S WITH OBLIQUE STROKE -A7A9,PVALID,LATIN SMALL LETTER S WITH OBLIQUE STROKE -A7AA,DISALLOWED,LATIN CAPITAL LETTER H WITH HOOK -A7AB-A7F7,UNASSIGNED,.. -A7F8-A7F9,DISALLOWED,MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE -A7FA-A827,PVALID,LATIN LETTER SMALL CAPITAL TURNED M..SYLOTI NAGRI VOWEL SIGN OO -A828-A82B,DISALLOWED,SYLOTI NAGRI POETRY MARK-1..SYLOTI NAGRI POETRY MARK-4 -A82C-A82F,UNASSIGNED,.. -A830-A839,DISALLOWED,NORTH INDIC FRACTION ONE QUARTER..NORTH INDIC QUANTITY MARK -A83A-A83F,UNASSIGNED,.. -A840-A873,PVALID,PHAGS-PA LETTER KA..PHAGS-PA LETTER CANDRABINDU -A874-A877,DISALLOWED,PHAGS-PA SINGLE HEAD MARK..PHAGS-PA MARK DOUBLE SHAD -A878-A87F,UNASSIGNED,.. -A880-A8C4,PVALID,SAURASHTRA SIGN ANUSVARA..SAURASHTRA SIGN VIRAMA -A8C5-A8CD,UNASSIGNED,.. -A8CE-A8CF,DISALLOWED,SAURASHTRA DANDA..SAURASHTRA DOUBLE DANDA -A8D0-A8D9,PVALID,SAURASHTRA DIGIT ZERO..SAURASHTRA DIGIT NINE -A8DA-A8DF,UNASSIGNED,.. -A8E0-A8F7,PVALID,COMBINING DEVANAGARI DIGIT ZERO..DEVANAGARI SIGN CANDRABINDU AVAGRAHA -A8F8-A8FA,DISALLOWED,DEVANAGARI SIGN PUSHPIKA..DEVANAGARI CARET -A8FB,PVALID,DEVANAGARI HEADSTROKE -A8FC-A8FF,UNASSIGNED,.. -A900-A92D,PVALID,KAYAH LI DIGIT ZERO..KAYAH LI TONE CALYA PLOPHU -A92E-A92F,DISALLOWED,KAYAH LI SIGN CWI..KAYAH LI SIGN SHYA -A930-A953,PVALID,REJANG LETTER KA..REJANG VIRAMA -A954-A95E,UNASSIGNED,.. -A95F-A97C,DISALLOWED,REJANG SECTION MARK..HANGUL CHOSEONG SSANGYEORINHIEUH -A97D-A97F,UNASSIGNED,.. -A980-A9C0,PVALID,JAVANESE SIGN PANYANGGA..JAVANESE PANGKON -A9C1-A9CD,DISALLOWED,JAVANESE LEFT RERENGGAN..JAVANESE TURNED PADA PISELEH -A9CE,UNASSIGNED, -A9CF-A9D9,PVALID,JAVANESE PANGRANGKEP..JAVANESE DIGIT NINE -A9DA-A9DD,UNASSIGNED,.. -A9DE-A9DF,DISALLOWED,JAVANESE PADA TIRTA TUMETES..JAVANESE PADA ISEN-ISEN -A9E0-A9FF,UNASSIGNED,.. -AA00-AA36,PVALID,CHAM LETTER A..CHAM CONSONANT SIGN WA -AA37-AA3F,UNASSIGNED,.. -AA40-AA4D,PVALID,CHAM LETTER FINAL K..CHAM CONSONANT SIGN FINAL H -AA4E-AA4F,UNASSIGNED,.. -AA50-AA59,PVALID,CHAM DIGIT ZERO..CHAM DIGIT NINE -AA5A-AA5B,UNASSIGNED,.. -AA5C-AA5F,DISALLOWED,CHAM PUNCTUATION SPIRAL..CHAM PUNCTUATION TRIPLE DANDA -AA60-AA76,PVALID,MYANMAR LETTER KHAMTI GA..MYANMAR LOGOGRAM KHAMTI HM -AA77-AA79,DISALLOWED,MYANMAR SYMBOL AITON EXCLAMATION..MYANMAR SYMBOL AITON TWO -AA7A-AA7B,PVALID,MYANMAR LETTER AITON RA..MYANMAR SIGN PAO KAREN TONE -AA7C-AA7F,UNASSIGNED,.. -AA80-AAC2,PVALID,TAI VIET LETTER LOW KO..TAI VIET TONE MAI SONG -AAC3-AADA,UNASSIGNED,.. -AADB-AADD,PVALID,TAI VIET SYMBOL KON..TAI VIET SYMBOL SAM -AADE-AADF,DISALLOWED,TAI VIET SYMBOL HO HOI..TAI VIET SYMBOL KOI KOI -AAE0-AAEF,PVALID,MEETEI MAYEK LETTER E..MEETEI MAYEK VOWEL SIGN AAU -AAF0-AAF1,DISALLOWED,MEETEI MAYEK CHEIKHAN..MEETEI MAYEK AHANG KHUDAM -AAF2-AAF6,PVALID,MEETEI MAYEK ANJI..MEETEI MAYEK VIRAMA -AAF7-AB00,UNASSIGNED,.. -AB01-AB06,PVALID,ETHIOPIC SYLLABLE TTHU..ETHIOPIC SYLLABLE TTHO -AB07-AB08,UNASSIGNED,.. -AB09-AB0E,PVALID,ETHIOPIC SYLLABLE DDHU..ETHIOPIC SYLLABLE DDHO -AB0F-AB10,UNASSIGNED,.. -AB11-AB16,PVALID,ETHIOPIC SYLLABLE DZU..ETHIOPIC SYLLABLE DZO -AB17-AB1F,UNASSIGNED,.. -AB20-AB26,PVALID,ETHIOPIC SYLLABLE CCHHA..ETHIOPIC SYLLABLE CCHHO -AB27,UNASSIGNED, -AB28-AB2E,PVALID,ETHIOPIC SYLLABLE BBA..ETHIOPIC SYLLABLE BBO -AB2F-ABBF,UNASSIGNED,.. -ABC0-ABEA,PVALID,MEETEI MAYEK LETTER KOK..MEETEI MAYEK VOWEL SIGN NUNG -ABEB,DISALLOWED,MEETEI MAYEK CHEIKHEI -ABEC-ABED,PVALID,MEETEI MAYEK LUM IYEK..MEETEI MAYEK APUN IYEK -ABEE-ABEF,UNASSIGNED,.. -ABF0-ABF9,PVALID,MEETEI MAYEK DIGIT ZERO..MEETEI MAYEK DIGIT NINE -ABFA-ABFF,UNASSIGNED,.. -AC00-D7A3,PVALID,".." -D7A4-D7AF,UNASSIGNED,.. -D7B0-D7C6,DISALLOWED,HANGUL JUNGSEONG O-YEO..HANGUL JUNGSEONG ARAEA-E -D7C7-D7CA,UNASSIGNED,.. -D7CB-D7FB,DISALLOWED,HANGUL JONGSEONG NIEUN-RIEUL..HANGUL JONGSEONG PHIEUPH-THIEUTH -D7FC-D7FF,UNASSIGNED,.. -D800-FA0D,DISALLOWED,"..CJK COMPATIBILITY IDEOGRAPH-FA0D" -FA0E-FA0F,PVALID,CJK COMPATIBILITY IDEOGRAPH-FA0E..CJK COMPATIBILITY IDEOGRAPH-FA0F -FA10,DISALLOWED,CJK COMPATIBILITY IDEOGRAPH-FA10 -FA11,PVALID,CJK COMPATIBILITY IDEOGRAPH-FA11 -FA12,DISALLOWED,CJK COMPATIBILITY IDEOGRAPH-FA12 -FA13-FA14,PVALID,CJK COMPATIBILITY IDEOGRAPH-FA13..CJK COMPATIBILITY IDEOGRAPH-FA14 -FA15-FA1E,DISALLOWED,CJK COMPATIBILITY IDEOGRAPH-FA15..CJK COMPATIBILITY IDEOGRAPH-FA1E -FA1F,PVALID,CJK COMPATIBILITY IDEOGRAPH-FA1F -FA20,DISALLOWED,CJK COMPATIBILITY IDEOGRAPH-FA20 -FA21,PVALID,CJK COMPATIBILITY IDEOGRAPH-FA21 -FA22,DISALLOWED,CJK COMPATIBILITY IDEOGRAPH-FA22 -FA23-FA24,PVALID,CJK COMPATIBILITY IDEOGRAPH-FA23..CJK COMPATIBILITY IDEOGRAPH-FA24 -FA25-FA26,DISALLOWED,CJK COMPATIBILITY IDEOGRAPH-FA25..CJK COMPATIBILITY IDEOGRAPH-FA26 -FA27-FA29,PVALID,CJK COMPATIBILITY IDEOGRAPH-FA27..CJK COMPATIBILITY IDEOGRAPH-FA29 -FA2A-FA6D,DISALLOWED,CJK COMPATIBILITY IDEOGRAPH-FA2A..CJK COMPATIBILITY IDEOGRAPH-FA6D -FA6E-FA6F,UNASSIGNED,.. -FA70-FAD9,DISALLOWED,CJK COMPATIBILITY IDEOGRAPH-FA70..CJK COMPATIBILITY IDEOGRAPH-FAD9 -FADA-FAFF,UNASSIGNED,.. -FB00-FB06,DISALLOWED,LATIN SMALL LIGATURE FF..LATIN SMALL LIGATURE ST -FB07-FB12,UNASSIGNED,.. -FB13-FB17,DISALLOWED,ARMENIAN SMALL LIGATURE MEN NOW..ARMENIAN SMALL LIGATURE MEN XEH -FB18-FB1C,UNASSIGNED,.. -FB1D,DISALLOWED,HEBREW LETTER YOD WITH HIRIQ -FB1E,PVALID,HEBREW POINT JUDEO-SPANISH VARIKA -FB1F-FB36,DISALLOWED,HEBREW LIGATURE YIDDISH YOD YOD PATAH..HEBREW LETTER ZAYIN WITH DAGESH -FB37,UNASSIGNED, -FB38-FB3C,DISALLOWED,HEBREW LETTER TET WITH DAGESH..HEBREW LETTER LAMED WITH DAGESH -FB3D,UNASSIGNED, -FB3E,DISALLOWED,HEBREW LETTER MEM WITH DAGESH -FB3F,UNASSIGNED, -FB40-FB41,DISALLOWED,HEBREW LETTER NUN WITH DAGESH..HEBREW LETTER SAMEKH WITH DAGESH -FB42,UNASSIGNED, -FB43-FB44,DISALLOWED,HEBREW LETTER FINAL PE WITH DAGESH..HEBREW LETTER PE WITH DAGESH -FB45,UNASSIGNED, -FB46-FBC1,DISALLOWED,HEBREW LETTER TSADI WITH DAGESH..ARABIC SYMBOL SMALL TAH BELOW -FBC2-FBD2,UNASSIGNED,.. -FBD3-FD3F,DISALLOWED,ARABIC LETTER NG ISOLATED FORM..ORNATE RIGHT PARENTHESIS -FD40-FD4F,UNASSIGNED,.. -FD50-FD8F,DISALLOWED,ARABIC LIGATURE TEH WITH JEEM WITH MEEM INITIAL FORM..ARABIC LIGATURE MEEM WITH KHAH WITH MEEM INITIAL FORM -FD90-FD91,UNASSIGNED,.. -FD92-FDC7,DISALLOWED,ARABIC LIGATURE MEEM WITH JEEM WITH KHAH INITIAL FORM..ARABIC LIGATURE NOON WITH JEEM WITH YEH FINAL FORM -FDC8-FDCF,UNASSIGNED,.. -FDD0-FDFD,DISALLOWED,..ARABIC LIGATURE BISMILLAH AR-RAHMAN AR-RAHEEM -FDFE-FDFF,UNASSIGNED,.. -FE00-FE19,DISALLOWED,VARIATION SELECTOR-1..PRESENTATION FORM FOR VERTICAL HORIZONTAL ELLIPSIS -FE1A-FE1F,UNASSIGNED,.. -FE20-FE26,PVALID,COMBINING LIGATURE LEFT HALF..COMBINING CONJOINING MACRON -FE27-FE2F,UNASSIGNED,.. -FE30-FE52,DISALLOWED,PRESENTATION FORM FOR VERTICAL TWO DOT LEADER..SMALL FULL STOP -FE53,UNASSIGNED, -FE54-FE66,DISALLOWED,SMALL SEMICOLON..SMALL EQUALS SIGN -FE67,UNASSIGNED, -FE68-FE6B,DISALLOWED,SMALL REVERSE SOLIDUS..SMALL COMMERCIAL AT -FE6C-FE6F,UNASSIGNED,.. -FE70-FE72,DISALLOWED,ARABIC FATHATAN ISOLATED FORM..ARABIC DAMMATAN ISOLATED FORM -FE73,PVALID,ARABIC TAIL FRAGMENT -FE74,DISALLOWED,ARABIC KASRATAN ISOLATED FORM -FE75,UNASSIGNED, -FE76-FEFC,DISALLOWED,ARABIC FATHA ISOLATED FORM..ARABIC LIGATURE LAM WITH ALEF FINAL FORM -FEFD-FEFE,UNASSIGNED,.. -FEFF,DISALLOWED,ZERO WIDTH NO-BREAK SPACE -FF00,UNASSIGNED, -FF01-FFBE,DISALLOWED,FULLWIDTH EXCLAMATION MARK..HALFWIDTH HANGUL LETTER HIEUH -FFBF-FFC1,UNASSIGNED,.. -FFC2-FFC7,DISALLOWED,HALFWIDTH HANGUL LETTER A..HALFWIDTH HANGUL LETTER E -FFC8-FFC9,UNASSIGNED,.. -FFCA-FFCF,DISALLOWED,HALFWIDTH HANGUL LETTER YEO..HALFWIDTH HANGUL LETTER OE -FFD0-FFD1,UNASSIGNED,.. -FFD2-FFD7,DISALLOWED,HALFWIDTH HANGUL LETTER YO..HALFWIDTH HANGUL LETTER YU -FFD8-FFD9,UNASSIGNED,.. -FFDA-FFDC,DISALLOWED,HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL LETTER I -FFDD-FFDF,UNASSIGNED,.. -FFE0-FFE6,DISALLOWED,FULLWIDTH CENT SIGN..FULLWIDTH WON SIGN -FFE7,UNASSIGNED, -FFE8-FFEE,DISALLOWED,HALFWIDTH FORMS LIGHT VERTICAL..HALFWIDTH WHITE CIRCLE -FFEF-FFF8,UNASSIGNED,.. -FFF9-FFFF,DISALLOWED,INTERLINEAR ANNOTATION ANCHOR.. -10000-1000B,PVALID,LINEAR B SYLLABLE B008 A..LINEAR B SYLLABLE B046 JE -1000C,UNASSIGNED, -1000D-10026,PVALID,LINEAR B SYLLABLE B036 JO..LINEAR B SYLLABLE B032 QO -10027,UNASSIGNED, -10028-1003A,PVALID,LINEAR B SYLLABLE B060 RA..LINEAR B SYLLABLE B042 WO -1003B,UNASSIGNED, -1003C-1003D,PVALID,LINEAR B SYLLABLE B017 ZA..LINEAR B SYLLABLE B074 ZE -1003E,UNASSIGNED, -1003F-1004D,PVALID,LINEAR B SYLLABLE B020 ZO..LINEAR B SYLLABLE B091 TWO -1004E-1004F,UNASSIGNED,.. -10050-1005D,PVALID,LINEAR B SYMBOL B018..LINEAR B SYMBOL B089 -1005E-1007F,UNASSIGNED,.. -10080-100FA,PVALID,LINEAR B IDEOGRAM B100 MAN..LINEAR B IDEOGRAM VESSEL B305 -100FB-100FF,UNASSIGNED,.. -10100-10102,DISALLOWED,AEGEAN WORD SEPARATOR LINE..AEGEAN CHECK MARK -10103-10106,UNASSIGNED,.. -10107-10133,DISALLOWED,AEGEAN NUMBER ONE..AEGEAN NUMBER NINETY THOUSAND -10134-10136,UNASSIGNED,.. -10137-1018A,DISALLOWED,AEGEAN WEIGHT BASE UNIT..GREEK ZERO SIGN -1018B-1018F,UNASSIGNED,.. -10190-1019B,DISALLOWED,ROMAN SEXTANS SIGN..ROMAN CENTURIAL SIGN -1019C-101CF,UNASSIGNED,.. -101D0-101FC,DISALLOWED,PHAISTOS DISC SIGN PEDESTRIAN..PHAISTOS DISC SIGN WAVY BAND -101FD,PVALID,PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE -101FE-1027F,UNASSIGNED,.. -10280-1029C,PVALID,LYCIAN LETTER A..LYCIAN LETTER X -1029D-1029F,UNASSIGNED,.. -102A0-102D0,PVALID,CARIAN LETTER A..CARIAN LETTER UUU3 -102D1-102FF,UNASSIGNED,.. -10300-1031E,PVALID,OLD ITALIC LETTER A..OLD ITALIC LETTER UU -1031F,UNASSIGNED, -10320-10323,DISALLOWED,OLD ITALIC NUMERAL ONE..OLD ITALIC NUMERAL FIFTY -10324-1032F,UNASSIGNED,.. -10330-10340,PVALID,GOTHIC LETTER AHSA..GOTHIC LETTER PAIRTHRA -10341,DISALLOWED,GOTHIC LETTER NINETY -10342-10349,PVALID,GOTHIC LETTER RAIDA..GOTHIC LETTER OTHAL -1034A,DISALLOWED,GOTHIC LETTER NINE HUNDRED -1034B-1037F,UNASSIGNED,.. -10380-1039D,PVALID,UGARITIC LETTER ALPA..UGARITIC LETTER SSU -1039E,UNASSIGNED, -1039F,DISALLOWED,UGARITIC WORD DIVIDER -103A0-103C3,PVALID,OLD PERSIAN SIGN A..OLD PERSIAN SIGN HA -103C4-103C7,UNASSIGNED,.. -103C8-103CF,PVALID,OLD PERSIAN SIGN AURAMAZDAA..OLD PERSIAN SIGN BUUMISH -103D0-103D5,DISALLOWED,OLD PERSIAN WORD DIVIDER..OLD PERSIAN NUMBER HUNDRED -103D6-103FF,UNASSIGNED,.. -10400-10427,DISALLOWED,DESERET CAPITAL LETTER LONG I..DESERET CAPITAL LETTER EW -10428-1049D,PVALID,DESERET SMALL LETTER LONG I..OSMANYA LETTER OO -1049E-1049F,UNASSIGNED,.. -104A0-104A9,PVALID,OSMANYA DIGIT ZERO..OSMANYA DIGIT NINE -104AA-107FF,UNASSIGNED,.. -10800-10805,PVALID,CYPRIOT SYLLABLE A..CYPRIOT SYLLABLE JA -10806-10807,UNASSIGNED,.. -10808,PVALID,CYPRIOT SYLLABLE JO -10809,UNASSIGNED, -1080A-10835,PVALID,CYPRIOT SYLLABLE KA..CYPRIOT SYLLABLE WO -10836,UNASSIGNED, -10837-10838,PVALID,CYPRIOT SYLLABLE XA..CYPRIOT SYLLABLE XE -10839-1083B,UNASSIGNED,.. -1083C,PVALID,CYPRIOT SYLLABLE ZA -1083D-1083E,UNASSIGNED,.. -1083F-10855,PVALID,CYPRIOT SYLLABLE ZO..IMPERIAL ARAMAIC LETTER TAW -10856,UNASSIGNED, -10857-1085F,DISALLOWED,IMPERIAL ARAMAIC SECTION SIGN..IMPERIAL ARAMAIC NUMBER TEN THOUSAND -10860-108FF,UNASSIGNED,.. -10900-10915,PVALID,PHOENICIAN LETTER ALF..PHOENICIAN LETTER TAU -10916-1091B,DISALLOWED,PHOENICIAN NUMBER ONE..PHOENICIAN NUMBER THREE -1091C-1091E,UNASSIGNED,.. -1091F,DISALLOWED,PHOENICIAN WORD SEPARATOR -10920-10939,PVALID,LYDIAN LETTER A..LYDIAN LETTER C -1093A-1093E,UNASSIGNED,.. -1093F,DISALLOWED,LYDIAN TRIANGULAR MARK -10940-1097F,UNASSIGNED,.. -10980-109B7,PVALID,MEROITIC HIEROGLYPHIC LETTER A..MEROITIC CURSIVE LETTER DA -109B8-109BD,UNASSIGNED,.. -109BE-109BF,PVALID,MEROITIC CURSIVE LOGOGRAM RMT..MEROITIC CURSIVE LOGOGRAM IMN -109C0-109FF,UNASSIGNED,.. -10A00-10A03,PVALID,KHAROSHTHI LETTER A..KHAROSHTHI VOWEL SIGN VOCALIC R -10A04,UNASSIGNED, -10A05-10A06,PVALID,KHAROSHTHI VOWEL SIGN E..KHAROSHTHI VOWEL SIGN O -10A07-10A0B,UNASSIGNED,.. -10A0C-10A13,PVALID,KHAROSHTHI VOWEL LENGTH MARK..KHAROSHTHI LETTER GHA -10A14,UNASSIGNED, -10A15-10A17,PVALID,KHAROSHTHI LETTER CA..KHAROSHTHI LETTER JA -10A18,UNASSIGNED, -10A19-10A33,PVALID,KHAROSHTHI LETTER NYA..KHAROSHTHI LETTER TTTHA -10A34-10A37,UNASSIGNED,.. -10A38-10A3A,PVALID,KHAROSHTHI SIGN BAR ABOVE..KHAROSHTHI SIGN DOT BELOW -10A3B-10A3E,UNASSIGNED,.. -10A3F,PVALID,KHAROSHTHI VIRAMA -10A40-10A47,DISALLOWED,KHAROSHTHI DIGIT ONE..KHAROSHTHI NUMBER ONE THOUSAND -10A48-10A4F,UNASSIGNED,.. -10A50-10A58,DISALLOWED,KHAROSHTHI PUNCTUATION DOT..KHAROSHTHI PUNCTUATION LINES -10A59-10A5F,UNASSIGNED,.. -10A60-10A7C,PVALID,OLD SOUTH ARABIAN LETTER HE..OLD SOUTH ARABIAN LETTER THETH -10A7D-10A7F,DISALLOWED,OLD SOUTH ARABIAN NUMBER ONE..OLD SOUTH ARABIAN NUMERIC INDICATOR -10A80-10AFF,UNASSIGNED,.. -10B00-10B35,PVALID,AVESTAN LETTER A..AVESTAN LETTER HE -10B36-10B38,UNASSIGNED,.. -10B39-10B3F,DISALLOWED,AVESTAN ABBREVIATION MARK..LARGE ONE RING OVER TWO RINGS PUNCTUATION -10B40-10B55,PVALID,INSCRIPTIONAL PARTHIAN LETTER ALEPH..INSCRIPTIONAL PARTHIAN LETTER TAW -10B56-10B57,UNASSIGNED,.. -10B58-10B5F,DISALLOWED,INSCRIPTIONAL PARTHIAN NUMBER ONE..INSCRIPTIONAL PARTHIAN NUMBER ONE THOUSAND -10B60-10B72,PVALID,INSCRIPTIONAL PAHLAVI LETTER ALEPH..INSCRIPTIONAL PAHLAVI LETTER TAW -10B73-10B77,UNASSIGNED,.. -10B78-10B7F,DISALLOWED,INSCRIPTIONAL PAHLAVI NUMBER ONE..INSCRIPTIONAL PAHLAVI NUMBER ONE THOUSAND -10B80-10BFF,UNASSIGNED,.. -10C00-10C48,PVALID,OLD TURKIC LETTER ORKHON A..OLD TURKIC LETTER ORKHON BASH -10C49-10E5F,UNASSIGNED,.. -10E60-10E7E,DISALLOWED,RUMI DIGIT ONE..RUMI FRACTION TWO THIRDS -10E7F-10FFF,UNASSIGNED,.. -11000-11046,PVALID,BRAHMI SIGN CANDRABINDU..BRAHMI VIRAMA -11047-1104D,DISALLOWED,BRAHMI DANDA..BRAHMI PUNCTUATION LOTUS -1104E-11051,UNASSIGNED,.. -11052-11065,DISALLOWED,BRAHMI NUMBER ONE..BRAHMI NUMBER ONE THOUSAND -11066-1106F,PVALID,BRAHMI DIGIT ZERO..BRAHMI DIGIT NINE -11070-1107F,UNASSIGNED,.. -11080-110BA,PVALID,KAITHI SIGN CANDRABINDU..KAITHI SIGN NUKTA -110BB-110C1,DISALLOWED,KAITHI ABBREVIATION SIGN..KAITHI DOUBLE DANDA -110C2-110CF,UNASSIGNED,.. -110D0-110E8,PVALID,SORA SOMPENG LETTER SAH..SORA SOMPENG LETTER MAE -110E9-110EF,UNASSIGNED,.. -110F0-110F9,PVALID,SORA SOMPENG DIGIT ZERO..SORA SOMPENG DIGIT NINE -110FA-110FF,UNASSIGNED,.. -11100-11134,PVALID,CHAKMA SIGN CANDRABINDU..CHAKMA MAAYYAA -11135,UNASSIGNED, -11136-1113F,PVALID,CHAKMA DIGIT ZERO..CHAKMA DIGIT NINE -11140-11143,DISALLOWED,CHAKMA SECTION MARK..CHAKMA QUESTION MARK -11144-1117F,UNASSIGNED,.. -11180-111C4,PVALID,SHARADA SIGN CANDRABINDU..SHARADA OM -111C5-111C8,DISALLOWED,SHARADA DANDA..SHARADA SEPARATOR -111C9-111CF,UNASSIGNED,.. -111D0-111D9,PVALID,SHARADA DIGIT ZERO..SHARADA DIGIT NINE -111DA-1167F,UNASSIGNED,.. -11680-116B7,PVALID,TAKRI LETTER A..TAKRI SIGN NUKTA -116B8-116BF,UNASSIGNED,.. -116C0-116C9,PVALID,TAKRI DIGIT ZERO..TAKRI DIGIT NINE -116CA-11FFF,UNASSIGNED,.. -12000-1236E,PVALID,CUNEIFORM SIGN A..CUNEIFORM SIGN ZUM -1236F-123FF,UNASSIGNED,.. -12400-12462,DISALLOWED,CUNEIFORM NUMERIC SIGN TWO ASH..CUNEIFORM NUMERIC SIGN OLD ASSYRIAN ONE QUARTER -12463-1246F,UNASSIGNED,.. -12470-12473,DISALLOWED,CUNEIFORM PUNCTUATION SIGN OLD ASSYRIAN WORD DIVIDER..CUNEIFORM PUNCTUATION SIGN DIAGONAL TRICOLON -12474-12FFF,UNASSIGNED,.. -13000-1342E,PVALID,EGYPTIAN HIEROGLYPH A001..EGYPTIAN HIEROGLYPH AA032 -1342F-167FF,UNASSIGNED,.. -16800-16A38,PVALID,BAMUM LETTER PHASE-A NGKUE MFON..BAMUM LETTER PHASE-F VUEQ -16A39-16EFF,UNASSIGNED,.. -16F00-16F44,PVALID,MIAO LETTER PA..MIAO LETTER HHA -16F45-16F4F,UNASSIGNED,.. -16F50-16F7E,PVALID,MIAO LETTER NASALIZATION..MIAO VOWEL SIGN NG -16F7F-16F8E,UNASSIGNED,.. -16F8F-16F9F,PVALID,MIAO TONE RIGHT..MIAO LETTER REFORMED TONE-8 -16FA0-1AFFF,UNASSIGNED,.. -1B000-1B001,PVALID,KATAKANA LETTER ARCHAIC E..HIRAGANA LETTER ARCHAIC YE -1B002-1CFFF,UNASSIGNED,.. -1D000-1D0F5,DISALLOWED,BYZANTINE MUSICAL SYMBOL PSILI..BYZANTINE MUSICAL SYMBOL GORGON NEO KATO -1D0F6-1D0FF,UNASSIGNED,.. -1D100-1D126,DISALLOWED,MUSICAL SYMBOL SINGLE BARLINE..MUSICAL SYMBOL DRUM CLEF-2 -1D127-1D128,UNASSIGNED,.. -1D129-1D1DD,DISALLOWED,MUSICAL SYMBOL MULTIPLE MEASURE REST..MUSICAL SYMBOL PES SUBPUNCTIS -1D1DE-1D1FF,UNASSIGNED,.. -1D200-1D245,DISALLOWED,GREEK VOCAL NOTATION SYMBOL-1..GREEK MUSICAL LEIMMA -1D246-1D2FF,UNASSIGNED,.. -1D300-1D356,DISALLOWED,MONOGRAM FOR EARTH..TETRAGRAM FOR FOSTERING -1D357-1D35F,UNASSIGNED,.. -1D360-1D371,DISALLOWED,COUNTING ROD UNIT DIGIT ONE..COUNTING ROD TENS DIGIT NINE -1D372-1D3FF,UNASSIGNED,.. -1D400-1D454,DISALLOWED,MATHEMATICAL BOLD CAPITAL A..MATHEMATICAL ITALIC SMALL G -1D455,UNASSIGNED, -1D456-1D49C,DISALLOWED,MATHEMATICAL ITALIC SMALL I..MATHEMATICAL SCRIPT CAPITAL A -1D49D,UNASSIGNED, -1D49E-1D49F,DISALLOWED,MATHEMATICAL SCRIPT CAPITAL C..MATHEMATICAL SCRIPT CAPITAL D -1D4A0-1D4A1,UNASSIGNED,.. -1D4A2,DISALLOWED,MATHEMATICAL SCRIPT CAPITAL G -1D4A3-1D4A4,UNASSIGNED,.. -1D4A5-1D4A6,DISALLOWED,MATHEMATICAL SCRIPT CAPITAL J..MATHEMATICAL SCRIPT CAPITAL K -1D4A7-1D4A8,UNASSIGNED,.. -1D4A9-1D4AC,DISALLOWED,MATHEMATICAL SCRIPT CAPITAL N..MATHEMATICAL SCRIPT CAPITAL Q -1D4AD,UNASSIGNED, -1D4AE-1D4B9,DISALLOWED,MATHEMATICAL SCRIPT CAPITAL S..MATHEMATICAL SCRIPT SMALL D -1D4BA,UNASSIGNED, -1D4BB,DISALLOWED,MATHEMATICAL SCRIPT SMALL F -1D4BC,UNASSIGNED, -1D4BD-1D4C3,DISALLOWED,MATHEMATICAL SCRIPT SMALL H..MATHEMATICAL SCRIPT SMALL N -1D4C4,UNASSIGNED, -1D4C5-1D505,DISALLOWED,MATHEMATICAL SCRIPT SMALL P..MATHEMATICAL FRAKTUR CAPITAL B -1D506,UNASSIGNED, -1D507-1D50A,DISALLOWED,MATHEMATICAL FRAKTUR CAPITAL D..MATHEMATICAL FRAKTUR CAPITAL G -1D50B-1D50C,UNASSIGNED,.. -1D50D-1D514,DISALLOWED,MATHEMATICAL FRAKTUR CAPITAL J..MATHEMATICAL FRAKTUR CAPITAL Q -1D515,UNASSIGNED, -1D516-1D51C,DISALLOWED,MATHEMATICAL FRAKTUR CAPITAL S..MATHEMATICAL FRAKTUR CAPITAL Y -1D51D,UNASSIGNED, -1D51E-1D539,DISALLOWED,MATHEMATICAL FRAKTUR SMALL A..MATHEMATICAL DOUBLE-STRUCK CAPITAL B -1D53A,UNASSIGNED, -1D53B-1D53E,DISALLOWED,MATHEMATICAL DOUBLE-STRUCK CAPITAL D..MATHEMATICAL DOUBLE-STRUCK CAPITAL G -1D53F,UNASSIGNED, -1D540-1D544,DISALLOWED,MATHEMATICAL DOUBLE-STRUCK CAPITAL I..MATHEMATICAL DOUBLE-STRUCK CAPITAL M -1D545,UNASSIGNED, -1D546,DISALLOWED,MATHEMATICAL DOUBLE-STRUCK CAPITAL O -1D547-1D549,UNASSIGNED,.. -1D54A-1D550,DISALLOWED,MATHEMATICAL DOUBLE-STRUCK CAPITAL S..MATHEMATICAL DOUBLE-STRUCK CAPITAL Y -1D551,UNASSIGNED, -1D552-1D6A5,DISALLOWED,MATHEMATICAL DOUBLE-STRUCK SMALL A..MATHEMATICAL ITALIC SMALL DOTLESS J -1D6A6-1D6A7,UNASSIGNED,.. -1D6A8-1D7CB,DISALLOWED,MATHEMATICAL BOLD CAPITAL ALPHA..MATHEMATICAL BOLD SMALL DIGAMMA -1D7CC-1D7CD,UNASSIGNED,.. -1D7CE-1D7FF,DISALLOWED,MATHEMATICAL BOLD DIGIT ZERO..MATHEMATICAL MONOSPACE DIGIT NINE -1D800-1EDFF,UNASSIGNED,.. -1EE00-1EE03,DISALLOWED,ARABIC MATHEMATICAL ALEF..ARABIC MATHEMATICAL DAL -1EE04,UNASSIGNED, -1EE05-1EE1F,DISALLOWED,ARABIC MATHEMATICAL WAW..ARABIC MATHEMATICAL DOTLESS QAF -1EE20,UNASSIGNED, -1EE21-1EE22,DISALLOWED,ARABIC MATHEMATICAL INITIAL BEH..ARABIC MATHEMATICAL INITIAL JEEM -1EE23,UNASSIGNED, -1EE24,DISALLOWED,ARABIC MATHEMATICAL INITIAL HEH -1EE25-1EE26,UNASSIGNED,.. -1EE27,DISALLOWED,ARABIC MATHEMATICAL INITIAL HAH -1EE28,UNASSIGNED, -1EE29-1EE32,DISALLOWED,ARABIC MATHEMATICAL INITIAL YEH..ARABIC MATHEMATICAL INITIAL QAF -1EE33,UNASSIGNED, -1EE34-1EE37,DISALLOWED,ARABIC MATHEMATICAL INITIAL SHEEN..ARABIC MATHEMATICAL INITIAL KHAH -1EE38,UNASSIGNED, -1EE39,DISALLOWED,ARABIC MATHEMATICAL INITIAL DAD -1EE3A,UNASSIGNED, -1EE3B,DISALLOWED,ARABIC MATHEMATICAL INITIAL GHAIN -1EE3C-1EE41,UNASSIGNED,.. -1EE42,DISALLOWED,ARABIC MATHEMATICAL TAILED JEEM -1EE43-1EE46,UNASSIGNED,.. -1EE47,DISALLOWED,ARABIC MATHEMATICAL TAILED HAH -1EE48,UNASSIGNED, -1EE49,DISALLOWED,ARABIC MATHEMATICAL TAILED YEH -1EE4A,UNASSIGNED, -1EE4B,DISALLOWED,ARABIC MATHEMATICAL TAILED LAM -1EE4C,UNASSIGNED, -1EE4D-1EE4F,DISALLOWED,ARABIC MATHEMATICAL TAILED NOON..ARABIC MATHEMATICAL TAILED AIN -1EE50,UNASSIGNED, -1EE51-1EE52,DISALLOWED,ARABIC MATHEMATICAL TAILED SAD..ARABIC MATHEMATICAL TAILED QAF -1EE53,UNASSIGNED, -1EE54,DISALLOWED,ARABIC MATHEMATICAL TAILED SHEEN -1EE55-1EE56,UNASSIGNED,.. -1EE57,DISALLOWED,ARABIC MATHEMATICAL TAILED KHAH -1EE58,UNASSIGNED, -1EE59,DISALLOWED,ARABIC MATHEMATICAL TAILED DAD -1EE5A,UNASSIGNED, -1EE5B,DISALLOWED,ARABIC MATHEMATICAL TAILED GHAIN -1EE5C,UNASSIGNED, -1EE5D,DISALLOWED,ARABIC MATHEMATICAL TAILED DOTLESS NOON -1EE5E,UNASSIGNED, -1EE5F,DISALLOWED,ARABIC MATHEMATICAL TAILED DOTLESS QAF -1EE60,UNASSIGNED, -1EE61-1EE62,DISALLOWED,ARABIC MATHEMATICAL STRETCHED BEH..ARABIC MATHEMATICAL STRETCHED JEEM -1EE63,UNASSIGNED, -1EE64,DISALLOWED,ARABIC MATHEMATICAL STRETCHED HEH -1EE65-1EE66,UNASSIGNED,.. -1EE67-1EE6A,DISALLOWED,ARABIC MATHEMATICAL STRETCHED HAH..ARABIC MATHEMATICAL STRETCHED KAF -1EE6B,UNASSIGNED, -1EE6C-1EE72,DISALLOWED,ARABIC MATHEMATICAL STRETCHED MEEM..ARABIC MATHEMATICAL STRETCHED QAF -1EE73,UNASSIGNED, -1EE74-1EE77,DISALLOWED,ARABIC MATHEMATICAL STRETCHED SHEEN..ARABIC MATHEMATICAL STRETCHED KHAH -1EE78,UNASSIGNED, -1EE79-1EE7C,DISALLOWED,ARABIC MATHEMATICAL STRETCHED DAD..ARABIC MATHEMATICAL STRETCHED DOTLESS BEH -1EE7D,UNASSIGNED, -1EE7E,DISALLOWED,ARABIC MATHEMATICAL STRETCHED DOTLESS FEH -1EE7F,UNASSIGNED, -1EE80-1EE89,DISALLOWED,ARABIC MATHEMATICAL LOOPED ALEF..ARABIC MATHEMATICAL LOOPED YEH -1EE8A,UNASSIGNED, -1EE8B-1EE9B,DISALLOWED,ARABIC MATHEMATICAL LOOPED LAM..ARABIC MATHEMATICAL LOOPED GHAIN -1EE9C-1EEA0,UNASSIGNED,.. -1EEA1-1EEA3,DISALLOWED,ARABIC MATHEMATICAL DOUBLE-STRUCK BEH..ARABIC MATHEMATICAL DOUBLE-STRUCK DAL -1EEA4,UNASSIGNED, -1EEA5-1EEA9,DISALLOWED,ARABIC MATHEMATICAL DOUBLE-STRUCK WAW..ARABIC MATHEMATICAL DOUBLE-STRUCK YEH -1EEAA,UNASSIGNED, -1EEAB-1EEBB,DISALLOWED,ARABIC MATHEMATICAL DOUBLE-STRUCK LAM..ARABIC MATHEMATICAL DOUBLE-STRUCK GHAIN -1EEBC-1EEEF,UNASSIGNED,.. -1EEF0-1EEF1,DISALLOWED,ARABIC MATHEMATICAL OPERATOR MEEM WITH HAH WITH TATWEEL..ARABIC MATHEMATICAL OPERATOR HAH WITH DAL -1EEF2-1EFFF,UNASSIGNED,.. -1F000-1F02B,DISALLOWED,MAHJONG TILE EAST WIND..MAHJONG TILE BACK -1F02C-1F02F,UNASSIGNED,.. -1F030-1F093,DISALLOWED,DOMINO TILE HORIZONTAL BACK..DOMINO TILE VERTICAL-06-06 -1F094-1F09F,UNASSIGNED,.. -1F0A0-1F0AE,DISALLOWED,PLAYING CARD BACK..PLAYING CARD KING OF SPADES -1F0AF-1F0B0,UNASSIGNED,.. -1F0B1-1F0BE,DISALLOWED,PLAYING CARD ACE OF HEARTS..PLAYING CARD KING OF HEARTS -1F0BF-1F0C0,UNASSIGNED,.. -1F0C1-1F0CF,DISALLOWED,PLAYING CARD ACE OF DIAMONDS..PLAYING CARD BLACK JOKER -1F0D0,UNASSIGNED, -1F0D1-1F0DF,DISALLOWED,PLAYING CARD ACE OF CLUBS..PLAYING CARD WHITE JOKER -1F0E0-1F0FF,UNASSIGNED,.. -1F100-1F10A,DISALLOWED,DIGIT ZERO FULL STOP..DIGIT NINE COMMA -1F10B-1F10F,UNASSIGNED,.. -1F110-1F12E,DISALLOWED,PARENTHESIZED LATIN CAPITAL LETTER A..CIRCLED WZ -1F12F,UNASSIGNED, -1F130-1F16B,DISALLOWED,SQUARED LATIN CAPITAL LETTER A..RAISED MD SIGN -1F16C-1F16F,UNASSIGNED,.. -1F170-1F19A,DISALLOWED,NEGATIVE SQUARED LATIN CAPITAL LETTER A..SQUARED VS -1F19B-1F1E5,UNASSIGNED,.. -1F1E6-1F202,DISALLOWED,REGIONAL INDICATOR SYMBOL LETTER A..SQUARED KATAKANA SA -1F203-1F20F,UNASSIGNED,.. -1F210-1F23A,DISALLOWED,SQUARED CJK UNIFIED IDEOGRAPH-624B..SQUARED CJK UNIFIED IDEOGRAPH-55B6 -1F23B-1F23F,UNASSIGNED,.. -1F240-1F248,DISALLOWED,TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-672C..TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-6557 -1F249-1F24F,UNASSIGNED,.. -1F250-1F251,DISALLOWED,CIRCLED IDEOGRAPH ADVANTAGE..CIRCLED IDEOGRAPH ACCEPT -1F252-1F2FF,UNASSIGNED,.. -1F300-1F320,DISALLOWED,CYCLONE..SHOOTING STAR -1F321-1F32F,UNASSIGNED,.. -1F330-1F335,DISALLOWED,CHESTNUT..CACTUS -1F336,UNASSIGNED, -1F337-1F37C,DISALLOWED,TULIP..BABY BOTTLE -1F37D-1F37F,UNASSIGNED,.. -1F380-1F393,DISALLOWED,RIBBON..GRADUATION CAP -1F394-1F39F,UNASSIGNED,.. -1F3A0-1F3C4,DISALLOWED,CAROUSEL HORSE..SURFER -1F3C5,UNASSIGNED, -1F3C6-1F3CA,DISALLOWED,TROPHY..SWIMMER -1F3CB-1F3DF,UNASSIGNED,.. -1F3E0-1F3F0,DISALLOWED,HOUSE BUILDING..EUROPEAN CASTLE -1F3F1-1F3FF,UNASSIGNED,.. -1F400-1F43E,DISALLOWED,RAT..PAW PRINTS -1F43F,UNASSIGNED, -1F440,DISALLOWED,EYES -1F441,UNASSIGNED, -1F442-1F4F7,DISALLOWED,EAR..CAMERA -1F4F8,UNASSIGNED, -1F4F9-1F4FC,DISALLOWED,VIDEO CAMERA..VIDEOCASSETTE -1F4FD-1F4FF,UNASSIGNED,.. -1F500-1F53D,DISALLOWED,TWISTED RIGHTWARDS ARROWS..DOWN-POINTING SMALL RED TRIANGLE -1F53E-1F53F,UNASSIGNED,.. -1F540-1F543,DISALLOWED,CIRCLED CROSS POMMEE..NOTCHED LEFT SEMICIRCLE WITH THREE DOTS -1F544-1F54F,UNASSIGNED,.. -1F550-1F567,DISALLOWED,CLOCK FACE ONE OCLOCK..CLOCK FACE TWELVE-THIRTY -1F568-1F5FA,UNASSIGNED,.. -1F5FB-1F640,DISALLOWED,MOUNT FUJI..WEARY CAT FACE -1F641-1F644,UNASSIGNED,.. -1F645-1F64F,DISALLOWED,FACE WITH NO GOOD GESTURE..PERSON WITH FOLDED HANDS -1F650-1F67F,UNASSIGNED,.. -1F680-1F6C5,DISALLOWED,ROCKET..LEFT LUGGAGE -1F6C6-1F6FF,UNASSIGNED,.. -1F700-1F773,DISALLOWED,ALCHEMICAL SYMBOL FOR QUINTESSENCE..ALCHEMICAL SYMBOL FOR HALF OUNCE -1F774-1FFFD,UNASSIGNED,.. -1FFFE-1FFFF,DISALLOWED,.. -20000-2A6D6,PVALID,".." -2A6D7-2A6FF,UNASSIGNED,.. -2A700-2B734,PVALID,".." -2B735-2B73F,UNASSIGNED,.. -2B740-2B81D,PVALID,".." -2B81E-2F7FF,UNASSIGNED,.. -2F800-2FA1D,DISALLOWED,CJK COMPATIBILITY IDEOGRAPH-2F800..CJK COMPATIBILITY IDEOGRAPH-2FA1D -2FA1E-2FFFD,UNASSIGNED,.. -2FFFE-2FFFF,DISALLOWED,.. -30000-3FFFD,UNASSIGNED,.. -3FFFE-3FFFF,DISALLOWED,.. -40000-4FFFD,UNASSIGNED,.. -4FFFE-4FFFF,DISALLOWED,.. -50000-5FFFD,UNASSIGNED,.. -5FFFE-5FFFF,DISALLOWED,.. -60000-6FFFD,UNASSIGNED,.. -6FFFE-6FFFF,DISALLOWED,.. -70000-7FFFD,UNASSIGNED,.. -7FFFE-7FFFF,DISALLOWED,.. -80000-8FFFD,UNASSIGNED,.. -8FFFE-8FFFF,DISALLOWED,.. -90000-9FFFD,UNASSIGNED,.. -9FFFE-9FFFF,DISALLOWED,.. -A0000-AFFFD,UNASSIGNED,.. -AFFFE-AFFFF,DISALLOWED,.. -B0000-BFFFD,UNASSIGNED,.. -BFFFE-BFFFF,DISALLOWED,.. -C0000-CFFFD,UNASSIGNED,.. -CFFFE-CFFFF,DISALLOWED,.. -D0000-DFFFD,UNASSIGNED,.. -DFFFE-DFFFF,DISALLOWED,.. -E0000,UNASSIGNED, -E0001,DISALLOWED,LANGUAGE TAG -E0002-E001F,UNASSIGNED,.. -E0020-E007F,DISALLOWED,TAG SPACE..CANCEL TAG -E0080-E00FF,UNASSIGNED,.. -E0100-E01EF,DISALLOWED,VARIATION SELECTOR-17..VARIATION SELECTOR-256 -E01F0-EFFFD,UNASSIGNED,.. -EFFFE-10FFFF,DISALLOWED,.. diff --git a/src/hyperlink/idna-tables-properties.csv.gz b/src/hyperlink/idna-tables-properties.csv.gz new file mode 100644 index 0000000000000000000000000000000000000000..48e9f06742fa3431a433f15157658315108bceb9 GIT binary patch literal 25555 zcmV)sK$yQDiwFpqa)Mq018HP#VJ&oFVr*q|EpT#gaAk6IX=QURV{>)@oV`nr>%NXJ zIzK>vhoHy*DkOdzXdFP6N=?u0K~GLM`u600`zunmW%(g9Yh&;1RW`{V zC6N>*>%sr?|M~a-`p^IPzyI^k^S}S|zyAHd|L6bvNBsN$`+xrXKmO}~{^LLY@4x)7 zv-;1+`Rwt3MA@?Yz5bTspQ^v#KRcrc*w#6Qn3b}z+0#p_wf>f=#XeT#1-@aA*g zT=AaTu6}-gwzcBXgg$`y=_p5(*RnhP$fo}M)c>x^N+Kasgwc5}k=OUS`uw~$>`R^) z=d$X6GP)?rn2jRutShTi`%qk`yX-pIoY?2*#?v2Qjx)uaMRS#hFKraYLnzeXU_HXRFmV3(Yj!gQ;4rkmurRzZJDdMaUZYC6UEH1~9|{RepV~D|thn@EBl5 z9UUTY&BJ)#X{BNZz=}Ab%I9Un`$vA4e~>Rd23c@0Dw6V$6U*wMinC)Nn}L%{#6~j_ zgu+b3@=(55vbcO_Nqjj|>tHe9eXY(+2)9-NG3jb@iEGb`q)QG&C`{4p;=rmvLa?w z`6m8&*)hP3no|*sF^XdVLoBKCOooGRb$6GL$fvfz+pq*?Xm z1QQF&0DQsJfv?VSwhXA$C^-yc@Q$3uai zjbUBPk1=cpLB*K@S1gY)3~_#tR^DsnCiqPtIXmQL%ixw=RAVK_c0Mo`b$#1ur-7oY zpBZaVw12F~jGE9-D6cuSB^p`c>?7uc5ftz^^{=0@I@LR6Lhkl5UUE~AXpqvTWglh0lCUvD>V=i^4?)*ok%4!Jsx#J;BQ*Rxi@Ud?Mg^p zgUuza4>|K+76>v`~*9xERTn~7-*XET6F3KjyqGBs$k@y+b~su>CcXC^;j|Z zx%R>cgS(0LIF!{mAIDtLX&^Z^!l&Kl%w=dxTCg~_5tzpAvDy2K4biRpu2D#G6b47a z22G}&c~_UL<{|Dc06m=Kid-5TDVfAv5Z-8oQqAgAvGg0p(TrsQ<51qObKU%?|VPR z-sIL#F_{!fgpipzr@CewqtLNozw4EToR(AMrOx6qq42qX+Mt&Tcm?r967d$KOs2@r4@t4U2ai(G9Ur9fcW|;Mh|QaY!Ch zjzg6btHf-4Eh56$a*sFuJM?LYBa(N)4Qu*$*rw{(yV!;``#Wr0lZFBy4-(>C1(4sB z3~S+%x-4n8UGrlX-Q$(yWnI%CbSZ(b$ZMCEu;gpCUPU?=eXYsK`MrteGCW=(Fr$vh zix5VWlrdom!bF^qzoJeoDdF?!M9ipB)yyWz>^Q8>sQG&cOEVEvuqC6UqzudIfI&un zK1dDL9qH!h@KIpedXXn*O0P!aKS@fr?{ahVX@){S3+M2w9u8XC~L(-UYoU{ zDPfJ)1{Z(xBfHU6kUz2;9}g;Oh5NHMi0T7W8&x|DYIt#rF9P)4nWwd_8JP@_{XI3M>v?Kf+cakBEhy-{*W z1;PodfUozu>zLjIiY%`A)fF)`eh^WY063dUE-ODEhD4_ zv6e0B@5;PKp^*|Qc6BuCqlV4YYncVR=cleIS#|C2OtHWYY!EF#Uzxe|1&NI+s!qXB z5=^?RL0(GwNysN_I+&Efk691aU~(h*p7vjL$xB>f?n&X&qpw)|EF0G0*E~M3Z=r+by=$P>@YA>3o3EGzq<0qbeb@6UzCdM za_oxtXTf%~Y6@88lZuRqtg%l|v_8DrnD_MEsRR6&Whe?pU9ls>`Km7E;strc*__#o09jRd6gZ zA4o%&z>z3AmUkPmW0$~*Dmj)|)QzT1T@oiMb}aF#vfxs;UgnZHqvMpeDYbE#@8a8y zGm^<;nPRA>+$C`!$|2jzHxil~Q?|N@4?F#4zQ}E+8Rrh)LLX=!bY#b&4)Ur6(yEp) zPUnfsB?g)l;Fu#`?`I23RE7yu4k<{^o!(fDB2o0i4}-(y;>I(C9!2hpbx7>HrH*6)MW)Ik zV3?!)KLDGf|AIIsfx2aoyrjP&?SWGaR3sm0>RydErXUs{_`#B#lVj=l>8ZQd z$SDY6b;(2Wg<99=fh%aLgyachMjIptzF-Kqy`d}WUUj3Q9p^2AL=C)xBwo)086GJ8 zd;(~C^Q>=;8e?G?PZE&Bw&$NFjzTpaOorNe?035=+i1G!$Rs^=9V7=Pvdu&?lvX~< zw$3|KJqK2@W+0h}Y(~q%?Fd0~U@S}elw>y7V)%yBKjDi2G9DPv7K`^5*7PMKNPQyK zj3syrYuK$eECC48kO;WsBL(80kz2@C1x4`Jzx*@cR>;w1T4u@&aB10UOF zBKjD7nryX(hdwr=CHWZN?3JuL*%{Y#f|N!>QkaKt`!crM@)?jhyPC3J%)H5 z&^qpkJ|qR}yFGy5(LtPE(#P*~-IHz!t=FFD(|1rN-53pJqR&X}Qm`9*-ZSRS`9aI9 zIRvRZkA;2`4ESgaHDlVFq8-f~gbGrRv^X+95C{Fxir#wzWq!RtAbb*%%|q;a*h!D- z#bM7gQPL)}mL4yTX0BEYq~50G#kX$6gRaF8GP2yQdd&zhTYUVzk*CEgs3T8{OTIPo zv{-ZE$aBsl&+qkc;s={qUZ`E#$UyR=<=NPQJaD2lL+}QUT@xV*%H|eH z32i*vz!V-%h|2V6G&`eZ??~TLFQb8Nv6B33(|!-UY?FuNWBtms4*YAyke}bcu{6vt zBq*E5dk4)vtTD)a8sL&GcyBag=^Ii=Gj}5yEZR>XT&{m3EE(LqL=TWm=R^3({Lt^@J#%O^C(Y3GaP=#F{B<{ z_AkxhQ-lw+0a^Obz!4IpkF*h8_mL&0C_fS>Wcl}jBPEC*X&G61HE?7E=_4&COK%4b zL6APuO0x8B;K&Km2ignC-lv5&Kbq;l=9lE&JA?GW=fjIn8>CP4B>TVPq)>P{F#VB? zWcAHJd|>Y*8Lf|Cy6TSlgn^7?-@i5ffn^U=B%A)$=m*w3P|@bc?y~Rn7nAUEG#56b z_0I3unEhcP^e!}0Te7|NMQ6!O{HC*H3*PzeGEYuIFQk2Sv6&+3oa`2Jd^|Lz#7;K!PZ+~U)ks9-pgFVcG@tEL0m$^Ha9X%Qa87_O*_Sqi2*{s zsqp-k67w*F8IzLDH&ukud3OPR2)BoZNwm_JacF-Dw#yi?p2+h>FZdLGXqXZYm#KUVnxFq$`K15&1(m^})u99vltd6% zARgw+Hklmer2=}{PUGV*_GLhKXkN8I!9EBM%qF&tO1#3rurN(-ziJEiDjz5-1X;8a zMX<3mvd0&!VVMf&RsYEJR-Ouc7oWi&u-)PdmmNoHl(FY+(K!Oa`C9NtSqbQ7`b?gh z8tiH={86@e!C7vXPk+#DV{i6sPBo4MqF#3ToU+?3N=TfK_fFNLbaIs|TV_NTypRac5itYRi5JhIK5#e&o`E<>T4ChW-7xvwh4)=JnBxo->`L z1)W0#@jl9DX03iC>bz^;E{f66b5JzjakaLP)1ogGJkZ5f_O+>Q48c4;TMpE!?3_*Q z_EchUm-*-C@S;$W=Pn@?Z9CXH&v83Je3rX0FSdjFAZII+jUk4|!4$&>(y33L>w%_z zgJz6_47fO{|B3@;sQ-BA4}XJ&;aLUN$qX?ZR2tj!xj?NqG@WWCM^=@r1{v%Zt6r*tDrANFAok}8m_T9< zcT#;dYt&e%n$^0BR^~A#bCvwv3#^XBlfX{|gN;^+eJYTVY~_C%U=BQg%_`LxJxa`X z?Jj}yBNB6y-Gfzv&0K=re=C6T=~eaWm1T`d$bnzbie_I;H%gr;jYKNKB`LA8*m>LE zv(cAz(zf#_QjHms zTrM<#6X{+m9qC}3`S47@p5ZB?0dKzF%qduKYOY)q@=H1GRauQr{-%h>RhxkPO7x$f zGpaAuV*0>`Q{RK{rC|aRt*AAlpXSCaDPu2;I|@@sZ&I@tZ%y}Aa|UxDO|z%VNO8ZT zzyJh>J1Pk9q0l@YtsV~@P-^e&nvq#yd>B-xvC0h7c(+A#>hNS- zpZn5n)#K0-@%}7A3K>!w=lga1Q*1U-mSK$~`y>ApF6c_lRa%*>uIe9Jd&`WhH(#oT znyVHbIE05FoG*o&ZKjJSlOZ6p0A|_*NSdf{lbUlP7INhZiNTJ$6sA4W)T2Sb#Zjrb z5N9E1-w4#PQ{$zDK?c_$09i%GTNCyayuX8ULa{z+QHq(vT5a3L&I1XALq#>DW5K$n=W&0orjE&@eneh6PYHAu;}hm+eM?k!^n zv=?kCfI%mVfj1$x0c7$lne_p`)EKkdO6BcWvrfQ~%QML3St3^A=bgatNL!eO#3NEz zA{~OEHq*P)6mhMUY+K{TlZ-!+ghF{3v84tIuZb;x4?Kd+>Z5?~5JHCUM z$=9W@^K}OTn#o?qj_)8&a^XU#*FH0bN!Yle@mj+yz2FiE##ne}j(R$=hDfaUfnj3ec0W@RQtC(6Tg3xkY0BPPWe zB*mFYitDC)v5jardu~Eu=E#RL$cM8tw*{LxX#G8((#XyN7$s4C6&Ev;5L;5^0tM59 z3^L)2mS* zTsQNf#Y`${x6o2vp~Yc|Xi+fpRTRvEV8hgg5U8K*&0u5!=toQ=Ge{$|V9|1RJX}4~ z1_JqGM$8{G$R9J4Kdwa93)dMUVg`w07SbY07DhlE+aRo*&s&TztJ#bUCa<%Q*hKJ& zBMYtB4BIH1m6>Wo2={ZB3^Kti+P2fz%hm1}eKVNr&WM?02AO1LCbqTcdFY)#8RBkc zPiJe2vQ?uuv^#S$nWZ{zD9mDT!Z=>@=MCc+tKnPT@18Y^XGEXg>2(%u`KObz>WUU6 zLW3n2T5%JAH~DPG9_47vRo{LwXs_ABS?qSCu^FVXS;B0oCU#qY;}9={g?L#ah)RR; zn|WwUoLq?4KAAegUxDF25S*o8BPng1m8B34((SsauFKls8X$A1%Qr>+ z(u~5vh_#k)EAm<@(&4y1V=AM_7_qm5)0s0CY$H@F1PuVD!eSx-m^Ry~%ccCMmJf!- zh=v9C&u1o^x5Pb`@^#>XJU+{a@-vWcmQ-qSi)oP6L+v0O>Xs9O7+htMnd$eHNVx%{ z9fdp64-Q2&Is8gOy$BU!U&XQ1w7feL3%AD9J%iLeBhJxhW{zHqSodi#L!S}z@eK0u zOfLLp-XqMPj6DKm;+ehNLX!=v8ar5OVLchL?+jL3aANk&A^YYg`|jkuzxEsH_xy#4 z;oOmbbI8BB$-f5)vmkCgm}lVpEF5B$F>sS`+fw9GEE*-h>_=~YQPG>54BKX}m+~;j z=8hbjLypaTe_P&jRc@UYb4OatAuZ-6End{yN@Ae}q8hX{NE|N5;wCYkH#Yl%^;U&J z*~^Z3u)mg;!^I;!SXS~PpsXJ1#tR&k9Y85f_PZ8QFHLudDID@&9tO1f384kTXw?F< z?00x(C&kFSKljcdHRj1E(GC_ z0CST7FT}`|VC{*HB$z`I%*{0Jg1=r#)PdA7iOXRUmuLQt#OJ#7Lnw93wsM$lscu6@e#Rj`<0e1b zkWsJfc#Nlzqj5our?9e{i)h^pvw%82Tt`$91GgYT$(_c=k3?MTujAt|gUjWf0nS6G zqdj%(g!x-e%ojQ2i`?Xkqwsm~w4aU9Gv8l9#&byY%*G$96@Qh*7kl*g^0kap{ga8} z!KxCMdvMs~BhIw2D9#=b`)0N4oxzIW$1*T3UYO7*9NDFS>{6KQ(g-oZarJB-9)%;v z6p&*IlVh#~$qnJ?Uc!-S3dl5t&vvEO8&W$xj230}}G{$$Svx0>8`fCZ~M_KT@BZc-rI|Hv<3MXNA}qArBBuvg@!db1s{W(L9?AYw=U zsO)_v2O>Oi(~N*kGlK6m&DW0Bxj}=^q z9*yOleLUe-cvFhm>zgW2sWR??24o6HzaSz96kNcz5EVN_L9{jJ0dB~)cDgrf5l=){`);C(YW# zu25MtFBpVqJYxs0|HJKM^T#SssdO8_q`ho$lkS0Z0pPG593))hJd3jGiXQnh;HTNK z^z*e+ebdN_zs7B787p8Rn}nTK(j*UCddZbuFEy08-EhmA401;WtH*1kPaTS}4Kc0J3k)vd zM7_k8N-wR0>Of>l+i50Ka1(Qb$4r{A%h#D2O!s#hN%$lv(|k)iTV8HjlkE=#@%z0MGoGrYWl`Hhp%^%(YjFy(?`-=dSF`vREkxw znb|Y$P)?1kWVYo;Lp@pF;(#NwBI%L@TRH>lFq2XXnsOx=zvszpf3cj*z{)eRBk`dk z$_-A7fah>{#O&Q*-H;whVtOPYJ(4`wL6hr>DUoYGgODcI0#@8ljE`dn3)lv?iacDn zUY_Jl>rj=~D=YMkYZ4}`B=Mu7gtJ+su$pKd<;~w?kQZQm#4S7$w(v-M3lB(CHaWVp z`sV{1Mu65L-weKw{&ZbNbMFxaT9iaHs9CEOu}yG_N9^=*ujb4Q*%f#a{J`M zkrFp#U$H|iucyaVHAJJb8U|Zl;fuzcEW5D}xJTJKW4<&?o`4@M0JTioyURbTgjhqGK&c5vnD-3Ro$r>0|97{=z8 z1d!6Da%%}6x8l_c{jqN!MyMuz%?(`vkRHdSD#a)!AhcX$CX;|*ZM@a3po;9VP?1L3@G(9`HSxueD4ev{2ztScpMms1dVsWhuSdkdAzz=Mt`E zhd#)T_GGU`XPPZ`h|UJl5G=xMNAzPu>mSX>U26Xf!c`iQN6C)x@3nB`ic*K@Tia?P zu;*y6G4t||6d!rrJWkCTMQ_~6BhW6UjZAXbnxDrjgI}&`T0bt4d(-6SRGKVs$VWGZ z^OEv7J&@M-o2po|v^mU8=E>QCC^w}{2h*a;$>l&7L$2vPKn*qn#}H-Vz?hR*cEK?1 zP+>}xC*f`wd$M~!5Jd_o-HuRqUg}^s>z?}TSFO4gVaKX;v*Fgn+TLOOl&7nxwIa9T z*Xpf~WR7uF4&$mk<;OTL7tRpfQb8)>80TPhWzH_g*p(9-WigKit&w5XWzM2wWEZfG zj4UPr4>1zPb2-ZnfgjEi>$u4{t;j}&rn_hnFPfZWW$P7!*sWqZLHFzL*&eukwX`W0xRMISTHU`t(Ip1y& zOXjJ8vwKv&{aB$^DqYKZo#;V6jTbQG+)+3f(B*=;6KItgn`C(r3E}2GnB6!%RNh~i zWKpQmbWR&NUC!t8$K@PMiR0tNDka=3|Ni_OAI(#4#lnEkO8-Yf9a{CZ@2$j-Y(~mZ zT~}F^UHOHgLQuh6Y`RbZPXrmCS)=l<)?;(UER_V?P8N7A)DR8;uCi{f8)p_5H7c{e zYf!zjqQ#z|SB10*sKjZFVBhYwCzwes84F8nUIBHRn?n{x(WA&}Rw2{E0EEdi;N+W% zO{dza_Jx0UHGB|WoYp*1SFkHY$r2zi-&I9!=mKNsVF0Q`T)sk$1tqZ&9= z0}q`-w(DVNP7W2pi4_4|fF~T@`Mp^W&w@3t>MB*-!wL}&FBJ;Hzc^~;XA|OzRpsw5 z{!;HeknE_c>vm0Dv7-D-1njhm@-I+|kU6lfo5jdqSd1_Oc3M~KhXRQ66&9}_Sy@3g zH9N=>E@#pyhqctx&7{}-TzrU0er$E1rU*JMTFNskmEg--uX-#2l48&~rUel{0oWwp zugq3vtjnaz`mCiz6<&W%UVrH*yebb>N29k*SGl0#_)wP9$ z>!jpto#gV->kIhu5cp^y!Qy5rRxv!bcATwmryCO#>E+`RnF*#1T9s68JmsuuN?Z{M z11Tj!fE;;%%ZKAyHbu@(n^vN7CC@A=+aUi9YbG|HXLrVLd0ls>ZxEE&dwH(~EWCWe zS@&o38MK+lmazJG@l5} zCo;DZG<{{Asotxfx==r-{PWX#`3JZqC~RLJsEJzT^ z(G=kbY4tPP<9cQH`f6<+ww_?ji}zOpt9L5rh%dp8#QRL7wI*KiguqDD+XKD*2}R}G zeO1>py~igA@??)*JmKmE#RM?+=aXllMzYVR8e4AG^z8L;E~|Aeu#y&oNTTKeXdaqF z0Q;g8^V37|Z+pU3(FedpM1asANAot#SoO{9MztR4R*f938bPbZ=RKMC4WSr#;&{K8 zk7Eyn`YWOK_x!ELSSc}73bA8~p<{|?`(uiSwB{M~gR&Cx3Fo<2-2nHSAyx)hnKj1O z9Ga@ERLB4(_Inhye1WwAscbA3S}bPo^+WtKgL9Q1tcCN&6d&kd-{?G}g)d_kGiQ&g z=ci{`s;;mvP(o}Y>$wtnQ{85b?-M}Cb`Uc8da?V6v0O~1x|=)7;2=Y60I@H2gQfJi zfYnl!b*h~BaZ<5@r3qrIN@!Jc-K`dBZT*-%877Zn$$*K7kOM-VZd8dbHq@R{pWRMv zZR&sd?x}*@N7n0bkpn(4K*Is=>c>8vysobG9eUNQY#=u^4+c{jr$tb}{)i^IfN=AT z-pH|5y)T{))tFouFcXUb@j5YQN*kH84JgZJ4_0y2W#SyPVopE;5e~dPF(((S;qYiu zI8Jt0iLPho_Eeb2?^+j@fq0@W8R#;*_I8~T)h}lb2hj+op+Hz3fiM~}9IbvyFH=Ww zFCW>kWA$T{t;_lOso%}E@hL_D+(am7-_*-Cpau^gAvV4lAU_9wBA5?g{sPRt7t`SB z8-zYeWCRpqN1fUi=**uu}9An1}_Ap;~7je=T{iJnX|;qTtgAnL}VEB7u9R=2KQBzfUN)m)4Cs za7O>eJG|7TN3&tjphQPZ!4Xq)?A~0m^?4fqsd8#H5C+$n(+!K{n%jp55;+5jM0l7C zPsJv@b*aee`4DhZ`)iG*Rx#~Kst4K(zywhdoEoYJzODV^=^VaKcHVbH8odK z7(^Rpt|ZO+tJ1rbbC;Z+S-UBK`NDyjrZ2llAhr+O9wk5FTCndq)^HcjFzEsO5Uk)( z)S01ovjHU9-i>Kn#}~Y=RPWA)8>w_=%fkJRl|c&5V!lEc*l%!ag8lGT^s@SZ>b|M& z2-Sp%7Zyueoy2ND&XRP6XeNnx^LlnQd_H`@B^{Ou%G&Miu}T&Lfi&A@Fk8dYHTujj zO^{l?84+Qkh7m{F3`W|F*{k5J3jJ1{>2+Dl(!5i1;m~#&d~nGkD?m+@Pvtn}jPudAdST}nM{ovHpqxm> zt;`Gw_jrs|ck&9CsT~tuYix!LMva+0F}laxs6fztz~u#}T8#=%qQCAV!S%2++Gjby z34kLu0Q8I5YzI|esmhs}UDe}Da#)_N#)UAbU=`%66-J9JhjQf^4JZe*up_>fIqnRC zbBs*nT^2rNk@*d7zq|$6~uiIy^lSJ{y-e zH3TUbOib}u0*h6`iN?B8vR5HR%mmC|4ii+G2Bd;XaoXO53+F&=uQM<5Q{oxD%j%iO^OrqB@X!#7t_)?^;pmEV-E={DirkW;j@ty-Z!h5tmSf=Mw?z) zw8a-|HI;H0o=3L)Vd?O%zR`a_N^!?|!7seWY(!ii_n<_@Adgl!0Uje}<0VyR)1Xt2 zWX9ze@X2Aka(j~ir(FaLpoF)WC$N`9%p~r;%21TqDZWPrxAzH64TCW_r-1-0_JJ!D zl^p?3{kNuvGTY0d_ZaInw5~-6(({88Hi^2vsq|9Q3!IX05s|aJ!(FtWpVvmp8w`0{ z-jvVI9-}Jf1$ofpi8!_`!6NK&Q_$-1pcEFszPof)38VesHzg6L3Nd94#IkDpJM5j< z1eQO;4R(#}nmWJBKf|5&N-7j4%a1E(P@dhN1>0+yu8`PKz>e~ZkdvpHK3yfTki@B+ z_Dc>odn<35UT^6B1M;NuR6*=21$33dEF7AHW2l`jsDtyq<#>OmA;To%q#UOHE+iB9 zRaeyC)$Gx51K>s49|pDh%Vk(Y)q`lmcX2f4HivhJl$lFOhihmRo9_VHWC-5DG3tdh z9K#S%aWI@4zW5ku!I2yyam~k7SD^-@*y4=$ovx770qA68hqz*^0Wt^PCIf#5$5e}; z;TQ%<#aYyUehg%DT+URaq2iJ0att#k_sAG!!}|wDO{1Yt0Ndg__O9VN=?CM@u%rZ| zC33r*%*7Y|1?ptSm-sDI!ag4XgckFYIr763-Y)KrPsq(FEA-Ei4*bLq~I8*Th zpN3-?j{U3JQqSdHn+(xAI7YpghGQ5KDvoWfL3#|d;9yjwEjtaeW4ukiW3Q^`osrck z1TLp?u*vr^tt2AI-@&z5!!s3oYdjKOj)6D%yuU!5>MQi$LN$EHzW7{(qtNAtE8>VM z-*VkBKE|*aj(xD%Vsp9RCIfp1XL6x1qv03^{x;5{*A2xRkV{@kMYrQqm>*->9MQ3_ zwHwV4xtwg1!FvbCsOQsg3`0Q0K^Gb3U+0R3$B28Z5fz)WF%Z}S;$!SR*5vK_-4K$# zQQI5r$G+KZnTE?ZHyQXlIL0&t4aYD@D$c^E@?#*IBR*4+w(^7ca*VghckGk>a=>Yr zU7qP+3#gcjfpr`n<1JYtD%NK0#K(Y}e93zyw94%D0*?36C*s zhKP(a58Fvhz_A#TcW_KIrZgPGaO^#Qw$yXE%q9aT<5<-#2sjqQvA?{w)N}dE7DIaM zFF(sTiF_`n+2-)5NLIt9{xOiv5KwXK{yPmn)K7YhX4p2@QoLp#=S?*;5T30y+y1T( z>kIIRNszLMa>pY`wi=WJl5-Yr&RVi7?yrX9IdAe13KvpqS_C_`QnhoCq zHoD)MLtESoXFGhL`^(x^1!IZ4GRGN!3Dse6!6cKL+1m%g*m( zPR5g6-oZ3%-usxgwPgM~zzg2see}stvhW>X!xp`dX_-bAzk_SDl2>uqY=q?-HUoLx z#-cq4%QhAq{H+xt$RdbqLy%bh#kv>UtMa$jzF55c{FmsHfAP!P=!Vz(OLV)5xc{~a z3jx7j>M^w<58qb7@J4@$Zg~Wc-`2q9PpIo07Il{%FK<|IF!G8DOTmn=j>2Lfe`T}T z2;bLi2J)A+MLiHcsM#EX{2gu2-v!}Y+Agp6m*|tf3;%6&!yEi1`l1TMw^djOi2hQK z$=^l%whD$f`Ac-mVnzD41~xyVTYR(lJ}NSb)vvht+la<;#2bi)OTLY`FqHfaSesRz z-^QLSL|)#2HC*1?h?Yf2{|!`|ll=X_W+Qw(uo=kT4i;@e_-e4=Na#pQJC`X1ZN~qv^r$nxQ`6-B@vN^Q z4&IqrHfn{mnPtNeQE}|H&f{aC1xG?fGR>PFgBaptUwf27+~vo&ytf5jgQ2)wUbaQg z-$VIACP4+!)Dm-OVw$J(@E7&oa zpA$f_HW_M3%Sj9)>)Dp~as=Eb+l7RKvU0OuWOUnzoCt7e2iL+4 zTk1y~*2BJPF2W7vB;e6DIKypP>L+&9SjP@)t2=5K59+`>wuNo5J)&JW*z7#JK5Ike zvOX@Bq1gw5a{0&Q+JW1p+69C|5%R!g;q{}$)c^;Dv&hy)1jksxm0Bbt3ZNE(V+-F` z%|*DO99THJZ%^;pT z#EZWbKekpV9n$l^mAnMeu)|cmS1xIdwY$SO6evw^1zve;>!u@ESMYK{c zqE{BHLh&~WwQPjrqFEteyI|Z=FdmT-V7pixi<%< zviqwmZ;Mnw_~cM%OBg*Ij%&(ANb_(3S*9T7>83*7D%!Flq4yTq8yUM@ox5aH1vru^ zApm^CWEM%;2)3ILz=7Ji^vGSqzD3yS)wWSc|D6tH+b$&7g>*G_Q`GOJ;I3wt(9H79 z?9#5LsrYcVc%ENqCY?hB&jWhb_)Y|0^r+i`E_VdFJR)Yuc5T9$X}LYmMCRWw+BCe$ zcaLcG;Oas3WBIGBPQQCNS`SVERCq!G;$0+2&w(BefT}wAr~CEKjHs%s4?NYnv3T_( zWR{9~u~P}oi|pzTy?#?4{ajXGWn1D66ZgIa#wgq`K@=&Wc0z6oEn7ICQNjs*s@{e( z$hJO?gS6NKX{6(<6hc@Rd=fM&rf#iN=1He zZ!80Hb4cEBc$==_9N*?*BNIo`5EQTmLN-QQ{sGgMpwQ#<^Q*sKS#!!&V8+ub(QCV+Y($IV z7>Fa*BN(fn`%6FkW=fbo?_}6e4UdH&XRmCijNx4A~ z5)mSW5ZNcv!U}`C6y{*_&(H6ogks#;b^0h){?vFqg8qJ?ua|;F=j9VTB0isYqfC5O z-jdmS?fd5w4t~2cAx|wY+qQ06Wv&pvX6>mRZZyGRe^#wH^`#q1r~t{dqQkMvUS?0> z>2f}#DEo&h3|zijNcQzQjdh|ZyRwnUu52|U+76u5>v8+Y8fBJn=|u-O$}PT({Pe0z z-F=Nt2`=?`wTb|g;og2w=RRPYW7lVb?O47*wYL{+V1^D%!fxm3ES1568|1rCPG7M& zRR75dn~?|R!CcXF#?EVd!*|EvHfJR}1c2yx>8)dTMz^_<1Et60qo!ob;ff?(>V$CA zl*N#ej})@l7~x1EiyGM(FdzpQOPc=Q+?V=9C61&(7PnBz~1b)A}xn6IF05@v4S9nmBZcG-T1S zOu_Dh0Nepsi=B3uF|DtRoiV7g06 za+k!BDm@=dghP-EP3^SE86CkUOV;aVOr1t)$Ou+>V^2-%G|GvJV3T*&-!-w*CJh0*t93xnoTfJt5o`~pcO~yWo~jp@IdVDeaD$#;ll5JkhBvINkjrVE z8}tM_weNHqy3)*Pwc9MoJM}ik@GPa(+eFXaLD6wD%V-z_HGj9x20lTrvVlXsWdoyq z=0qD9=sCeZTK3R^mpeVgka?6ks#};_YYVA)e0TjUsNeICiH+(Y6GQYCj>Ksx&SN6X zNKfz$b}Jqw7{_f?&FLCz^0zz#X1$P$=o=F~f2+PGHbJbgiQzrp(1Tu1bq!4}2%d5O zXbbjKhB>e}{bNb&y;la^j>4xFVxSM+tf>yULUL7U`uF_g%BpLrD}m}NGnV8nEb&Wo zr9_~Sp1m1ihTKnN-dC2XH%Q+9o;*ed+;%5y^NWOcz5pXs7eKM7bii=fEea%d5TMZgZu}&-}*giFmrWez^5oNxgt*q8>5mkw&Z1n(KSV zMH;dz%KC``0d8Br%hUDxjg6khAcI)Fut-3#32-g7ZP^xB+hQfNc;CPnGys$PqE*hg zC7N?BdjFf4$Uy7Nq4m$trs$s^6rDM=&OqzTsdcfVcAGfv2;tEB&$j44TC{LzEkJAG(E7S9`Z|bqXe~kOty}MwcTr~6 zij?KsvIZxF%kj$V>4i(0Bn0QhttrDDdDRtsV_dE<#s~1%X|c!eUbfR4kY;XAfou4% zBN(<~dJ>`^+-YGUDJ`EW3c&5xMWcFI`GECe1=4dxPg_Lp$pnO_*4Iq_>vn3GKqVUa zlnibooIhhv;7oTn-Kck&l4`FdnM+V!0$+BmM;~qZc;0n2cYV{{vFyqagEm&N0@mkX z=aF}(uGU{YKR=HdYGn;~RtiMqfJjo$hh>54C)iUDmjix&{$fvk<1Jir3yV$gTRN)* zYs8RbqOcHzP2KNYP}#w15z`0G-&Or1gAY#5GdMD+2*+Ba_^Iv8Bab`PE(DpSxB=RJuI<8g~|%h+N(hPc$}AzFY4u+{yErP2c+Iz6o|${W;ALNl^71!A_2t~a=6TOoW*P+xV)VQQ`J@xA~ksf)ao zkMd*1gP0j$a@)rVXm4RaYo&5kkxOG=;;qdg&6OnkyVS%aN^ED9sW#=7e#5$);`J;b zWma;aee9wptof}C>#%%B**cPN0FX-nqtdgf;b7FAs@<;!drUOZ zk2P?sJ5Ui;a;l$zWPBG0N*5Cjviey+>b#yh^v}=bqp|=#aQusZrh)jX$}jd8^C3!% zAC$_MgoP#cQx(1?1An^guMYNM!KM1i_hK}ws=CwlcrU9PILbOi%!pu_=uqJo2BI^z zTRHu@c79&Km}S67>trq*P~@tY*Q>EkL)I|-^&1#u01QOwIY>9T+qSvi`nA35H`wWT zU{aoaGo)F+{e#n_*l=Sz+AnW)Bbn77pNbyDUiO>S^8hk;Ku+za(~)s4%@ zzwbk-eA}to-@O7ksSaHYJU>5&Kd%fra9C;^2=n3YU;hmn#;BoUf~;DwA@+~zZ<@A) z#&FQwnIieufB9cZXatf;EgHbcClI#Uh5W-{>Iawjsv0_RRpVXxrQ)7sU$TM)qce(3 zs$U7SLQmD$C2R@|AQMk>&UZ?;S0Ymi#VnKrT5820wPHas!M#c|A}N*4&;NeC8pRPL z0yt>mgA~NupM+i}S^ZsWi1BaewH0{`JVXEi0JAexN|mRKaRHY0&HmQ;&x@UY$sSm9 z39MEFBKfngc-QMNq}t{&ikUy3=Dn1l5b7{GPj&dkW|bzDPgoD`vbrkMz&$96O8~2- z15gjJ@Xy8IibWSdGxu*=!Yfg$-<&Nl^^t|Sf#h<@mVDe_4C&3> z=tyfX+cK0MMY8yjchuV4uD31N5C#;v)6iA%Ay?=YzoL&QD6m}+sbjbFi7p}4$uHGD zNS+?l$wbH~avyayM8_WO%-oZ=Eq+DMDJWS}God$+A(L=}EXgGWqmq3GW389=F>?pr zHpYTHB-y+o9?k%R?G+R6f&K4rc|>c{^9e8bfT+0qL#wjs-}8)Dk4#j@7|5fyN`h?~ z#>8@HYLbuedksq6%dUft3+A{K>w&*)Gm(5p1=&hqEoi0#*IAPDw`y%$PeH83ZEliZ z!S@qId*Du*DmuSqZC#6THM$VZZKNl8PFv^QNoa-xr_=_y?5nO|d^_w7MQH&@6UUSJ{jFi@jT*U?+td0_ruvau z3*GkP8~b|QFnq@AI+?L2B&QYM=pV^}ua0EIL~o!z%<~31l5_qP1NDJtPIM%<Oe=bMOU`Fbr-esgqRLn{xZS0ulidcrn z0d7i7RCvqSqkov`NRT5VkYf#zEviSoWUH@5ja^|55!Dfd8Jyw;@p#niyI9A&JA>^amMaYiPF*@XCFwcm2R1}{qd(1kM^ud+Hl z@shD6nf#;1H6L(EEFM9jI-iGT!QHV6BnG6(qn7OO&1m{il)-lA)w&TSx~zZ(TJI0P zv&Vd@oW&lL#q(TcX**T=R-YIRSCe%(jktZG#ue69jZ2I4!ha>+d6CFA3*qVzn-Nz3 zE548pHEMs(d(I1j=Y+?{AoHGe z%|!1y0okUMbjia;*c!K6DpnPZV*f%xnsGX}x4YuIDQjK>VujGW( zlI}`4_L2-}RRlnCm#pD)wm>wT-v1B3N>!Pm#)fJ-G;-E#+pnwgugxg#&*agm8TaY9?_N1p}FG?7KPuVYLVaTzA0e|oZ|A2 zQ7!s_T-M)x*)*`)2+uQ7tRbO*E2d*F`c0@Cmvd406RH*)fgnte!ME6seGm3bcYP(W zhYww@W>X$}R-=X*a}JkhEotd@xo;Gja-;j2jd1GE7X}sQF+T&zu^+*qeb>5!WhI#H zMlL5>5g&U{hj+g$deg||KHD78`$*gNAI0xr@5ngzp)U!iqbX9C$4o?o8$U78iJVt_ zYLa7*z+rkExg232Aq*sH9Kb~`V;^YjT^7p$E`DK&OP8TB174(cG+S`j>FHuhJ9LcIhL;p zJ#zWb#S))TeVQ^LOGXn*LV{#X8IdKU>m@Kzlq3Z4-S#BW{9Zz?!i|=ig4E18(FH3s zcdPR{go0`Xm`Nq_I2~`K3aD5#eW#TgX=L)QOSrr0jm7D)Jyxru%@mu< zE;kvD?URp75;TL$Ko_KeYQdB^ugQ)JJRBfdXZP}VFK4|ZbCr6Zi#)d7;3W)gH@E_7 zqi*nq$1S_T6%jJfJ>J-vjqY(B=LqdCuPbTT=VH`j5Qh&{G0T);<}uAMVRk;wQ)e6+ z&3x6Du}3oGHRc*50)$^cU@=aJ>aC+d)<|%id~!X^rcj~pTx|Wy+g2ac~AudNu0)USv&wLi_+I~ElImYbsbNP&`c-*iIwgiC$`0Ae6 zH^59B!&aF>vT-lK^a{TP4eJHkf(aA_tq>D6Pe5}%?V@j4->|mmhV!Xar_r}xtWo+e z-+Yw~!x`*^haf`G@VEqGdrh^GEMQZVg*B87UjRQWt4+)S5OYdA_!qW8*#hvf-rb}) zp;ZO3ZX{sk@%lrdkY7w$EmLMYN1Z*!Dw;L%iG9KH>H^CEex2AiKQ39HeOo+Bu$$Q% z&KewXBVF6;?ghx4%+A5G|UxoY>m+d zfvx28ZAN;F0x#-T;-q4$9=v4TlvPp69@4H91`a9FwNh}cluvhN{Zb}?A4hcqv+AYf zUm0u<9A2nr*Bg%9QhotmD<_=Da64Zk8wu>yjDHBzPbO5;l&!o=RGRPJe5T$(C|ukkezI;N>MLWVX6=K0a4(VDbchU zm^SmL-Fm!WkJ&W<_@~+Mk1g{JCdT|2_=q47{@HBB@C*bF6}X>n+fe#tfg&V`AmW99 zH(NCdqUXLkdoVN>3mLFvMEM+u?GrXYhE5gXv9u#dAu=cLmHdsDU+zv6G>JVK}FSl`1sNzxErE76l$@uVg`um{Wxp zdTG*Trq6l8QO4-dvq&L-m)AM0xtO}Sler9Lf zR{{j1p@|HL#R_da{|0Jk`LJbJ+u*LucGo3kQLF!!YYDfyg#UpQ@Kk0l$$m)X+odYc zOWvj_G*!X#$FrQ0;YVHuYh_q&U0h;u@Isc#QmTa@2Ju{(P%m_eV2unj7th$V)vt1? z$Jp7hTp3<=ChcTYbQd6YoZrxOMZa~*f_h$~07OfOXekgoj&u-W8%;geXe#_?7h>5i zP_~PsY~p7d><6)6QBOv@5SV4KJ}2`c=g}a$XM^andAny}sCNm7cT`-KZ3@paEgGsV zBNl~2Q6zIY!439A?zutLqw8n(5mg&507oe)G7fNJ8?JwxO7)MWtElje&yimOKe24! zo0+-r+>$!)kTzZn9a9FQP~wFY3@)S)vl)yt3S;Z?3< z-ILb9eS>TzT|GTjaNn1U;13MqmBa+Za-t5x-u$06^4px)C&txUP5B$C%@Hh`5>LER z-I%KUkM_wbxUvrJhlHIviab}E*sC~4gi-HTDFiS97W_}OC@bKMfw>|-3>JtF7aw2o z_+%w_H)&JvpbG3IleWZ8c5``4Ks79sVg)E1fN~$}As;JI`M#(CEHSw3%TrmNOLsLa zlec1t7K=L)gZplevIa6SPU0T!cWJY+{do(aE6+ zf638&9`LWSDmyq7Ol!2{dQ8M>m|N_yfd(mPkX~r&rm9}yltYKQ2Kbk)f?MCJ9&fTW zY%f)EZE&NqJOO-S-3lmKZbdbfjdmwy4vqQWYellMg$<8NVb**!tbn6?^uMG;9dZd` zX*$69C%L?&_K6rxIn0JhI6b{?;Iq~f*s9^UAiP8o&QzH;tjv~y%lo~2YFm>R2I7gj zc%X|J9?z}-9m_u41$xh|otbq7XPW9wMEbIKB_;-0L|`9)?Zu)8#qGXp+^}<(Ou$FC z;T}+POo@``@5U4v1`5z#i>4*OxnjdA4E;dk0-poi1Y1@LIr?T&ve8avA~;q_E-@ zzH(k4xQ0bh1RHoa);2o=_rto3Ralof-_w6bd1Hxehyk$4$qoWC7a&^F>d`(C$GTAY zL-jK3HlE*?=ll0~s&|))!*VFGBuY~eTxm_gisumot zF#99t#nSi0>Vwcw7+5at)OAId0-n7E9o-vmf=DG5S`Od6UtYZw0Af1GEm6J_Rd1vC-syLZg7zno;bj6S zK69Z&XZ(n?Sr!k^paT(^1I6cvB3vk!`1G@b5(lRrU1Pp1U_L(u@AA z1~Mc{BDsnp4ACCKEnplHsKO-{=I}I0H9MlkRI~)q@^zC9GYyYZrryILk2B2a-Ir`t zqIb&tbBNON45bmlFKGIgvz-aZE1Z4!UBgkp(}DH?t$I@Zju(>OhIL86@1vB63K|2s z?{9z(CkNLvtl<$%&UY_ZoZst9$3aAZf~dGY;@XEnjD?jLqB5p3t-gZO{H~3ZH+`+T zj)K6Np#W;-rdYXYw!YN8m48&uGoE>(pFt4r^+C)dUJ8D8;Z!_o@uAT??Hav>scf*W{LH}KF6ywogCesP$7GMR_{ z;14pT0-2~73^ByC{)NL}X^vq(==&zsLCugJoY&_4)ynAUu0SclP$w%B^s<~kU&{&)Tu8FXpJz3dwMZ=KY zo@TNMwPR~C4^h%>l!ldJ1*86elTLw+xiL;fUx`KvO$?swQ;pL{t+>j ze=5zEPuo_vXzJ+YeES&SNd!jU=|&huhqmjRd+eZfpK4@?bF{cNLASW|tkBiZv9Wnf zEG73rILXW$+r*YpEJx$X^R@7{=CgZ1j49Z7@!vhUTm*v zR1~sX?JtJ!lO-%{+c!?8Yk^s!2x<|q%>%O#d=xtW zj%`2FpRj?6Db|=;SqWBFa-JQS^K8nxGx-z*$K|tMO!G#_8&jc@U65b3rbzewB{@UE z5`SOQ+m@<25lsBZAsN$y#NSu%&z9srQZuK>oDnO*+!nAB_@isp7bs9t1wyi%g!|P3 zX>$2c?uAoE;J+qNbqp;$30>6_V)fWd@KyrH^{YCuS-l>-8qR&2Cvr)I=0~;G6sUkErevH?yXfYhc5Zfrd15}3ETc+4srsfu3m3!3KsTHOA*Y*B- z7FH@Wz306O{$Fz1<%ynrQq>qa^n@6_=KE5s=n1!bti$gU|hQ`Ux zQ%8L(SW~x!N=Ifzo;{qctNN-rrfZEB6zqE=JBNr8ZJ^8q$~~l73yD!kMlBhbJG=P1 znEJf#@p6b&xvx4Mfwd?51-oFuF7bZ#akeG=*C0D_$%Y`!d`Ht%uT9}y*A2Skkdyhu z&W`yBlCup&oh-pTRaSr54TZ1Z3&rrs5kzDtNK$e0twtIakgE6S(JZi%qZy@6CiI<2 z=BWvbR`g~87Lq!>!f<+p*+)>!Q05D_1sr<6^A08?S}hQpHKgKWXT!|aY)CuXx`)pJ z@SVT})nXV#ZNBUZ*F!E$3HDX^9 z?sS@koyC&^yRpoVkS`&^V5*ZXK4}kvS(Y{OtDJwWP(8_2qS4@8OmHF&eu0C(+Ifqj za5T)!XK6K#tFTK7yR2x=8P%LQnllfQxu14@bDItR zn_E#&Bj3$;8Ph{i9)Q(KYLJlFF48`bp-}_dd1%DHr+u7Y)2gi-fk8>ekdPCv7P(yT z8-yCRP5pTWiv!l1_C7y{7gH=(Ofj-rOA!XDVe{hfmlZwh4i4J@MhWcUg{Wxg0eKW| zpv^5~^=NJiia$ar`J%YI<p!L?RnDYFN$g~e-IFCI+SlT1+v6*EUn zvuxfHZEa&+znI!4XDEb-iQgVB7eAQo8M{5$M%xT89*n(S46F3w?=B92mk|oi7lIet z(afW<0G9wY3jD<`Gt|o3m^nOL99|2&0Z5>l=LOC4GTZA6A11|C9gJ__uwGyZk8YM6P7lN!!nK0i0#s4>0=asp4aTST2} zjc~3tVrHJvI*|KWuvUJsQ+2)d^7P2fY1fPxt)8x ze&Co8*p%D1xV&*#kML|uJZ63G{k3kC3!N$n+lS#AP7oa-08t<8!NtYihq6E!4hqRw zqZbe?UJE#Ek>6$h;h{t?Mu`bg0WSq|Ash-k`g@1FzaNsdu=r0&=vI0>WeXQ~W1NA- zuOL-|Y-(M2SQlPkwT)gwfU|n1X)TgQgjKov^{B~RmU#X)NP%Xces31|y;=C-;u!p@ zo-R+6FbY+jo9r5MqhemS`g&xP0kiw+mkJpnWPGtNcW#{6t`%-uhr&Az`H?9>NbD%lIJ=;@bS>@L61cxx)$w!u^a=y@Un{?8}Gu z1(pjmJw`UlpvY7>@ZdY42<%V8E|J`gC{QTFDq|O74cjMzxy0)VWIl=ysNzE;kEkHO z+(o=;p)m?gs5+!b&Z#<7ZXJGWp{|By*sxhjMQpbbwVml^&6oo(%Z`wS#qXM$yF>|4 zo%=L_g-h`Y1@gdw1_K-soEV2w_hUfjyo9bnZfNU3VDN`I^UbWZ^ z#d4ZOhOkOhgqMJ<#jgV`&?du+Be;d|F;x9>GG=>#`O;#A3lsbx^wUGk>27oDt)61} zXuGE8w?!-a4u1%JYz})D9YL}n;p-OP-*cy4!VH7@4c^2rN%#*9;cDw3dK0|^4ILC%j^>muvH6=~$F`4L_7uEJs1j^C(PI2z#ydTi3zRj)DjmT^pncJ!CCVvnvF zyIeEI1R>^haIqJ{W+a0}XM+{Q!li+H1A7phkAicD-~{vDQo zS8#$YYf`2e5ma?-PD~TRQZ!I@CVCRXIKd8+etTrL8!K$GaXi3W;{k@Q&(ilaaEuP- zWMf+}S6eV_i!6IjgWV>v$xO>+*fNqj9Lv!x?bA%R;8gqTouK9nmg(%5j&c~9m( zMl0$^S=Cyub$uqkeSXegj@#Wo&XM{r%1Z;3M}=ny&#ZX#yu%q)S2%cG44~Gd8?Ld; z;oJ#<%VU**9K>X2B5HO)@_kg80Z<49d{t_9;Pp!_58*i#o+G?{F2okRDA}W~#8QFm zuIl9UtG?mlh5Rh`lNa?8vBV)3zxUY^NS|C_fFw2+)t2$osrt1~+@EG%%waUjrl!f3 z@rGVt%#fXmc0n}zN?>!a)DH&!t(#syKlRZJs<=NW$lLGW@(a4lWpRiNtR!*BZ&(Re zxjw|W>sbllD27_L7*SKJCM$VWcwh2#1V6t2LR-D0SgkT7Ld!^zd-m@6Mqc%5*l8*E z_>FmuIxQS%u~j|TN3VW~CHJsZJ^Pm6Q8XM}*jM_lcynZ7^6OBw~0HYbDvwILlv^?9uCS-U3Lpq+&8%n+&&K-b-*K( zWw}__)X(CjWPF!!2A8G-U-1Q1MeHp6+-iPU9<5T9zPt<7_Me}!S2%dshRSM&dd|@f zs8)uHB=W%i+NDx4)swMrI5xS`U3B31WR(Z@z1s4nvx*?NxOl)Dg4`NHs*C_^@Bi=Y z?3NpaVJQ46d4Xh#!8TkP|1h0lp@mSo@Bbnz9nvOb+eG7=%p?)|G{UiEA`UD>0Gm`l zaF7}}*o{5z3LFochtmn~C^a+Bs@pLE#|V?7ksMv_F1DQ4*O_M;^o^~w%+CimP2)Oq zX}1nX$dKZfdC_JIm-{8R^P|fMpVAO8W$L?rW&fb>FixUrh32sh%DwkLA-%H)a)lY zA(n+|lBw<<2f7Srs(tv;2P=)2zjU_G;PD}*DsYIb!-Nbq%y-otVn@=bOz*~#cC<7t zE)(DA8>jh8B{GeDz{Q*acXK3zBuSf@6PlTm@p`ZP!pqp{gj3irT}O)wj4{U;>A1BR zw|3uO>v|d;e{LJvxWnsxVL2@Ng$$nMbbhp9DXs&heM_`kLAJgQkDnH=dl7p m^cvGMQEE#i{Qn;-ZQv!57is3>R)@UgrT!m7f@!L{4FUi!5T|+o literal 0 HcmV?d00001 From 30c6937151a6b5f270657dea8d2e8d131f7b496e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Mon, 30 Mar 2020 17:06:55 -0700 Subject: [PATCH 191/419] Don't use "rt" mode because 2.7 on Windows. --- src/hyperlink/hypothesis.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/hyperlink/hypothesis.py b/src/hyperlink/hypothesis.py index 157f8aa6..b01decad 100644 --- a/src/hyperlink/hypothesis.py +++ b/src/hyperlink/hypothesis.py @@ -63,8 +63,11 @@ def idna_characters(): dataFileName = join( dirname(__file__), "idna-tables-properties.csv.gz" ) - with open_gzip(dataFileName, "rt") as dataFile: - reader = csv_reader(dataFile, delimiter=",") + with open_gzip(dataFileName) as dataFile: + reader = csv_reader( + (line.decode("utf-8") for line in dataFile), + delimiter=",", + ) next(reader) # Skip header row for row in reader: codes, prop, description = row From a1b360c56d84fcd30dae758c6fc202e700c5435e Mon Sep 17 00:00:00 2001 From: Glyph Date: Tue, 31 Mar 2020 00:10:05 -0700 Subject: [PATCH 192/419] per CR: explain in much more detail --- src/hyperlink/_url.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index 8fe0ef7b..51b926cd 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -815,9 +815,18 @@ class URL(object): that starts with a slash. userinfo (Text): The username or colon-separated username:password pair. - uses_netloc (bool): Indicates whether ``://`` will appear to separate - the scheme from the path, even in cases where no host is present. - May be implied by scheme, or set explictly. + uses_netloc (Optional[bool]): Indicates whether ``://`` (the "netloc + separator") will appear to separate the scheme from the *path* in + cases where no host is present. Setting this to ``True`` is a + non-spec-compliant affordance for the common practice of having URIs + that are *not* URLs (cannot have a 'host' part) but nevertheless use + the common ``://`` idiom that most people associate with URLs; + e.g. ``message:`` URIs like ``message://message-id`` being + equivalent to ``message:message-id``. This may be inferred based on + the scheme depending on whether :func:`register_scheme` has been + used to register the scheme and should not be passed directly unless + you know the scheme works like this and you know it has not been + registered. All of these parts are also exposed as read-only attributes of URL instances, along with several useful methods. From 97c62c27a0a500ff7b4c81ebb9784a18b392fb4d Mon Sep 17 00:00:00 2001 From: Glyph Date: Tue, 31 Mar 2020 00:18:29 -0700 Subject: [PATCH 193/419] match __init__ doc --- src/hyperlink/_url.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index 51b926cd..d8329985 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -1028,8 +1028,8 @@ def userinfo(self): def uses_netloc(self): # type: () -> Optional[bool] """ - Whether the textual URL representation will contain a ``://`` netloc - separator. + Indicates whether ``://`` (the "netloc separator") will appear to + separate the scheme from the *path* in cases where no host is present. """ return self._uses_netloc From 4b81bb5db8a77410649c3f39bce6e08097e9d9cf Mon Sep 17 00:00:00 2001 From: Glyph Date: Tue, 31 Mar 2020 00:22:14 -0700 Subject: [PATCH 194/419] per CR: match __init__ --- src/hyperlink/_url.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index d8329985..244406f2 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -1158,8 +1158,19 @@ def replace( slash. userinfo (Text): The username or colon-separated username:password pair. - uses_netloc (bool): Indicates whether rooted paths should include a - scheme-separator by default. + uses_netloc (bool): Indicates whether ``://`` (the "netloc + separator") will appear to separate the scheme from the *path* + in cases where no host is present. Setting this to ``True`` is + a non-spec-compliant affordance for the common practice of + having URIs that are *not* URLs (cannot have a 'host' part) but + nevertheless use the common ``://`` idiom that most people + associate with URLs; e.g. ``message:`` URIs like + ``message://message-id`` being equivalent to + ``message:message-id``. This may be inferred based on the + scheme depending on whether :func:`register_scheme` has been + used to register the scheme and should not be passed directly + unless you know the scheme works like this and you know it has + not been registered. Returns: URL: A copy of the current :class:`URL`, with new values for From aae83a8b4af091cb5377bab32d643f74d197a586 Mon Sep 17 00:00:00 2001 From: Glyph Date: Tue, 31 Mar 2020 00:28:06 -0700 Subject: [PATCH 195/419] per CR: add https:/, enumerate the cases --- src/hyperlink/test/test_url.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/hyperlink/test/test_url.py b/src/hyperlink/test/test_url.py index 668ddd84..12b8ffcf 100644 --- a/src/hyperlink/test/test_url.py +++ b/src/hyperlink/test/test_url.py @@ -951,7 +951,11 @@ def test_netloc(self): url = URL(scheme='https') self.assertEqual(url.uses_netloc, True) self.assertEqual(url.to_text(), u'https://') + # scheme, no host, no path, no netloc hack self.assertEqual(URL.from_text('https:').uses_netloc, False) + # scheme, no host, absolute path, no netloc hack + self.assertEqual(URL.from_text('https:/').uses_netloc, False) + # scheme, no host, no path, netloc hack to indicate :// syntax self.assertEqual(URL.from_text('https://').uses_netloc, True) url = URL(scheme='https', uses_netloc=False) From a5bc24123a96c04f16067a555aeafa686195f82b Mon Sep 17 00:00:00 2001 From: Glyph Date: Tue, 31 Mar 2020 00:37:00 -0700 Subject: [PATCH 196/419] per CR: rephrase gibberish test docstring --- src/hyperlink/test/test_url.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/hyperlink/test/test_url.py b/src/hyperlink/test/test_url.py index 12b8ffcf..6952e42f 100644 --- a/src/hyperlink/test/test_url.py +++ b/src/hyperlink/test/test_url.py @@ -1145,10 +1145,13 @@ def test_autorooted(self): def test_rooted_with_port_but_no_host(self): # type: () -> None """ - URLs which include a netloc separator are inherently rooted, regardless - of whether they include one because they specify an explicit host or - port, whether they are parsed or directly constructed, and whether the - ``rooted`` constructor argument is supplied or not. + URLs which include a ``://`` netloc-separator for any reason are + inherently rooted, regardless of the value or presence of the + ``rooted`` constructor argument. + + They may include a netloc-separator because their constructor was + directly invoked with an explicit host or port, or because they were + parsed from a string which included the literal ``://`` separator. """ directly_constructed = URL(scheme='udp', port=4900, rooted=False) directly_constructed_implict = URL(scheme='udp', port=4900) From e064fd561af4f549aec302450bf41c5be97d2939 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Wed, 1 Apr 2020 19:33:45 -0700 Subject: [PATCH 197/419] Archor a few paths to the root of the source tree. Minor re-ordering. --- .gitignore | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 35a65f26..71b62599 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,6 @@ -docs/_build +/docs/_build tmp.py -htmlcov/ -.coverage.* *.py[cod] -.mypy_cache # emacs *~ @@ -32,11 +29,16 @@ lib64 # Installer logs pip-log.txt -# Unit test / coverage reports -.coverage -.tox/ +# Testing +/.tox/ nosetests.xml +# Coverage +/.coverage +/.coverage.* +/htmlcov/ +/.mypy_cache + # Translations *.mo From cd9e666ca3c3ef60aefa6eac74597edba5ea9c96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Wed, 1 Apr 2020 19:34:16 -0700 Subject: [PATCH 198/419] There is no requirements-test.txt file in the source tree. --- MANIFEST.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index 3e8a8eb5..4581a1df 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ -include README.md LICENSE CHANGELOG.md tox.ini requirements-test.txt .coveragerc Makefile pytest.ini .tox-coveragerc +include README.md LICENSE CHANGELOG.md tox.ini .coveragerc Makefile pytest.ini .tox-coveragerc exclude TODO.md .appveyor.yml graft docs From a08690cae1f8b308c13cbad5792d1bd37541f930 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Wed, 1 Apr 2020 19:34:40 -0700 Subject: [PATCH 199/419] Spiff up the tox config a bit more --- .travis.yml | 2 +- tox.ini | 109 ++++++++++++++++++++++++++++++---------------------- 2 files changed, 64 insertions(+), 47 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6e9403cc..80b3525c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,7 +22,7 @@ matrix: - python: "3.8" env: TOXENV=test-py38,codecov - python: "pypy" - env: TOXENV=test-pypy,codecov + env: TOXENV=test-pypy2,codecov - python: "pypy3" env: TOXENV=test-pypy3,codecov - python: "2.7" diff --git a/tox.ini b/tox.ini index 8c15db59..8369b4b7 100644 --- a/tox.ini +++ b/tox.ini @@ -10,8 +10,27 @@ envlist = skip_missing_interpreters = {tty:True:False} +[default] + +basepython = python3.8 + +deps = + idna==2.9 + + test: typing==3.7.4.1 + test: {[testenv:coverage_report]deps} + test-{py26,py27,py34}: pytest==4.6.9 + test-{py35,py36,py37,py38}: pytest==5.2.4 + test: pytest-cov==2.8.1 + +setenv = + PY_MODULE=hyperlink + + PYTHONPYCACHEPREFIX={envtmpdir}/pycache + + ## -# Build (default environment) +# Default environment: unit tests ## [testenv] @@ -27,39 +46,16 @@ basepython = py37: python3.7 py38: python3.8 py39: python3.9 - pypy: pypy - pypy3: pypy3 -deps = - test: coverage==4.5.4 # rq.filter: <5 - test: idna==2.9 - test: typing==3.7.4.1 - test: {py26,py27,py34}: pytest==4.6.9 - test: {py35,py36,py37,py38}: pytest==5.2.4 - test: pytest-cov==2.8.1 - -passenv = - # See https://github.com/codecov/codecov-python/blob/master/README.md#using-tox - # And CI-specific docs: - # https://help.github.com/en/articles/virtual-environments-for-github-actions#default-environment-variables - # https://docs.travis-ci.com/user/environment-variables#default-environment-variables - # https://www.appveyor.com/docs/environment-variables/ - codecov: TOXENV CODECOV_* CI - codecov: GITHUB_* - codecov: TRAVIS TRAVIS_* - codecov: APPVEYOR APPVEYOR_* + pypy2: pypy + pypy3: pypy3 - # Used in our AppVeyor config - codecov: OS +deps = {[default]deps} setenv = - PY_MODULE=hyperlink - - test: PYTHONPYCACHEPREFIX={envtmpdir}/pycache + {[default]setenv} test: COVERAGE_FILE={toxworkdir}/coverage.{envname} - {coverage_report,codecov}: COVERAGE_FILE={toxworkdir}/coverage - codecov: COVERAGE_XML={envlogdir}/coverage_report.xml commands = test: pytest --cov={env:PY_MODULE} --cov-report=term-missing:skip-covered --doctest-modules {posargs:src/{env:PY_MODULE}} @@ -73,16 +69,16 @@ commands = description = run Flake8 (linter) -basepython = python3.8 +basepython = {[default]basepython} skip_install = True deps = flake8-bugbear==20.1.4 - #flake8-docstrings==1.5.0 flake8==3.7.9 mccabe==0.6.1 pep8-naming==0.10.0 + pycodestyle==2.5.0 pydocstyle==5.0.2 # pin pyflakes pending a release with https://github.com/PyCQA/pyflakes/pull/455 git+git://github.com/PyCQA/pyflakes@ffe9386#egg=pyflakes @@ -137,13 +133,13 @@ application-import-names = deploy description = run Mypy (static type checker) -basepython = python3.8 - -skip_install = True +basepython = {[default]basepython} deps = mypy==0.770 + {[default]deps} + commands = mypy \ --config-file="{toxinidir}/tox.ini" \ @@ -170,11 +166,7 @@ warn_return_any = True warn_unreachable = True warn_unused_ignores = True -[mypy-hyperlink._url] # Don't complain about dependencies known to lack type hints -# 4 at time of writing (2020-20-01), so maybe disable this soon -allow_untyped_defs = True - [mypy-idna] ignore_missing_imports = True @@ -188,12 +180,19 @@ ignore_missing_imports = True description = generate coverage report +depends = test-py{26,27,34,35,36,37,38,py,py3} + basepython = python skip_install = True deps = - coverage==4.5.4 + coverage==4.5.4 # rq.filter: <5 + +setenv = + {[default]setenv} + + COVERAGE_FILE={toxworkdir}/coverage commands = coverage combine @@ -209,17 +208,34 @@ commands = description = upload coverage to Codecov +depends = {[coverage_report]depends} + basepython = python skip_install = True deps = - coverage==4.5.4 + {[testenv:coverage_report]deps} codecov==2.0.22 -commands = - # Note documentation for CI variables in default environment's passenv +passenv = + # See https://github.com/codecov/codecov-python/blob/master/README.md#using-tox + # And CI-specific docs: + # https://help.github.com/en/articles/virtual-environments-for-github-actions#default-environment-variables + # https://docs.travis-ci.com/user/environment-variables#default-environment-variables + # https://www.appveyor.com/docs/environment-variables/ + TOXENV CODECOV_* CI + GITHUB_* + TRAVIS TRAVIS_* + APPVEYOR APPVEYOR_* +setenv = + {[testenv:coverage_report]setenv} + + COVERAGE_XML={envlogdir}/coverage_report.xml + +commands = + # Note documentation for CI variables in passenv above coverage combine coverage xml -o "{env:COVERAGE_XML}" codecov --file="{env:COVERAGE_XML}" --env \ @@ -239,7 +255,7 @@ commands = description = build documentation -basepython = python3.8 +basepython = {[default]basepython} deps = Sphinx==2.3.1 @@ -256,11 +272,10 @@ commands = description = build documentation and rebuild automatically -basepython = python3.8 +basepython = {[default]basepython} deps = - Sphinx==2.2.2 - sphinx-rtd-theme==0.4.3 + {[testenv:docs]deps} sphinx-autobuild==0.7.1 commands = @@ -279,7 +294,9 @@ commands = description = check for potential packaging problems -basepython = python +basepython = {[default]basepython} + +skip_install = True deps = check-manifest==0.41 From bcf88a4d467aa94429daf3256e2386a8d8a73c42 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Wed, 1 Apr 2020 23:31:00 -0700 Subject: [PATCH 200/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 8369b4b7..b0ac199e 100644 --- a/tox.ini +++ b/tox.ini @@ -258,7 +258,7 @@ description = build documentation basepython = {[default]basepython} deps = - Sphinx==2.3.1 + Sphinx==2.4.4 sphinx-rtd-theme==0.4.3 commands = From 1da7c0ac4059406435aca15dec8e1636812c3d32 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Wed, 1 Apr 2020 23:31:05 -0700 Subject: [PATCH 201/419] [requires.io] dependency update From 2f90eb75d87d70a0e5014b943926c95f6629fead Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Thu, 2 Apr 2020 08:04:36 -0700 Subject: [PATCH 202/419] Ignore /htmldocs --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 71b62599..a9fd06cf 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,9 @@ nosetests.xml /htmlcov/ /.mypy_cache +# Documentation +/htmldocs/ + # Translations *.mo From fdf05b8462823f6c53c23587e73f72c1ca9f012f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Thu, 2 Apr 2020 09:39:41 -0700 Subject: [PATCH 203/419] Couple more cleanup bits --- .gitignore | 7 +++++-- tox.ini | 12 ++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 71b62599..85b0e78f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -/docs/_build +/docs/_build/ tmp.py *.py[cod] @@ -37,7 +37,10 @@ nosetests.xml /.coverage /.coverage.* /htmlcov/ -/.mypy_cache +/.mypy_cache/ + +# Documentation +/htmldocs/ # Translations *.mo diff --git a/tox.ini b/tox.ini index b0ac199e..cc46f197 100644 --- a/tox.ini +++ b/tox.ini @@ -2,10 +2,10 @@ envlist = flake8, mypy - test-py{26,27,34,35,36,37,38,py,py3} + test-py{26,27,34,35,36,37,38,py2,py3} coverage_report - packaging docs + packaging skip_missing_interpreters = {tty:True:False} @@ -182,12 +182,12 @@ description = generate coverage report depends = test-py{26,27,34,35,36,37,38,py,py3} -basepython = python +basepython = {[default]basepython} skip_install = True deps = - coverage==4.5.4 # rq.filter: <5 + coverage==4.5.4 # rq.filter: <5 # coverage 5.0 drops Python 3.4 support setenv = {[default]setenv} @@ -265,7 +265,7 @@ commands = sphinx-build \ -b html -d "{envtmpdir}/doctrees" \ "{toxinidir}/docs" \ - "{toxworkdir}/docs/html" + "{toxworkdir}/htmldocs" [testenv:docs-auto] @@ -283,7 +283,7 @@ commands = -b html -d "{envtmpdir}/doctrees" \ --host=localhost \ "{toxinidir}/docs" \ - "{toxworkdir}/docs/html" + "{toxworkdir}/htmldocs" ## From e3ca8098053ab0f2aaa1114e7ff1043c99b1f0af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Thu, 2 Apr 2020 09:45:19 -0700 Subject: [PATCH 204/419] Run black-reformat --- src/hyperlink/_socket.py | 17 +- src/hyperlink/_url.py | 933 +++++++++------ src/hyperlink/test/common.py | 7 +- src/hyperlink/test/test_common.py | 13 +- src/hyperlink/test/test_decoded_url.py | 131 +-- src/hyperlink/test/test_parse.py | 13 +- .../test/test_scheme_registration.py | 41 +- src/hyperlink/test/test_url.py | 1042 +++++++++-------- tox.ini | 2 +- 9 files changed, 1254 insertions(+), 945 deletions(-) diff --git a/src/hyperlink/_socket.py b/src/hyperlink/_socket.py index 769b9d54..3bcf8970 100644 --- a/src/hyperlink/_socket.py +++ b/src/hyperlink/_socket.py @@ -2,6 +2,7 @@ from socket import inet_pton except ImportError: from typing import TYPE_CHECKING + if TYPE_CHECKING: # pragma: no cover pass else: @@ -25,7 +26,7 @@ class SockAddr(ctypes.Structure): def inet_pton(address_family, ip_string): # type: (int, str) -> bytes addr = SockAddr() - ip_string_bytes = ip_string.encode('ascii') + ip_string_bytes = ip_string.encode("ascii") addr.sa_family = address_family addr_size = ctypes.c_int(ctypes.sizeof(addr)) @@ -37,10 +38,16 @@ def inet_pton(address_family, ip_string): except KeyError: raise socket.error("unknown address family") - if WSAStringToAddressA( - ip_string_bytes, address_family, None, - ctypes.byref(addr), ctypes.byref(addr_size) - ) != 0: + if ( + WSAStringToAddressA( + ip_string_bytes, + address_family, + None, + ctypes.byref(addr), + ctypes.byref(addr_size), + ) + != 0 + ): raise socket.error(ctypes.FormatError()) return ctypes.string_at(getattr(addr, attribute), size) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index 244406f2..73740397 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -20,16 +20,31 @@ import string import socket from socket import AF_INET, AF_INET6 + try: from socket import AddressFamily except ImportError: AddressFamily = int # type: ignore[assignment,misc] from typing import ( - Any, Callable, Dict, Iterable, Iterator, List, Mapping, Optional, - Sequence, Text, Tuple, Type, TypeVar, Union, cast, + Any, + Callable, + Dict, + Iterable, + Iterator, + List, + Mapping, + Optional, + Sequence, + Text, + Tuple, + Type, + TypeVar, + Union, + cast, ) from unicodedata import normalize from ._socket import inet_pton + try: from collections.abc import Mapping as MappingABC except ImportError: # Python 2 @@ -38,7 +53,7 @@ from idna import encode as idna_encode, decode as idna_decode -PY2 = (sys.version_info[0] == 2) +PY2 = sys.version_info[0] == 2 try: unichr except NameError: # Py3 @@ -46,14 +61,15 @@ NoneType = type(None) # type: Type[None] QueryPairs = Tuple[Tuple[Text, Optional[Text]], ...] # internal representation QueryParameters = Union[ - Mapping[Text, Optional[Text]], QueryPairs, + Mapping[Text, Optional[Text]], + QueryPairs, Sequence[Tuple[Text, Optional[Text]]], ] -T = TypeVar('T') +T = TypeVar("T") # from boltons.typeutils -def make_sentinel(name='_MISSING', var_name=""): +def make_sentinel(name="_MISSING", var_name=""): # type: (str, str) -> object """Creates and returns a new **instance** of a new class, suitable for usage as a "sentinel", a kind of singleton often used to indicate @@ -83,6 +99,7 @@ def make_sentinel(name='_MISSING', var_name=""): >>> type(make_sentinel('TEST')) == type(make_sentinel('TEST')) False """ + class Sentinel(object): def __init__(self): # type: () -> None @@ -93,7 +110,8 @@ def __repr__(self): # type: () -> str if self.var_name: return self.var_name - return '%s(%r)' % (self.__class__.__name__, self.name) + return "%s(%r)" % (self.__class__.__name__, self.name) + if var_name: # superclass type hints don't allow str return type, but it is # allowed in the docs, hence the ignore[override] below @@ -110,53 +128,62 @@ def __nonzero__(self): return Sentinel() -_unspecified = _UNSET = make_sentinel('_UNSET') # type: Any +_unspecified = _UNSET = make_sentinel("_UNSET") # type: Any # RFC 3986 Section 2.3, Unreserved URI Characters # https://tools.ietf.org/html/rfc3986#section-2.3 -_UNRESERVED_CHARS = frozenset('~-._0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' - 'abcdefghijklmnopqrstuvwxyz') +_UNRESERVED_CHARS = frozenset( + "~-._0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" +) # URL parsing regex (based on RFC 3986 Appendix B, with modifications) -_URL_RE = re.compile(r'^((?P[^:/?#]+):)?' - r'((?P<_netloc_sep>//)' - r'(?P[^/?#]*))?' - r'(?P[^?#]*)' - r'(\?(?P[^#]*))?' - r'(#(?P.*))?$') -_SCHEME_RE = re.compile(r'^[a-zA-Z0-9+-.]*$') -_AUTHORITY_RE = re.compile(r'^(?:(?P[^@/?#]*)@)?' - r'(?P' - r'(?:\[(?P[^[\]/?#]*)\])' - r'|(?P[^:/?#[\]]*)' - r'|(?P.*?))?' - r'(?::(?P.*))?$') - - -_HEX_CHAR_MAP = dict([((a + b).encode('ascii'), - unichr(int(a + b, 16)).encode('charmap')) - for a in string.hexdigits for b in string.hexdigits]) -_ASCII_RE = re.compile('([\x00-\x7f]+)') +_URL_RE = re.compile( + r"^((?P[^:/?#]+):)?" + r"((?P<_netloc_sep>//)" + r"(?P[^/?#]*))?" + r"(?P[^?#]*)" + r"(\?(?P[^#]*))?" + r"(#(?P.*))?$" +) +_SCHEME_RE = re.compile(r"^[a-zA-Z0-9+-.]*$") +_AUTHORITY_RE = re.compile( + r"^(?:(?P[^@/?#]*)@)?" + r"(?P" + r"(?:\[(?P[^[\]/?#]*)\])" + r"|(?P[^:/?#[\]]*)" + r"|(?P.*?))?" + r"(?::(?P.*))?$" +) + + +_HEX_CHAR_MAP = dict( + [ + ((a + b).encode("ascii"), unichr(int(a + b, 16)).encode("charmap")) + for a in string.hexdigits + for b in string.hexdigits + ] +) +_ASCII_RE = re.compile("([\x00-\x7f]+)") # RFC 3986 section 2.2, Reserved Characters # https://tools.ietf.org/html/rfc3986#section-2.2 -_GEN_DELIMS = frozenset(u':/?#[]@') +_GEN_DELIMS = frozenset(u":/?#[]@") _SUB_DELIMS = frozenset(u"!$&'()*+,;=") _ALL_DELIMS = _GEN_DELIMS | _SUB_DELIMS -_USERINFO_SAFE = _UNRESERVED_CHARS | _SUB_DELIMS | set(u'%') +_USERINFO_SAFE = _UNRESERVED_CHARS | _SUB_DELIMS | set(u"%") _USERINFO_DELIMS = _ALL_DELIMS - _USERINFO_SAFE -_PATH_SAFE = _USERINFO_SAFE | set(u':@') +_PATH_SAFE = _USERINFO_SAFE | set(u":@") _PATH_DELIMS = _ALL_DELIMS - _PATH_SAFE -_SCHEMELESS_PATH_SAFE = _PATH_SAFE - set(':') +_SCHEMELESS_PATH_SAFE = _PATH_SAFE - set(":") _SCHEMELESS_PATH_DELIMS = _ALL_DELIMS - _SCHEMELESS_PATH_SAFE -_FRAGMENT_SAFE = _UNRESERVED_CHARS | _PATH_SAFE | set(u'/?') +_FRAGMENT_SAFE = _UNRESERVED_CHARS | _PATH_SAFE | set(u"/?") _FRAGMENT_DELIMS = _ALL_DELIMS - _FRAGMENT_SAFE -_QUERY_VALUE_SAFE = _UNRESERVED_CHARS | _FRAGMENT_SAFE - set(u'&+') +_QUERY_VALUE_SAFE = _UNRESERVED_CHARS | _FRAGMENT_SAFE - set(u"&+") _QUERY_VALUE_DELIMS = _ALL_DELIMS - _QUERY_VALUE_SAFE -_QUERY_KEY_SAFE = _UNRESERVED_CHARS | _QUERY_VALUE_SAFE - set(u'=') +_QUERY_KEY_SAFE = _UNRESERVED_CHARS | _QUERY_VALUE_SAFE - set(u"=") _QUERY_KEY_DELIMS = _ALL_DELIMS - _QUERY_KEY_SAFE @@ -164,9 +191,9 @@ def _make_decode_map(delims, allow_percent=False): # type: (Iterable[Text], bool) -> Mapping[bytes, bytes] ret = dict(_HEX_CHAR_MAP) if not allow_percent: - delims = set(delims) | set([u'%']) + delims = set(delims) | set([u"%"]) for delim in delims: - _hexord = '{0:02X}'.format(ord(delim)).encode('ascii') + _hexord = "{0:02X}".format(ord(delim)).encode("ascii") _hexord_lower = _hexord.lower() ret.pop(_hexord) if _hexord != _hexord_lower: @@ -184,7 +211,7 @@ def _make_quote_map(safe_chars): if c in safe_chars: ret[c] = ret[v] = c else: - ret[c] = ret[v] = '%{0:02X}'.format(i) + ret[c] = ret[v] = "%{0:02X}".format(i) return ret @@ -200,11 +227,15 @@ def _make_quote_map(safe_chars): _FRAGMENT_QUOTE_MAP = _make_quote_map(_FRAGMENT_SAFE) _FRAGMENT_DECODE_MAP = _make_decode_map(_FRAGMENT_DELIMS) _UNRESERVED_QUOTE_MAP = _make_quote_map(_UNRESERVED_CHARS) -_UNRESERVED_DECODE_MAP = dict([(k, v) for k, v in _HEX_CHAR_MAP.items() - if v.decode('ascii', 'replace') - in _UNRESERVED_CHARS]) +_UNRESERVED_DECODE_MAP = dict( + [ + (k, v) + for k, v in _HEX_CHAR_MAP.items() + if v.decode("ascii", "replace") in _UNRESERVED_CHARS + ] +) -_ROOT_PATHS = frozenset(((), (u'',))) +_ROOT_PATHS = frozenset(((), (u"",))) def _encode_reserved(text, maximal=True): @@ -215,20 +246,25 @@ def _encode_reserved(text, maximal=True): bytes. """ if maximal: - bytestr = normalize('NFC', text).encode('utf8') - return u''.join([_UNRESERVED_QUOTE_MAP[b] for b in bytestr]) - return u''.join([_UNRESERVED_QUOTE_MAP[t] if t in _UNRESERVED_CHARS - else t for t in text]) + bytestr = normalize("NFC", text).encode("utf8") + return u"".join([_UNRESERVED_QUOTE_MAP[b] for b in bytestr]) + return u"".join( + [ + _UNRESERVED_QUOTE_MAP[t] if t in _UNRESERVED_CHARS else t + for t in text + ] + ) def _encode_path_part(text, maximal=True): # type: (Text, bool) -> Text "Percent-encode a single segment of a URL path." if maximal: - bytestr = normalize('NFC', text).encode('utf8') - return u''.join([_PATH_PART_QUOTE_MAP[b] for b in bytestr]) - return u''.join([_PATH_PART_QUOTE_MAP[t] if t in _PATH_DELIMS else t - for t in text]) + bytestr = normalize("NFC", text).encode("utf8") + return u"".join([_PATH_PART_QUOTE_MAP[b] for b in bytestr]) + return u"".join( + [_PATH_PART_QUOTE_MAP[t] if t in _PATH_DELIMS else t for t in text] + ) def _encode_schemeless_path_part(text, maximal=True): @@ -237,18 +273,24 @@ def _encode_schemeless_path_part(text, maximal=True): scheme specified. """ if maximal: - bytestr = normalize('NFC', text).encode('utf8') - return u''.join([_SCHEMELESS_PATH_PART_QUOTE_MAP[b] for b in bytestr]) - return u''.join([_SCHEMELESS_PATH_PART_QUOTE_MAP[t] - if t in _SCHEMELESS_PATH_DELIMS else t for t in text]) + bytestr = normalize("NFC", text).encode("utf8") + return u"".join([_SCHEMELESS_PATH_PART_QUOTE_MAP[b] for b in bytestr]) + return u"".join( + [ + _SCHEMELESS_PATH_PART_QUOTE_MAP[t] + if t in _SCHEMELESS_PATH_DELIMS + else t + for t in text + ] + ) def _encode_path_parts( - text_parts, # type: Sequence[Text] - rooted=False, # type: bool - has_scheme=True, # type: bool + text_parts, # type: Sequence[Text] + rooted=False, # type: bool + has_scheme=True, # type: bool has_authority=True, # type: bool - maximal=True, # type: bool + maximal=True, # type: bool ): # type: (...) -> Sequence[Text] """ @@ -274,17 +316,23 @@ def _encode_path_parts( if not text_parts: return () if rooted: - text_parts = (u'',) + tuple(text_parts) + text_parts = (u"",) + tuple(text_parts) # elif has_authority and text_parts: # raise Exception('see rfc above') # TODO: too late to fail like this? encoded_parts = [] # type: List[Text] if has_scheme: - encoded_parts = [_encode_path_part(part, maximal=maximal) - if part else part for part in text_parts] + encoded_parts = [ + _encode_path_part(part, maximal=maximal) if part else part + for part in text_parts + ] else: encoded_parts = [_encode_schemeless_path_part(text_parts[0])] - encoded_parts.extend([_encode_path_part(part, maximal=maximal) - if part else part for part in text_parts[1:]]) + encoded_parts.extend( + [ + _encode_path_part(part, maximal=maximal) if part else part + for part in text_parts[1:] + ] + ) return tuple(encoded_parts) @@ -294,10 +342,11 @@ def _encode_query_key(text, maximal=True): Percent-encode a single query string key or value. """ if maximal: - bytestr = normalize('NFC', text).encode('utf8') - return u''.join([_QUERY_KEY_QUOTE_MAP[b] for b in bytestr]) - return u''.join([_QUERY_KEY_QUOTE_MAP[t] if t in _QUERY_KEY_DELIMS else t - for t in text]) + bytestr = normalize("NFC", text).encode("utf8") + return u"".join([_QUERY_KEY_QUOTE_MAP[b] for b in bytestr]) + return u"".join( + [_QUERY_KEY_QUOTE_MAP[t] if t in _QUERY_KEY_DELIMS else t for t in text] + ) def _encode_query_value(text, maximal=True): @@ -306,10 +355,14 @@ def _encode_query_value(text, maximal=True): Percent-encode a single query string key or value. """ if maximal: - bytestr = normalize('NFC', text).encode('utf8') - return u''.join([_QUERY_VALUE_QUOTE_MAP[b] for b in bytestr]) - return u''.join([_QUERY_VALUE_QUOTE_MAP[t] - if t in _QUERY_VALUE_DELIMS else t for t in text]) + bytestr = normalize("NFC", text).encode("utf8") + return u"".join([_QUERY_VALUE_QUOTE_MAP[b] for b in bytestr]) + return u"".join( + [ + _QUERY_VALUE_QUOTE_MAP[t] if t in _QUERY_VALUE_DELIMS else t + for t in text + ] + ) def _encode_fragment_part(text, maximal=True): @@ -318,10 +371,11 @@ def _encode_fragment_part(text, maximal=True): subdelimiters, so the whole URL fragment can be passed. """ if maximal: - bytestr = normalize('NFC', text).encode('utf8') - return u''.join([_FRAGMENT_QUOTE_MAP[b] for b in bytestr]) - return u''.join([_FRAGMENT_QUOTE_MAP[t] if t in _FRAGMENT_DELIMS else t - for t in text]) + bytestr = normalize("NFC", text).encode("utf8") + return u"".join([_FRAGMENT_QUOTE_MAP[b] for b in bytestr]) + return u"".join( + [_FRAGMENT_QUOTE_MAP[t] if t in _FRAGMENT_DELIMS else t for t in text] + ) def _encode_userinfo_part(text, maximal=True): @@ -330,32 +384,85 @@ def _encode_userinfo_part(text, maximal=True): section of the URL. """ if maximal: - bytestr = normalize('NFC', text).encode('utf8') - return u''.join([_USERINFO_PART_QUOTE_MAP[b] for b in bytestr]) - return u''.join([_USERINFO_PART_QUOTE_MAP[t] if t in _USERINFO_DELIMS - else t for t in text]) + bytestr = normalize("NFC", text).encode("utf8") + return u"".join([_USERINFO_PART_QUOTE_MAP[b] for b in bytestr]) + return u"".join( + [ + _USERINFO_PART_QUOTE_MAP[t] if t in _USERINFO_DELIMS else t + for t in text + ] + ) # This port list painstakingly curated by hand searching through # https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml # and # https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml -SCHEME_PORT_MAP = {'acap': 674, 'afp': 548, 'dict': 2628, 'dns': 53, - 'file': None, 'ftp': 21, 'git': 9418, 'gopher': 70, - 'http': 80, 'https': 443, 'imap': 143, 'ipp': 631, - 'ipps': 631, 'irc': 194, 'ircs': 6697, 'ldap': 389, - 'ldaps': 636, 'mms': 1755, 'msrp': 2855, 'msrps': None, - 'mtqp': 1038, 'nfs': 111, 'nntp': 119, 'nntps': 563, - 'pop': 110, 'prospero': 1525, 'redis': 6379, 'rsync': 873, - 'rtsp': 554, 'rtsps': 322, 'rtspu': 5005, 'sftp': 22, - 'smb': 445, 'snmp': 161, 'ssh': 22, 'steam': None, - 'svn': 3690, 'telnet': 23, 'ventrilo': 3784, 'vnc': 5900, - 'wais': 210, 'ws': 80, 'wss': 443, 'xmpp': None} +SCHEME_PORT_MAP = { + "acap": 674, + "afp": 548, + "dict": 2628, + "dns": 53, + "file": None, + "ftp": 21, + "git": 9418, + "gopher": 70, + "http": 80, + "https": 443, + "imap": 143, + "ipp": 631, + "ipps": 631, + "irc": 194, + "ircs": 6697, + "ldap": 389, + "ldaps": 636, + "mms": 1755, + "msrp": 2855, + "msrps": None, + "mtqp": 1038, + "nfs": 111, + "nntp": 119, + "nntps": 563, + "pop": 110, + "prospero": 1525, + "redis": 6379, + "rsync": 873, + "rtsp": 554, + "rtsps": 322, + "rtspu": 5005, + "sftp": 22, + "smb": 445, + "snmp": 161, + "ssh": 22, + "steam": None, + "svn": 3690, + "telnet": 23, + "ventrilo": 3784, + "vnc": 5900, + "wais": 210, + "ws": 80, + "wss": 443, + "xmpp": None, +} # This list of schemes that don't use authorities is also from the link above. -NO_NETLOC_SCHEMES = set(['urn', 'about', 'bitcoin', 'blob', 'data', 'geo', - 'magnet', 'mailto', 'news', 'pkcs11', - 'sip', 'sips', 'tel']) +NO_NETLOC_SCHEMES = set( + [ + "urn", + "about", + "bitcoin", + "blob", + "data", + "geo", + "magnet", + "mailto", + "news", + "pkcs11", + "sip", + "sips", + "tel", + ] +) # As of Mar 11, 2017, there were 44 netloc schemes, and 13 non-netloc @@ -384,18 +491,22 @@ def register_scheme(text, uses_netloc=True, default_port=None): try: default_port = int(default_port) except (ValueError, TypeError): - raise ValueError('default_port expected integer or None, not %r' - % (default_port,)) + raise ValueError( + "default_port expected integer or None, not %r" + % (default_port,) + ) if uses_netloc is True: SCHEME_PORT_MAP[text] = default_port elif uses_netloc is False: if default_port is not None: - raise ValueError('unexpected default port while specifying' - ' non-netloc scheme: %r' % default_port) + raise ValueError( + "unexpected default port while specifying" + " non-netloc scheme: %r" % default_port + ) NO_NETLOC_SCHEMES.add(text) else: - raise ValueError('uses_netloc expected bool, not: %r' % uses_netloc) + raise ValueError("uses_netloc expected bool, not: %r" % uses_netloc) return @@ -427,7 +538,7 @@ def scheme_uses_netloc(scheme, default=None): return True if scheme in NO_NETLOC_SCHEMES: return False - if scheme.split('+')[-1] in SCHEME_PORT_MAP: + if scheme.split("+")[-1] in SCHEME_PORT_MAP: return True return default @@ -436,6 +547,7 @@ class URLParseError(ValueError): """Exception inheriting from :exc:`ValueError`, raised when failing to parse a URL. Mostly raised on invalid ports and IPv6 addresses. """ + pass @@ -454,11 +566,12 @@ def _typecheck(name, value, *types): exception describing the problem using *name*. """ if not types: - raise ValueError('expected one or more types, maybe use _textcheck?') + raise ValueError("expected one or more types, maybe use _textcheck?") if not isinstance(value, types): - raise TypeError("expected %s for %s, got %r" - % (" or ".join([t.__name__ for t in types]), - name, value)) + raise TypeError( + "expected %s for %s, got %r" + % (" or ".join([t.__name__ for t in types]), name, value) + ) return value @@ -470,11 +583,13 @@ def _textcheck(name, value, delims=frozenset(), nullable=False): return value # type: ignore[unreachable] else: str_name = "unicode" if PY2 else "str" - exp = str_name + ' or NoneType' if nullable else str_name - raise TypeError('expected %s for %s, got %r' % (exp, name, value)) + exp = str_name + " or NoneType" if nullable else str_name + raise TypeError("expected %s for %s, got %r" % (exp, name, value)) if delims and set(value) & set(delims): # TODO: test caching into regexes - raise ValueError('one or more reserved delimiters %s present in %s: %r' - % (''.join(delims), name, value)) + raise ValueError( + "one or more reserved delimiters %s present in %s: %r" + % ("".join(delims), name, value) + ) return value # type: ignore[return-value] # T vs. Text @@ -492,27 +607,29 @@ def iter_pairs(iterable): return iter(iterable) -def _decode_unreserved( - text, normalize_case=False, encode_stray_percents=False -): +def _decode_unreserved(text, normalize_case=False, encode_stray_percents=False): # type: (Text, bool, bool) -> Text - return _percent_decode(text, normalize_case=normalize_case, - encode_stray_percents=encode_stray_percents, - _decode_map=_UNRESERVED_DECODE_MAP) + return _percent_decode( + text, + normalize_case=normalize_case, + encode_stray_percents=encode_stray_percents, + _decode_map=_UNRESERVED_DECODE_MAP, + ) def _decode_userinfo_part( text, normalize_case=False, encode_stray_percents=False ): # type: (Text, bool, bool) -> Text - return _percent_decode(text, normalize_case=normalize_case, - encode_stray_percents=encode_stray_percents, - _decode_map=_USERINFO_DECODE_MAP) + return _percent_decode( + text, + normalize_case=normalize_case, + encode_stray_percents=encode_stray_percents, + _decode_map=_USERINFO_DECODE_MAP, + ) -def _decode_path_part( - text, normalize_case=False, encode_stray_percents=False -): +def _decode_path_part(text, normalize_case=False, encode_stray_percents=False): # type: (Text, bool, bool) -> Text """ >>> _decode_path_part(u'%61%77%2f%7a') @@ -520,45 +637,55 @@ def _decode_path_part( >>> _decode_path_part(u'%61%77%2f%7a', normalize_case=True) u'aw%2Fz' """ - return _percent_decode(text, normalize_case=normalize_case, - encode_stray_percents=encode_stray_percents, - _decode_map=_PATH_DECODE_MAP) + return _percent_decode( + text, + normalize_case=normalize_case, + encode_stray_percents=encode_stray_percents, + _decode_map=_PATH_DECODE_MAP, + ) -def _decode_query_key( - text, normalize_case=False, encode_stray_percents=False -): +def _decode_query_key(text, normalize_case=False, encode_stray_percents=False): # type: (Text, bool, bool) -> Text - return _percent_decode(text, normalize_case=normalize_case, - encode_stray_percents=encode_stray_percents, - _decode_map=_QUERY_KEY_DECODE_MAP) + return _percent_decode( + text, + normalize_case=normalize_case, + encode_stray_percents=encode_stray_percents, + _decode_map=_QUERY_KEY_DECODE_MAP, + ) def _decode_query_value( text, normalize_case=False, encode_stray_percents=False ): # type: (Text, bool, bool) -> Text - return _percent_decode(text, normalize_case=normalize_case, - encode_stray_percents=encode_stray_percents, - _decode_map=_QUERY_VALUE_DECODE_MAP) + return _percent_decode( + text, + normalize_case=normalize_case, + encode_stray_percents=encode_stray_percents, + _decode_map=_QUERY_VALUE_DECODE_MAP, + ) def _decode_fragment_part( text, normalize_case=False, encode_stray_percents=False ): # type: (Text, bool, bool) -> Text - return _percent_decode(text, normalize_case=normalize_case, - encode_stray_percents=encode_stray_percents, - _decode_map=_FRAGMENT_DECODE_MAP) + return _percent_decode( + text, + normalize_case=normalize_case, + encode_stray_percents=encode_stray_percents, + _decode_map=_FRAGMENT_DECODE_MAP, + ) def _percent_decode( - text, # type: Text - normalize_case=False, # type: bool - subencoding="utf-8", # type: Text + text, # type: Text + normalize_case=False, # type: bool + subencoding="utf-8", # type: Text raise_subencoding_exc=False, # type: bool encode_stray_percents=False, # type: bool - _decode_map=_HEX_CHAR_MAP # type: Mapping[bytes, bytes] + _decode_map=_HEX_CHAR_MAP, # type: Mapping[bytes, bytes] ): # type: (...) -> Text """Convert percent-encoded text characters to their normal, @@ -594,7 +721,7 @@ def _percent_decode( except UnicodeEncodeError: return text - bits = quoted_bytes.split(b'%') + bits = quoted_bytes.split(b"%") if len(bits) == 1: return text @@ -609,20 +736,20 @@ def _percent_decode( except KeyError: pair_is_hex = hexpair in _HEX_CHAR_MAP if pair_is_hex or not encode_stray_percents: - append(b'%') + append(b"%") else: # if it's undecodable, treat as a real percent sign, # which is reserved (because it wasn't in the # context-aware _decode_map passed in), and should # stay in an encoded state. - append(b'%25') + append(b"%25") if normalize_case and pair_is_hex: append(hexpair.upper()) append(rest) else: append(item) - unquoted_bytes = b''.join(res) + unquoted_bytes = b"".join(res) try: return unquoted_bytes.decode(subencoding) @@ -679,7 +806,7 @@ def _decode_host(host): u'm\xe9hmoud.io' """ # noqa: E501 if not host: - return u'' + return u"" try: host_bytes = host.encode("ascii") except UnicodeEncodeError: @@ -713,16 +840,16 @@ def _resolve_dot_segments(path): segs = [] # type: List[Text] for seg in path: - if seg == u'.': + if seg == u".": pass - elif seg == u'..': + elif seg == u"..": if segs: segs.pop() else: segs.append(seg) - if list(path[-1:]) in ([u'.'], [u'..']): - segs.append(u'') + if list(path[-1:]) in ([u"."], [u".."]): + segs.append(u"") return segs @@ -747,13 +874,13 @@ def parse_host(host): True """ if not host: - return None, u'' + return None, u"" - if u':' in host: + if u":" in host: try: inet_pton(AF_INET6, host) except socket.error as se: - raise URLParseError('invalid IPv6 host: %r (%r)' % (host, se)) + raise URLParseError("invalid IPv6 host: %r (%r)" % (host, se)) except UnicodeEncodeError: pass # TODO: this can't be a real host right? else: @@ -837,33 +964,33 @@ class URL(object): def __init__( self, - scheme=None, # type: Optional[Text] - host=None, # type: Optional[Text] - path=(), # type: Iterable[Text] - query=(), # type: QueryParameters - fragment=u"", # type: Text - port=None, # type: Optional[int] - rooted=None, # type: Optional[bool] - userinfo=u"", # type: Text + scheme=None, # type: Optional[Text] + host=None, # type: Optional[Text] + path=(), # type: Iterable[Text] + query=(), # type: QueryParameters + fragment=u"", # type: Text + port=None, # type: Optional[int] + rooted=None, # type: Optional[bool] + userinfo=u"", # type: Text uses_netloc=None, # type: Optional[bool] ): # type: (...) -> None if host is not None and scheme is None: - scheme = u'http' # TODO: why + scheme = u"http" # TODO: why if port is None and scheme is not None: port = SCHEME_PORT_MAP.get(scheme) if host and query and not path: # per RFC 3986 6.2.3, "a URI that uses the generic syntax # for authority with an empty path should be normalized to # a path of '/'." - path = (u'',) + path = (u"",) # Now that we're done detecting whether they were passed, we can set # them to their defaults: if scheme is None: - scheme = u'' + scheme = u"" if host is None: - host = u'' + host = u"" if rooted is None: rooted = bool(host) @@ -871,33 +998,39 @@ def __init__( self._scheme = _textcheck("scheme", scheme) if self._scheme: if not _SCHEME_RE.match(self._scheme): - raise ValueError('invalid scheme: %r. Only alphanumeric, "+",' - ' "-", and "." allowed. Did you meant to call' - ' %s.from_text()?' - % (self._scheme, self.__class__.__name__)) + raise ValueError( + 'invalid scheme: %r. Only alphanumeric, "+",' + ' "-", and "." allowed. Did you meant to call' + " %s.from_text()?" % (self._scheme, self.__class__.__name__) + ) - _, self._host = parse_host(_textcheck('host', host, '/?#@')) + _, self._host = parse_host(_textcheck("host", host, "/?#@")) if isinstance(path, Text): - raise TypeError("expected iterable of text for path, not: %r" - % (path,)) - self._path = tuple((_textcheck("path segment", segment, '/?#') - for segment in path)) + raise TypeError( + "expected iterable of text for path, not: %r" % (path,) + ) + self._path = tuple( + (_textcheck("path segment", segment, "/?#") for segment in path) + ) self._query = tuple( - (_textcheck("query parameter name", k, '&=#'), - _textcheck("query parameter value", v, '&#', nullable=True)) - for k, v in iter_pairs(query)) + ( + _textcheck("query parameter name", k, "&=#"), + _textcheck("query parameter value", v, "&#", nullable=True), + ) + for k, v in iter_pairs(query) + ) self._fragment = _textcheck("fragment", fragment) self._port = _typecheck("port", port, int, NoneType) self._rooted = _typecheck("rooted", rooted, bool) - self._userinfo = _textcheck("userinfo", userinfo, '/?#@') + self._userinfo = _textcheck("userinfo", userinfo, "/?#@") if uses_netloc is None: uses_netloc = scheme_uses_netloc(self._scheme, uses_netloc) - self._uses_netloc = _typecheck("uses_netloc", - uses_netloc, bool, NoneType) - will_have_authority = ( - self._host or - (self._port and self._port != SCHEME_PORT_MAP.get(scheme)) + self._uses_netloc = _typecheck( + "uses_netloc", uses_netloc, bool, NoneType + ) + will_have_authority = self._host or ( + self._port and self._port != SCHEME_PORT_MAP.get(scheme) ) if will_have_authority: # fixup for rooted consistency; if there's any 'authority' @@ -905,7 +1038,7 @@ def __init__( # we're definitely using a netloc (there must be a ://). self._rooted = True self._uses_netloc = True - if (not self._rooted) and self.path[:1] == (u'',): + if (not self._rooted) and self.path[:1] == (u"",): self._rooted = True self._path = self._path[1:] if not will_have_authority and self._path and not self._rooted: @@ -1039,7 +1172,7 @@ def user(self): """ The user portion of :attr:`~hyperlink.URL.userinfo`. """ - return self.userinfo.split(u':')[0] + return self.userinfo.split(u":")[0] def authority(self, with_password=False, **kw): # type: (bool, Any) -> Text @@ -1061,12 +1194,12 @@ def authority(self, with_password=False, **kw): of the URL. """ # first, a bit of twisted compat - with_password = kw.pop('includeSecrets', with_password) + with_password = kw.pop("includeSecrets", with_password) if kw: - raise TypeError('got unexpected keyword arguments: %r' % kw.keys()) + raise TypeError("got unexpected keyword arguments: %r" % kw.keys()) host = self.host - if ':' in host: - hostport = ['[' + host + ']'] + if ":" in host: + hostport = ["[" + host + "]"] else: hostport = [self.host] if self.port != SCHEME_PORT_MAP.get(self.scheme): @@ -1075,7 +1208,7 @@ def authority(self, with_password=False, **kw): if self.userinfo: userinfo = self.userinfo if not with_password and u":" in userinfo: - userinfo = userinfo[:userinfo.index(u":") + 1] + userinfo = userinfo[: userinfo.index(u":") + 1] authority.append(userinfo) authority.append(u":".join(hostport)) return u"@".join(authority) @@ -1084,13 +1217,20 @@ def __eq__(self, other): # type: (Any) -> bool if not isinstance(other, self.__class__): return NotImplemented - for attr in ['scheme', 'userinfo', 'host', 'query', - 'fragment', 'port', 'uses_netloc', 'rooted']: + for attr in [ + "scheme", + "userinfo", + "host", + "query", + "fragment", + "port", + "uses_netloc", + "rooted", + ]: if getattr(self, attr) != getattr(other, attr): return False - if ( - self.path == other.path or - (self.path in _ROOT_PATHS and other.path in _ROOT_PATHS) + if self.path == other.path or ( + self.path in _ROOT_PATHS and other.path in _ROOT_PATHS ): return True return False @@ -1103,9 +1243,20 @@ def __ne__(self, other): def __hash__(self): # type: () -> int - return hash((self.__class__, self.scheme, self.userinfo, self.host, - self.path, self.query, self.fragment, self.port, - self.rooted, self.uses_netloc)) + return hash( + ( + self.__class__, + self.scheme, + self.userinfo, + self.host, + self.path, + self.query, + self.fragment, + self.port, + self.rooted, + self.uses_netloc, + ) + ) @property def absolute(self): @@ -1125,15 +1276,15 @@ def absolute(self): def replace( self, - scheme=_UNSET, # type: Optional[Text] - host=_UNSET, # type: Optional[Text] - path=_UNSET, # type: Iterable[Text] - query=_UNSET, # type: QueryParameters - fragment=_UNSET, # type: Text - port=_UNSET, # type: Optional[int] - rooted=_UNSET, # type: Optional[bool] - userinfo=_UNSET, # type: Text - uses_netloc=_UNSET # type: Optional[bool] + scheme=_UNSET, # type: Optional[Text] + host=_UNSET, # type: Optional[Text] + path=_UNSET, # type: Iterable[Text] + query=_UNSET, # type: QueryParameters + fragment=_UNSET, # type: Text + port=_UNSET, # type: Optional[int] + rooted=_UNSET, # type: Optional[bool] + userinfo=_UNSET, # type: Text + uses_netloc=_UNSET, # type: Optional[bool] ): # type: (...) -> URL """:class:`URL` objects are immutable, which means that attributes @@ -1189,7 +1340,7 @@ def replace( port=_optional(port, self.port), rooted=_optional(rooted, self.rooted), userinfo=_optional(userinfo, self.userinfo), - uses_netloc=_optional(uses_netloc, self.uses_netloc) + uses_netloc=_optional(uses_netloc, self.uses_netloc), ) @classmethod @@ -1223,41 +1374,41 @@ def from_text(cls, text): method only raises :class:`URLParseError` on invalid port and IPv6 values in the host portion of the URL. """ - um = _URL_RE.match(_textcheck('text', text)) + um = _URL_RE.match(_textcheck("text", text)) if um is None: - raise URLParseError('could not parse url: %r' % text) + raise URLParseError("could not parse url: %r" % text) gs = um.groupdict() - au_text = gs['authority'] or u'' + au_text = gs["authority"] or u"" au_m = _AUTHORITY_RE.match(au_text) if au_m is None: raise URLParseError( - 'invalid authority %r in url: %r' % (au_text, text) + "invalid authority %r in url: %r" % (au_text, text) ) au_gs = au_m.groupdict() - if au_gs['bad_host']: + if au_gs["bad_host"]: raise URLParseError( - 'invalid host %r in url: %r' % (au_gs['bad_host'], text) + "invalid host %r in url: %r" % (au_gs["bad_host"], text) ) - userinfo = au_gs['userinfo'] or u'' + userinfo = au_gs["userinfo"] or u"" - host = au_gs['ipv6_host'] or au_gs['plain_host'] - port = au_gs['port'] + host = au_gs["ipv6_host"] or au_gs["plain_host"] + port = au_gs["port"] if port is not None: try: port = int(port) # type: ignore[assignment] # FIXME, see below except ValueError: if not port: # TODO: excessive? - raise URLParseError('port must not be empty: %r' % au_text) - raise URLParseError('expected integer for port, not %r' % port) + raise URLParseError("port must not be empty: %r" % au_text) + raise URLParseError("expected integer for port, not %r" % port) - scheme = gs['scheme'] or u'' - fragment = gs['fragment'] or u'' - uses_netloc = bool(gs['_netloc_sep']) + scheme = gs["scheme"] or u"" + fragment = gs["fragment"] or u"" + uses_netloc = bool(gs["_netloc_sep"]) - if gs['path']: - path = tuple(gs['path'].split(u"/")) + if gs["path"]: + path = tuple(gs["path"].split(u"/")) if not path[0]: path = path[1:] rooted = True @@ -1266,24 +1417,39 @@ def from_text(cls, text): else: path = () rooted = bool(au_text) - if gs['query']: + if gs["query"]: query = tuple( ( qe.split(u"=", 1) # type: ignore[misc] - if u'=' in qe else (qe, None) + if u"=" in qe + else (qe, None) ) - for qe in gs['query'].split(u"&") + for qe in gs["query"].split(u"&") ) # type: QueryPairs else: query = () return cls( - scheme, host, path, query, fragment, + scheme, + host, + path, + query, + fragment, port, # type: ignore[arg-type] # FIXME, see above - rooted, userinfo, uses_netloc, + rooted, + userinfo, + uses_netloc, ) - def normalize(self, scheme=True, host=True, path=True, query=True, - fragment=True, userinfo=True, percents=True): + def normalize( + self, + scheme=True, + host=True, + path=True, + query=True, + fragment=True, + userinfo=True, + percents=True, + ): # type: (bool, bool, bool, bool, bool, bool, bool) -> URL """Return a new URL object with several standard normalizations applied: @@ -1324,30 +1490,34 @@ def normalize(self, scheme=True, host=True, path=True, query=True, """ # noqa: E501 kw = {} # type: Dict[str, Any] if scheme: - kw['scheme'] = self.scheme.lower() + kw["scheme"] = self.scheme.lower() if host: - kw['host'] = self.host.lower() + kw["host"] = self.host.lower() def _dec_unres(target): # type: (Text) -> Text return _decode_unreserved( target, normalize_case=True, encode_stray_percents=percents ) + if path: if self.path: - kw['path'] = [ + kw["path"] = [ _dec_unres(p) for p in _resolve_dot_segments(self.path) ] else: - kw['path'] = (u'',) + kw["path"] = (u"",) if query: - kw['query'] = [(_dec_unres(k), _dec_unres(v) if v else v) - for k, v in self.query] + kw["query"] = [ + (_dec_unres(k), _dec_unres(v) if v else v) + for k, v in self.query + ] if fragment: - kw['fragment'] = _dec_unres(self.fragment) + kw["fragment"] = _dec_unres(self.fragment) if userinfo: - kw['userinfo'] = u':'.join([_dec_unres(p) - for p in self.userinfo.split(':', 1)]) + kw["userinfo"] = u":".join( + [_dec_unres(p) for p in self.userinfo.split(":", 1)] + ) return self.replace(**kw) @@ -1376,7 +1546,7 @@ def child(self, *segments): return self segments = [ # type: ignore[assignment] # variable is tuple - _textcheck('path segment', s) for s in segments + _textcheck("path segment", s) for s in segments ] new_path = tuple(self.path) if self.path and self.path[-1] == u"": @@ -1397,11 +1567,11 @@ def sibling(self, segment): replaced by *segment*. Special characters such as ``/?#`` will be percent encoded. """ - _textcheck('path segment', segment) + _textcheck("path segment", segment) new_path = tuple(self.path)[:-1] + (_encode_path_part(segment),) return self.replace(path=new_path) - def click(self, href=u''): + def click(self, href=u""): # type: (Union[Text, URL]) -> URL """Resolve the given URL relative to this URL. @@ -1432,7 +1602,7 @@ def click(self, href=u''): # TODO: This error message is not completely accurate, # as URL objects are now also valid, but Twisted's # test suite (wrongly) relies on this exact message. - _textcheck('relative URL', href) + _textcheck("relative URL", href) clicked = URL.from_text(href) if clicked.absolute: return clicked @@ -1444,8 +1614,9 @@ def click(self, href=u''): # Schemes with relative paths are not well-defined. RFC 3986 calls # them a "loophole in prior specifications" that should be avoided, # or supported only for backwards compatibility. - raise NotImplementedError('absolute URI with rootless path: %r' - % (href,)) + raise NotImplementedError( + "absolute URI with rootless path: %r" % (href,) + ) else: if clicked.rooted: path = clicked.path @@ -1455,12 +1626,14 @@ def click(self, href=u''): path = self.path if not query: query = self.query - return self.replace(scheme=clicked.scheme or self.scheme, - host=clicked.host or self.host, - port=clicked.port or self.port, - path=_resolve_dot_segments(path), - query=query, - fragment=clicked.fragment) + return self.replace( + scheme=clicked.scheme or self.scheme, + host=clicked.host or self.host, + port=clicked.port or self.port, + path=_resolve_dot_segments(path), + query=query, + fragment=clicked.fragment, + ) def to_uri(self): # type: () -> URL @@ -1478,10 +1651,12 @@ def to_uri(self): hostname encoded, so that they are all in the standard US-ASCII range. """ - new_userinfo = u':'.join([_encode_userinfo_part(p) for p in - self.userinfo.split(':', 1)]) - new_path = _encode_path_parts(self.path, has_scheme=bool(self.scheme), - rooted=False, maximal=True) + new_userinfo = u":".join( + [_encode_userinfo_part(p) for p in self.userinfo.split(":", 1)] + ) + new_path = _encode_path_parts( + self.path, has_scheme=bool(self.scheme), rooted=False, maximal=True + ) new_host = ( self.host if not self.host @@ -1491,11 +1666,18 @@ def to_uri(self): userinfo=new_userinfo, host=new_host, path=new_path, - query=tuple([(_encode_query_key(k, maximal=True), - _encode_query_value(v, maximal=True) - if v is not None else None) - for k, v in self.query]), - fragment=_encode_fragment_part(self.fragment, maximal=True) + query=tuple( + [ + ( + _encode_query_key(k, maximal=True), + _encode_query_value(v, maximal=True) + if v is not None + else None, + ) + for k, v in self.query + ] + ), + fragment=_encode_fragment_part(self.fragment, maximal=True), ) def to_iri(self): @@ -1522,9 +1704,9 @@ def to_iri(self): URL: A new instance with its path segments, query parameters, and hostname decoded for display purposes. """ # noqa: E501 - new_userinfo = u':'.join([ - _decode_userinfo_part(p) for p in self.userinfo.split(':', 1) - ]) + new_userinfo = u":".join( + [_decode_userinfo_part(p) for p in self.userinfo.split(":", 1)] + ) host_text = _decode_host(self.host) return self.replace( @@ -1533,8 +1715,8 @@ def to_iri(self): path=[_decode_path_part(segment) for segment in self.path], query=tuple( ( - _decode_query_key(k), _decode_query_value(v) - if v is not None else None + _decode_query_key(k), + _decode_query_value(v) if v is not None else None, ) for k, v in self.query ), @@ -1569,23 +1751,29 @@ def to_text(self, with_password=False): """ scheme = self.scheme authority = self.authority(with_password) - path = "/".join(_encode_path_parts( - self.path, - rooted=self.rooted, - has_scheme=bool(scheme), - has_authority=bool(authority), - maximal=False - )) + path = "/".join( + _encode_path_parts( + self.path, + rooted=self.rooted, + has_scheme=bool(scheme), + has_authority=bool(authority), + maximal=False, + ) + ) query_parts = [] for k, v in self.query: if v is None: query_parts.append(_encode_query_key(k, maximal=False)) else: - query_parts.append(u'='.join(( - _encode_query_key(k, maximal=False), - _encode_query_value(v, maximal=False) - ))) - query_string = u'&'.join(query_parts) + query_parts.append( + u"=".join( + ( + _encode_query_key(k, maximal=False), + _encode_query_value(v, maximal=False), + ) + ) + ) + query_string = u"&".join(query_parts) fragment = self.fragment @@ -1593,23 +1781,23 @@ def to_text(self, with_password=False): _add = parts.append if scheme: _add(scheme) - _add(':') + _add(":") if authority: - _add('//') + _add("//") _add(authority) - elif (scheme and path[:2] != '//' and self.uses_netloc): - _add('//') + elif scheme and path[:2] != "//" and self.uses_netloc: + _add("//") if path: - if scheme and authority and path[:1] != '/': - _add('/') # relpaths with abs authorities auto get '/' + if scheme and authority and path[:1] != "/": + _add("/") # relpaths with abs authorities auto get '/' _add(path) if query_string: - _add('?') + _add("?") _add(query_string) if fragment: - _add('#') + _add("#") _add(fragment) - return u''.join(parts) + return u"".join(parts) def __repr__(self): # type: () -> str @@ -1617,7 +1805,7 @@ def __repr__(self): constituent parts, as well as being a valid argument to :func:`eval`. """ - return '%s.from_text(%r)' % (self.__class__.__name__, self.to_text()) + return "%s.from_text(%r)" % (self.__class__.__name__, self.to_text()) def _to_bytes(self): # type: () -> bytes @@ -1626,7 +1814,7 @@ def _to_bytes(self): requests, which automatically stringify URL parameters. See issue #49. """ - return self.to_uri().to_text().encode('ascii') + return self.to_uri().to_text().encode("ascii") if PY2: __str__ = _to_bytes @@ -1655,7 +1843,7 @@ def __dir__(self): except AttributeError: # object.__dir__ == AttributeError # pdw for py2 ret = dir(self.__class__) + list(self.__dict__.keys()) - ret = sorted(set(ret) - set(['fromText', 'asURI', 'asIRI', 'asText'])) + ret = sorted(set(ret) - set(["fromText", "asURI", "asIRI", "asText"])) return ret # # End Twisted Compat Code @@ -1705,8 +1893,9 @@ def set(self, name, value=None): """ # Preserve the original position of the query key in the list q = [(k, v) for (k, v) in self.query if k != name] - idx = next((i for (i, (k, v)) in enumerate(self.query) - if k == name), -1) + idx = next( + (i for (i, (k, v)) in enumerate(self.query) if k == name), -1 + ) q[idx:idx] = [(name, value)] return self.replace(query=q) @@ -1734,9 +1923,9 @@ def get(self, name): def remove( self, - name, # type: Text + name, # type: Text value=_UNSET, # type: Text - limit=None, # type: Optional[int] + limit=None, # type: Optional[int] ): # type: (...) -> URL """Make a new :class:`URL` instance with occurrences of the query @@ -1760,7 +1949,8 @@ def remove( nq = [(k, v) for (k, v) in self.query if k != name] else: nq = [ - (k, v) for (k, v) in self.query + (k, v) + for (k, v) in self.query if not (k == name and v == value) ] else: @@ -1768,9 +1958,9 @@ def remove( for k, v in self.query: if ( - k == name and - (value is _UNSET or v == value) and - removed_count < limit + k == name + and (value is _UNSET or v == value) + and removed_count < limit ): removed_count += 1 # drop it else: @@ -1802,6 +1992,7 @@ class DecodedURL(object): check for validity. Defaults to False. """ + def __init__(self, url, lazy=False): # type: (URL, bool) -> None self._url = url @@ -1848,7 +2039,7 @@ def to_iri(self): "Passthrough to :meth:`~hyperlink.URL.to_iri()`" return self._url.to_iri() - def click(self, href=u''): + def click(self, href=u""): # type: (Union[Text, URL, DecodedURL]) -> DecodedURL """Return a new DecodedURL wrapping the result of :meth:`~hyperlink.URL.click()` @@ -1876,15 +2067,25 @@ def child(self, *segments): new_segs = [_encode_reserved(s) for s in segments] return self.__class__(self._url.child(*new_segs)) - def normalize(self, scheme=True, host=True, path=True, query=True, - fragment=True, userinfo=True, percents=True): + def normalize( + self, + scheme=True, + host=True, + path=True, + query=True, + fragment=True, + userinfo=True, + percents=True, + ): # type: (bool, bool, bool, bool, bool, bool, bool) -> DecodedURL """Return a new `DecodedURL` wrapping the result of :meth:`~hyperlink.URL.normalize()` """ - return self.__class__(self._url.normalize( - scheme, host, path, query, fragment, userinfo, percents - )) + return self.__class__( + self._url.normalize( + scheme, host, path, query, fragment, userinfo, percents + ) + ) @property def absolute(self): @@ -1915,24 +2116,30 @@ def rooted(self): def path(self): # type: () -> Sequence[Text] if not hasattr(self, "_path"): - self._path = tuple([ - _percent_decode(p, raise_subencoding_exc=True) - for p in self._url.path - ]) + self._path = tuple( + [ + _percent_decode(p, raise_subencoding_exc=True) + for p in self._url.path + ] + ) return self._path @property def query(self): # type: () -> QueryPairs if not hasattr(self, "_query"): - self._query = cast(QueryPairs, tuple( + self._query = cast( + QueryPairs, tuple( - _percent_decode(x, raise_subencoding_exc=True) - if x is not None else None - for x in (k, v) - ) - for k, v in self._url.query - )) + tuple( + _percent_decode(x, raise_subencoding_exc=True) + if x is not None + else None + for x in (k, v) + ) + for k, v in self._url.query + ), + ) return self._query @property @@ -1952,9 +2159,9 @@ def userinfo(self): tuple( tuple( _percent_decode(p, raise_subencoding_exc=True) - for p in self._url.userinfo.split(':', 1) + for p in self._url.userinfo.split(":", 1) ) - ) + ), ) return self._userinfo @@ -1970,15 +2177,15 @@ def uses_netloc(self): def replace( self, - scheme=_UNSET, # type: Optional[Text] - host=_UNSET, # type: Optional[Text] - path=_UNSET, # type: Iterable[Text] - query=_UNSET, # type: QueryParameters - fragment=_UNSET, # type: Text - port=_UNSET, # type: Optional[int] - rooted=_UNSET, # type: Optional[bool] - userinfo=_UNSET, # type: Union[Tuple[str], Tuple[str, str]] - uses_netloc=_UNSET # type: Optional[bool] + scheme=_UNSET, # type: Optional[Text] + host=_UNSET, # type: Optional[Text] + path=_UNSET, # type: Iterable[Text] + query=_UNSET, # type: QueryParameters + fragment=_UNSET, # type: Text + port=_UNSET, # type: Optional[int] + rooted=_UNSET, # type: Optional[bool] + userinfo=_UNSET, # type: Union[Tuple[str], Tuple[str, str]] + uses_netloc=_UNSET, # type: Optional[bool] ): # type: (...) -> DecodedURL """While the signature is the same, this `replace()` differs a little @@ -1991,30 +2198,36 @@ def replace( if path is not _UNSET: path = tuple(_encode_reserved(p) for p in path) if query is not _UNSET: - query = cast(QueryPairs, tuple( + query = cast( + QueryPairs, tuple( - _encode_reserved(x) - if x is not None else None - for x in (k, v) - ) - for k, v in iter_pairs(query) - )) + tuple( + _encode_reserved(x) if x is not None else None + for x in (k, v) + ) + for k, v in iter_pairs(query) + ), + ) if userinfo is not _UNSET: if len(userinfo) > 2: - raise ValueError('userinfo expected sequence of ["user"] or' - ' ["user", "password"], got %r' % (userinfo,)) - userinfo_text = u':'.join([_encode_reserved(p) for p in userinfo]) + raise ValueError( + 'userinfo expected sequence of ["user"] or' + ' ["user", "password"], got %r' % (userinfo,) + ) + userinfo_text = u":".join([_encode_reserved(p) for p in userinfo]) else: userinfo_text = _UNSET - new_url = self._url.replace(scheme=scheme, - host=host, - path=path, - query=query, - fragment=fragment, - port=port, - rooted=rooted, - userinfo=userinfo_text, - uses_netloc=uses_netloc) + new_url = self._url.replace( + scheme=scheme, + host=host, + path=path, + query=query, + fragment=fragment, + port=port, + rooted=rooted, + userinfo=userinfo_text, + uses_netloc=uses_netloc, + ) return self.__class__(url=new_url) def get(self, name): @@ -2039,9 +2252,9 @@ def set(self, name, value=None): def remove( self, - name, # type: Text + name, # type: Text value=_UNSET, # type: Text - limit=None, # type: Optional[int] + limit=None, # type: Optional[int] ): # type: (...) -> DecodedURL """Return a new DecodedURL with query parameter *name* removed. @@ -2054,16 +2267,17 @@ def remove( nq = [(k, v) for (k, v) in self.query if k != name] else: nq = [ - (k, v) for (k, v) in self.query + (k, v) + for (k, v) in self.query if not (k == name and v == value) ] else: nq, removed_count = [], 0 for k, v in self.query: if ( - k == name and - (value is _UNSET or v == value) and - removed_count < limit + k == name + and (value is _UNSET or v == value) + and removed_count < limit ): removed_count += 1 # drop it else: @@ -2074,7 +2288,7 @@ def remove( def __repr__(self): # type: () -> str cn = self.__class__.__name__ - return '%s(url=%r)' % (cn, self._url) + return "%s(url=%r)" % (cn, self._url) def __str__(self): # type: () -> str @@ -2096,9 +2310,20 @@ def __ne__(self, other): def __hash__(self): # type: () -> int - return hash((self.__class__, self.scheme, self.userinfo, self.host, - self.path, self.query, self.fragment, self.port, - self.rooted, self.uses_netloc)) + return hash( + ( + self.__class__, + self.scheme, + self.userinfo, + self.host, + self.path, + self.query, + self.fragment, + self.port, + self.rooted, + self.uses_netloc, + ) + ) # # Begin Twisted Compat Code asURI = to_uri @@ -2120,7 +2345,7 @@ def __dir__(self): except AttributeError: # object.__dir__ == AttributeError # pdw for py2 ret = dir(self.__class__) + list(self.__dict__.keys()) - ret = sorted(set(ret) - set(['fromText', 'asURI', 'asIRI', 'asText'])) + ret = sorted(set(ret) - set(["fromText", "asURI", "asIRI", "asText"])) return ret # # End Twisted Compat Code diff --git a/src/hyperlink/test/common.py b/src/hyperlink/test/common.py index f489266e..1eec0db1 100644 --- a/src/hyperlink/test/common.py +++ b/src/hyperlink/test/common.py @@ -6,12 +6,13 @@ class HyperlinkTestCase(TestCase): """This type mostly exists to provide a backwards-compatible assertRaises method for Python 2.6 testing. """ + def assertRaises( # type: ignore[override] self, expected_exception, # type: Type[BaseException] - callableObj=None, # type: Optional[Callable[..., Any]] - *args, # type: Any - **kwargs # type: Any + callableObj=None, # type: Optional[Callable[..., Any]] + *args, # type: Any + **kwargs # type: Any ): # type: (...) -> Any """Fail unless an exception of class expected_exception is raised diff --git a/src/hyperlink/test/test_common.py b/src/hyperlink/test/test_common.py index af495d8b..6827d0b9 100644 --- a/src/hyperlink/test/test_common.py +++ b/src/hyperlink/test/test_common.py @@ -39,8 +39,9 @@ def raisesExpected(*args, **kwargs): called_with.append((args, kwargs)) raise _ExpectedException - self.hyperlink_test.assertRaises(_ExpectedException, - raisesExpected, 1, keyword=True) + self.hyperlink_test.assertRaises( + _ExpectedException, raisesExpected, 1, keyword=True + ) self.assertEqual(called_with, [((1,), {"keyword": True})]) def test_assertRaisesWithCallableUnexpectedException(self): @@ -55,8 +56,9 @@ def doesNotRaiseExpected(*args, **kwargs): raise _UnexpectedException try: - self.hyperlink_test.assertRaises(_ExpectedException, - doesNotRaiseExpected) + self.hyperlink_test.assertRaises( + _ExpectedException, doesNotRaiseExpected + ) except _UnexpectedException: pass @@ -72,8 +74,7 @@ def doesNotRaise(*args, **kwargs): pass try: - self.hyperlink_test.assertRaises(_ExpectedException, - doesNotRaise) + self.hyperlink_test.assertRaises(_ExpectedException, doesNotRaise) except AssertionError: pass diff --git a/src/hyperlink/test/test_decoded_url.py b/src/hyperlink/test/test_decoded_url.py index 35491072..7104bea5 100644 --- a/src/hyperlink/test/test_decoded_url.py +++ b/src/hyperlink/test/test_decoded_url.py @@ -7,7 +7,7 @@ from .._url import _percent_decode from .common import HyperlinkTestCase -BASIC_URL = 'http://example.com/#' +BASIC_URL = "http://example.com/#" TOTAL_URL = ( "https://%75%73%65%72:%00%00%00%00@xn--bcher-kva.ch:8080/" "a/nice%20nice/./path/?zot=23%25&zut#frég" @@ -15,27 +15,26 @@ class TestURL(HyperlinkTestCase): - def test_durl_basic(self): # type: () -> None bdurl = DecodedURL.from_text(BASIC_URL) - assert bdurl.scheme == 'http' - assert bdurl.host == 'example.com' + assert bdurl.scheme == "http" + assert bdurl.host == "example.com" assert bdurl.port == 80 - assert bdurl.path == ('',) - assert bdurl.fragment == '' + assert bdurl.path == ("",) + assert bdurl.fragment == "" durl = DecodedURL.from_text(TOTAL_URL) - assert durl.scheme == 'https' - assert durl.host == 'bücher.ch' + assert durl.scheme == "https" + assert durl.host == "bücher.ch" assert durl.port == 8080 - assert durl.path == ('a', 'nice nice', '.', 'path', '') - assert durl.fragment == 'frég' - assert durl.get('zot') == ['23%'] + assert durl.path == ("a", "nice nice", ".", "path", "") + assert durl.fragment == "frég" + assert durl.get("zot") == ["23%"] - assert durl.user == 'user' - assert durl.userinfo == ('user', '\0\0\0\0') + assert durl.user == "user" + assert durl.userinfo == ("user", "\0\0\0\0") def test_passthroughs(self): # type: () -> None @@ -44,18 +43,18 @@ def test_passthroughs(self): # through to the underlying URL durl = DecodedURL.from_text(TOTAL_URL) - assert durl.sibling('te%t').path[-1] == 'te%t' - assert durl.child('../test2%').path[-1] == '../test2%' + assert durl.sibling("te%t").path[-1] == "te%t" + assert durl.child("../test2%").path[-1] == "../test2%" assert durl.child() == durl assert durl.child() is durl - assert durl.click('/').path[-1] == '' - assert durl.user == 'user' + assert durl.click("/").path[-1] == "" + assert durl.user == "user" - assert '.' in durl.path - assert '.' not in durl.normalize().path + assert "." in durl.path + assert "." not in durl.normalize().path - assert durl.to_uri().fragment == 'fr%C3%A9g' - assert ' ' in durl.to_iri().path[1] + assert durl.to_uri().fragment == "fr%C3%A9g" + assert " " in durl.to_iri().path[1] assert durl.to_text(with_password=True) == TOTAL_URL @@ -68,8 +67,8 @@ def test_passthroughs(self): assert durl2 == durl2.encoded_url.get_decoded_url(lazy=True) assert ( - str(DecodedURL.from_text(BASIC_URL).child(' ')) == - 'http://example.com/%20' + str(DecodedURL.from_text(BASIC_URL).child(" ")) + == "http://example.com/%20" ) assert not (durl == 1) @@ -78,46 +77,42 @@ def test_passthroughs(self): def test_repr(self): # type: () -> None durl = DecodedURL.from_text(TOTAL_URL) - assert repr(durl) == 'DecodedURL(url=' + repr(durl._url) + ')' + assert repr(durl) == "DecodedURL(url=" + repr(durl._url) + ")" def test_query_manipulation(self): # type: () -> None durl = DecodedURL.from_text(TOTAL_URL) - assert durl.get('zot') == ['23%'] - durl = durl.add(' ', 'space') - assert durl.get(' ') == ['space'] - durl = durl.set(' ', 'spa%ed') - assert durl.get(' ') == ['spa%ed'] + assert durl.get("zot") == ["23%"] + durl = durl.add(" ", "space") + assert durl.get(" ") == ["space"] + durl = durl.set(" ", "spa%ed") + assert durl.get(" ") == ["spa%ed"] durl = DecodedURL(url=durl.to_uri()) - assert durl.get(' ') == ['spa%ed'] - durl = durl.remove(' ') - assert durl.get(' ') == [] + assert durl.get(" ") == ["spa%ed"] + durl = durl.remove(" ") + assert durl.get(" ") == [] - durl = DecodedURL.from_text('/?%61rg=b&arg=c') - assert durl.get('arg') == ['b', 'c'] + durl = DecodedURL.from_text("/?%61rg=b&arg=c") + assert durl.get("arg") == ["b", "c"] - assert durl.set('arg', 'd').get('arg') == ['d'] + assert durl.set("arg", "d").get("arg") == ["d"] durl = DecodedURL.from_text( - u"https://example.com/a/b/?fóó=1&bar=2&fóó=3" + "https://example.com/a/b/?fóó=1&bar=2&fóó=3" ) - assert ( - durl.remove("fóó") == - DecodedURL.from_text("https://example.com/a/b/?bar=2") + assert durl.remove("fóó") == DecodedURL.from_text( + "https://example.com/a/b/?bar=2" ) - assert ( - durl.remove("fóó", value="1") == - DecodedURL.from_text("https://example.com/a/b/?bar=2&fóó=3") + assert durl.remove("fóó", value="1") == DecodedURL.from_text( + "https://example.com/a/b/?bar=2&fóó=3" ) - assert ( - durl.remove("fóó", limit=1) == - DecodedURL.from_text("https://example.com/a/b/?bar=2&fóó=3") + assert durl.remove("fóó", limit=1) == DecodedURL.from_text( + "https://example.com/a/b/?bar=2&fóó=3" ) - assert ( - durl.remove("fóó", value="1", limit=0) == - DecodedURL.from_text("https://example.com/a/b/?fóó=1&bar=2&fóó=3") + assert durl.remove("fóó", value="1", limit=0) == DecodedURL.from_text( + "https://example.com/a/b/?fóó=1&bar=2&fóó=3" ) def test_equality_and_hashability(self): @@ -153,15 +148,17 @@ def test_replace_roundtrip(self): # type: () -> None durl = DecodedURL.from_text(TOTAL_URL) - durl2 = durl.replace(scheme=durl.scheme, - host=durl.host, - path=durl.path, - query=durl.query, - fragment=durl.fragment, - port=durl.port, - rooted=durl.rooted, - userinfo=durl.userinfo, - uses_netloc=durl.uses_netloc) + durl2 = durl.replace( + scheme=durl.scheme, + host=durl.host, + path=durl.path, + query=durl.query, + fragment=durl.fragment, + port=durl.port, + rooted=durl.rooted, + userinfo=durl.userinfo, + uses_netloc=durl.uses_netloc, + ) assert durl == durl2 @@ -171,7 +168,9 @@ def test_replace_userinfo(self): with self.assertRaises(ValueError): durl.replace( userinfo=( # type: ignore[arg-type] - 'user', 'pw', 'thiswillcauseafailure' + "user", + "pw", + "thiswillcauseafailure", ) ) return @@ -181,8 +180,8 @@ def test_twisted_compat(self): durl = DecodedURL.from_text(TOTAL_URL) assert durl == DecodedURL.fromText(TOTAL_URL) - assert 'to_text' in dir(durl) - assert 'asText' not in dir(durl) + assert "to_text" in dir(durl) + assert "asText" not in dir(durl) assert durl.to_text() == durl.asText() def test_percent_decode_mixed(self): @@ -190,24 +189,24 @@ def test_percent_decode_mixed(self): # See https://github.com/python-hyper/hyperlink/pull/59 for a # nice discussion of the possibilities - assert _percent_decode('abcdé%C3%A9éfg') == 'abcdéééfg' + assert _percent_decode("abcdé%C3%A9éfg") == "abcdéééfg" # still allow percent encoding in the case of an error - assert _percent_decode('abcdé%C3éfg') == 'abcdé%C3éfg' + assert _percent_decode("abcdé%C3éfg") == "abcdé%C3éfg" # ...unless explicitly told otherwise with self.assertRaises(UnicodeDecodeError): - _percent_decode('abcdé%C3éfg', raise_subencoding_exc=True) + _percent_decode("abcdé%C3éfg", raise_subencoding_exc=True) # when not encodable as subencoding - assert _percent_decode('é%25é', subencoding='ascii') == 'é%25é' + assert _percent_decode("é%25é", subencoding="ascii") == "é%25é" def test_click_decoded_url(self): # type: () -> None durl = DecodedURL.from_text(TOTAL_URL) - durl_dest = DecodedURL.from_text('/tëst') + durl_dest = DecodedURL.from_text("/tëst") clicked = durl.click(durl_dest) assert clicked.host == durl.host assert clicked.path == durl_dest.path - assert clicked.path == ('tëst',) + assert clicked.path == ("tëst",) diff --git a/src/hyperlink/test/test_parse.py b/src/hyperlink/test/test_parse.py index 8fdbf351..66b02709 100644 --- a/src/hyperlink/test/test_parse.py +++ b/src/hyperlink/test/test_parse.py @@ -5,29 +5,28 @@ from .common import HyperlinkTestCase from hyperlink import parse, EncodedURL, DecodedURL -BASIC_URL = 'http://example.com/#' +BASIC_URL = "http://example.com/#" TOTAL_URL = ( "https://%75%73%65%72:%00%00%00%00@xn--bcher-kva.ch:8080" "/a/nice%20nice/./path/?zot=23%25&zut#frég" ) -UNDECODABLE_FRAG_URL = TOTAL_URL + '%C3' +UNDECODABLE_FRAG_URL = TOTAL_URL + "%C3" # the %C3 above percent-decodes to an unpaired \xc3 byte which makes this # invalid utf8 class TestURL(HyperlinkTestCase): - def test_parse(self): # type: () -> None purl = parse(TOTAL_URL) assert isinstance(purl, DecodedURL) - assert purl.user == 'user' - assert purl.get('zot') == ['23%'] - assert purl.fragment == 'frég' + assert purl.user == "user" + assert purl.get("zot") == ["23%"] + assert purl.fragment == "frég" purl2 = parse(TOTAL_URL, decoded=False) assert isinstance(purl2, EncodedURL) - assert purl2.get('zot') == ['23%25'] + assert purl2.get("zot") == ["23%25"] with self.assertRaises(UnicodeDecodeError): purl3 = parse(UNDECODABLE_FRAG_URL) diff --git a/src/hyperlink/test/test_scheme_registration.py b/src/hyperlink/test/test_scheme_registration.py index a8bbbef5..f98109a3 100644 --- a/src/hyperlink/test/test_scheme_registration.py +++ b/src/hyperlink/test/test_scheme_registration.py @@ -9,7 +9,6 @@ class TestSchemeRegistration(HyperlinkTestCase): - def setUp(self): # type: () -> None self._orig_scheme_port_map = dict(_url.SCHEME_PORT_MAP) @@ -22,52 +21,52 @@ def tearDown(self): def test_register_scheme_basic(self): # type: () -> None - register_scheme('deltron', uses_netloc=True, default_port=3030) + register_scheme("deltron", uses_netloc=True, default_port=3030) - u1 = URL.from_text('deltron://example.com') - assert u1.scheme == 'deltron' + u1 = URL.from_text("deltron://example.com") + assert u1.scheme == "deltron" assert u1.port == 3030 assert u1.uses_netloc is True # test netloc works even when the original gives no indication - u2 = URL.from_text('deltron:') - u2 = u2.replace(host='example.com') - assert u2.to_text() == 'deltron://example.com' + u2 = URL.from_text("deltron:") + u2 = u2.replace(host="example.com") + assert u2.to_text() == "deltron://example.com" # test default port means no emission - u3 = URL.from_text('deltron://example.com:3030') - assert u3.to_text() == 'deltron://example.com' + u3 = URL.from_text("deltron://example.com:3030") + assert u3.to_text() == "deltron://example.com" - register_scheme('nonetron', default_port=3031) - u4 = URL(scheme='nonetron') - u4 = u4.replace(host='example.com') - assert u4.to_text() == 'nonetron://example.com' + register_scheme("nonetron", default_port=3031) + u4 = URL(scheme="nonetron") + u4 = u4.replace(host="example.com") + assert u4.to_text() == "nonetron://example.com" def test_register_no_netloc_scheme(self): # type: () -> None - register_scheme('noloctron', uses_netloc=False) - u4 = URL(scheme='noloctron') + register_scheme("noloctron", uses_netloc=False) + u4 = URL(scheme="noloctron") u4 = u4.replace(path=("example", "path")) - assert u4.to_text() == 'noloctron:example/path' + assert u4.to_text() == "noloctron:example/path" def test_register_no_netloc_with_port(self): # type: () -> None with self.assertRaises(ValueError): - register_scheme('badnetlocless', uses_netloc=False, default_port=7) + register_scheme("badnetlocless", uses_netloc=False, default_port=7) def test_invalid_uses_netloc(self): # type: () -> None with self.assertRaises(ValueError): - register_scheme('badnetloc', uses_netloc=cast(bool, None)) + register_scheme("badnetloc", uses_netloc=cast(bool, None)) with self.assertRaises(ValueError): - register_scheme('badnetloc', uses_netloc=cast(bool, object())) + register_scheme("badnetloc", uses_netloc=cast(bool, object())) def test_register_invalid_uses_netloc(self): # type: () -> None with self.assertRaises(ValueError): - register_scheme('lol', uses_netloc=cast(bool, object())) + register_scheme("lol", uses_netloc=cast(bool, object())) def test_register_invalid_port(self): # type: () -> None with self.assertRaises(ValueError): - register_scheme('nope', default_port=cast(bool, object())) + register_scheme("nope", default_port=cast(bool, object())) diff --git a/src/hyperlink/test/test_url.py b/src/hyperlink/test/test_url.py index 6952e42f..159d6a58 100644 --- a/src/hyperlink/test/test_url.py +++ b/src/hyperlink/test/test_url.py @@ -14,65 +14,63 @@ from .._url import inet_pton, SCHEME_PORT_MAP -PY2 = (sys.version_info[0] == 2) -unicode = type(u'') +PY2 = sys.version_info[0] == 2 +unicode = type("") BASIC_URL = "http://www.foo.com/a/nice/path/?zot=23&zut" # Examples from RFC 3986 section 5.4, Reference Resolution Examples -relativeLinkBaseForRFC3986 = 'http://a/b/c/d;p?q' +relativeLinkBaseForRFC3986 = "http://a/b/c/d;p?q" relativeLinkTestsForRFC3986 = [ # "Normal" # ('g:h', 'g:h'), # can't click on a scheme-having url without an abs path - ('g', 'http://a/b/c/g'), - ('./g', 'http://a/b/c/g'), - ('g/', 'http://a/b/c/g/'), - ('/g', 'http://a/g'), - ('//g', 'http://g'), - ('?y', 'http://a/b/c/d;p?y'), - ('g?y', 'http://a/b/c/g?y'), - ('#s', 'http://a/b/c/d;p?q#s'), - ('g#s', 'http://a/b/c/g#s'), - ('g?y#s', 'http://a/b/c/g?y#s'), - (';x', 'http://a/b/c/;x'), - ('g;x', 'http://a/b/c/g;x'), - ('g;x?y#s', 'http://a/b/c/g;x?y#s'), - ('', 'http://a/b/c/d;p?q'), - ('.', 'http://a/b/c/'), - ('./', 'http://a/b/c/'), - ('..', 'http://a/b/'), - ('../', 'http://a/b/'), - ('../g', 'http://a/b/g'), - ('../..', 'http://a/'), - ('../../', 'http://a/'), - ('../../g', 'http://a/g'), - + ("g", "http://a/b/c/g"), + ("./g", "http://a/b/c/g"), + ("g/", "http://a/b/c/g/"), + ("/g", "http://a/g"), + ("//g", "http://g"), + ("?y", "http://a/b/c/d;p?y"), + ("g?y", "http://a/b/c/g?y"), + ("#s", "http://a/b/c/d;p?q#s"), + ("g#s", "http://a/b/c/g#s"), + ("g?y#s", "http://a/b/c/g?y#s"), + (";x", "http://a/b/c/;x"), + ("g;x", "http://a/b/c/g;x"), + ("g;x?y#s", "http://a/b/c/g;x?y#s"), + ("", "http://a/b/c/d;p?q"), + (".", "http://a/b/c/"), + ("./", "http://a/b/c/"), + ("..", "http://a/b/"), + ("../", "http://a/b/"), + ("../g", "http://a/b/g"), + ("../..", "http://a/"), + ("../../", "http://a/"), + ("../../g", "http://a/g"), # Abnormal examples # ".." cannot be used to change the authority component of a URI. - ('../../../g', 'http://a/g'), - ('../../../../g', 'http://a/g'), - + ("../../../g", "http://a/g"), + ("../../../../g", "http://a/g"), # Only include "." and ".." when they are only part of a larger segment, # not by themselves. - ('/./g', 'http://a/g'), - ('/../g', 'http://a/g'), - ('g.', 'http://a/b/c/g.'), - ('.g', 'http://a/b/c/.g'), - ('g..', 'http://a/b/c/g..'), - ('..g', 'http://a/b/c/..g'), + ("/./g", "http://a/g"), + ("/../g", "http://a/g"), + ("g.", "http://a/b/c/g."), + (".g", "http://a/b/c/.g"), + ("g..", "http://a/b/c/g.."), + ("..g", "http://a/b/c/..g"), # Unnecessary or nonsensical forms of "." and "..". - ('./../g', 'http://a/b/g'), - ('./g/.', 'http://a/b/c/g/'), - ('g/./h', 'http://a/b/c/g/h'), - ('g/../h', 'http://a/b/c/h'), - ('g;x=1/./y', 'http://a/b/c/g;x=1/y'), - ('g;x=1/../y', 'http://a/b/c/y'), + ("./../g", "http://a/b/g"), + ("./g/.", "http://a/b/c/g/"), + ("g/./h", "http://a/b/c/g/h"), + ("g/../h", "http://a/b/c/h"), + ("g;x=1/./y", "http://a/b/c/g;x=1/y"), + ("g;x=1/../y", "http://a/b/c/y"), # Separating the reference's query and fragment components from the path. - ('g?y/./x', 'http://a/b/c/g?y/./x'), - ('g?y/../x', 'http://a/b/c/g?y/../x'), - ('g#s/./x', 'http://a/b/c/g#s/./x'), - ('g#s/../x', 'http://a/b/c/g#s/../x') + ("g?y/./x", "http://a/b/c/g?y/./x"), + ("g?y/../x", "http://a/b/c/g?y/../x"), + ("g#s/./x", "http://a/b/c/g#s/./x"), + ("g#s/../x", "http://a/b/c/g#s/../x"), ] @@ -95,52 +93,50 @@ "http://(%2525)/(%2525)?(%2525)&(%2525)=(%2525)#(%2525)", "http://(%C3%A9)/(%C3%A9)?(%C3%A9)&(%C3%A9)=(%C3%A9)#(%C3%A9)", "?sslrootcert=/Users/glyph/Downloads/rds-ca-2015-root.pem&sslmode=verify", - # from boltons.urlutils' tests - - 'http://googlewebsite.com/e-shops.aspx', - 'http://example.com:8080/search?q=123&business=Nothing%20Special', - 'http://hatnote.com:9000/?arg=1&arg=2&arg=3', - 'https://xn--bcher-kva.ch', - 'http://xn--ggbla1c4e.xn--ngbc5azd/', - 'http://tools.ietf.org/html/rfc3986#section-3.4', + "http://googlewebsite.com/e-shops.aspx", + "http://example.com:8080/search?q=123&business=Nothing%20Special", + "http://hatnote.com:9000/?arg=1&arg=2&arg=3", + "https://xn--bcher-kva.ch", + "http://xn--ggbla1c4e.xn--ngbc5azd/", + "http://tools.ietf.org/html/rfc3986#section-3.4", # 'http://wiki:pedia@hatnote.com', - 'ftp://ftp.rfc-editor.org/in-notes/tar/RFCs0001-0500.tar.gz', - 'http://[1080:0:0:0:8:800:200C:417A]/index.html', - 'ssh://192.0.2.16:2222/', - 'https://[::101.45.75.219]:80/?hi=bye', - 'ldap://[::192.9.5.5]/dc=example,dc=com??sub?(sn=Jensen)', - 'mailto:me@example.com?to=me@example.com&body=hi%20http://wikipedia.org', - 'news:alt.rec.motorcycle', - 'tel:+1-800-867-5309', - 'urn:oasis:member:A00024:x', - ('magnet:?xt=urn:btih:1a42b9e04e122b97a5254e3df77ab3c4b7da725f&dn=Puppy%' - '20Linux%20precise-5.7.1.iso&tr=udp://tracker.openbittorrent.com:80&' - 'tr=udp://tracker.publicbt.com:80&tr=udp://tracker.istole.it:6969&' - 'tr=udp://tracker.ccc.de:80&tr=udp://open.demonii.com:1337'), - + "ftp://ftp.rfc-editor.org/in-notes/tar/RFCs0001-0500.tar.gz", + "http://[1080:0:0:0:8:800:200C:417A]/index.html", + "ssh://192.0.2.16:2222/", + "https://[::101.45.75.219]:80/?hi=bye", + "ldap://[::192.9.5.5]/dc=example,dc=com??sub?(sn=Jensen)", + "mailto:me@example.com?to=me@example.com&body=hi%20http://wikipedia.org", + "news:alt.rec.motorcycle", + "tel:+1-800-867-5309", + "urn:oasis:member:A00024:x", + ( + "magnet:?xt=urn:btih:1a42b9e04e122b97a5254e3df77ab3c4b7da725f&dn=Puppy%" + "20Linux%20precise-5.7.1.iso&tr=udp://tracker.openbittorrent.com:80&" + "tr=udp://tracker.publicbt.com:80&tr=udp://tracker.istole.it:6969&" + "tr=udp://tracker.ccc.de:80&tr=udp://open.demonii.com:1337" + ), # percent-encoded delimiters in percent-encodable fields - - 'https://%3A@example.com/', # colon in username - 'https://%40@example.com/', # at sign in username - 'https://%2f@example.com/', # slash in username - 'https://a:%3a@example.com/', # colon in password - 'https://a:%40@example.com/', # at sign in password - 'https://a:%2f@example.com/', # slash in password - 'https://a:%3f@example.com/', # question mark in password - 'https://example.com/%2F/', # slash in path - 'https://example.com/%3F/', # question mark in path - 'https://example.com/%23/', # hash in path - 'https://example.com/?%23=b', # hash in query param name - 'https://example.com/?%3D=b', # equals in query param name - 'https://example.com/?%26=b', # ampersand in query param name - 'https://example.com/?a=%23', # hash in query param value - 'https://example.com/?a=%26', # ampersand in query param value - 'https://example.com/?a=%3D', # equals in query param value + "https://%3A@example.com/", # colon in username + "https://%40@example.com/", # at sign in username + "https://%2f@example.com/", # slash in username + "https://a:%3a@example.com/", # colon in password + "https://a:%40@example.com/", # at sign in password + "https://a:%2f@example.com/", # slash in password + "https://a:%3f@example.com/", # question mark in password + "https://example.com/%2F/", # slash in path + "https://example.com/%3F/", # question mark in path + "https://example.com/%23/", # hash in path + "https://example.com/?%23=b", # hash in query param name + "https://example.com/?%3D=b", # equals in query param name + "https://example.com/?%26=b", # ampersand in query param name + "https://example.com/?a=%23", # hash in query param value + "https://example.com/?a=%26", # ampersand in query param value + "https://example.com/?a=%3D", # equals in query param value # double-encoded percent sign in all percent-encodable positions: "http://(%2525):(%2525)@example.com/(%2525)/?(%2525)=(%2525)#(%2525)", # colon in first part of schemeless relative url - 'first_seg_rel_path__colon%3Anotok/second_seg__colon%3Aok', + "first_seg_rel_path__colon%3Anotok/second_seg__colon%3Aok", ) @@ -156,10 +152,10 @@ def assertUnicoded(self, u): @param u: The L{URL} to test. """ - self.assertTrue(isinstance(u.scheme, unicode) or u.scheme is None, - repr(u)) - self.assertTrue(isinstance(u.host, unicode) or u.host is None, - repr(u)) + self.assertTrue( + isinstance(u.scheme, unicode) or u.scheme is None, repr(u) + ) + self.assertTrue(isinstance(u.host, unicode) or u.host is None, repr(u)) for seg in u.path: self.assertEqual(type(seg), unicode, repr(u)) for (_k, v) in u.query: @@ -169,14 +165,14 @@ def assertUnicoded(self, u): def assertURL( self, - u, # type: URL - scheme, # type: Text - host, # type: Text - path, # type: Iterable[Text] - query, # type: Iterable[Tuple[Text, Optional[Text]]] - fragment, # type: Text - port, # type: Optional[int] - userinfo='', # type: Text + u, # type: URL + scheme, # type: Text + host, # type: Text + path, # type: Iterable[Text] + query, # type: Iterable[Tuple[Text, Optional[Text]]] + fragment, # type: Text + port, # type: Optional[int] + userinfo="", # type: Text ): # type: (...) -> None """ @@ -198,10 +194,24 @@ def assertURL( @param userinfo: The expected userinfo. """ - actual = (u.scheme, u.host, u.path, u.query, - u.fragment, u.port, u.userinfo) - expected = (scheme, host, tuple(path), tuple(query), - fragment, port, u.userinfo) + actual = ( + u.scheme, + u.host, + u.path, + u.query, + u.fragment, + u.port, + u.userinfo, + ) + expected = ( + scheme, + host, + tuple(path), + tuple(query), + fragment, + port, + u.userinfo, + ) self.assertEqual(actual, expected) def test_initDefaults(self): @@ -209,42 +219,45 @@ def test_initDefaults(self): """ L{URL} should have appropriate default values. """ + def check(u): # type: (URL) -> None self.assertUnicoded(u) - self.assertURL(u, 'http', '', [], [], '', 80, '') + self.assertURL(u, "http", "", [], [], "", 80, "") - check(URL('http', '')) - check(URL('http', '', [], [])) - check(URL('http', '', [], [], '')) + check(URL("http", "")) + check(URL("http", "", [], [])) + check(URL("http", "", [], [], "")) def test_init(self): # type: () -> None """ L{URL} should accept L{unicode} parameters. """ - u = URL('s', 'h', ['p'], [('k', 'v'), ('k', None)], 'f') + u = URL("s", "h", ["p"], [("k", "v"), ("k", None)], "f") self.assertUnicoded(u) - self.assertURL(u, 's', 'h', ['p'], [('k', 'v'), ('k', None)], - 'f', None) - - self.assertURL(URL('http', '\xe0', ['\xe9'], - [('\u03bb', '\u03c0')], '\u22a5'), - 'http', '\xe0', ['\xe9'], - [('\u03bb', '\u03c0')], '\u22a5', 80) + self.assertURL(u, "s", "h", ["p"], [("k", "v"), ("k", None)], "f", None) + + self.assertURL( + URL("http", "\xe0", ["\xe9"], [("\u03bb", "\u03c0")], "\u22a5"), + "http", + "\xe0", + ["\xe9"], + [("\u03bb", "\u03c0")], + "\u22a5", + 80, + ) def test_initPercent(self): # type: () -> None """ L{URL} should accept (and not interpret) percent characters. """ - u = URL('s', '%68', ['%70'], [('%6B', '%76'), ('%6B', None)], - '%66') + u = URL("s", "%68", ["%70"], [("%6B", "%76"), ("%6B", None)], "%66") self.assertUnicoded(u) - self.assertURL(u, - 's', '%68', ['%70'], - [('%6B', '%76'), ('%6B', None)], - '%66', None) + self.assertURL( + u, "s", "%68", ["%70"], [("%6B", "%76"), ("%6B", None)], "%66", None + ) def test_repr(self): # type: () -> None @@ -254,10 +267,16 @@ def test_repr(self): easy to read. """ self.assertEqual( - repr(URL(scheme='http', host='foo', path=['bar'], - query=[('baz', None), ('k', 'v')], - fragment='frob')), - "URL.from_text(%s)" % (repr(u"http://foo/bar?baz&k=v#frob"),) + repr( + URL( + scheme="http", + host="foo", + path=["bar"], + query=[("baz", None), ("k", "v")], + fragment="frob", + ) + ), + "URL.from_text(%s)" % (repr("http://foo/bar?baz&k=v#frob"),), ) def test_from_text(self): @@ -302,8 +321,9 @@ def test_equality(self): self.assertEqual(urlpath, URL.from_text(BASIC_URL)) self.assertNotEqual( urlpath, - URL.from_text('ftp://www.anotherinvaliddomain.com/' - 'foo/bar/baz/?zot=21&zut') + URL.from_text( + "ftp://www.anotherinvaliddomain.com/" "foo/bar/baz/?zot=21&zut" + ), ) def test_fragmentEquality(self): @@ -312,9 +332,11 @@ def test_fragmentEquality(self): An URL created with the empty string for a fragment compares equal to an URL created with an unspecified fragment. """ - self.assertEqual(URL(fragment=''), URL()) - self.assertEqual(URL.from_text(u"http://localhost/#"), - URL.from_text(u"http://localhost/")) + self.assertEqual(URL(fragment=""), URL()) + self.assertEqual( + URL.from_text("http://localhost/#"), + URL.from_text("http://localhost/"), + ) def test_child(self): # type: () -> None @@ -323,17 +345,21 @@ def test_child(self): or fragment. """ urlpath = URL.from_text(BASIC_URL) - self.assertEqual("http://www.foo.com/a/nice/path/gong?zot=23&zut", - urlpath.child('gong').to_text()) - self.assertEqual("http://www.foo.com/a/nice/path/gong%2F?zot=23&zut", - urlpath.child('gong/').to_text()) + self.assertEqual( + "http://www.foo.com/a/nice/path/gong?zot=23&zut", + urlpath.child("gong").to_text(), + ) + self.assertEqual( + "http://www.foo.com/a/nice/path/gong%2F?zot=23&zut", + urlpath.child("gong/").to_text(), + ) self.assertEqual( "http://www.foo.com/a/nice/path/gong%2Fdouble?zot=23&zut", - urlpath.child('gong/double').to_text() + urlpath.child("gong/double").to_text(), ) self.assertEqual( "http://www.foo.com/a/nice/path/gong%2Fdouble%2F?zot=23&zut", - urlpath.child('gong/double/').to_text() + urlpath.child("gong/double/").to_text(), ) def test_multiChild(self): @@ -342,9 +368,10 @@ def test_multiChild(self): L{URL.child} receives multiple segments as C{*args} and appends each in turn. """ - url = URL.from_text('http://example.com/a/b') - self.assertEqual(url.child('c', 'd', 'e').to_text(), - 'http://example.com/a/b/c/d/e') + url = URL.from_text("http://example.com/a/b") + self.assertEqual( + url.child("c", "d", "e").to_text(), "http://example.com/a/b/c/d/e" + ) def test_childInitRoot(self): # type: () -> None @@ -352,7 +379,7 @@ def test_childInitRoot(self): L{URL.child} of a L{URL} without a path produces a L{URL} with a single path segment. """ - childURL = URL(host=u"www.foo.com").child(u"c") + childURL = URL(host="www.foo.com").child("c") self.assertTrue(childURL.rooted) self.assertEqual("http://www.foo.com/c", childURL.to_text()) @@ -361,7 +388,7 @@ def test_emptyChild(self): """ L{URL.child} without any new segments returns the original L{URL}. """ - url = URL(host=u"www.foo.com") + url = URL(host="www.foo.com") self.assertEqual(url.child(), url) def test_sibling(self): @@ -373,14 +400,14 @@ def test_sibling(self): urlpath = URL.from_text(BASIC_URL) self.assertEqual( "http://www.foo.com/a/nice/path/sister?zot=23&zut", - urlpath.sibling('sister').to_text() + urlpath.sibling("sister").to_text(), ) # Use an url without trailing '/' to check child removal. url_text = "http://www.foo.com/a/nice/path?zot=23&zut" urlpath = URL.from_text(url_text) self.assertEqual( "http://www.foo.com/a/nice/sister?zot=23&zut", - urlpath.sibling('sister').to_text() + urlpath.sibling("sister").to_text(), ) def test_click(self): @@ -391,47 +418,59 @@ def test_click(self): """ urlpath = URL.from_text(BASIC_URL) # A null uri should be valid (return here). - self.assertEqual("http://www.foo.com/a/nice/path/?zot=23&zut", - urlpath.click("").to_text()) + self.assertEqual( + "http://www.foo.com/a/nice/path/?zot=23&zut", + urlpath.click("").to_text(), + ) # A simple relative path remove the query. - self.assertEqual("http://www.foo.com/a/nice/path/click", - urlpath.click("click").to_text()) + self.assertEqual( + "http://www.foo.com/a/nice/path/click", + urlpath.click("click").to_text(), + ) # An absolute path replace path and query. - self.assertEqual("http://www.foo.com/click", - urlpath.click("/click").to_text()) + self.assertEqual( + "http://www.foo.com/click", urlpath.click("/click").to_text() + ) # Replace just the query. - self.assertEqual("http://www.foo.com/a/nice/path/?burp", - urlpath.click("?burp").to_text()) + self.assertEqual( + "http://www.foo.com/a/nice/path/?burp", + urlpath.click("?burp").to_text(), + ) # One full url to another should not generate '//' between authority. # and path - self.assertTrue("//foobar" not in - urlpath.click('http://www.foo.com/foobar').to_text()) + self.assertTrue( + "//foobar" + not in urlpath.click("http://www.foo.com/foobar").to_text() + ) # From a url with no query clicking a url with a query, the query # should be handled properly. - u = URL.from_text('http://www.foo.com/me/noquery') - self.assertEqual('http://www.foo.com/me/17?spam=158', - u.click('/me/17?spam=158').to_text()) + u = URL.from_text("http://www.foo.com/me/noquery") + self.assertEqual( + "http://www.foo.com/me/17?spam=158", + u.click("/me/17?spam=158").to_text(), + ) # Check that everything from the path onward is removed when the click # link has no path. - u = URL.from_text('http://localhost/foo?abc=def') - self.assertEqual(u.click('http://www.python.org').to_text(), - 'http://www.python.org') + u = URL.from_text("http://localhost/foo?abc=def") + self.assertEqual( + u.click("http://www.python.org").to_text(), "http://www.python.org" + ) # https://twistedmatrix.com/trac/ticket/8184 - u = URL.from_text('http://hatnote.com/a/b/../c/./d/e/..') - res = 'http://hatnote.com/a/c/d/' - self.assertEqual(u.click('').to_text(), res) + u = URL.from_text("http://hatnote.com/a/b/../c/./d/e/..") + res = "http://hatnote.com/a/c/d/" + self.assertEqual(u.click("").to_text(), res) # test click default arg is same as empty string above self.assertEqual(u.click().to_text(), res) # test click on a URL instance - u = URL.fromText('http://localhost/foo/?abc=def') - u2 = URL.from_text('bar') + u = URL.fromText("http://localhost/foo/?abc=def") + u2 = URL.from_text("bar") u3 = u.click(u2) - self.assertEqual(u3.to_text(), 'http://localhost/foo/bar') + self.assertEqual(u3.to_text(), "http://localhost/foo/bar") def test_clickRFC3986(self): # type: () -> None @@ -448,8 +487,8 @@ def test_clickSchemeRelPath(self): L{URL.click} should not accept schemes with relative paths. """ base = URL.from_text(relativeLinkBaseForRFC3986) - self.assertRaises(NotImplementedError, base.click, 'g:h') - self.assertRaises(NotImplementedError, base.click, 'http:h') + self.assertRaises(NotImplementedError, base.click, "g:h") + self.assertRaises(NotImplementedError, base.click, "http:h") def test_cloneUnchanged(self): # type: () -> None @@ -457,14 +496,18 @@ def test_cloneUnchanged(self): Verify that L{URL.replace} doesn't change any of the arguments it is passed. """ - urlpath = URL.from_text('https://x:1/y?z=1#A') - self.assertEqual(urlpath.replace(urlpath.scheme, - urlpath.host, - urlpath.path, - urlpath.query, - urlpath.fragment, - urlpath.port), - urlpath) + urlpath = URL.from_text("https://x:1/y?z=1#A") + self.assertEqual( + urlpath.replace( + urlpath.scheme, + urlpath.host, + urlpath.path, + urlpath.query, + urlpath.fragment, + urlpath.port, + ), + urlpath, + ) self.assertEqual(urlpath.replace(), urlpath) def test_clickCollapse(self): @@ -474,21 +517,27 @@ def test_clickCollapse(self): 5.2.4. """ tests = [ - ['http://localhost/', '.', 'http://localhost/'], - ['http://localhost/', '..', 'http://localhost/'], - ['http://localhost/a/b/c', '.', 'http://localhost/a/b/'], - ['http://localhost/a/b/c', '..', 'http://localhost/a/'], - ['http://localhost/a/b/c', './d/e', 'http://localhost/a/b/d/e'], - ['http://localhost/a/b/c', '../d/e', 'http://localhost/a/d/e'], - ['http://localhost/a/b/c', '/./d/e', 'http://localhost/d/e'], - ['http://localhost/a/b/c', '/../d/e', 'http://localhost/d/e'], - ['http://localhost/a/b/c/', '../../d/e/', - 'http://localhost/a/d/e/'], - ['http://localhost/a/./c', '../d/e', 'http://localhost/d/e'], - ['http://localhost/a/./c/', '../d/e', 'http://localhost/a/d/e'], - ['http://localhost/a/b/c/d', './e/../f/../g', - 'http://localhost/a/b/c/g'], - ['http://localhost/a/b/c', 'd//e', 'http://localhost/a/b/d//e'], + ["http://localhost/", ".", "http://localhost/"], + ["http://localhost/", "..", "http://localhost/"], + ["http://localhost/a/b/c", ".", "http://localhost/a/b/"], + ["http://localhost/a/b/c", "..", "http://localhost/a/"], + ["http://localhost/a/b/c", "./d/e", "http://localhost/a/b/d/e"], + ["http://localhost/a/b/c", "../d/e", "http://localhost/a/d/e"], + ["http://localhost/a/b/c", "/./d/e", "http://localhost/d/e"], + ["http://localhost/a/b/c", "/../d/e", "http://localhost/d/e"], + [ + "http://localhost/a/b/c/", + "../../d/e/", + "http://localhost/a/d/e/", + ], + ["http://localhost/a/./c", "../d/e", "http://localhost/d/e"], + ["http://localhost/a/./c/", "../d/e", "http://localhost/a/d/e"], + [ + "http://localhost/a/b/c/d", + "./e/../f/../g", + "http://localhost/a/b/c/g", + ], + ["http://localhost/a/b/c", "d//e", "http://localhost/a/b/d//e"], ] for start, click, expected in tests: actual = URL.from_text(start).click(click).to_text() @@ -500,7 +549,7 @@ def test_clickCollapse(self): click=repr(click), actual=actual, expected=expected, - ) + ), ) def test_queryAdd(self): @@ -511,30 +560,36 @@ def test_queryAdd(self): self.assertEqual( "http://www.foo.com/a/nice/path/?foo=bar", URL.from_text("http://www.foo.com/a/nice/path/") - .add(u"foo", u"bar").to_text()) + .add("foo", "bar") + .to_text(), + ) self.assertEqual( "http://www.foo.com/?foo=bar", - URL(host=u"www.foo.com").add(u"foo", u"bar") - .to_text()) + URL(host="www.foo.com").add("foo", "bar").to_text(), + ) urlpath = URL.from_text(BASIC_URL) self.assertEqual( "http://www.foo.com/a/nice/path/?zot=23&zut&burp", - urlpath.add(u"burp").to_text()) + urlpath.add("burp").to_text(), + ) self.assertEqual( "http://www.foo.com/a/nice/path/?zot=23&zut&burp=xxx", - urlpath.add(u"burp", u"xxx").to_text()) + urlpath.add("burp", "xxx").to_text(), + ) self.assertEqual( "http://www.foo.com/a/nice/path/?zot=23&zut&burp=xxx&zing", - urlpath.add(u"burp", u"xxx").add(u"zing").to_text()) + urlpath.add("burp", "xxx").add("zing").to_text(), + ) # Note the inversion! self.assertEqual( "http://www.foo.com/a/nice/path/?zot=23&zut&zing&burp=xxx", - urlpath.add(u"zing").add(u"burp", u"xxx").to_text()) + urlpath.add("zing").add("burp", "xxx").to_text(), + ) # Note the two values for the same name. self.assertEqual( "http://www.foo.com/a/nice/path/?zot=23&zut&burp=xxx&zot=32", - urlpath.add(u"burp", u"xxx").add(u"zot", '32') - .to_text()) + urlpath.add("burp", "xxx").add("zot", "32").to_text(), + ) def test_querySet(self): # type: () -> None @@ -544,17 +599,18 @@ def test_querySet(self): urlpath = URL.from_text(BASIC_URL) self.assertEqual( "http://www.foo.com/a/nice/path/?zot=32&zut", - urlpath.set(u"zot", '32').to_text()) + urlpath.set("zot", "32").to_text(), + ) # Replace name without value with name/value and vice-versa. self.assertEqual( "http://www.foo.com/a/nice/path/?zot&zut=itworked", - urlpath.set(u"zot").set(u"zut", u"itworked").to_text() + urlpath.set("zot").set("zut", "itworked").to_text(), ) # Q: what happens when the query has two values and we replace? # A: we replace both values with a single one self.assertEqual( "http://www.foo.com/a/nice/path/?zot=32&zut", - urlpath.add(u"zot", u"xxx").set(u"zot", '32').to_text() + urlpath.add("zot", "xxx").set("zot", "32").to_text(), ) def test_queryRemove(self): @@ -562,25 +618,24 @@ def test_queryRemove(self): """ L{URL.remove} removes instances of a query parameter. """ - url = URL.from_text(u"https://example.com/a/b/?foo=1&bar=2&foo=3") + url = URL.from_text("https://example.com/a/b/?foo=1&bar=2&foo=3") self.assertEqual( - url.remove(u"foo"), - URL.from_text(u"https://example.com/a/b/?bar=2") + url.remove("foo"), URL.from_text("https://example.com/a/b/?bar=2") ) self.assertEqual( - url.remove(name=u"foo", value=u"1"), - URL.from_text(u"https://example.com/a/b/?bar=2&foo=3") + url.remove(name="foo", value="1"), + URL.from_text("https://example.com/a/b/?bar=2&foo=3"), ) self.assertEqual( - url.remove(name=u"foo", limit=1), - URL.from_text(u"https://example.com/a/b/?bar=2&foo=3") + url.remove(name="foo", limit=1), + URL.from_text("https://example.com/a/b/?bar=2&foo=3"), ) self.assertEqual( - url.remove(name=u"foo", value=u"1", limit=0), - URL.from_text(u"https://example.com/a/b/?foo=1&bar=2&foo=3") + url.remove(name="foo", value="1", limit=0), + URL.from_text("https://example.com/a/b/?foo=1&bar=2&foo=3"), ) def test_parseEqualSignInParamValue(self): @@ -589,42 +644,42 @@ def test_parseEqualSignInParamValue(self): Every C{=}-sign after the first in a query parameter is simply included in the value of the parameter. """ - u = URL.from_text('http://localhost/?=x=x=x') - self.assertEqual(u.get(''), ['x=x=x']) - self.assertEqual(u.to_text(), 'http://localhost/?=x=x=x') - u = URL.from_text('http://localhost/?foo=x=x=x&bar=y') - self.assertEqual(u.query, (('foo', 'x=x=x'), ('bar', 'y'))) - self.assertEqual(u.to_text(), 'http://localhost/?foo=x=x=x&bar=y') + u = URL.from_text("http://localhost/?=x=x=x") + self.assertEqual(u.get(""), ["x=x=x"]) + self.assertEqual(u.to_text(), "http://localhost/?=x=x=x") + u = URL.from_text("http://localhost/?foo=x=x=x&bar=y") + self.assertEqual(u.query, (("foo", "x=x=x"), ("bar", "y"))) + self.assertEqual(u.to_text(), "http://localhost/?foo=x=x=x&bar=y") u = URL.from_text( - 'https://example.com/?argument=3&argument=4&operator=%3D' + "https://example.com/?argument=3&argument=4&operator=%3D" ) iri = u.to_iri() - self.assertEqual(iri.get('operator'), ['=']) + self.assertEqual(iri.get("operator"), ["="]) # assert that the equals is not unnecessarily escaped - self.assertEqual(iri.to_uri().get('operator'), ['=']) + self.assertEqual(iri.to_uri().get("operator"), ["="]) def test_empty(self): # type: () -> None """ An empty L{URL} should serialize as the empty string. """ - self.assertEqual(URL().to_text(), '') + self.assertEqual(URL().to_text(), "") def test_justQueryText(self): # type: () -> None """ An L{URL} with query text should serialize as just query text. """ - u = URL(query=[(u"hello", u"world")]) - self.assertEqual(u.to_text(), '?hello=world') + u = URL(query=[("hello", "world")]) + self.assertEqual(u.to_text(), "?hello=world") def test_identicalEqual(self): # type: () -> None """ L{URL} compares equal to itself. """ - u = URL.from_text('http://localhost/') + u = URL.from_text("http://localhost/") self.assertEqual(u, u) def test_similarEqual(self): @@ -632,8 +687,8 @@ def test_similarEqual(self): """ URLs with equivalent components should compare equal. """ - u1 = URL.from_text('http://u@localhost:8080/p/a/t/h?q=p#f') - u2 = URL.from_text('http://u@localhost:8080/p/a/t/h?q=p#f') + u1 = URL.from_text("http://u@localhost:8080/p/a/t/h?q=p#f") + u2 = URL.from_text("http://u@localhost:8080/p/a/t/h?q=p#f") self.assertEqual(u1, u2) def test_differentNotEqual(self): @@ -642,8 +697,8 @@ def test_differentNotEqual(self): L{URL}s that refer to different resources are both unequal (C{!=}) and also not equal (not C{==}). """ - u1 = URL.from_text('http://localhost/a') - u2 = URL.from_text('http://localhost/b') + u1 = URL.from_text("http://localhost/a") + u2 = URL.from_text("http://localhost/b") self.assertFalse(u1 == u2, "%r != %r" % (u1, u2)) self.assertNotEqual(u1, u2) @@ -652,7 +707,7 @@ def test_otherTypesNotEqual(self): """ L{URL} is not equal (C{==}) to other types. """ - u = URL.from_text('http://localhost/') + u = URL.from_text("http://localhost/") self.assertFalse(u == 42, "URL must not equal a number.") self.assertFalse(u == object(), "URL must not equal an object.") self.assertNotEqual(u, 42) @@ -663,7 +718,7 @@ def test_identicalNotUnequal(self): """ Identical L{URL}s are not unequal (C{!=}) to each other. """ - u = URL.from_text('http://u@localhost:8080/p/a/t/h?q=p#f') + u = URL.from_text("http://u@localhost:8080/p/a/t/h?q=p#f") self.assertFalse(u != u, "%r == itself" % u) def test_similarNotUnequal(self): @@ -671,8 +726,8 @@ def test_similarNotUnequal(self): """ Structurally similar L{URL}s are not unequal (C{!=}) to each other. """ - u1 = URL.from_text('http://u@localhost:8080/p/a/t/h?q=p#f') - u2 = URL.from_text('http://u@localhost:8080/p/a/t/h?q=p#f') + u1 = URL.from_text("http://u@localhost:8080/p/a/t/h?q=p#f") + u2 = URL.from_text("http://u@localhost:8080/p/a/t/h?q=p#f") self.assertFalse(u1 != u2, "%r == %r" % (u1, u2)) def test_differentUnequal(self): @@ -680,8 +735,8 @@ def test_differentUnequal(self): """ Structurally different L{URL}s are unequal (C{!=}) to each other. """ - u1 = URL.from_text('http://localhost/a') - u2 = URL.from_text('http://localhost/b') + u1 = URL.from_text("http://localhost/a") + u2 = URL.from_text("http://localhost/b") self.assertTrue(u1 != u2, "%r == %r" % (u1, u2)) def test_otherTypesUnequal(self): @@ -689,7 +744,7 @@ def test_otherTypesUnequal(self): """ L{URL} is unequal (C{!=}) to other types. """ - u = URL.from_text('http://localhost/') + u = URL.from_text("http://localhost/") self.assertTrue(u != 42, "URL must differ from a number.") self.assertTrue(u != object(), "URL must be differ from an object.") @@ -699,21 +754,25 @@ def test_asURI(self): L{URL.asURI} produces an URI which converts any URI unicode encoding into pure US-ASCII and returns a new L{URL}. """ - unicodey = ('http://\N{LATIN SMALL LETTER E WITH ACUTE}.com/' - '\N{LATIN SMALL LETTER E}\N{COMBINING ACUTE ACCENT}' - '?\N{LATIN SMALL LETTER A}\N{COMBINING ACUTE ACCENT}=' - '\N{LATIN SMALL LETTER I}\N{COMBINING ACUTE ACCENT}' - '#\N{LATIN SMALL LETTER U}\N{COMBINING ACUTE ACCENT}') + unicodey = ( + "http://\N{LATIN SMALL LETTER E WITH ACUTE}.com/" + "\N{LATIN SMALL LETTER E}\N{COMBINING ACUTE ACCENT}" + "?\N{LATIN SMALL LETTER A}\N{COMBINING ACUTE ACCENT}=" + "\N{LATIN SMALL LETTER I}\N{COMBINING ACUTE ACCENT}" + "#\N{LATIN SMALL LETTER U}\N{COMBINING ACUTE ACCENT}" + ) iri = URL.from_text(unicodey) uri = iri.asURI() - self.assertEqual(iri.host, '\N{LATIN SMALL LETTER E WITH ACUTE}.com') - self.assertEqual(iri.path[0], - '\N{LATIN SMALL LETTER E}\N{COMBINING ACUTE ACCENT}') + self.assertEqual(iri.host, "\N{LATIN SMALL LETTER E WITH ACUTE}.com") + self.assertEqual( + iri.path[0], "\N{LATIN SMALL LETTER E}\N{COMBINING ACUTE ACCENT}" + ) self.assertEqual(iri.to_text(), unicodey) - expectedURI = 'http://xn--9ca.com/%C3%A9?%C3%A1=%C3%AD#%C3%BA' + expectedURI = "http://xn--9ca.com/%C3%A9?%C3%A1=%C3%AD#%C3%BA" actualURI = uri.to_text() - self.assertEqual(actualURI, expectedURI, - '%r != %r' % (actualURI, expectedURI)) + self.assertEqual( + actualURI, expectedURI, "%r != %r" % (actualURI, expectedURI) + ) def test_asIRI(self): # type: () -> None @@ -721,20 +780,23 @@ def test_asIRI(self): L{URL.asIRI} decodes any percent-encoded text in the URI, making it more suitable for reading by humans, and returns a new L{URL}. """ - asciiish = 'http://xn--9ca.com/%C3%A9?%C3%A1=%C3%AD#%C3%BA' + asciiish = "http://xn--9ca.com/%C3%A9?%C3%A1=%C3%AD#%C3%BA" uri = URL.from_text(asciiish) iri = uri.asIRI() - self.assertEqual(uri.host, 'xn--9ca.com') - self.assertEqual(uri.path[0], '%C3%A9') + self.assertEqual(uri.host, "xn--9ca.com") + self.assertEqual(uri.path[0], "%C3%A9") self.assertEqual(uri.to_text(), asciiish) - expectedIRI = ('http://\N{LATIN SMALL LETTER E WITH ACUTE}.com/' - '\N{LATIN SMALL LETTER E WITH ACUTE}' - '?\N{LATIN SMALL LETTER A WITH ACUTE}=' - '\N{LATIN SMALL LETTER I WITH ACUTE}' - '#\N{LATIN SMALL LETTER U WITH ACUTE}') + expectedIRI = ( + "http://\N{LATIN SMALL LETTER E WITH ACUTE}.com/" + "\N{LATIN SMALL LETTER E WITH ACUTE}" + "?\N{LATIN SMALL LETTER A WITH ACUTE}=" + "\N{LATIN SMALL LETTER I WITH ACUTE}" + "#\N{LATIN SMALL LETTER U WITH ACUTE}" + ) actualIRI = iri.to_text() - self.assertEqual(actualIRI, expectedIRI, - '%r != %r' % (actualIRI, expectedIRI)) + self.assertEqual( + actualIRI, expectedIRI, "%r != %r" % (actualIRI, expectedIRI) + ) def test_badUTF8AsIRI(self): # type: () -> None @@ -742,26 +804,31 @@ def test_badUTF8AsIRI(self): Bad UTF-8 in a path segment, query parameter, or fragment results in that portion of the URI remaining percent-encoded in the IRI. """ - urlWithBinary = 'http://xn--9ca.com/%00%FF/%C3%A9' + urlWithBinary = "http://xn--9ca.com/%00%FF/%C3%A9" uri = URL.from_text(urlWithBinary) iri = uri.asIRI() - expectedIRI = ('http://\N{LATIN SMALL LETTER E WITH ACUTE}.com/' - '%00%FF/' - '\N{LATIN SMALL LETTER E WITH ACUTE}') + expectedIRI = ( + "http://\N{LATIN SMALL LETTER E WITH ACUTE}.com/" + "%00%FF/" + "\N{LATIN SMALL LETTER E WITH ACUTE}" + ) actualIRI = iri.to_text() - self.assertEqual(actualIRI, expectedIRI, - '%r != %r' % (actualIRI, expectedIRI)) + self.assertEqual( + actualIRI, expectedIRI, "%r != %r" % (actualIRI, expectedIRI) + ) def test_alreadyIRIAsIRI(self): # type: () -> None """ A L{URL} composed of non-ASCII text will result in non-ASCII text. """ - unicodey = ('http://\N{LATIN SMALL LETTER E WITH ACUTE}.com/' - '\N{LATIN SMALL LETTER E}\N{COMBINING ACUTE ACCENT}' - '?\N{LATIN SMALL LETTER A}\N{COMBINING ACUTE ACCENT}=' - '\N{LATIN SMALL LETTER I}\N{COMBINING ACUTE ACCENT}' - '#\N{LATIN SMALL LETTER U}\N{COMBINING ACUTE ACCENT}') + unicodey = ( + "http://\N{LATIN SMALL LETTER E WITH ACUTE}.com/" + "\N{LATIN SMALL LETTER E}\N{COMBINING ACUTE ACCENT}" + "?\N{LATIN SMALL LETTER A}\N{COMBINING ACUTE ACCENT}=" + "\N{LATIN SMALL LETTER I}\N{COMBINING ACUTE ACCENT}" + "#\N{LATIN SMALL LETTER U}\N{COMBINING ACUTE ACCENT}" + ) iri = URL.from_text(unicodey) alsoIRI = iri.asIRI() self.assertEqual(alsoIRI.to_text(), unicodey) @@ -771,7 +838,7 @@ def test_alreadyURIAsURI(self): """ A L{URL} composed of encoded text will remain encoded. """ - expectedURI = 'http://xn--9ca.com/%C3%A9?%C3%A1=%C3%AD#%C3%BA' + expectedURI = "http://xn--9ca.com/%C3%A9?%C3%A1=%C3%AD#%C3%BA" uri = URL.from_text(expectedURI) actualURI = uri.asURI().to_text() self.assertEqual(actualURI, expectedURI) @@ -783,18 +850,20 @@ def test_userinfo(self): separately from the host and port. """ url = URL.from_text( - 'http://someuser:somepassword@example.com/some-segment@ignore' - ) - self.assertEqual(url.authority(True), - 'someuser:somepassword@example.com') - self.assertEqual(url.authority(False), 'someuser:@example.com') - self.assertEqual(url.userinfo, 'someuser:somepassword') - self.assertEqual(url.user, 'someuser') - self.assertEqual(url.to_text(), - 'http://someuser:@example.com/some-segment@ignore') + "http://someuser:somepassword@example.com/some-segment@ignore" + ) self.assertEqual( - url.replace(userinfo=u"someuser").to_text(), - 'http://someuser@example.com/some-segment@ignore' + url.authority(True), "someuser:somepassword@example.com" + ) + self.assertEqual(url.authority(False), "someuser:@example.com") + self.assertEqual(url.userinfo, "someuser:somepassword") + self.assertEqual(url.user, "someuser") + self.assertEqual( + url.to_text(), "http://someuser:@example.com/some-segment@ignore" + ) + self.assertEqual( + url.replace(userinfo="someuser").to_text(), + "http://someuser@example.com/some-segment@ignore", ) def test_portText(self): @@ -802,9 +871,9 @@ def test_portText(self): """ L{URL.from_text} parses custom port numbers as integers. """ - portURL = URL.from_text(u"http://www.example.com:8080/") + portURL = URL.from_text("http://www.example.com:8080/") self.assertEqual(portURL.port, 8080) - self.assertEqual(portURL.to_text(), u"http://www.example.com:8080/") + self.assertEqual(portURL.to_text(), "http://www.example.com:8080/") def test_mailto(self): # type: () -> None @@ -814,8 +883,10 @@ def test_mailto(self): L{URL.from_text}/L{URL.to_text} round-trips cleanly for a C{mailto:} URL representing an email address. """ - self.assertEqual(URL.from_text(u"mailto:user@example.com").to_text(), - u"mailto:user@example.com") + self.assertEqual( + URL.from_text("mailto:user@example.com").to_text(), + "mailto:user@example.com", + ) def test_httpWithoutHost(self): # type: () -> None @@ -823,11 +894,11 @@ def test_httpWithoutHost(self): An HTTP URL without a hostname, but with a path, should also round-trip cleanly. """ - without_host = URL.from_text(u"http:relative-path") - self.assertEqual(without_host.host, u'') - self.assertEqual(without_host.path, (u'relative-path',)) + without_host = URL.from_text("http:relative-path") + self.assertEqual(without_host.host, "") + self.assertEqual(without_host.path, ("relative-path",)) self.assertEqual(without_host.uses_netloc, False) - self.assertEqual(without_host.to_text(), u"http:relative-path") + self.assertEqual(without_host.to_text(), "http:relative-path") def test_queryIterable(self): # type: () -> None @@ -836,10 +907,10 @@ def test_queryIterable(self): argument is converted into an N-tuple of 2-tuples, sensibly handling dictionaries. """ - expected = (('alpha', 'beta'),) - url = URL(query=[('alpha', 'beta')]) + expected = (("alpha", "beta"),) + url = URL(query=[("alpha", "beta")]) self.assertEqual(url.query, expected) - url = URL(query={'alpha': 'beta'}) + url = URL(query={"alpha": "beta"}) self.assertEqual(url.query, expected) def test_pathIterable(self): @@ -848,8 +919,8 @@ def test_pathIterable(self): When a L{URL} is created with a C{path} argument, the C{path} is converted into a tuple. """ - url = URL(path=['hello', 'world']) - self.assertEqual(url.path, ('hello', 'world')) + url = URL(path=["hello", "world"]) + self.assertEqual(url.path, ("hello", "world")) def test_invalidArguments(self): # type: () -> None @@ -862,6 +933,7 @@ def test_invalidArguments(self): bad data crops up in a method call long after the code that called the constructor is off the stack. """ + class Unexpected(object): def __str__(self): # type: () -> str @@ -875,10 +947,12 @@ def __repr__(self): def assertRaised(raised, expectation, name): # type: (Any, Text, Text) -> None - self.assertEqual(str(raised.exception), - "expected {0} for {1}, got {2}".format( - expectation, - name, "")) + self.assertEqual( + str(raised.exception), + "expected {0} for {1}, got {2}".format( + expectation, name, "" + ), + ) def check(param, expectation=defaultExpectation): # type: (Any, str) -> None @@ -900,13 +974,14 @@ def check(param, expectation=defaultExpectation): assertRaised(raised, defaultExpectation, "path segment") with self.assertRaises(TypeError) as raised: - URL(query=[(u"name", cast(Text, Unexpected()))]) + URL(query=[("name", cast(Text, Unexpected()))]) - assertRaised(raised, defaultExpectation + " or NoneType", - "query parameter value") + assertRaised( + raised, defaultExpectation + " or NoneType", "query parameter value" + ) with self.assertRaises(TypeError) as raised: - URL(query=[(cast(Text, Unexpected()), u"value")]) + URL(query=[(cast(Text, Unexpected()), "value")]) assertRaised(raised, defaultExpectation, "query parameter name") # No custom error message for this one, just want to make sure @@ -916,10 +991,10 @@ def check(param, expectation=defaultExpectation): URL(query=[cast(Tuple[Text, Text], Unexpected())]) with self.assertRaises(ValueError): - URL(query=[cast(Tuple[Text, Text], ('k', 'v', 'vv'))]) + URL(query=[cast(Tuple[Text, Text], ("k", "v", "vv"))]) with self.assertRaises(ValueError): - URL(query=[cast(Tuple[Text, Text], ('k',))]) + URL(query=[cast(Tuple[Text, Text], ("k",))]) url = URL.from_text("https://valid.example.com/") with self.assertRaises(TypeError) as raised: @@ -940,168 +1015,175 @@ def test_technicallyTextIsIterableBut(self): you want. """ with self.assertRaises(TypeError) as raised: - URL(path='foo') + URL(path="foo") self.assertEqual( str(raised.exception), - "expected iterable of text for path, not: {0}".format(repr('foo')) + "expected iterable of text for path, not: {0}".format(repr("foo")), ) def test_netloc(self): # type: () -> None - url = URL(scheme='https') + url = URL(scheme="https") self.assertEqual(url.uses_netloc, True) - self.assertEqual(url.to_text(), u'https://') + self.assertEqual(url.to_text(), "https://") # scheme, no host, no path, no netloc hack - self.assertEqual(URL.from_text('https:').uses_netloc, False) + self.assertEqual(URL.from_text("https:").uses_netloc, False) # scheme, no host, absolute path, no netloc hack - self.assertEqual(URL.from_text('https:/').uses_netloc, False) + self.assertEqual(URL.from_text("https:/").uses_netloc, False) # scheme, no host, no path, netloc hack to indicate :// syntax - self.assertEqual(URL.from_text('https://').uses_netloc, True) + self.assertEqual(URL.from_text("https://").uses_netloc, True) - url = URL(scheme='https', uses_netloc=False) + url = URL(scheme="https", uses_netloc=False) self.assertEqual(url.uses_netloc, False) - self.assertEqual(url.to_text(), u'https:') + self.assertEqual(url.to_text(), "https:") - url = URL(scheme='git+https') + url = URL(scheme="git+https") self.assertEqual(url.uses_netloc, True) - self.assertEqual(url.to_text(), u'git+https://') + self.assertEqual(url.to_text(), "git+https://") - url = URL(scheme='mailto') + url = URL(scheme="mailto") self.assertEqual(url.uses_netloc, False) - self.assertEqual(url.to_text(), u'mailto:') + self.assertEqual(url.to_text(), "mailto:") - url = URL(scheme='ztp') + url = URL(scheme="ztp") self.assertEqual(url.uses_netloc, None) - self.assertEqual(url.to_text(), u'ztp:') + self.assertEqual(url.to_text(), "ztp:") - url = URL.from_text('ztp://test.com') + url = URL.from_text("ztp://test.com") self.assertEqual(url.uses_netloc, True) - url = URL.from_text('ztp:test:com') + url = URL.from_text("ztp:test:com") self.assertEqual(url.uses_netloc, False) def test_ipv6_with_port(self): # type: () -> None - t = 'https://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:80/' + t = "https://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:80/" url = URL.from_text(t) - assert url.host == '2001:0db8:85a3:0000:0000:8a2e:0370:7334' + assert url.host == "2001:0db8:85a3:0000:0000:8a2e:0370:7334" assert url.port == 80 assert SCHEME_PORT_MAP[url.scheme] != url.port def test_basic(self): # type: () -> None - text = 'https://user:pass@example.com/path/to/here?k=v#nice' + text = "https://user:pass@example.com/path/to/here?k=v#nice" url = URL.from_text(text) - assert url.scheme == 'https' - assert url.userinfo == 'user:pass' - assert url.host == 'example.com' - assert url.path == ('path', 'to', 'here') - assert url.fragment == 'nice' + assert url.scheme == "https" + assert url.userinfo == "user:pass" + assert url.host == "example.com" + assert url.path == ("path", "to", "here") + assert url.fragment == "nice" - text = 'https://user:pass@127.0.0.1/path/to/here?k=v#nice' + text = "https://user:pass@127.0.0.1/path/to/here?k=v#nice" url = URL.from_text(text) - assert url.scheme == 'https' - assert url.userinfo == 'user:pass' - assert url.host == '127.0.0.1' - assert url.path == ('path', 'to', 'here') + assert url.scheme == "https" + assert url.userinfo == "user:pass" + assert url.host == "127.0.0.1" + assert url.path == ("path", "to", "here") - text = 'https://user:pass@[::1]/path/to/here?k=v#nice' + text = "https://user:pass@[::1]/path/to/here?k=v#nice" url = URL.from_text(text) - assert url.scheme == 'https' - assert url.userinfo == 'user:pass' - assert url.host == '::1' - assert url.path == ('path', 'to', 'here') + assert url.scheme == "https" + assert url.userinfo == "user:pass" + assert url.host == "::1" + assert url.path == ("path", "to", "here") def test_invalid_url(self): # type: () -> None - self.assertRaises(URLParseError, URL.from_text, '#\n\n') + self.assertRaises(URLParseError, URL.from_text, "#\n\n") def test_invalid_authority_url(self): # type: () -> None - self.assertRaises(URLParseError, URL.from_text, 'http://abc:\n\n/#') + self.assertRaises(URLParseError, URL.from_text, "http://abc:\n\n/#") def test_invalid_ipv6(self): # type: () -> None - invalid_ipv6_ips = ['2001::0234:C1ab::A0:aabc:003F', - '2001::1::3F', - ':', - '::::', - '::256.0.0.1'] + invalid_ipv6_ips = [ + "2001::0234:C1ab::A0:aabc:003F", + "2001::1::3F", + ":", + "::::", + "::256.0.0.1", + ] for ip in invalid_ipv6_ips: - url_text = 'http://[' + ip + ']' + url_text = "http://[" + ip + "]" self.assertRaises(socket.error, inet_pton, socket.AF_INET6, ip) self.assertRaises(URLParseError, URL.from_text, url_text) def test_invalid_port(self): # type: () -> None + self.assertRaises(URLParseError, URL.from_text, "ftp://portmouth:smash") self.assertRaises( - URLParseError, URL.from_text, 'ftp://portmouth:smash' + ValueError, + URL.from_text, + "http://reader.googlewebsite.com:neverforget", ) - self.assertRaises(ValueError, URL.from_text, - 'http://reader.googlewebsite.com:neverforget') def test_idna(self): # type: () -> None - u1 = URL.from_text('http://bücher.ch') - self.assertEqual(u1.host, 'bücher.ch') - self.assertEqual(u1.to_text(), 'http://bücher.ch') - self.assertEqual(u1.to_uri().to_text(), 'http://xn--bcher-kva.ch') + u1 = URL.from_text("http://bücher.ch") + self.assertEqual(u1.host, "bücher.ch") + self.assertEqual(u1.to_text(), "http://bücher.ch") + self.assertEqual(u1.to_uri().to_text(), "http://xn--bcher-kva.ch") - u2 = URL.from_text('https://xn--bcher-kva.ch') - self.assertEqual(u2.host, 'xn--bcher-kva.ch') - self.assertEqual(u2.to_text(), 'https://xn--bcher-kva.ch') - self.assertEqual(u2.to_iri().to_text(), u'https://bücher.ch') + u2 = URL.from_text("https://xn--bcher-kva.ch") + self.assertEqual(u2.host, "xn--bcher-kva.ch") + self.assertEqual(u2.to_text(), "https://xn--bcher-kva.ch") + self.assertEqual(u2.to_iri().to_text(), "https://bücher.ch") def test_netloc_slashes(self): # type: () -> None # basic sanity checks - url = URL.from_text('mailto:mahmoud@hatnote.com') - self.assertEqual(url.scheme, 'mailto') - self.assertEqual(url.to_text(), 'mailto:mahmoud@hatnote.com') + url = URL.from_text("mailto:mahmoud@hatnote.com") + self.assertEqual(url.scheme, "mailto") + self.assertEqual(url.to_text(), "mailto:mahmoud@hatnote.com") - url = URL.from_text('http://hatnote.com') - self.assertEqual(url.scheme, 'http') - self.assertEqual(url.to_text(), 'http://hatnote.com') + url = URL.from_text("http://hatnote.com") + self.assertEqual(url.scheme, "http") + self.assertEqual(url.to_text(), "http://hatnote.com") # test that unrecognized schemes stay consistent with '//' - url = URL.from_text('newscheme:a:b:c') - self.assertEqual(url.scheme, 'newscheme') - self.assertEqual(url.to_text(), 'newscheme:a:b:c') + url = URL.from_text("newscheme:a:b:c") + self.assertEqual(url.scheme, "newscheme") + self.assertEqual(url.to_text(), "newscheme:a:b:c") - url = URL.from_text('newerscheme://a/b/c') - self.assertEqual(url.scheme, 'newerscheme') - self.assertEqual(url.to_text(), 'newerscheme://a/b/c') + url = URL.from_text("newerscheme://a/b/c") + self.assertEqual(url.scheme, "newerscheme") + self.assertEqual(url.to_text(), "newerscheme://a/b/c") # test that reasonable guesses are made - url = URL.from_text('git+ftp://gitstub.biz/glyph/lefkowitz') - self.assertEqual(url.scheme, 'git+ftp') - self.assertEqual(url.to_text(), - 'git+ftp://gitstub.biz/glyph/lefkowitz') + url = URL.from_text("git+ftp://gitstub.biz/glyph/lefkowitz") + self.assertEqual(url.scheme, "git+ftp") + self.assertEqual(url.to_text(), "git+ftp://gitstub.biz/glyph/lefkowitz") - url = URL.from_text('what+mailto:freerealestate@enotuniq.org') - self.assertEqual(url.scheme, 'what+mailto') - self.assertEqual(url.to_text(), - 'what+mailto:freerealestate@enotuniq.org') + url = URL.from_text("what+mailto:freerealestate@enotuniq.org") + self.assertEqual(url.scheme, "what+mailto") + self.assertEqual( + url.to_text(), "what+mailto:freerealestate@enotuniq.org" + ) - url = URL(scheme='ztp', path=('x', 'y', 'z'), rooted=True) - self.assertEqual(url.to_text(), 'ztp:/x/y/z') + url = URL(scheme="ztp", path=("x", "y", "z"), rooted=True) + self.assertEqual(url.to_text(), "ztp:/x/y/z") # also works when the input doesn't include '//' - url = URL(scheme='git+ftp', path=('x', 'y', 'z', ''), - rooted=True, uses_netloc=True) + url = URL( + scheme="git+ftp", + path=("x", "y", "z", ""), + rooted=True, + uses_netloc=True, + ) # broken bc urlunsplit - self.assertEqual(url.to_text(), 'git+ftp:///x/y/z/') + self.assertEqual(url.to_text(), "git+ftp:///x/y/z/") # really why would this ever come up but ok - url = URL.from_text('file:///path/to/heck') - url2 = url.replace(scheme='mailto') - self.assertEqual(url2.to_text(), 'mailto:/path/to/heck') + url = URL.from_text("file:///path/to/heck") + url2 = url.replace(scheme="mailto") + self.assertEqual(url2.to_text(), "mailto:/path/to/heck") - url_text = 'unregisteredscheme:///a/b/c' + url_text = "unregisteredscheme:///a/b/c" url = URL.from_text(url_text) no_netloc_url = url.replace(uses_netloc=False) - self.assertEqual(no_netloc_url.to_text(), 'unregisteredscheme:/a/b/c') + self.assertEqual(no_netloc_url.to_text(), "unregisteredscheme:/a/b/c") netloc_url = url.replace(uses_netloc=True) self.assertEqual(netloc_url.to_text(), url_text) @@ -1113,10 +1195,10 @@ def test_rooted_to_relative(self): On host-relative URLs, the C{rooted} flag can be updated to indicate that the path should no longer be treated as absolute. """ - a = URL(path=['hello']) - self.assertEqual(a.to_text(), 'hello') + a = URL(path=["hello"]) + self.assertEqual(a.to_text(), "hello") b = a.replace(rooted=True) - self.assertEqual(b.to_text(), '/hello') + self.assertEqual(b.to_text(), "/hello") self.assertNotEqual(a, b) def test_autorooted(self): @@ -1130,13 +1212,13 @@ def test_autorooted(self): elided and it becomes rooted, because these cases are syntactically indistinguisable in real URL text. """ - relative_path_rooted = URL(path=['', 'foo'], rooted=False) + relative_path_rooted = URL(path=["", "foo"], rooted=False) self.assertEqual(relative_path_rooted.rooted, True) - relative_flag_rooted = URL(path=['foo'], rooted=True) + relative_flag_rooted = URL(path=["foo"], rooted=True) self.assertEqual(relative_flag_rooted.rooted, True) self.assertEqual(relative_path_rooted, relative_flag_rooted) - attempt_unrooted_absolute = URL(host="foo", path=['bar'], rooted=False) + attempt_unrooted_absolute = URL(host="foo", path=["bar"], rooted=False) normal_absolute = URL(host="foo", path=["bar"]) self.assertEqual(attempt_unrooted_absolute, normal_absolute) self.assertEqual(normal_absolute.rooted, True) @@ -1153,14 +1235,13 @@ def test_rooted_with_port_but_no_host(self): directly invoked with an explicit host or port, or because they were parsed from a string which included the literal ``://`` separator. """ - directly_constructed = URL(scheme='udp', port=4900, rooted=False) - directly_constructed_implict = URL(scheme='udp', port=4900) - directly_constructed_rooted = URL(scheme=u'udp', port=4900, - rooted=True) + directly_constructed = URL(scheme="udp", port=4900, rooted=False) + directly_constructed_implict = URL(scheme="udp", port=4900) + directly_constructed_rooted = URL(scheme="udp", port=4900, rooted=True) self.assertEqual(directly_constructed.rooted, True) self.assertEqual(directly_constructed_implict.rooted, True) self.assertEqual(directly_constructed_rooted.rooted, True) - parsed = URL.from_text('udp://:4900') + parsed = URL.from_text("udp://:4900") self.assertEqual(str(directly_constructed), str(parsed)) self.assertEqual(str(directly_constructed_implict), str(parsed)) self.assertEqual(directly_constructed.asText(), parsed.asText()) @@ -1177,37 +1258,33 @@ def test_wrong_constructor(self): URL(BASIC_URL) with self.assertRaises(ValueError): # explicitly bad scheme not allowed - URL('HTTP_____more_like_imHoTTeP') + URL("HTTP_____more_like_imHoTTeP") def test_encoded_userinfo(self): # type: () -> None - url = URL.from_text('http://user:pass@example.com') - assert url.userinfo == 'user:pass' - url = url.replace(userinfo='us%20her:pass') + url = URL.from_text("http://user:pass@example.com") + assert url.userinfo == "user:pass" + url = url.replace(userinfo="us%20her:pass") iri = url.to_iri() assert ( - iri.to_text(with_password=True) == - 'http://us her:pass@example.com' + iri.to_text(with_password=True) == "http://us her:pass@example.com" ) + assert iri.to_text(with_password=False) == "http://us her:@example.com" assert ( - iri.to_text(with_password=False) == - 'http://us her:@example.com' - ) - assert ( - iri.to_uri().to_text(with_password=True) == - 'http://us%20her:pass@example.com' + iri.to_uri().to_text(with_password=True) + == "http://us%20her:pass@example.com" ) def test_hash(self): # type: () -> None url_map = {} - url1 = URL.from_text('http://blog.hatnote.com/ask?utm_source=geocity') + url1 = URL.from_text("http://blog.hatnote.com/ask?utm_source=geocity") assert hash(url1) == hash(url1) # sanity url_map[url1] = 1 - url2 = URL.from_text('http://blog.hatnote.com/ask') - url2 = url2.set('utm_source', 'geocity') + url2 = URL.from_text("http://blog.hatnote.com/ask") + url2 = url2.set("utm_source", "geocity") url_map[url2] = 2 @@ -1223,26 +1300,26 @@ def test_dir(self): assert len(res) > 15 # twisted compat - assert 'fromText' not in res - assert 'asText' not in res - assert 'asURI' not in res - assert 'asIRI' not in res + assert "fromText" not in res + assert "asText" not in res + assert "asURI" not in res + assert "asIRI" not in res def test_twisted_compat(self): # type: () -> None - url = URL.fromText(u'http://example.com/a%20té%C3%A9st') - assert url.asText() == 'http://example.com/a%20té%C3%A9st' - assert url.asURI().asText() == 'http://example.com/a%20t%C3%A9%C3%A9st' + url = URL.fromText("http://example.com/a%20té%C3%A9st") + assert url.asText() == "http://example.com/a%20té%C3%A9st" + assert url.asURI().asText() == "http://example.com/a%20t%C3%A9%C3%A9st" # TODO: assert url.asIRI().asText() == u'http://example.com/a%20téést' def test_set_ordering(self): # type: () -> None # TODO - url = URL.from_text('http://example.com/?a=b&c') - url = url.set(u'x', u'x') - url = url.add(u'x', u'y') - assert url.to_text() == u'http://example.com/?a=b&x=x&c&x=y' + url = URL.from_text("http://example.com/?a=b&c") + url = url.set("x", "x") + url = url.add("x", "y") + assert url.to_text() == "http://example.com/?a=b&x=x&c&x=y" # Would expect: # assert url.to_text() == u'http://example.com/?a=b&c&x=x&x=y' @@ -1260,7 +1337,7 @@ def test_schemeless_path(self): # test that colons are ok past the first segment u4 = URL.from_text("first-segment/urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob") u5 = u4.to_iri() - assert u5.to_text() == u'first-segment/urn:ietf:wg:oauth:2.0:oob' + assert u5.to_text() == "first-segment/urn:ietf:wg:oauth:2.0:oob" u6 = URL.from_text(u5.to_text()).to_uri() assert u5 == u6 # colons stay decoded bc they're not in the first seg @@ -1268,7 +1345,7 @@ def test_schemeless_path(self): def test_emoji_domain(self): # type: () -> None "See issue #7, affecting only narrow builds (2.6-3.3)" - url = URL.from_text('https://xn--vi8hiv.ws') + url = URL.from_text("https://xn--vi8hiv.ws") iri = url.to_iri() iri.to_text() # as long as we don't get ValueErrors, we're good @@ -1276,118 +1353,120 @@ def test_emoji_domain(self): def test_delim_in_param(self): # type: () -> None "Per issue #6 and #8" - self.assertRaises(ValueError, URL, scheme=u'http', host=u'a/c') - self.assertRaises(ValueError, URL, path=(u"?",)) - self.assertRaises(ValueError, URL, path=(u"#",)) - self.assertRaises(ValueError, URL, query=((u"&", "test"))) + self.assertRaises(ValueError, URL, scheme="http", host="a/c") + self.assertRaises(ValueError, URL, path=("?",)) + self.assertRaises(ValueError, URL, path=("#",)) + self.assertRaises(ValueError, URL, query=(("&", "test"))) def test_empty_paths_eq(self): # type: () -> None - u1 = URL.from_text('http://example.com/') - u2 = URL.from_text('http://example.com') + u1 = URL.from_text("http://example.com/") + u2 = URL.from_text("http://example.com") assert u1 == u2 - u1 = URL.from_text('http://example.com') - u2 = URL.from_text('http://example.com') + u1 = URL.from_text("http://example.com") + u2 = URL.from_text("http://example.com") assert u1 == u2 - u1 = URL.from_text('http://example.com') - u2 = URL.from_text('http://example.com/') + u1 = URL.from_text("http://example.com") + u2 = URL.from_text("http://example.com/") assert u1 == u2 - u1 = URL.from_text('http://example.com/') - u2 = URL.from_text('http://example.com/') + u1 = URL.from_text("http://example.com/") + u2 = URL.from_text("http://example.com/") assert u1 == u2 def test_from_text_type(self): # type: () -> None - assert URL.from_text(u'#ok').fragment == u'ok' # sanity - self.assertRaises(TypeError, URL.from_text, b'bytes://x.y.z') + assert URL.from_text("#ok").fragment == "ok" # sanity + self.assertRaises(TypeError, URL.from_text, b"bytes://x.y.z") self.assertRaises(TypeError, URL.from_text, object()) def test_from_text_bad_authority(self): # type: () -> None # bad ipv6 brackets - self.assertRaises(URLParseError, URL.from_text, 'http://[::1/') - self.assertRaises(URLParseError, URL.from_text, 'http://::1]/') - self.assertRaises(URLParseError, URL.from_text, 'http://[[::1]/') - self.assertRaises(URLParseError, URL.from_text, 'http://[::1]]/') + self.assertRaises(URLParseError, URL.from_text, "http://[::1/") + self.assertRaises(URLParseError, URL.from_text, "http://::1]/") + self.assertRaises(URLParseError, URL.from_text, "http://[[::1]/") + self.assertRaises(URLParseError, URL.from_text, "http://[::1]]/") # empty port - self.assertRaises(URLParseError, URL.from_text, 'http://127.0.0.1:') + self.assertRaises(URLParseError, URL.from_text, "http://127.0.0.1:") # non-integer port - self.assertRaises(URLParseError, URL.from_text, 'http://127.0.0.1:hi') + self.assertRaises(URLParseError, URL.from_text, "http://127.0.0.1:hi") # extra port colon (makes for an invalid host) - self.assertRaises(URLParseError, URL.from_text, 'http://127.0.0.1::80') + self.assertRaises(URLParseError, URL.from_text, "http://127.0.0.1::80") def test_normalize(self): # type: () -> None - url = URL.from_text('HTTP://Example.com/A%61/./../A%61?B%62=C%63#D%64') - assert url.get('Bb') == [] - assert url.get('B%62') == ['C%63'] + url = URL.from_text("HTTP://Example.com/A%61/./../A%61?B%62=C%63#D%64") + assert url.get("Bb") == [] + assert url.get("B%62") == ["C%63"] assert len(url.path) == 4 # test that most expected normalizations happen norm_url = url.normalize() - assert norm_url.scheme == 'http' - assert norm_url.host == 'example.com' - assert norm_url.path == ('Aa',) - assert norm_url.get('Bb') == ['Cc'] - assert norm_url.fragment == 'Dd' - assert norm_url.to_text() == 'http://example.com/Aa?Bb=Cc#Dd' + assert norm_url.scheme == "http" + assert norm_url.host == "example.com" + assert norm_url.path == ("Aa",) + assert norm_url.get("Bb") == ["Cc"] + assert norm_url.fragment == "Dd" + assert norm_url.to_text() == "http://example.com/Aa?Bb=Cc#Dd" # test that flags work - noop_norm_url = url.normalize(scheme=False, host=False, - path=False, query=False, fragment=False) + noop_norm_url = url.normalize( + scheme=False, host=False, path=False, query=False, fragment=False + ) assert noop_norm_url == url # test that empty paths get at least one slash - slashless_url = URL.from_text('http://example.io') + slashless_url = URL.from_text("http://example.io") slashful_url = slashless_url.normalize() - assert slashful_url.to_text() == 'http://example.io/' + assert slashful_url.to_text() == "http://example.io/" # test case normalization for percent encoding - delimited_url = URL.from_text('/a%2fb/cd%3f?k%3d=v%23#test') + delimited_url = URL.from_text("/a%2fb/cd%3f?k%3d=v%23#test") norm_delimited_url = delimited_url.normalize() - assert norm_delimited_url.to_text() == '/a%2Fb/cd%3F?k%3D=v%23#test' + assert norm_delimited_url.to_text() == "/a%2Fb/cd%3F?k%3D=v%23#test" # test invalid percent encoding during normalize assert ( - URL(path=('', '%te%sts')).normalize(percents=False).to_text() == - '/%te%sts' - ) - assert ( - URL(path=('', '%te%sts')).normalize().to_text() == '/%25te%25sts' + URL(path=("", "%te%sts")).normalize(percents=False).to_text() + == "/%te%sts" ) + assert URL(path=("", "%te%sts")).normalize().to_text() == "/%25te%25sts" percenty_url = URL( - scheme='ftp', path=['%%%', '%a%b'], query=[('%', '%%')], - fragment='%', userinfo='%:%', + scheme="ftp", + path=["%%%", "%a%b"], + query=[("%", "%%")], + fragment="%", + userinfo="%:%", ) assert ( - percenty_url.to_text(with_password=True) == - 'ftp://%:%@/%%%/%a%b?%=%%#%' + percenty_url.to_text(with_password=True) + == "ftp://%:%@/%%%/%a%b?%=%%#%" ) assert ( - percenty_url.normalize().to_text(with_password=True) == - 'ftp://%25:%25@/%25%25%25/%25a%25b?%25=%25%25#%25' + percenty_url.normalize().to_text(with_password=True) + == "ftp://%25:%25@/%25%25%25/%25a%25b?%25=%25%25#%25" ) def test_str(self): # type: () -> None # see also issue #49 - text = u'http://example.com/á/y%20a%20y/?b=%25' + text = "http://example.com/á/y%20a%20y/?b=%25" url = URL.from_text(text) assert unicode(url) == text - assert bytes(url) == b'http://example.com/%C3%A1/y%20a%20y/?b=%25' + assert bytes(url) == b"http://example.com/%C3%A1/y%20a%20y/?b=%25" if PY2: assert isinstance(str(url), bytes) @@ -1398,18 +1477,17 @@ def test_str(self): def test_idna_corners(self): # type: () -> None - url = URL.from_text(u'http://abé.com/') - assert url.to_iri().host == u'abé.com' - assert url.to_uri().host == u'xn--ab-cja.com' + url = URL.from_text("http://abé.com/") + assert url.to_iri().host == "abé.com" + assert url.to_uri().host == "xn--ab-cja.com" url = URL.from_text("http://ドメイン.テスト.co.jp#test") - assert url.to_iri().host == u'ドメイン.テスト.co.jp' - assert url.to_uri().host == u'xn--eckwd4c7c.xn--zckzah.co.jp' + assert url.to_iri().host == "ドメイン.テスト.co.jp" + assert url.to_uri().host == "xn--eckwd4c7c.xn--zckzah.co.jp" - assert url.to_uri().get_decoded_url().host == u'ドメイン.テスト.co.jp' + assert url.to_uri().get_decoded_url().host == "ドメイン.テスト.co.jp" - text = 'http://Example.com' + text = "http://Example.com" assert ( - URL.from_text(text).to_uri().get_decoded_url().host == - 'example.com' + URL.from_text(text).to_uri().get_decoded_url().host == "example.com" ) diff --git a/tox.ini b/tox.ini index 15c1a8ed..a6fa631c 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,7 @@ [tox] envlist = - flake8, mypy # black + flake8, mypy, black test-py{26,27,34,35,36,37,38,py2,py3} coverage_report docs From c87a0a04750444c08b1f8b2c80c3484cb6c02843 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Thu, 2 Apr 2020 09:46:53 -0700 Subject: [PATCH 205/419] Apply black to setup.py also --- setup.py | 83 ++++++++++++++++++++++++++------------------------------ tox.ini | 2 +- 2 files changed, 40 insertions(+), 45 deletions(-) diff --git a/setup.py b/setup.py index fceb4fac..2f09f9cd 100644 --- a/setup.py +++ b/setup.py @@ -10,52 +10,47 @@ from setuptools import find_packages, setup -__author__ = 'Mahmoud Hashemi and Glyph Lefkowitz' -__version__ = '19.0.1dev' -__contact__ = 'mahmoud@hatnote.com' -__url__ = 'https://github.com/python-hyper/hyperlink' -__license__ = 'MIT' +__author__ = "Mahmoud Hashemi and Glyph Lefkowitz" +__version__ = "19.0.1dev" +__contact__ = "mahmoud@hatnote.com" +__url__ = "https://github.com/python-hyper/hyperlink" +__license__ = "MIT" -setup(name='hyperlink', - version=__version__, - description="A featureful, immutable, and correct URL for Python.", - long_description=__doc__, - author=__author__, - author_email=__contact__, - url=__url__, - packages=find_packages(where="src"), - package_dir={"": "src"}, - package_data=dict( - hyperlink=[ - "py.typed", - ], - ), - zip_safe=False, - license=__license__, - platforms='any', - install_requires=[ - 'idna>=2.5', - 'typing ; python_version<"3.5"', - ], - python_requires='>=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', - classifiers=[ - 'Topic :: Utilities', - 'Intended Audience :: Developers', - 'Topic :: Software Development :: Libraries', - 'Development Status :: 5 - Production/Stable', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.6', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 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', - 'Programming Language :: Python :: Implementation :: PyPy', - 'License :: OSI Approved :: MIT License', ] - ) +setup( + name="hyperlink", + version=__version__, + description="A featureful, immutable, and correct URL for Python.", + long_description=__doc__, + author=__author__, + author_email=__contact__, + url=__url__, + packages=find_packages(where="src"), + package_dir={"": "src"}, + package_data=dict(hyperlink=["py.typed",],), + zip_safe=False, + license=__license__, + platforms="any", + install_requires=["idna>=2.5", 'typing ; python_version<"3.5"',], + python_requires=">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*", + classifiers=[ + "Topic :: Utilities", + "Intended Audience :: Developers", + "Topic :: Software Development :: Libraries", + "Development Status :: 5 - Production/Stable", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 2.6", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 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", + "Programming Language :: Python :: Implementation :: PyPy", + "License :: OSI Approved :: MIT License", + ], +) """ A brief checklist for release: diff --git a/tox.ini b/tox.ini index a6fa631c..17199753 100644 --- a/tox.ini +++ b/tox.ini @@ -80,7 +80,7 @@ setenv = BLACK_LINT_ARGS=--check commands = - black {env:BLACK_LINT_ARGS:} src + black {env:BLACK_LINT_ARGS:} setup.py src [testenv:black-reformat] From ddcac260637ab11b05bf6459c7183a392c26f680 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Thu, 2 Apr 2020 10:38:01 -0700 Subject: [PATCH 206/419] Note changes since 19.0.0 --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bc4f61e..696fce59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## dev (not yet released) +* CPython 3.7 and 3.8 and PyPy3 added to test matrix +* Hyperlink now has type hints and they are now exported per + [PEP 561](https://www.python.org/dev/peps/pep-0561/). + ## 19.0.0 *(April 7, 2019)* @@ -13,7 +17,8 @@ A query parameter-centric release, with two enhancements: [#39](https://github.com/python-hyper/hyperlink/pull/39)) * `URL.remove()` now accepts *value* and *limit* parameters, allowing for removal of specific name-value pairs, as well as limiting the - number of removals. (see [#71](https://github.com/python-hyper/hyperlink/pull/71)) + number of removals. + (See [#71](https://github.com/python-hyper/hyperlink/pull/71)) ## 18.0.0 From cf08a393e98cd218a7e9c5591a26047a216721aa Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sun, 5 Apr 2020 08:28:33 -0700 Subject: [PATCH 207/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index b0ac199e..e6ba3298 100644 --- a/tox.ini +++ b/tox.ini @@ -258,7 +258,7 @@ description = build documentation basepython = {[default]basepython} deps = - Sphinx==2.4.4 + Sphinx==3.0.0 sphinx-rtd-theme==0.4.3 commands = From e882420ba63d1d97212af7af6357451229c0b105 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sun, 5 Apr 2020 20:30:46 -0700 Subject: [PATCH 208/419] [requires.io] dependency update --- tox.ini | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tox.ini b/tox.ini index e6ba3298..d2ed538a 100644 --- a/tox.ini +++ b/tox.ini @@ -2,10 +2,10 @@ envlist = flake8, mypy - test-py{26,27,34,35,36,37,38,py,py3} + test-py{26,27,34,35,36,37,38,py2,py3} coverage_report - packaging docs + packaging skip_missing_interpreters = {tty:True:False} @@ -182,12 +182,12 @@ description = generate coverage report depends = test-py{26,27,34,35,36,37,38,py,py3} -basepython = python +basepython = {[default]basepython} skip_install = True deps = - coverage==4.5.4 # rq.filter: <5 + coverage==5.0.4 setenv = {[default]setenv} @@ -265,7 +265,7 @@ commands = sphinx-build \ -b html -d "{envtmpdir}/doctrees" \ "{toxinidir}/docs" \ - "{toxworkdir}/docs/html" + "{toxworkdir}/htmldocs" [testenv:docs-auto] @@ -283,7 +283,7 @@ commands = -b html -d "{envtmpdir}/doctrees" \ --host=localhost \ "{toxinidir}/docs" \ - "{toxworkdir}/docs/html" + "{toxworkdir}/htmldocs" ## From f3866df48cffff1d66fb7b58a033baa484296cd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Mon, 6 Apr 2020 12:07:35 -0700 Subject: [PATCH 209/419] Add Glyph's text for #112. --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 696fce59..b23dfb44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ * CPython 3.7 and 3.8 and PyPy3 added to test matrix * Hyperlink now has type hints and they are now exported per [PEP 561](https://www.python.org/dev/peps/pep-0561/). +* Several bugs related to hidden state were fixed, making it so that all data + on a `URL` object (including `rooted` and `uses_netloc`) is reflected by and + consistent with its textual representation. + This does mean that sometimes these constructor arguments are ignored, if it + would create invalid or unparseable URL text. ## 19.0.0 From 81514f7ec99f1b557f6b0e38b37a3ac7c3f3f4c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Mon, 6 Apr 2020 13:06:30 -0700 Subject: [PATCH 210/419] Remove newly-redundant type info in API description text, as Sphinx adds type info to the signature now. --- src/hyperlink/_url.py | 214 ++++++++++++++++++++---------------------- 1 file changed, 104 insertions(+), 110 deletions(-) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index 244406f2..d95fc824 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -369,12 +369,12 @@ def register_scheme(text, uses_netloc=True, default_port=None): `file an issue`_! Args: - text (Text): A string representation of the scheme. + text: A string representation of the scheme. (the 'http' in 'http://hatnote.com') - uses_netloc (bool): Does the scheme support specifying a + uses_netloc: Does the scheme support specifying a network host? For instance, "http" does, "mailto" does not. Defaults to True. - default_port (Optional[int]): The default port, if any, for + default_port: The default port, if any, for netloc-using schemes. .. _file an issue: https://github.com/mahmoud/hyperlink/issues @@ -798,38 +798,36 @@ class URL(object): constructor arguments is below. Args: - scheme (Optional[Text]): The text name of the scheme. - host (Optional[Text]): The host portion of the network location - port (Optional[int]): The port part of the network location. If - ``None`` or no port is passed, the port will default to - the default port of the scheme, if it is known. See the - ``SCHEME_PORT_MAP`` and :func:`register_default_port` - for more info. - path (Iterable[Text]): A tuple of strings representing the - slash-separated parts of the path. - query (Sequence[Tuple[Text, Optional[Text]]]): The query parameters, as - a dictionary or as an sequence of key-value pairs. - fragment (Text): The fragment part of the URL. - rooted (bool): A rooted URL is one which indicates an absolute path. - This is True on any URL that includes a host, or any relative URL - that starts with a slash. - userinfo (Text): The username or colon-separated - username:password pair. - uses_netloc (Optional[bool]): Indicates whether ``://`` (the "netloc - separator") will appear to separate the scheme from the *path* in - cases where no host is present. Setting this to ``True`` is a - non-spec-compliant affordance for the common practice of having URIs - that are *not* URLs (cannot have a 'host' part) but nevertheless use - the common ``://`` idiom that most people associate with URLs; - e.g. ``message:`` URIs like ``message://message-id`` being - equivalent to ``message:message-id``. This may be inferred based on - the scheme depending on whether :func:`register_scheme` has been - used to register the scheme and should not be passed directly unless - you know the scheme works like this and you know it has not been - registered. - - All of these parts are also exposed as read-only attributes of - URL instances, along with several useful methods. + scheme: The text name of the scheme. + host: The host portion of the network location + port: The port part of the network location. If ``None`` or no port is + passed, the port will default to the default port of the scheme, if + it is known. See the ``SCHEME_PORT_MAP`` and + :func:`register_default_port` for more info. + path: A tuple of strings representing the slash-separated parts of the + path. + query: The query parameters, as a dictionary or as an sequence of + key-value pairs. + fragment: The fragment part of the URL. + rooted: A rooted URL is one which indicates an absolute path. + This is True on any URL that includes a host, or any relative URL + that starts with a slash. + userinfo: The username or colon-separated username:password pair. + uses_netloc: Indicates whether ``://`` (the "netloc separator") will + appear to separate the scheme from the *path* in cases where no + host is present. + Setting this to ``True`` is a non-spec-compliant affordance for the + common practice of having URIs that are *not* URLs (cannot have a + 'host' part) but nevertheless use the common ``://`` idiom that + most people associate with URLs; e.g. ``message:`` URIs like + ``message://message-id`` being equivalent to ``message:message-id``. + This may be inferred based on the scheme depending on whether + :func:`register_scheme` has been used to register the scheme and + should not be passed directly unless you know the scheme works like + this and you know it has not been registered. + + All of these parts are also exposed as read-only attributes of :class:`URL` + instances, along with several useful methods. .. _RFC 3986: https://tools.ietf.org/html/rfc3986 .. _RFC 3987: https://tools.ietf.org/html/rfc3987 @@ -1052,9 +1050,9 @@ def authority(self, with_password=False, **kw): u'user:pass@localhost:8080' Args: - with_password (bool): Whether the return value of this - method include the password in the URL, if it is - set. Defaults to False. + with_password: Whether the return value of this method include the + password in the URL, if it is set. + Defaults to False. Returns: Text: The authority (network location and user information) portion @@ -1145,32 +1143,29 @@ def replace( the value on the current URL. Args: - scheme (Optional[Text]): The text name of the scheme. - host (Optional[Text]): The host portion of the network location. - path (Iterable[Text]): A tuple of strings representing the - slash-separated parts of the path. - query (Sequence[Tuple[Text, Optional[Text]]]): The query - parameters, as a dictionary or as an sequence of key-value - pairs. - fragment (Text): The fragment part of the URL. - port (Optional[int]): The port part of the network location. - rooted (Optional[bool]): Whether or not the path begins with a - slash. - userinfo (Text): The username or colon-separated username:password - pair. - uses_netloc (bool): Indicates whether ``://`` (the "netloc - separator") will appear to separate the scheme from the *path* - in cases where no host is present. Setting this to ``True`` is - a non-spec-compliant affordance for the common practice of - having URIs that are *not* URLs (cannot have a 'host' part) but - nevertheless use the common ``://`` idiom that most people - associate with URLs; e.g. ``message:`` URIs like - ``message://message-id`` being equivalent to - ``message:message-id``. This may be inferred based on the - scheme depending on whether :func:`register_scheme` has been - used to register the scheme and should not be passed directly - unless you know the scheme works like this and you know it has - not been registered. + scheme: The text name of the scheme. + host: The host portion of the network location. + path: A tuple of strings representing the slash-separated parts of + the path. + query: The query parameters, as a dictionary or as an sequence of + key-value pairs. + fragment: The fragment part of the URL. + port: The port part of the network location. + rooted: Whether or not the path begins with a slash. + userinfo: The username or colon-separated username:password pair. + uses_netloc: Indicates whether ``://`` (the "netloc separator") + will appear to separate the scheme from the *path* in cases + where no host is present. + Setting this to ``True`` is a non-spec-compliant affordance for + the common practice of having URIs that are *not* URLs (cannot + have a 'host' part) but nevertheless use the common ``://`` + idiom that most people associate with URLs; e.g. ``message:`` + URIs like ``message://message-id`` being equivalent to + ``message:message-id``. + This may be inferred based on the scheme depending on whether + :func:`register_scheme` has been used to register the scheme + and should not be passed directly unless you know the scheme + works like this and you know it has not been registered. Returns: URL: A copy of the current :class:`URL`, with new values for @@ -1210,7 +1205,7 @@ def from_text(cls, text): sure to decode those bytestrings. Args: - text (Text): A valid URL string. + text: A valid URL string. Returns: URL: The structured object version of the parsed string. @@ -1301,15 +1296,14 @@ def normalize(self, scheme=True, host=True, path=True, query=True, name. Args: - scheme (bool): Convert the scheme to lowercase - host (bool): Convert the host to lowercase - path (bool): Normalize the path (see above for details) - query (bool): Normalize the query string - fragment (bool): Normalize the fragment - userinfo (bool): Normalize the userinfo - percents (bool): Encode isolated percent signs for any - percent-encoded fields which are being normalized - (defaults to True). + scheme: Convert the scheme to lowercase + host: Convert the host to lowercase + path: Normalize the path (see above for details) + query: Normalize the query string + fragment: Normalize the fragment + userinfo: Normalize the userinfo + percents: Encode isolated percent signs for any percent-encoded + fields which are being normalized (defaults to `True`). >>> url = URL.from_text(u'Http://example.COM/a/../b/./c%2f?%61%') >>> print(url.normalize().to_text()) @@ -1365,9 +1359,9 @@ def child(self, *segments): u'http://localhost/a/b/c/d?x=y' Args: - segments (Text): Additional parts to be joined and added to - the path, like :func:`os.path.join`. Special characters - in segments will be percent encoded. + segments: Additional parts to be joined and added to the path, like + :func:`os.path.join`. Special characters in segments will be + percent encoded. Returns: URL: A copy of the current URL with the extra path segments. @@ -1390,7 +1384,7 @@ def sibling(self, segment): sibling of this URL path. Args: - segment (Text): A single path segment. + segment: A single path segment. Returns: URL: A copy of the current URL with the last path segment @@ -1671,11 +1665,11 @@ def add(self, name, value=None): URL.from_text(u'https://example.com/?x=y&x=z') Args: - name (Text): The name of the query parameter to add. + name: The name of the query parameter to add. The part before the ``=``. - value (Optional[Text]): The value of the query parameter to add. - The part after the ``=``. Defaults to ``None``, meaning no - value. + value: The value of the query parameter to add. + The part after the ``=``. + Defaults to ``None``, meaning no value. Returns: URL: A new :class:`URL` instance with the parameter added. @@ -1694,11 +1688,11 @@ def set(self, name, value=None): URL.from_text(u'https://example.com/?x=z') Args: - name (Text): The name of the query parameter to set. + name: The name of the query parameter to set. The part before the ``=``. - value (Optional[Text]): The value of the query parameter to set. - The part after the ``=``. Defaults to ``None``, meaning no - value. + value: The value of the query parameter to set. + The part after the ``=``. + Defaults to ``None``, meaning no value. Returns: URL: A new :class:`URL` instance with the parameter set. @@ -1724,7 +1718,7 @@ def get(self, name): list is always returned, and this method raises no exceptions. Args: - name (Text): The name of the query parameter to get. + name: The name of the query parameter to get. Returns: List[Optional[Text]]: A list of all the values associated with the @@ -1745,12 +1739,11 @@ def remove( parameter is not already set. Args: - name (Text): The name of the query parameter to remove. - value (Text): Optional value to additionally filter on. + name: The name of the query parameter to remove. + value: Optional value to additionally filter on. Setting this removes query parameters which match both name and value. - limit (Optional[int]): Optional maximum number of parameters to - remove. + limit: Optional maximum number of parameters to remove. Returns: URL: A new :class:`URL` instance with the parameter removed. @@ -1797,9 +1790,10 @@ class DecodedURL(object): special characters encoded with codecs other than UTF-8. Args: - url (URL): A :class:`URL` object to wrap. - lazy (bool): Set to True to avoid pre-decode all parts of the URL to - check for validity. Defaults to False. + url: A :class:`URL` object to wrap. + lazy: Set to True to avoid pre-decode all parts of the URL to check for + validity. + Defaults to False. """ def __init__(self, url, lazy=False): @@ -1818,9 +1812,10 @@ def from_text(cls, text, lazy=False): Make a `DecodedURL` instance from any text string containing a URL. Args: - text (Text): Text containing the URL - lazy (bool): Whether to pre-decode all parts of the URL to check for - validity. Defaults to True. + text: Text containing the URL + lazy: Whether to pre-decode all parts of the URL to check for + validity. + Defaults to True. """ _url = URL.from_text(text) return cls(_url, lazy=lazy) @@ -2131,19 +2126,18 @@ def parse(url, decoded=True, lazy=False): """Automatically turn text into a structured URL object. Args: - url (Text): A string representation of a URL. - - decoded (bool): Whether or not to return a :class:`DecodedURL`, - which automatically handles all - encoding/decoding/quoting/unquoting for all the various - accessors of parts of the URL, or an :class:`EncodedURL`, - which has the same API, but requires handling of special - characters for different parts of the URL. - - lazy (bool): In the case of `decoded=True`, this controls - whether the URL is decoded immediately or as accessed. The - default, `lazy=False`, checks all encoded parts of the URL - for decodability. + url: A string representation of a URL. + + decoded: Whether or not to return a :class:`DecodedURL`, which + automatically handles all encoding/decoding/quoting/unquoting for + all the various accessors of parts of the URL, or an + :class:`EncodedURL`, which has the same API, but requires handling + of special characters for different parts of the URL. + + lazy: In the case of `decoded=True`, this controls whether the URL is + decoded immediately or as accessed. + The default, `lazy=False`, checks all encoded parts of the URL for + decodability. """ enc_url = EncodedURL.from_text(url) if not decoded: From e5fe8d41a15a178a982147686954a7f6b688dbdf Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Mon, 6 Apr 2020 15:23:58 -0700 Subject: [PATCH 211/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 704ac425..d2ed538a 100644 --- a/tox.ini +++ b/tox.ini @@ -187,7 +187,7 @@ basepython = {[default]basepython} skip_install = True deps = - coverage==4.5.4 # rq.filter: <5 # coverage 5.0 drops Python 3.4 support + coverage==5.0.4 setenv = {[default]setenv} From a06eedb69c47987b25ef79a91e6cd147d7992c51 Mon Sep 17 00:00:00 2001 From: Mahmoud Hashemi Date: Fri, 10 Apr 2020 00:02:34 -0700 Subject: [PATCH 212/419] adding initial draft of parse and DecodedURL API docs. Also added default argument to DecodedURL() to ease programmatic construction. Fixes #125 --- docs/api.rst | 36 +++++++++++++++++++++++++++++++++-- docs/index.rst | 6 +++--- src/hyperlink/_url.py | 44 ++++++++++++++++++++++++++++++++----------- 3 files changed, 70 insertions(+), 16 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index e5473f84..93ebb782 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -5,11 +5,43 @@ Hyperlink API .. automodule:: hyperlink._url +.. contents:: + :local: + Creation -------- -Before you can work with URLs, you must create URLs. There are two -ways to create URLs, from parts and from text. +Before you can work with URLs, you must create URLs. + +Parsing Text +^^^^^^^^^^^^ + +If you already have a textual URL, the easiest way to get URL objects +is with the :func:`parse()` function: + +.. autofunction:: hyperlink.parse + +By default, :func:`~hyperlink.parse()` returns an instance of +:class:`DecodedURL`, a URL type that handles all encoding for you, by +wrapping the lower-level :class:`URL`. + +DecodedURL +^^^^^^^^^^ + +.. autoclass:: hyperlink.DecodedURL +.. automethod:: hyperlink.DecodedURL.from_text + +The Encoded URL +^^^^^^^^^^^^^^^ + +The lower-level :class:`URL` looks very similar to the +:class:`DecodedURL`, but does not handle all encoding cases for +you. Use with caution. + +.. note:: + + :class:`URL` is also available as an alias, + ``hyperlink.EncodedURL`` for more explicit usage. .. autoclass:: hyperlink.URL .. automethod:: hyperlink.URL.from_text diff --git a/docs/index.rst b/docs/index.rst index 57c78a87..cfc0c47d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -39,9 +39,9 @@ library. The easiest way to install is with pip:: Then, URLs are just an import away:: - from hyperlink import URL + import hyperlink - url = URL.from_text(u'http://github.com/python-hyper/hyperlink?utm_source=readthedocs') + url = hyperlink.parse(u'http://github.com/python-hyper/hyperlink?utm_source=readthedocs') better_url = url.replace(scheme=u'https', port=443) org_url = better_url.click(u'.') @@ -49,7 +49,7 @@ Then, URLs are just an import away:: print(org_url.to_text()) # prints: https://github.com/python-hyper/ - print(better_url.get(u'utm_source')) + print(better_url.get(u'utm_source')[0]) # prints: readthedocs See :ref:`the API docs ` for more usage examples. diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index f39525d3..b6bdaf0a 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -3,16 +3,18 @@ Usage is straightforward:: - >>> from hyperlink import URL - >>> url = URL.from_text(u'http://github.com/mahmoud/hyperlink?utm_source=docs') + >>> import hyperlink + >>> url = hyperlink.parse(u'http://github.com/mahmoud/hyperlink?utm_source=docs') >>> url.host u'github.com' >>> secure_url = url.replace(scheme=u'https') >>> secure_url.get('utm_source')[0] u'docs' -As seen here, the API revolves around the lightweight and immutable -:class:`URL` type, documented below. +Hyperlink's API centers on the :class:`DecodedURL` type, which wraps +the lower-level :class:`URL`, both of which can be returned by the +:func:`parse()` convenience function. + """ # noqa: E501 import re @@ -1743,13 +1745,23 @@ def remove( EncodedURL = URL # An alias better describing what the URL really is +_EMPTY_URL = URL() class DecodedURL(object): - """DecodedURL is a type meant to act as a higher-level interface to - the URL. It is the `unicode` to URL's `bytes`. `DecodedURL` has - almost exactly the same API as `URL`, but everything going in and - out is in its maximally decoded state. All percent decoding is - handled automatically. + """:class:`DecodedURL` is a type designed to act as a higher-level + interface to :class:`URL` and the recommended type for most + operations. By analogy, :class:`DecodedURL` is the + :class:`unicode` to URL's :class:`bytes`. + + :class:`DecodedURL` automatically handles encoding and decoding + all its components, such that all inputs and outputs are in a + maximally-decoded state. Note that this means, for some special + cases, a URL may not "roundtrip" character-for-character, but this + is considered a good tradeoff for the safety of automatic + encoding. + + Otherwise, :class:`DecodedURL` has almost exactly the same API as + :class:`URL`. Where applicable, a UTF-8 encoding is presumed. Be advised that some interactions can raise :exc:`UnicodeEncodeErrors` and @@ -1763,8 +1775,18 @@ class DecodedURL(object): lazy (bool): Set to True to avoid pre-decode all parts of the URL to check for validity. Defaults to False. + .. note:: + + The :class:`DecodedURL` initializer takes a :class:`URL` object, + not URL components, like :class:`URL`. To programmatically + construct a :class:`DecodedURL`, you can use this pattern: + + >>> DecodedURL().replace(host='pypi.org', path=('projects', 'hyperlink').to_text() + "http://pypi.org/projects/hyperlink" + + """ - def __init__(self, url, lazy=False): + def __init__(self, url=_EMPTY_URL, lazy=False): # type: (URL, bool) -> None self._url = url if not lazy: @@ -2098,7 +2120,7 @@ def parse(url, decoded=True, lazy=False): decoded (bool): Whether or not to return a :class:`DecodedURL`, which automatically handles all encoding/decoding/quoting/unquoting for all the various - accessors of parts of the URL, or an :class:`EncodedURL`, + accessors of parts of the URL, or a :class:`URL`, which has the same API, but requires handling of special characters for different parts of the URL. From 1f57a36cbde55e091ce5fd67fe0c94794ddc11d5 Mon Sep 17 00:00:00 2001 From: Mahmoud Hashemi Date: Fri, 10 Apr 2020 00:23:40 -0700 Subject: [PATCH 213/419] switch intersphinx to py3.7 --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 0eb8cf66..b6e0155c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -76,7 +76,7 @@ pygments_style = 'sphinx' # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'python': ('https://docs.python.org/2.7', None)} +intersphinx_mapping = {'python': ('https://docs.python.org/3.7', None)} # -- Options for HTML output ---------------------------------------------- From 4afeb55fcbb760cdc3450e8fc28d2ebefd38db23 Mon Sep 17 00:00:00 2001 From: Mahmoud Hashemi Date: Fri, 10 Apr 2020 00:24:13 -0700 Subject: [PATCH 214/419] add parse doctest and 'new in' messages --- src/hyperlink/_url.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index b6bdaf0a..45a84fac 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -1748,7 +1748,8 @@ def remove( _EMPTY_URL = URL() class DecodedURL(object): - """:class:`DecodedURL` is a type designed to act as a higher-level + """ + :class:`DecodedURL` is a type designed to act as a higher-level interface to :class:`URL` and the recommended type for most operations. By analogy, :class:`DecodedURL` is the :class:`unicode` to URL's :class:`bytes`. @@ -1784,7 +1785,7 @@ class DecodedURL(object): >>> DecodedURL().replace(host='pypi.org', path=('projects', 'hyperlink').to_text() "http://pypi.org/projects/hyperlink" - + *(New in 18.0.0)* """ def __init__(self, url=_EMPTY_URL, lazy=False): # type: (URL, bool) -> None @@ -2112,10 +2113,15 @@ def __dir__(self): def parse(url, decoded=True, lazy=False): # type: (Text, bool, bool) -> Union[URL, DecodedURL] - """Automatically turn text into a structured URL object. + """ + Automatically turn text into a structured URL object. + + >>> url = parse("https://github.com/python-hyper/hyperlink") + >>> print(url.to_text()) + "https://github.com/python-hyper/hyperlink" Args: - url (Text): A string representation of a URL. + url (str): A text string representation of a URL. decoded (bool): Whether or not to return a :class:`DecodedURL`, which automatically handles all @@ -2128,6 +2134,8 @@ def parse(url, decoded=True, lazy=False): whether the URL is decoded immediately or as accessed. The default, `lazy=False`, checks all encoded parts of the URL for decodability. + + *(New in 18.0.0)* """ enc_url = EncodedURL.from_text(url) if not decoded: From 6682bd93e7536991303dc7c54d155628fde948c9 Mon Sep 17 00:00:00 2001 From: Mahmoud Hashemi Date: Fri, 10 Apr 2020 00:32:12 -0700 Subject: [PATCH 215/419] remove extra space in doctest --- src/hyperlink/_url.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index 45a84fac..10ed9228 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -1782,7 +1782,7 @@ class DecodedURL(object): not URL components, like :class:`URL`. To programmatically construct a :class:`DecodedURL`, you can use this pattern: - >>> DecodedURL().replace(host='pypi.org', path=('projects', 'hyperlink').to_text() + >>> DecodedURL().replace(host='pypi.org', path=('projects', 'hyperlink').to_text() "http://pypi.org/projects/hyperlink" *(New in 18.0.0)* From 478d9c6ceaf557ba54eccd6ecfba211cf973b3fb Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Fri, 10 Apr 2020 08:24:59 -0700 Subject: [PATCH 216/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index d2ed538a..5222951f 100644 --- a/tox.ini +++ b/tox.ini @@ -258,7 +258,7 @@ description = build documentation basepython = {[default]basepython} deps = - Sphinx==3.0.0 + Sphinx==3.0.1 sphinx-rtd-theme==0.4.3 commands = From b086352bdd923e49846e4f7e9d9373f62d8c34c2 Mon Sep 17 00:00:00 2001 From: Mahmoud Hashemi Date: Sat, 11 Apr 2020 15:36:05 -0700 Subject: [PATCH 217/419] bunch of minor doctest fixups --- src/hyperlink/_url.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index 10ed9228..90bbb61a 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -1782,8 +1782,9 @@ class DecodedURL(object): not URL components, like :class:`URL`. To programmatically construct a :class:`DecodedURL`, you can use this pattern: - >>> DecodedURL().replace(host='pypi.org', path=('projects', 'hyperlink').to_text() - "http://pypi.org/projects/hyperlink" + >>> print(DecodedURL().replace(scheme=u'https', + ... host=u'pypi.org', path=(u'projects', u'hyperlink')).to_text()) + https://pypi.org/projects/hyperlink *(New in 18.0.0)* """ @@ -2116,9 +2117,9 @@ def parse(url, decoded=True, lazy=False): """ Automatically turn text into a structured URL object. - >>> url = parse("https://github.com/python-hyper/hyperlink") + >>> url = parse(u"https://github.com/python-hyper/hyperlink") >>> print(url.to_text()) - "https://github.com/python-hyper/hyperlink" + https://github.com/python-hyper/hyperlink Args: url (str): A text string representation of a URL. From 182f83465854e30676701cc716e1f7a7f761444c Mon Sep 17 00:00:00 2001 From: Mahmoud Hashemi Date: Sat, 11 Apr 2020 16:23:01 -0700 Subject: [PATCH 218/419] fix a lint error --- src/hyperlink/_url.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index 90bbb61a..4db1acc1 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -1747,6 +1747,7 @@ def remove( _EMPTY_URL = URL() + class DecodedURL(object): """ :class:`DecodedURL` is a type designed to act as a higher-level From 2bae6f641696934eafb447c162d4905471d766fc Mon Sep 17 00:00:00 2001 From: Mahmoud Hashemi Date: Sat, 11 Apr 2020 16:24:48 -0700 Subject: [PATCH 219/419] use versionadded --- src/hyperlink/_url.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index 4db1acc1..6f7098c2 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -1787,7 +1787,7 @@ class DecodedURL(object): ... host=u'pypi.org', path=(u'projects', u'hyperlink')).to_text()) https://pypi.org/projects/hyperlink - *(New in 18.0.0)* + .. versionadded:: 18.0.0 """ def __init__(self, url=_EMPTY_URL, lazy=False): # type: (URL, bool) -> None @@ -2137,7 +2137,7 @@ def parse(url, decoded=True, lazy=False): default, `lazy=False`, checks all encoded parts of the URL for decodability. - *(New in 18.0.0)* + .. versionadded:: 18.0.0 """ enc_url = EncodedURL.from_text(url) if not decoded: From d139aaee15a95a263125c645233d3c1e632061ac Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sun, 12 Apr 2020 15:31:54 -0700 Subject: [PATCH 220/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 5222951f..8020cd3f 100644 --- a/tox.ini +++ b/tox.ini @@ -187,7 +187,7 @@ basepython = {[default]basepython} skip_install = True deps = - coverage==5.0.4 + coverage==5.1 setenv = {[default]setenv} From f53dc66663ad8d446d5f8b7867c3416477ecd045 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Tue, 14 Apr 2020 16:18:41 -0700 Subject: [PATCH 221/419] Remove W504 --- tox.ini | 3 --- 1 file changed, 3 deletions(-) diff --git a/tox.ini b/tox.ini index 17199753..a6633446 100644 --- a/tox.ini +++ b/tox.ini @@ -151,9 +151,6 @@ ignore = # line break before binary operator W503, - # line break after binary operator - W504, - # End of list (allows last item to end with trailing ',') EOL From e93b263d6f0abd80334383eeeee276e38276ba26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Tue, 14 Apr 2020 16:28:24 -0700 Subject: [PATCH 222/419] Revert this change --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 8020cd3f..8bb53241 100644 --- a/tox.ini +++ b/tox.ini @@ -187,7 +187,7 @@ basepython = {[default]basepython} skip_install = True deps = - coverage==5.1 + coverage==4.5.4 # rq.filter: <5 # coverage 5.0 drops Python 3.4 support setenv = {[default]setenv} From 8be9b27cbd8e2c547bd160d76d0a591110c70201 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Tue, 14 Apr 2020 16:46:09 -0700 Subject: [PATCH 223/419] [requires.io] dependency update --- tox.ini | 45 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/tox.ini b/tox.ini index 8bb53241..28440f10 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,7 @@ [tox] envlist = - flake8, mypy + flake8, mypy, black test-py{26,27,34,35,36,37,38,py2,py3} coverage_report docs @@ -61,6 +61,37 @@ commands = test: pytest --cov={env:PY_MODULE} --cov-report=term-missing:skip-covered --doctest-modules {posargs:src/{env:PY_MODULE}} +## +# Black code formatting +## + +[testenv:black] + +description = run Black (linter) + +basepython = {[default]basepython} + +skip_install = True + +deps = + black==19.10b0 + +setenv = + BLACK_LINT_ARGS=--check + +commands = + black {env:BLACK_LINT_ARGS:} setup.py src + + +[testenv:black-reformat] + +description = {[testenv:black]description} and reformat +basepython = {[testenv:black]basepython} +skip_install = {[testenv:black]skip_install} +deps = {[testenv:black]deps} +commands = {[testenv:black]commands} + + ## # Flake8 linting ## @@ -95,6 +126,8 @@ select = A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z show-source = True doctests = True +max-line-length = 80 + # Codes: http://flake8.pycqa.org/en/latest/user/error-codes.html ignore = # syntax error in type comment @@ -115,8 +148,8 @@ ignore = # variable in global scope should not be mixedCase N816, - # line break after binary operator - W504, + # line break before binary operator + W503, # End of list (allows last item to end with trailing ',') EOL @@ -187,7 +220,7 @@ basepython = {[default]basepython} skip_install = True deps = - coverage==4.5.4 # rq.filter: <5 # coverage 5.0 drops Python 3.4 support + coverage==5.1 setenv = {[default]setenv} @@ -265,7 +298,7 @@ commands = sphinx-build \ -b html -d "{envtmpdir}/doctrees" \ "{toxinidir}/docs" \ - "{toxworkdir}/htmldocs" + "{toxinidir}/htmldocs" [testenv:docs-auto] @@ -283,7 +316,7 @@ commands = -b html -d "{envtmpdir}/doctrees" \ --host=localhost \ "{toxinidir}/docs" \ - "{toxworkdir}/htmldocs" + "{toxinidir}/htmldocs" ## From ca2226450a233be53644de7c01cc84831fc71163 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Tue, 14 Apr 2020 16:46:10 -0700 Subject: [PATCH 224/419] [requires.io] dependency update --- setup.py | 83 ++++++++++++++++++++++++++------------------------------ 1 file changed, 39 insertions(+), 44 deletions(-) diff --git a/setup.py b/setup.py index fceb4fac..2f09f9cd 100644 --- a/setup.py +++ b/setup.py @@ -10,52 +10,47 @@ from setuptools import find_packages, setup -__author__ = 'Mahmoud Hashemi and Glyph Lefkowitz' -__version__ = '19.0.1dev' -__contact__ = 'mahmoud@hatnote.com' -__url__ = 'https://github.com/python-hyper/hyperlink' -__license__ = 'MIT' +__author__ = "Mahmoud Hashemi and Glyph Lefkowitz" +__version__ = "19.0.1dev" +__contact__ = "mahmoud@hatnote.com" +__url__ = "https://github.com/python-hyper/hyperlink" +__license__ = "MIT" -setup(name='hyperlink', - version=__version__, - description="A featureful, immutable, and correct URL for Python.", - long_description=__doc__, - author=__author__, - author_email=__contact__, - url=__url__, - packages=find_packages(where="src"), - package_dir={"": "src"}, - package_data=dict( - hyperlink=[ - "py.typed", - ], - ), - zip_safe=False, - license=__license__, - platforms='any', - install_requires=[ - 'idna>=2.5', - 'typing ; python_version<"3.5"', - ], - python_requires='>=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', - classifiers=[ - 'Topic :: Utilities', - 'Intended Audience :: Developers', - 'Topic :: Software Development :: Libraries', - 'Development Status :: 5 - Production/Stable', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.6', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 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', - 'Programming Language :: Python :: Implementation :: PyPy', - 'License :: OSI Approved :: MIT License', ] - ) +setup( + name="hyperlink", + version=__version__, + description="A featureful, immutable, and correct URL for Python.", + long_description=__doc__, + author=__author__, + author_email=__contact__, + url=__url__, + packages=find_packages(where="src"), + package_dir={"": "src"}, + package_data=dict(hyperlink=["py.typed",],), + zip_safe=False, + license=__license__, + platforms="any", + install_requires=["idna>=2.5", 'typing ; python_version<"3.5"',], + python_requires=">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*", + classifiers=[ + "Topic :: Utilities", + "Intended Audience :: Developers", + "Topic :: Software Development :: Libraries", + "Development Status :: 5 - Production/Stable", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 2.6", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 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", + "Programming Language :: Python :: Implementation :: PyPy", + "License :: OSI Approved :: MIT License", + ], +) """ A brief checklist for release: From a3b8b06d62b226224dad5a253f6a23f10aa1805b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Tue, 14 Apr 2020 17:23:32 -0700 Subject: [PATCH 225/419] Run black --- src/hyperlink/hypothesis.py | 73 ++++++++++++++++----------- src/hyperlink/test/__init__.py | 8 +-- src/hyperlink/test/test_hypothesis.py | 26 ++++++---- 3 files changed, 63 insertions(+), 44 deletions(-) diff --git a/src/hyperlink/hypothesis.py b/src/hyperlink/hypothesis.py index b01decad..6228ad3e 100644 --- a/src/hyperlink/hypothesis.py +++ b/src/hyperlink/hypothesis.py @@ -6,9 +6,11 @@ try: import hypothesis + del hypothesis except ImportError: from typing import Tuple + __all__ = () # type: Tuple[str, ...] else: from csv import reader as csv_reader @@ -16,7 +18,14 @@ from string import ascii_letters, digits from sys import maxunicode from typing import ( - Callable, Iterable, List, Optional, Sequence, Text, TypeVar, cast + Callable, + Iterable, + List, + Optional, + Sequence, + Text, + TypeVar, + cast, ) from gzip import open as open_gzip @@ -24,7 +33,11 @@ from hypothesis import assume from hypothesis.strategies import ( - composite, integers, lists, sampled_from, text + composite, + integers, + lists, + sampled_from, + text, ) from idna import IDNAError, check_label, encode as idna_encode @@ -39,7 +52,7 @@ "port_numbers", ) - T = TypeVar('T') + T = TypeVar("T") DrawCallable = Callable[[Callable[..., T]], T] try: @@ -65,8 +78,7 @@ def idna_characters(): ) with open_gzip(dataFileName) as dataFile: reader = csv_reader( - (line.decode("utf-8") for line in dataFile), - delimiter=",", + (line.decode("utf-8") for line in dataFile), delimiter=",", ) next(reader) # Skip header row for row in reader: @@ -116,9 +128,7 @@ def idna_text(draw, min_size=1, max_size=None): result = cast( Text, - draw(text( - min_size=min_size, max_size=max_size, alphabet=alphabet - )) + draw(text(min_size=min_size, max_size=max_size, alphabet=alphabet)), ) # FIXME: There should be a more efficient way to ensure we produce @@ -143,9 +153,7 @@ def port_numbers(draw, allow_zero=False): else: min_value = 1 - return cast( - int, draw(integers(min_value=min_value, max_value=65535)) - ) + return cast(int, draw(integers(min_value=min_value, max_value=65535))) @composite def hostname_labels(draw, allow_idn=True): @@ -165,9 +173,7 @@ def hostname_labels(draw, allow_idn=True): # If the label doesn't encode to ASCII, then we need to check # the length of the label after encoding to punycode and adding # the xn-- prefix. - while ( - len(label.encode("punycode")) > 63 - len("xn--") - ): + while len(label.encode("punycode")) > 63 - len("xn--"): # Rather than bombing out, just trim from the end until it # is short enough, so hypothesis doesn't have to generate # new data. @@ -176,10 +182,13 @@ def hostname_labels(draw, allow_idn=True): else: label = cast( Text, - draw(text( - min_size=1, max_size=63, - alphabet=Text(ascii_letters + digits + u"-") - )) + draw( + text( + min_size=1, + max_size=63, + alphabet=Text(ascii_letters + digits + u"-"), + ) + ), ) # Filter invalid labels. @@ -208,20 +217,25 @@ def hostnames(draw, allow_leading_digit=True, allow_idn=True): labels = [ cast( Text, - draw(hostname_labels(allow_idn=allow_idn).filter( - lambda l: ( - True if allow_leading_digit else l[0] not in digits + draw( + hostname_labels(allow_idn=allow_idn).filter( + lambda l: ( + True if allow_leading_digit else l[0] not in digits + ) ) - )) + ), ) ] # Draw remaining labels labels += cast( List[Text], - draw(lists( - hostname_labels(allow_idn=allow_idn), - min_size=1, max_size=4, - )) + draw( + lists( + hostname_labels(allow_idn=allow_idn), + min_size=1, + max_size=4, + ) + ), ) # Trim off labels until the total host name length fits in 252 @@ -239,6 +253,7 @@ def path_characters(): global _path_characters if _path_characters is None: + def chars(): # type: () -> Iterable[Text] for i in range(maxunicode): @@ -268,10 +283,8 @@ def paths(draw): return cast( List[Text], draw( - lists( - text(min_size=1, alphabet=path_characters()), max_size=10 - ) - ) + lists(text(min_size=1, alphabet=path_characters()), max_size=10) + ), ) @composite diff --git a/src/hyperlink/test/__init__.py b/src/hyperlink/test/__init__.py index 2a651514..e10ca70f 100644 --- a/src/hyperlink/test/__init__.py +++ b/src/hyperlink/test/__init__.py @@ -17,11 +17,13 @@ def _init_hypothesis(): return settings.register_profile( - "patience", settings( + "patience", + settings( suppress_health_check=[ - HealthCheck.too_slow, HealthCheck.filter_too_much + HealthCheck.too_slow, + HealthCheck.filter_too_much, ] - ) + ), ) settings.load_profile("patience") diff --git a/src/hyperlink/test/test_hypothesis.py b/src/hyperlink/test/test_hypothesis.py index 5039ff91..776ed7b7 100644 --- a/src/hyperlink/test/test_hypothesis.py +++ b/src/hyperlink/test/test_hypothesis.py @@ -5,12 +5,14 @@ try: import hypothesis + del hypothesis except ImportError: pass else: from string import digits from typing import Sequence, Text + try: from unittest.mock import patch except ImportError: @@ -24,8 +26,15 @@ from .common import HyperlinkTestCase from .. import DecodedURL, EncodedURL from ..hypothesis import ( - DrawCallable, composite, decoded_urls, encoded_urls, - hostname_labels, hostnames, idna_text, paths, port_numbers, + DrawCallable, + composite, + decoded_urls, + encoded_urls, + hostname_labels, + hostnames, + idna_text, + paths, + port_numbers, ) class TestHypothesisStrategies(HyperlinkTestCase): @@ -42,9 +51,7 @@ def test_idna_text_valid(self, text): try: idna_encode(text) except IDNAError: # pragma: no cover - raise AssertionError( - "Invalid IDNA text: {!r}".format(text) - ) + raise AssertionError("Invalid IDNA text: {!r}".format(text)) @given(data()) def test_idna_text_min_max(self, data): @@ -84,9 +91,7 @@ def test_hostname_labels_valid_idn(self, label): check_label(label) idna_encode(label) except UnicodeError: # pragma: no cover - raise AssertionError( - "Invalid IDN label: {!r}".format(label) - ) + raise AssertionError("Invalid IDN label: {!r}".format(label)) @given(data()) @settings(max_examples=10) @@ -96,6 +101,7 @@ def test_hostname_labels_long_idn_punycode(self, data): hostname_labels() handles case where idna_text() generates text that encoded to punycode ends up as longer than allowed. """ + @composite def mock_idna_text(draw, min_size, max_size): # type: (DrawCallable, int, int) -> Text @@ -126,9 +132,7 @@ def test_hostname_labels_valid_ascii(self, label): check_label(label) label.encode("ascii") except UnicodeError: # pragma: no cover - raise AssertionError( - "Invalid ASCII label: {!r}".format(label) - ) + raise AssertionError("Invalid ASCII label: {!r}".format(label)) @given(hostnames()) def test_hostnames_idn(self, hostname): From 3103f9fe68f0b91f6bf2eb3601582e7dcab7ffde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Tue, 14 Apr 2020 17:26:08 -0700 Subject: [PATCH 226/419] Pass CI --- tox.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tox.ini b/tox.ini index 610a6188..f12565fb 100644 --- a/tox.ini +++ b/tox.ini @@ -65,6 +65,8 @@ setenv = test: COVERAGE_FILE={toxworkdir}/coverage.{envname} +passenv = CI + commands = test: pytest --cov={env:PY_MODULE} --cov-report=term-missing:skip-covered --doctest-modules {posargs:src/{env:PY_MODULE}} From dc25104c814ab36a36a3a99d93c840e3921f9bea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Tue, 14 Apr 2020 17:26:19 -0700 Subject: [PATCH 227/419] Don't need test: --- tox.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tox.ini b/tox.ini index f12565fb..b47934a5 100644 --- a/tox.ini +++ b/tox.ini @@ -60,15 +60,15 @@ deps = {py35,py36,py37,py38,py39,py2,py3}: hypothesis==5.8.4 setenv = - test: HYPOTHESIS_STORAGE_DIRECTORY={toxworkdir}/hypothesis {[default]setenv} - test: COVERAGE_FILE={toxworkdir}/coverage.{envname} + COVERAGE_FILE={toxworkdir}/coverage.{envname} + HYPOTHESIS_STORAGE_DIRECTORY={toxworkdir}/hypothesis passenv = CI commands = - test: pytest --cov={env:PY_MODULE} --cov-report=term-missing:skip-covered --doctest-modules {posargs:src/{env:PY_MODULE}} + pytest --cov={env:PY_MODULE} --cov-report=term-missing:skip-covered --doctest-modules {posargs:src/{env:PY_MODULE}} ## From c2bcc057d20dea8388533ed06c76339e92fdf788 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Tue, 14 Apr 2020 17:32:41 -0700 Subject: [PATCH 228/419] Revert extra diffs --- tox.ini | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tox.ini b/tox.ini index b47934a5..c3bd0ae1 100644 --- a/tox.ini +++ b/tox.ini @@ -40,7 +40,8 @@ basepython = py37: python3.7 py38: python3.8 py39: python3.9 - pypy: pypy + + pypy2: pypy pypy3: pypy3 deps = @@ -221,9 +222,6 @@ ignore_missing_imports = True [mypy-hypothesis.*] ignore_missing_imports = True -[mypy-twisted.*] -ignore_missing_imports = True - ## # Coverage report From d96c06feee63d91d5329661e2089f812446fc5f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Tue, 14 Apr 2020 18:03:49 -0700 Subject: [PATCH 229/419] Clean up the Tox config a bit more --- tox.ini | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/tox.ini b/tox.ini index a6633446..2a3eee14 100644 --- a/tox.ini +++ b/tox.ini @@ -17,12 +17,6 @@ basepython = python3.8 deps = idna==2.9 - test: typing==3.7.4.1 - test: {[testenv:coverage_report]deps} - test-{py26,py27,py34}: pytest==4.6.9 - test-{py35,py36,py37,py38}: pytest==5.2.4 - test: pytest-cov==2.8.1 - setenv = PY_MODULE=hyperlink @@ -50,15 +44,27 @@ basepython = pypy2: pypy pypy3: pypy3 -deps = {[default]deps} +deps = + {[default]deps} + + # In Python 2, we need to pull in typing + {py26,py27}: typing==3.7.4.1 + + # For pytest + {py26,py27,py34}: pytest==4.6.9 + {py35,py36,py37,py38}: pytest==5.2.4 + + # For code coverage + {[testenv:coverage_report]deps} + pytest-cov==2.8.1 setenv = {[default]setenv} - test: COVERAGE_FILE={toxworkdir}/coverage.{envname} + COVERAGE_FILE={toxworkdir}/coverage.{envname} commands = - test: pytest --cov={env:PY_MODULE} --cov-report=term-missing:skip-covered --doctest-modules {posargs:src/{env:PY_MODULE}} + pytest --cov={env:PY_MODULE} --cov-report=term-missing:skip-covered --doctest-modules {posargs:src/{env:PY_MODULE}} ## From 0b95ff82c623dde584428566e09edf36b210b283 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Tue, 14 Apr 2020 18:11:10 -0700 Subject: [PATCH 230/419] Comments --- tox.ini | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index c3bd0ae1..066c7c6d 100644 --- a/tox.ini +++ b/tox.ini @@ -47,16 +47,19 @@ basepython = deps = {[default]deps} + # In Python 2, we need to pull in typing {py26,py27}: typing==3.7.4.1 + # For pytest {py26,py27,py34}: pytest==4.6.9 {py35,py36,py37,py38}: pytest==5.2.4 py27: mock==3.0.5 + # For code coverage {[testenv:coverage_report]deps} pytest-cov==2.8.1 - # py34 isn't supported by hypothesis + # For hypothesis. Note Python 3.4 isn't supported by hypothesis. py27: hypothesis==4.43.3 # rq.filter: <4.44 {py35,py36,py37,py38,py39,py2,py3}: hypothesis==5.8.4 From 2d21b706369c752af990a88f9636d92d90bbfac5 Mon Sep 17 00:00:00 2001 From: Mahmoud Hashemi Date: Wed, 15 Apr 2020 14:48:07 -0700 Subject: [PATCH 231/419] sigh, GitHub's 'resolve conflicts' editor left whitespace on a blank line. removing it. --- src/hyperlink/_url.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index 58e6f040..4867d58c 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -2017,7 +2017,7 @@ class DecodedURL(object): .. versionadded:: 18.0.0 """ - + def __init__(self, url=_EMPTY_URL, lazy=False): # type: (URL, bool) -> None self._url = url From 5bb308e1b46346d76241a6f3b8a90e947db7bb7a Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Fri, 17 Apr 2020 00:42:30 -0700 Subject: [PATCH 232/419] [requires.io] dependency update --- tox.ini | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/tox.ini b/tox.ini index 28440f10..68e45f71 100644 --- a/tox.ini +++ b/tox.ini @@ -17,12 +17,6 @@ basepython = python3.8 deps = idna==2.9 - test: typing==3.7.4.1 - test: {[testenv:coverage_report]deps} - test-{py26,py27,py34}: pytest==4.6.9 - test-{py35,py36,py37,py38}: pytest==5.2.4 - test: pytest-cov==2.8.1 - setenv = PY_MODULE=hyperlink @@ -50,15 +44,27 @@ basepython = pypy2: pypy pypy3: pypy3 -deps = {[default]deps} +deps = + {[default]deps} + + # In Python 2, we need to pull in typing + {py26,py27}: typing==3.7.4.1 + + # For pytest + {py26,py27,py34}: pytest==4.6.9 + {py35,py36,py37,py38}: pytest==5.2.4 + + # For code coverage + {[testenv:coverage_report]deps} + pytest-cov==2.8.1 setenv = {[default]setenv} - test: COVERAGE_FILE={toxworkdir}/coverage.{envname} + COVERAGE_FILE={toxworkdir}/coverage.{envname} commands = - test: pytest --cov={env:PY_MODULE} --cov-report=term-missing:skip-covered --doctest-modules {posargs:src/{env:PY_MODULE}} + pytest --cov={env:PY_MODULE} --cov-report=term-missing:skip-covered --doctest-modules {posargs:src/{env:PY_MODULE}} ## From 8c8f7781e0c0a187bd869bc42dd37d9a519929f0 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sun, 19 Apr 2020 07:58:11 -0700 Subject: [PATCH 233/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 68e45f71..c05fda1a 100644 --- a/tox.ini +++ b/tox.ini @@ -297,7 +297,7 @@ description = build documentation basepython = {[default]basepython} deps = - Sphinx==3.0.1 + Sphinx==3.0.2 sphinx-rtd-theme==0.4.3 commands = From c5a0897f88fae39015a6c23bda9dfb57a7ff5da8 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Wed, 22 Apr 2020 20:26:44 -0700 Subject: [PATCH 234/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index c05fda1a..9c00c89e 100644 --- a/tox.ini +++ b/tox.ini @@ -339,7 +339,7 @@ skip_install = True deps = check-manifest==0.41 - readme-renderer==25.0 + readme-renderer==26.0 twine==3.1.1 commands = From d9d0b016d240701fa44d58f4e3d3d3c47b7a060d Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sun, 26 Apr 2020 07:13:19 -0700 Subject: [PATCH 235/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 9c00c89e..d011ad8e 100644 --- a/tox.ini +++ b/tox.ini @@ -297,7 +297,7 @@ description = build documentation basepython = {[default]basepython} deps = - Sphinx==3.0.2 + Sphinx==3.0.3 sphinx-rtd-theme==0.4.3 commands = From 9bf0e088d4d7d2286c194c6682eba04b1fcf76f8 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sun, 3 May 2020 07:41:02 -0700 Subject: [PATCH 236/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index d011ad8e..f57ebc87 100644 --- a/tox.ini +++ b/tox.ini @@ -338,7 +338,7 @@ basepython = {[default]basepython} skip_install = True deps = - check-manifest==0.41 + check-manifest==0.42 readme-renderer==26.0 twine==3.1.1 From e43a840a086c81855eceeda5279a65856eb15a52 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Fri, 8 May 2020 10:40:22 -0700 Subject: [PATCH 237/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index f57ebc87..e080de6e 100644 --- a/tox.ini +++ b/tox.ini @@ -51,7 +51,7 @@ deps = {py26,py27}: typing==3.7.4.1 # For pytest - {py26,py27,py34}: pytest==4.6.9 + {py26,py27,py34}: pytest==4.6.10 {py35,py36,py37,py38}: pytest==5.2.4 # For code coverage From 65d85ef89fd5ac3c97885f1bcc9de1e49c8da43f Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Mon, 11 May 2020 15:14:20 -0700 Subject: [PATCH 238/419] [requires.io] dependency update --- tox.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index e080de6e..397f9af4 100644 --- a/tox.ini +++ b/tox.ini @@ -112,10 +112,10 @@ skip_install = True deps = flake8-bugbear==20.1.4 - flake8==3.7.9 + flake8==3.8.0 mccabe==0.6.1 pep8-naming==0.10.0 - pycodestyle==2.5.0 + pycodestyle==2.6.0 pydocstyle==5.0.2 # pin pyflakes pending a release with https://github.com/PyCQA/pyflakes/pull/455 git+git://github.com/PyCQA/pyflakes@ffe9386#egg=pyflakes From d04fe7507da8cfef17290b77fc26fb8f5ae8df01 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Mon, 11 May 2020 23:03:19 -0700 Subject: [PATCH 239/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 397f9af4..dc50f710 100644 --- a/tox.ini +++ b/tox.ini @@ -112,7 +112,7 @@ skip_install = True deps = flake8-bugbear==20.1.4 - flake8==3.8.0 + flake8==3.8.1 mccabe==0.6.1 pep8-naming==0.10.0 pycodestyle==2.6.0 From 51316fee97c53e7914f8a60872f36bea5c4a428d Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Mon, 18 May 2020 06:15:03 -0700 Subject: [PATCH 240/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index dc50f710..a212ee7f 100644 --- a/tox.ini +++ b/tox.ini @@ -255,7 +255,7 @@ skip_install = True deps = {[testenv:coverage_report]deps} - codecov==2.0.22 + codecov==2.1.0 passenv = # See https://github.com/codecov/codecov-python/blob/master/README.md#using-tox From 62c76991b3e0dd8deb94bd1e556500f9eea86b5b Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Wed, 20 May 2020 08:22:08 -0700 Subject: [PATCH 241/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index a212ee7f..9288591c 100644 --- a/tox.ini +++ b/tox.ini @@ -255,7 +255,7 @@ skip_install = True deps = {[testenv:coverage_report]deps} - codecov==2.1.0 + codecov==2.1.1 passenv = # See https://github.com/codecov/codecov-python/blob/master/README.md#using-tox From cc2a45702d5fe29b6cb4c5e0c71c75fd40203ffe Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Thu, 21 May 2020 10:01:41 -0700 Subject: [PATCH 242/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 9288591c..b11428a8 100644 --- a/tox.ini +++ b/tox.ini @@ -255,7 +255,7 @@ skip_install = True deps = {[testenv:coverage_report]deps} - codecov==2.1.1 + codecov==2.1.3 passenv = # See https://github.com/codecov/codecov-python/blob/master/README.md#using-tox From dea3175a68ca4ac4a9fbdca63f51b5c1935509a5 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Fri, 22 May 2020 10:21:55 -0700 Subject: [PATCH 243/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index b11428a8..d7901534 100644 --- a/tox.ini +++ b/tox.ini @@ -56,7 +56,7 @@ deps = # For code coverage {[testenv:coverage_report]deps} - pytest-cov==2.8.1 + pytest-cov==2.9.0 setenv = {[default]setenv} From 1f88ddb46c20604a5f29325bf56f9c888487998e Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Fri, 22 May 2020 16:05:48 -0700 Subject: [PATCH 244/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index d7901534..f198ac76 100644 --- a/tox.ini +++ b/tox.ini @@ -112,7 +112,7 @@ skip_install = True deps = flake8-bugbear==20.1.4 - flake8==3.8.1 + flake8==3.8.2 mccabe==0.6.1 pep8-naming==0.10.0 pycodestyle==2.6.0 From d9fbf8a65787effd32c795ee5430b317a19025d3 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Tue, 26 May 2020 08:54:05 -0700 Subject: [PATCH 245/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index f198ac76..c8184a46 100644 --- a/tox.ini +++ b/tox.ini @@ -297,7 +297,7 @@ description = build documentation basepython = {[default]basepython} deps = - Sphinx==3.0.3 + Sphinx==3.0.4 sphinx-rtd-theme==0.4.3 commands = From 823ca7a9777bccb731e7f42930c1d475a9d16c48 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Wed, 3 Jun 2020 04:15:22 -0700 Subject: [PATCH 246/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index c8184a46..d7f72777 100644 --- a/tox.ini +++ b/tox.ini @@ -175,7 +175,7 @@ description = run Mypy (static type checker) basepython = {[default]basepython} deps = - mypy==0.770 + mypy==0.780 {[default]deps} From 4f772165ef384abf7fa1ecfd4f7148ed3366c71e Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Wed, 3 Jun 2020 08:15:32 -0700 Subject: [PATCH 247/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index d7f72777..7453633e 100644 --- a/tox.ini +++ b/tox.ini @@ -255,7 +255,7 @@ skip_install = True deps = {[testenv:coverage_report]deps} - codecov==2.1.3 + codecov==2.1.4 passenv = # See https://github.com/codecov/codecov-python/blob/master/README.md#using-tox From 8d0dbcc2ee76eb9455436b92fae6cb44ee7e3c5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Thu, 4 Jun 2020 14:39:04 -0700 Subject: [PATCH 248/419] Don't disable disallow_any_generics accross the board --- tox.ini | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tox.ini b/tox.ini index f74c45f0..299db5ee 100644 --- a/tox.ini +++ b/tox.ini @@ -200,7 +200,7 @@ commands = # Global settings check_untyped_defs = True -disallow_any_generics = False +disallow_any_generics = True disallow_incomplete_defs = True disallow_untyped_defs = True no_implicit_optional = True @@ -213,10 +213,12 @@ warn_return_any = True warn_unreachable = True warn_unused_ignores = True -# Don't complain about dependencies known to lack type hints +# DrawCallable is generic -[mypy-idna] -ignore_missing_imports = True +[mypy-hyperlink.hypothesis] +disallow_any_generics = False +[mypy-hyperlink.test.test_hypothesis] +disallow_any_generics = False # Don't complain about dependencies known to lack type hints @@ -225,6 +227,9 @@ ignore_missing_imports = True [mypy-hypothesis.*] ignore_missing_imports = True +[mypy-idna] +ignore_missing_imports = True + ## # Coverage report From 18ca93ebbb188e739954b45a19015bdebb8e899a Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Fri, 5 Jun 2020 06:01:32 -0700 Subject: [PATCH 249/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 7453633e..f79fd334 100644 --- a/tox.ini +++ b/tox.ini @@ -51,7 +51,7 @@ deps = {py26,py27}: typing==3.7.4.1 # For pytest - {py26,py27,py34}: pytest==4.6.10 + {py26,py27,py34}: pytest==4.6.11 {py35,py36,py37,py38}: pytest==5.2.4 # For code coverage From 5762dfd7266c9cf0079b4e1d40ac8eaaf4daf755 Mon Sep 17 00:00:00 2001 From: Mahmoud Hashemi Date: Sun, 7 Jun 2020 14:15:35 -0700 Subject: [PATCH 250/419] bump codecov from 2.0 to 2.1 in an attempt to fix client errors on upload from travis --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 299db5ee..fa089ba3 100644 --- a/tox.ini +++ b/tox.ini @@ -275,7 +275,7 @@ skip_install = True deps = {[testenv:coverage_report]deps} - codecov==2.0.22 + codecov==2.1.4 passenv = # See https://github.com/codecov/codecov-python/blob/master/README.md#using-tox From 2519202a9e3f6295683468d71bb289db14a521b9 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sun, 7 Jun 2020 14:32:51 -0700 Subject: [PATCH 251/419] [requires.io] dependency update --- tox.ini | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tox.ini b/tox.ini index f79fd334..55c71804 100644 --- a/tox.ini +++ b/tox.ini @@ -58,10 +58,18 @@ deps = {[testenv:coverage_report]deps} pytest-cov==2.9.0 + # For hypothesis. Note Python 3.4 isn't supported by hypothesis. + py27: hypothesis==4.43.9 + {py35,py36,py37,py38,py39,py2,py3}: hypothesis==5.8.6 + py27: mock==4.0.2 + setenv = {[default]setenv} COVERAGE_FILE={toxworkdir}/coverage.{envname} + HYPOTHESIS_STORAGE_DIRECTORY={toxworkdir}/hypothesis + +passenv = CI commands = pytest --cov={env:PY_MODULE} --cov-report=term-missing:skip-covered --doctest-modules {posargs:src/{env:PY_MODULE}} @@ -205,8 +213,20 @@ warn_return_any = True warn_unreachable = True warn_unused_ignores = True +# DrawCallable is generic + +[mypy-hyperlink.hypothesis] +disallow_any_generics = False +[mypy-hyperlink.test.test_hypothesis] +disallow_any_generics = False + # Don't complain about dependencies known to lack type hints +[mypy-hypothesis] +ignore_missing_imports = True +[mypy-hypothesis.*] +ignore_missing_imports = True + [mypy-idna] ignore_missing_imports = True From 7d72c07d9e77bf6c1323d40ea3dbd1acf8aae2ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sun, 7 Jun 2020 16:15:46 -0700 Subject: [PATCH 252/419] Bump Sphinx to v3 --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index fa089ba3..e24ec530 100644 --- a/tox.ini +++ b/tox.ini @@ -317,7 +317,7 @@ description = build documentation basepython = {[default]basepython} deps = - Sphinx==2.4.4 + Sphinx==3.0.4 sphinx-rtd-theme==0.4.3 commands = From 7923706415170c5a73fafc0c5764f5b6aa94c6e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sun, 7 Jun 2020 16:17:02 -0700 Subject: [PATCH 253/419] Remove types from docstrings, since Sphinx sees the type hints now. --- src/hyperlink/_url.py | 195 ++++++++++++++++++++---------------------- 1 file changed, 95 insertions(+), 100 deletions(-) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index 4867d58c..4fb84133 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -478,12 +478,12 @@ def register_scheme(text, uses_netloc=True, default_port=None): `file an issue`_! Args: - text (Text): A string representation of the scheme. + text: A string representation of the scheme. (the 'http' in 'http://hatnote.com') - uses_netloc (bool): Does the scheme support specifying a + uses_netloc: Does the scheme support specifying a network host? For instance, "http" does, "mailto" does not. Defaults to True. - default_port (Optional[int]): The default port, if any, for + default_port: The default port, if any, for netloc-using schemes. .. _file an issue: https://github.com/mahmoud/hyperlink/issues @@ -927,38 +927,36 @@ class URL(object): constructor arguments is below. Args: - scheme (Optional[Text]): The text name of the scheme. - host (Optional[Text]): The host portion of the network location - port (Optional[int]): The port part of the network location. If - ``None`` or no port is passed, the port will default to - the default port of the scheme, if it is known. See the - ``SCHEME_PORT_MAP`` and :func:`register_default_port` - for more info. - path (Iterable[Text]): A tuple of strings representing the - slash-separated parts of the path. - query (Sequence[Tuple[Text, Optional[Text]]]): The query parameters, as - a dictionary or as an sequence of key-value pairs. - fragment (Text): The fragment part of the URL. - rooted (bool): A rooted URL is one which indicates an absolute path. - This is True on any URL that includes a host, or any relative URL - that starts with a slash. - userinfo (Text): The username or colon-separated - username:password pair. - uses_netloc (Optional[bool]): Indicates whether ``://`` (the "netloc - separator") will appear to separate the scheme from the *path* in - cases where no host is present. Setting this to ``True`` is a - non-spec-compliant affordance for the common practice of having URIs - that are *not* URLs (cannot have a 'host' part) but nevertheless use - the common ``://`` idiom that most people associate with URLs; - e.g. ``message:`` URIs like ``message://message-id`` being - equivalent to ``message:message-id``. This may be inferred based on - the scheme depending on whether :func:`register_scheme` has been - used to register the scheme and should not be passed directly unless - you know the scheme works like this and you know it has not been - registered. - - All of these parts are also exposed as read-only attributes of - URL instances, along with several useful methods. + scheme: The text name of the scheme. + host: The host portion of the network location + port: The port part of the network location. If ``None`` or no port is + passed, the port will default to the default port of the scheme, if + it is known. See the ``SCHEME_PORT_MAP`` and + :func:`register_default_port` for more info. + path: A tuple of strings representing the slash-separated parts of the + path. + query: The query parameters, as a dictionary or as an sequence of + key-value pairs. + fragment: The fragment part of the URL. + rooted: A rooted URL is one which indicates an absolute path. + This is True on any URL that includes a host, or any relative URL + that starts with a slash. + userinfo: The username or colon-separated username:password pair. + uses_netloc: Indicates whether ``://`` (the "netloc separator") will + appear to separate the scheme from the *path* in cases where no + host is present. + Setting this to ``True`` is a non-spec-compliant affordance for the + common practice of having URIs that are *not* URLs (cannot have a + 'host' part) but nevertheless use the common ``://`` idiom that + most people associate with URLs; e.g. ``message:`` URIs like + ``message://message-id`` being equivalent to ``message:message-id``. + This may be inferred based on the scheme depending on whether + :func:`register_scheme` has been used to register the scheme and + should not be passed directly unless you know the scheme works like + this and you know it has not been registered. + + All of these parts are also exposed as read-only attributes of :class:`URL` + instances, along with several useful methods. .. _RFC 3986: https://tools.ietf.org/html/rfc3986 .. _RFC 3987: https://tools.ietf.org/html/rfc3987 @@ -1187,9 +1185,9 @@ def authority(self, with_password=False, **kw): u'user:pass@localhost:8080' Args: - with_password (bool): Whether the return value of this - method include the password in the URL, if it is - set. Defaults to False. + with_password: Whether the return value of this method include the + password in the URL, if it is set. + Defaults to False. Returns: Text: The authority (network location and user information) portion @@ -1298,32 +1296,29 @@ def replace( the value on the current URL. Args: - scheme (Optional[Text]): The text name of the scheme. - host (Optional[Text]): The host portion of the network location. - path (Iterable[Text]): A tuple of strings representing the - slash-separated parts of the path. - query (Sequence[Tuple[Text, Optional[Text]]]): The query - parameters, as a dictionary or as an sequence of key-value - pairs. - fragment (Text): The fragment part of the URL. - port (Optional[int]): The port part of the network location. - rooted (Optional[bool]): Whether or not the path begins with a - slash. - userinfo (Text): The username or colon-separated username:password - pair. - uses_netloc (bool): Indicates whether ``://`` (the "netloc - separator") will appear to separate the scheme from the *path* - in cases where no host is present. Setting this to ``True`` is - a non-spec-compliant affordance for the common practice of - having URIs that are *not* URLs (cannot have a 'host' part) but - nevertheless use the common ``://`` idiom that most people - associate with URLs; e.g. ``message:`` URIs like - ``message://message-id`` being equivalent to - ``message:message-id``. This may be inferred based on the - scheme depending on whether :func:`register_scheme` has been - used to register the scheme and should not be passed directly - unless you know the scheme works like this and you know it has - not been registered. + scheme: The text name of the scheme. + host: The host portion of the network location. + path: A tuple of strings representing the slash-separated parts of + the path. + query: The query parameters, as a dictionary or as an sequence of + key-value pairs. + fragment: The fragment part of the URL. + port: The port part of the network location. + rooted: Whether or not the path begins with a slash. + userinfo: The username or colon-separated username:password pair. + uses_netloc: Indicates whether ``://`` (the "netloc separator") + will appear to separate the scheme from the *path* in cases + where no host is present. + Setting this to ``True`` is a non-spec-compliant affordance for + the common practice of having URIs that are *not* URLs (cannot + have a 'host' part) but nevertheless use the common ``://`` + idiom that most people associate with URLs; e.g. ``message:`` + URIs like ``message://message-id`` being equivalent to + ``message:message-id``. + This may be inferred based on the scheme depending on whether + :func:`register_scheme` has been used to register the scheme + and should not be passed directly unless you know the scheme + works like this and you know it has not been registered. Returns: URL: A copy of the current :class:`URL`, with new values for @@ -1363,7 +1358,7 @@ def from_text(cls, text): sure to decode those bytestrings. Args: - text (Text): A valid URL string. + text: A valid URL string. Returns: URL: The structured object version of the parsed string. @@ -1469,15 +1464,14 @@ def normalize( name. Args: - scheme (bool): Convert the scheme to lowercase - host (bool): Convert the host to lowercase - path (bool): Normalize the path (see above for details) - query (bool): Normalize the query string - fragment (bool): Normalize the fragment - userinfo (bool): Normalize the userinfo - percents (bool): Encode isolated percent signs for any - percent-encoded fields which are being normalized - (defaults to True). + scheme: Convert the scheme to lowercase + host: Convert the host to lowercase + path: Normalize the path (see above for details) + query: Normalize the query string + fragment: Normalize the fragment + userinfo: Normalize the userinfo + percents: Encode isolated percent signs for any percent-encoded + fields which are being normalized (defaults to `True`). >>> url = URL.from_text(u'Http://example.COM/a/../b/./c%2f?%61%') >>> print(url.normalize().to_text()) @@ -1537,9 +1531,9 @@ def child(self, *segments): u'http://localhost/a/b/c/d?x=y' Args: - segments (Text): Additional parts to be joined and added to - the path, like :func:`os.path.join`. Special characters - in segments will be percent encoded. + segments: Additional parts to be joined and added to the path, like + :func:`os.path.join`. Special characters in segments will be + percent encoded. Returns: URL: A copy of the current URL with the extra path segments. @@ -1562,7 +1556,7 @@ def sibling(self, segment): sibling of this URL path. Args: - segment (Text): A single path segment. + segment: A single path segment. Returns: URL: A copy of the current URL with the last path segment @@ -1861,11 +1855,11 @@ def add(self, name, value=None): URL.from_text(u'https://example.com/?x=y&x=z') Args: - name (Text): The name of the query parameter to add. + name: The name of the query parameter to add. The part before the ``=``. - value (Optional[Text]): The value of the query parameter to add. - The part after the ``=``. Defaults to ``None``, meaning no - value. + value: The value of the query parameter to add. + The part after the ``=``. + Defaults to ``None``, meaning no value. Returns: URL: A new :class:`URL` instance with the parameter added. @@ -1884,11 +1878,11 @@ def set(self, name, value=None): URL.from_text(u'https://example.com/?x=z') Args: - name (Text): The name of the query parameter to set. + name: The name of the query parameter to set. The part before the ``=``. - value (Optional[Text]): The value of the query parameter to set. - The part after the ``=``. Defaults to ``None``, meaning no - value. + value: The value of the query parameter to set. + The part after the ``=``. + Defaults to ``None``, meaning no value. Returns: URL: A new :class:`URL` instance with the parameter set. @@ -1915,7 +1909,7 @@ def get(self, name): list is always returned, and this method raises no exceptions. Args: - name (Text): The name of the query parameter to get. + name: The name of the query parameter to get. Returns: List[Optional[Text]]: A list of all the values associated with the @@ -1936,12 +1930,11 @@ def remove( parameter is not already set. Args: - name (Text): The name of the query parameter to remove. - value (Text): Optional value to additionally filter on. + name: The name of the query parameter to remove. + value: Optional value to additionally filter on. Setting this removes query parameters which match both name and value. - limit (Optional[int]): Optional maximum number of parameters to - remove. + limit: Optional maximum number of parameters to remove. Returns: URL: A new :class:`URL` instance with the parameter removed. @@ -2001,9 +1994,10 @@ class DecodedURL(object): special characters encoded with codecs other than UTF-8. Args: - url (URL): A :class:`URL` object to wrap. - lazy (bool): Set to True to avoid pre-decode all parts of the URL to - check for validity. Defaults to False. + url: A :class:`URL` object to wrap. + lazy: Set to True to avoid pre-decode all parts of the URL to check for + validity. + Defaults to False. .. note:: @@ -2034,9 +2028,10 @@ def from_text(cls, text, lazy=False): Make a `DecodedURL` instance from any text string containing a URL. Args: - text (Text): Text containing the URL - lazy (bool): Whether to pre-decode all parts of the URL to check for - validity. Defaults to True. + text: Text containing the URL + lazy: Whether to pre-decode all parts of the URL to check for + validity. + Defaults to True. """ _url = URL.from_text(text) return cls(_url, lazy=lazy) @@ -2386,16 +2381,16 @@ def parse(url, decoded=True, lazy=False): https://github.com/python-hyper/hyperlink Args: - url (str): A text string representation of a URL. + url: A text string representation of a URL. - decoded (bool): Whether or not to return a :class:`DecodedURL`, + decoded: Whether or not to return a :class:`DecodedURL`, which automatically handles all encoding/decoding/quoting/unquoting for all the various accessors of parts of the URL, or a :class:`URL`, which has the same API, but requires handling of special characters for different parts of the URL. - lazy (bool): In the case of `decoded=True`, this controls + lazy: In the case of `decoded=True`, this controls whether the URL is decoded immediately or as accessed. The default, `lazy=False`, checks all encoded parts of the URL for decodability. From f3c7609994aacb29c298955b90f4a21fceb06295 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sun, 7 Jun 2020 16:42:30 -0700 Subject: [PATCH 254/419] py27 mock is 3.0.5 try setting coverage back to 4.5.4 --- tox.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 55c71804..623d20e7 100644 --- a/tox.ini +++ b/tox.ini @@ -61,7 +61,7 @@ deps = # For hypothesis. Note Python 3.4 isn't supported by hypothesis. py27: hypothesis==4.43.9 {py35,py36,py37,py38,py39,py2,py3}: hypothesis==5.8.6 - py27: mock==4.0.2 + py27: mock==3.0.5 setenv = {[default]setenv} @@ -246,7 +246,7 @@ basepython = {[default]basepython} skip_install = True deps = - coverage==5.1 + coverage==4.5.4 # rq.filter: <5 # coverage 5.0 drops Python 3.4 support setenv = {[default]setenv} From 1472ccdae13bbfe7ad6a1d14ba60d27426e33d32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Sun, 7 Jun 2020 16:45:07 -0700 Subject: [PATCH 255/419] Remove duplicate section --- tox.ini | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/tox.ini b/tox.ini index 4cc39016..114d18b2 100644 --- a/tox.ini +++ b/tox.ini @@ -59,15 +59,10 @@ deps = pytest-cov==2.9.0 # For hypothesis. Note Python 3.4 isn't supported by hypothesis. - py27: hypothesis==4.43.9 + py27: hypothesis==4.43.9 # rq.filter: <4.44 {py35,py36,py37,py38,py39,py2,py3}: hypothesis==5.8.6 py27: mock==3.0.5 - # For hypothesis. Note Python 3.4 isn't supported by hypothesis. - py27: hypothesis==4.43.3 # rq.filter: <4.44 - {py35,py36,py37,py38,py39,py2,py3}: hypothesis==5.8.4 - py27: mock==3.0.5 - setenv = {[default]setenv} From c550d7048ca97b9ffb6dbf542b92bdec0996ce9d Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Mon, 8 Jun 2020 06:56:58 -0700 Subject: [PATCH 256/419] [requires.io] dependency update --- tox.ini | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tox.ini b/tox.ini index 114d18b2..5793b0fe 100644 --- a/tox.ini +++ b/tox.ini @@ -59,9 +59,9 @@ deps = pytest-cov==2.9.0 # For hypothesis. Note Python 3.4 isn't supported by hypothesis. - py27: hypothesis==4.43.9 # rq.filter: <4.44 + py27: hypothesis==4.43.9 {py35,py36,py37,py38,py39,py2,py3}: hypothesis==5.8.6 - py27: mock==3.0.5 + py27: mock==4.0.2 setenv = {[default]setenv} @@ -246,7 +246,7 @@ basepython = {[default]basepython} skip_install = True deps = - coverage==4.5.4 # rq.filter: <5 # coverage 5.0 drops Python 3.4 support + coverage==5.1 setenv = {[default]setenv} @@ -317,7 +317,7 @@ description = build documentation basepython = {[default]basepython} deps = - Sphinx==3.0.4 + Sphinx==3.1.0 sphinx-rtd-theme==0.4.3 commands = From 09aa8d14afcf5beb5f496b5a8a46362069678524 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Mon, 8 Jun 2020 15:38:36 -0700 Subject: [PATCH 257/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 5793b0fe..28fd7c60 100644 --- a/tox.ini +++ b/tox.ini @@ -120,7 +120,7 @@ skip_install = True deps = flake8-bugbear==20.1.4 - flake8==3.8.2 + flake8==3.8.3 mccabe==0.6.1 pep8-naming==0.10.0 pycodestyle==2.6.0 From 2739beb77e9afff5e1df2fa6a98acbce688faa17 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Fri, 12 Jun 2020 07:02:09 -0700 Subject: [PATCH 258/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 28fd7c60..baac6142 100644 --- a/tox.ini +++ b/tox.ini @@ -56,7 +56,7 @@ deps = # For code coverage {[testenv:coverage_report]deps} - pytest-cov==2.9.0 + pytest-cov==2.10.0 # For hypothesis. Note Python 3.4 isn't supported by hypothesis. py27: hypothesis==4.43.9 From 25a0058a7331b0ee21700867febcd753c82bbba2 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Fri, 12 Jun 2020 09:44:47 -0700 Subject: [PATCH 259/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index baac6142..f33862d4 100644 --- a/tox.ini +++ b/tox.ini @@ -275,7 +275,7 @@ skip_install = True deps = {[testenv:coverage_report]deps} - codecov==2.1.4 + codecov==2.1.5 passenv = # See https://github.com/codecov/codecov-python/blob/master/README.md#using-tox From 342d988e7e7573f81de53eabb5f2084323ff649f Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sat, 13 Jun 2020 16:51:38 -0700 Subject: [PATCH 260/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index f33862d4..5e90c3e4 100644 --- a/tox.ini +++ b/tox.ini @@ -275,7 +275,7 @@ skip_install = True deps = {[testenv:coverage_report]deps} - codecov==2.1.5 + codecov==2.1.6 passenv = # See https://github.com/codecov/codecov-python/blob/master/README.md#using-tox From 21804a330cdfe10ee7044354f3d49adc7800fa80 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sat, 13 Jun 2020 21:12:44 -0700 Subject: [PATCH 261/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 5e90c3e4..6e3a2503 100644 --- a/tox.ini +++ b/tox.ini @@ -317,7 +317,7 @@ description = build documentation basepython = {[default]basepython} deps = - Sphinx==3.1.0 + Sphinx==3.1.1 sphinx-rtd-theme==0.4.3 commands = From d66952cdec3e270dbd5842ab27f2c6cd4b6d7eba Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sun, 14 Jun 2020 11:24:05 -0700 Subject: [PATCH 262/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 6e3a2503..4f68c4b8 100644 --- a/tox.ini +++ b/tox.ini @@ -275,7 +275,7 @@ skip_install = True deps = {[testenv:coverage_report]deps} - codecov==2.1.6 + codecov==2.1.7 passenv = # See https://github.com/codecov/codecov-python/blob/master/README.md#using-tox From 2d0b290acc1289ecabbe40c776071a2f3dca6e2d Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Tue, 16 Jun 2020 16:24:13 -0700 Subject: [PATCH 263/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 4f68c4b8..3872ab5b 100644 --- a/tox.ini +++ b/tox.ini @@ -122,7 +122,7 @@ deps = flake8-bugbear==20.1.4 flake8==3.8.3 mccabe==0.6.1 - pep8-naming==0.10.0 + pep8-naming==0.11.0 pycodestyle==2.6.0 pydocstyle==5.0.2 # pin pyflakes pending a release with https://github.com/PyCQA/pyflakes/pull/455 From 6132faeb1727722f52808c7a45c4ed45a7f66239 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Tue, 16 Jun 2020 16:50:08 -0700 Subject: [PATCH 264/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 3872ab5b..01ad8a2c 100644 --- a/tox.ini +++ b/tox.ini @@ -122,7 +122,7 @@ deps = flake8-bugbear==20.1.4 flake8==3.8.3 mccabe==0.6.1 - pep8-naming==0.11.0 + pep8-naming==0.11.1 pycodestyle==2.6.0 pydocstyle==5.0.2 # pin pyflakes pending a release with https://github.com/PyCQA/pyflakes/pull/455 From 772a6b24efbf6662e6f2616e741ed2e1f6120ddb Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Wed, 17 Jun 2020 09:11:40 -0700 Subject: [PATCH 265/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 01ad8a2c..cfbe7f61 100644 --- a/tox.ini +++ b/tox.ini @@ -318,7 +318,7 @@ basepython = {[default]basepython} deps = Sphinx==3.1.1 - sphinx-rtd-theme==0.4.3 + sphinx-rtd-theme==0.5.0 commands = sphinx-build \ From 6054f13f4565402330cac8fe74d0e221ecccad2e Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Fri, 19 Jun 2020 09:27:04 -0700 Subject: [PATCH 266/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index cfbe7f61..e45dad45 100644 --- a/tox.ini +++ b/tox.ini @@ -183,7 +183,7 @@ description = run Mypy (static type checker) basepython = {[default]basepython} deps = - mypy==0.780 + mypy==0.781 {[default]deps} From 777b71f17acf697762c2711b602a369815ad1132 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Tue, 23 Jun 2020 01:47:56 -0700 Subject: [PATCH 267/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index e45dad45..de8d17cf 100644 --- a/tox.ini +++ b/tox.ini @@ -183,7 +183,7 @@ description = run Mypy (static type checker) basepython = {[default]basepython} deps = - mypy==0.781 + mypy==0.782 {[default]deps} From 7dafa2d4e42b19933616b6e419b1b050cadd710c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Tue, 23 Jun 2020 14:26:26 -0700 Subject: [PATCH 268/419] Updates, put a cap on Sphinx. --- tox.ini | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tox.ini b/tox.ini index fa089ba3..09af1731 100644 --- a/tox.ini +++ b/tox.ini @@ -51,17 +51,17 @@ deps = {py26,py27}: typing==3.7.4.1 # For pytest - {py26,py27,py34}: pytest==4.6.9 + {py26,py27,py34}: pytest==4.6.11 {py35,py36,py37,py38}: pytest==5.2.4 # For code coverage {[testenv:coverage_report]deps} - pytest-cov==2.8.1 + pytest-cov==2.10.0 # For hypothesis. Note Python 3.4 isn't supported by hypothesis. - py27: hypothesis==4.43.3 # rq.filter: <4.44 - {py35,py36,py37,py38,py39,py2,py3}: hypothesis==5.8.4 - py27: mock==3.0.5 + py27: hypothesis==4.43.9 # rq.filter: <4.44 + {py35,py36,py37,py38,py39,py2,py3}: hypothesis==5.8.6 + py27: mock==4.0.2 setenv = {[default]setenv} @@ -120,10 +120,10 @@ skip_install = True deps = flake8-bugbear==20.1.4 - flake8==3.7.9 + flake8==3.8.3 mccabe==0.6.1 - pep8-naming==0.10.0 - pycodestyle==2.5.0 + pep8-naming==0.11.1 + pycodestyle==2.6.0 pydocstyle==5.0.2 # pin pyflakes pending a release with https://github.com/PyCQA/pyflakes/pull/455 git+git://github.com/PyCQA/pyflakes@ffe9386#egg=pyflakes @@ -183,7 +183,7 @@ description = run Mypy (static type checker) basepython = {[default]basepython} deps = - mypy==0.770 + mypy==0.782 {[default]deps} @@ -275,7 +275,7 @@ skip_install = True deps = {[testenv:coverage_report]deps} - codecov==2.1.4 + codecov==2.1.7 passenv = # See https://github.com/codecov/codecov-python/blob/master/README.md#using-tox @@ -318,7 +318,7 @@ basepython = {[default]basepython} deps = Sphinx==2.4.4 - sphinx-rtd-theme==0.4.3 + sphinx-rtd-theme==0.5.0 commands = sphinx-build \ @@ -358,8 +358,8 @@ basepython = {[default]basepython} skip_install = True deps = - check-manifest==0.41 - readme-renderer==25.0 + check-manifest==0.42 + readme-renderer==26.0 twine==3.1.1 commands = From 0205363288f05a15aae19fe4f7acfe7c7c02998e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Tue, 23 Jun 2020 14:26:26 -0700 Subject: [PATCH 269/419] Updates, put a cap on Sphinx. Add setup.py to flake8's radar. --- setup.py | 4 ++-- tox.ini | 30 +++++++++++++++--------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/setup.py b/setup.py index 2f09f9cd..2e771699 100644 --- a/setup.py +++ b/setup.py @@ -27,11 +27,11 @@ url=__url__, packages=find_packages(where="src"), package_dir={"": "src"}, - package_data=dict(hyperlink=["py.typed",],), + package_data=dict(hyperlink=["py.typed"]), zip_safe=False, license=__license__, platforms="any", - install_requires=["idna>=2.5", 'typing ; python_version<"3.5"',], + install_requires=["idna>=2.5", 'typing ; python_version<"3.5"'], python_requires=">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*", classifiers=[ "Topic :: Utilities", diff --git a/tox.ini b/tox.ini index fa089ba3..943534fb 100644 --- a/tox.ini +++ b/tox.ini @@ -51,17 +51,17 @@ deps = {py26,py27}: typing==3.7.4.1 # For pytest - {py26,py27,py34}: pytest==4.6.9 + {py26,py27,py34}: pytest==4.6.11 {py35,py36,py37,py38}: pytest==5.2.4 # For code coverage {[testenv:coverage_report]deps} - pytest-cov==2.8.1 + pytest-cov==2.10.0 # For hypothesis. Note Python 3.4 isn't supported by hypothesis. - py27: hypothesis==4.43.3 # rq.filter: <4.44 - {py35,py36,py37,py38,py39,py2,py3}: hypothesis==5.8.4 - py27: mock==3.0.5 + py27: hypothesis==4.43.9 # rq.filter: <4.44 + {py35,py36,py37,py38,py39,py2,py3}: hypothesis==5.8.6 + py27: mock==3.0.5 # rq.filter: <4 setenv = {[default]setenv} @@ -94,7 +94,7 @@ setenv = BLACK_LINT_ARGS=--check commands = - black {env:BLACK_LINT_ARGS:} setup.py src + black {env:BLACK_LINT_ARGS:} {posargs:setup.py src} [testenv:black-reformat] @@ -120,16 +120,16 @@ skip_install = True deps = flake8-bugbear==20.1.4 - flake8==3.7.9 + flake8==3.8.3 mccabe==0.6.1 - pep8-naming==0.10.0 - pycodestyle==2.5.0 + pep8-naming==0.11.1 + pycodestyle==2.6.0 pydocstyle==5.0.2 # pin pyflakes pending a release with https://github.com/PyCQA/pyflakes/pull/455 git+git://github.com/PyCQA/pyflakes@ffe9386#egg=pyflakes commands = - flake8 {posargs:src/{env:PY_MODULE}} + flake8 {posargs:setup.py src/{env:PY_MODULE}} [flake8] @@ -183,7 +183,7 @@ description = run Mypy (static type checker) basepython = {[default]basepython} deps = - mypy==0.770 + mypy==0.782 {[default]deps} @@ -275,7 +275,7 @@ skip_install = True deps = {[testenv:coverage_report]deps} - codecov==2.1.4 + codecov==2.1.7 passenv = # See https://github.com/codecov/codecov-python/blob/master/README.md#using-tox @@ -318,7 +318,7 @@ basepython = {[default]basepython} deps = Sphinx==2.4.4 - sphinx-rtd-theme==0.4.3 + sphinx-rtd-theme==0.5.0 commands = sphinx-build \ @@ -358,8 +358,8 @@ basepython = {[default]basepython} skip_install = True deps = - check-manifest==0.41 - readme-renderer==25.0 + check-manifest==0.42 + readme-renderer==26.0 twine==3.1.1 commands = From 3546e129e04f4d00e999f130f4a65eef232b2b28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Tue, 23 Jun 2020 14:46:17 -0700 Subject: [PATCH 270/419] Tox cleanup --- tox.ini | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/tox.ini b/tox.ini index 943534fb..8b9f1c8e 100644 --- a/tox.ini +++ b/tox.ini @@ -48,20 +48,21 @@ deps = {[default]deps} # In Python 2, we need to pull in typing - {py26,py27}: typing==3.7.4.1 + py{26,27,py2}: typing==3.7.4.1 + py{26,27,py2}: mock==3.0.5 # rq.filter: <4 # For pytest - {py26,py27,py34}: pytest==4.6.11 - {py35,py36,py37,py38}: pytest==5.2.4 + py{26,27,34,py2}: pytest==4.6.11 + py{34,35,36,37,38,39,py3}: pytest==5.2.4 # For code coverage {[testenv:coverage_report]deps} - pytest-cov==2.10.0 + py{26,27,34,py2}: pytest-cov==2.8.1 # rq.filter: <2.9 + py{34,35,36,37,38,39,py3}: pytest-cov==2.10.0 # For hypothesis. Note Python 3.4 isn't supported by hypothesis. - py27: hypothesis==4.43.9 # rq.filter: <4.44 - {py35,py36,py37,py38,py39,py2,py3}: hypothesis==5.8.6 - py27: mock==3.0.5 # rq.filter: <4 + py{26,27,py2}: hypothesis==4.43.9 # rq.filter: <4.44 + py{35,36,37,38,39,py3}: hypothesis==5.8.6 setenv = {[default]setenv} @@ -239,7 +240,7 @@ ignore_missing_imports = True description = generate coverage report -depends = test-py{26,27,34,35,36,37,38,py,py3} +depends = test-py{26,27,34,35,36,37,38,39,py2,py3} basepython = {[default]basepython} From f8a2db5e6bf1bb46b8bb53b8c2f66d49f056c5f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Tue, 23 Jun 2020 14:56:07 -0700 Subject: [PATCH 271/419] Fix pytest spec for py3.5 --- tox.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tox.ini b/tox.ini index 8b9f1c8e..b963a019 100644 --- a/tox.ini +++ b/tox.ini @@ -47,13 +47,13 @@ basepython = deps = {[default]deps} - # In Python 2, we need to pull in typing + # In Python 2, we need to pull in typing, mock py{26,27,py2}: typing==3.7.4.1 py{26,27,py2}: mock==3.0.5 # rq.filter: <4 # For pytest - py{26,27,34,py2}: pytest==4.6.11 - py{34,35,36,37,38,39,py3}: pytest==5.2.4 + py{26,27,34,py2}: pytest==4.6.11 # rq.filter: <5 + py{35,36,37,38,39,py3}: pytest==5.2.4 # For code coverage {[testenv:coverage_report]deps} From 2f493a86bbf00b6133a8ccb319245c535030e364 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Tue, 23 Jun 2020 15:03:13 -0700 Subject: [PATCH 272/419] Fix pytest-cov spec for Py3.4 --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index b963a019..bf84fa5c 100644 --- a/tox.ini +++ b/tox.ini @@ -58,7 +58,7 @@ deps = # For code coverage {[testenv:coverage_report]deps} py{26,27,34,py2}: pytest-cov==2.8.1 # rq.filter: <2.9 - py{34,35,36,37,38,39,py3}: pytest-cov==2.10.0 + py{35,36,37,38,39,py3}: pytest-cov==2.10.0 # For hypothesis. Note Python 3.4 isn't supported by hypothesis. py{26,27,py2}: hypothesis==4.43.9 # rq.filter: <4.44 From 385789549596944dc442a10f418c01116d0e0aee Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Wed, 24 Jun 2020 03:11:11 -0700 Subject: [PATCH 273/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index de8d17cf..5e817c08 100644 --- a/tox.ini +++ b/tox.ini @@ -360,7 +360,7 @@ skip_install = True deps = check-manifest==0.42 readme-renderer==26.0 - twine==3.1.1 + twine==3.2.0 commands = check-manifest From 8b610727b009478a9a8a5e78151ce81a63caa28c Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Thu, 25 Jun 2020 00:24:22 -0700 Subject: [PATCH 274/419] [requires.io] dependency update --- tox.ini | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/tox.ini b/tox.ini index 5e817c08..dae214e4 100644 --- a/tox.ini +++ b/tox.ini @@ -47,21 +47,22 @@ basepython = deps = {[default]deps} - # In Python 2, we need to pull in typing - {py26,py27}: typing==3.7.4.1 + # In Python 2, we need to pull in typing, mock + py{26,27,py2}: typing==3.7.4.1 + py{26,27,py2}: mock==3.0.5 # rq.filter: <4 # For pytest - {py26,py27,py34}: pytest==4.6.11 - {py35,py36,py37,py38}: pytest==5.2.4 + py{26,27,34,py2}: pytest==4.6.11 # rq.filter: <5 + py{35,36,37,38,39,py3}: pytest==5.2.4 # For code coverage {[testenv:coverage_report]deps} - pytest-cov==2.10.0 + py{26,27,34,py2}: pytest-cov==2.8.1 # rq.filter: <2.9 + py{35,36,37,38,39,py3}: pytest-cov==2.10.0 # For hypothesis. Note Python 3.4 isn't supported by hypothesis. - py27: hypothesis==4.43.9 - {py35,py36,py37,py38,py39,py2,py3}: hypothesis==5.8.6 - py27: mock==4.0.2 + py{26,27,py2}: hypothesis==4.43.9 # rq.filter: <4.44 + py{35,36,37,38,39,py3}: hypothesis==5.8.6 setenv = {[default]setenv} @@ -94,7 +95,7 @@ setenv = BLACK_LINT_ARGS=--check commands = - black {env:BLACK_LINT_ARGS:} setup.py src + black {env:BLACK_LINT_ARGS:} {posargs:setup.py src} [testenv:black-reformat] @@ -129,7 +130,7 @@ deps = git+git://github.com/PyCQA/pyflakes@ffe9386#egg=pyflakes commands = - flake8 {posargs:src/{env:PY_MODULE}} + flake8 {posargs:setup.py src/{env:PY_MODULE}} [flake8] @@ -239,7 +240,7 @@ ignore_missing_imports = True description = generate coverage report -depends = test-py{26,27,34,35,36,37,38,py,py3} +depends = test-py{26,27,34,35,36,37,38,39,py2,py3} basepython = {[default]basepython} From 16addb50f6e25cc9f6ce9493e68b4b734a18acf1 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Thu, 25 Jun 2020 00:24:23 -0700 Subject: [PATCH 275/419] [requires.io] dependency update --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 2f09f9cd..2e771699 100644 --- a/setup.py +++ b/setup.py @@ -27,11 +27,11 @@ url=__url__, packages=find_packages(where="src"), package_dir={"": "src"}, - package_data=dict(hyperlink=["py.typed",],), + package_data=dict(hyperlink=["py.typed"]), zip_safe=False, license=__license__, platforms="any", - install_requires=["idna>=2.5", 'typing ; python_version<"3.5"',], + install_requires=["idna>=2.5", 'typing ; python_version<"3.5"'], python_requires=">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*", classifiers=[ "Topic :: Utilities", From 0cac45d2c81ad4a2a530c5e6555b0fe852fdea87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Thu, 25 Jun 2020 14:39:38 -0700 Subject: [PATCH 276/419] Revert docstring changes --- src/hyperlink/_url.py | 195 ++++++++++++++++++++++-------------------- 1 file changed, 100 insertions(+), 95 deletions(-) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index 4fb84133..4867d58c 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -478,12 +478,12 @@ def register_scheme(text, uses_netloc=True, default_port=None): `file an issue`_! Args: - text: A string representation of the scheme. + text (Text): A string representation of the scheme. (the 'http' in 'http://hatnote.com') - uses_netloc: Does the scheme support specifying a + uses_netloc (bool): Does the scheme support specifying a network host? For instance, "http" does, "mailto" does not. Defaults to True. - default_port: The default port, if any, for + default_port (Optional[int]): The default port, if any, for netloc-using schemes. .. _file an issue: https://github.com/mahmoud/hyperlink/issues @@ -927,36 +927,38 @@ class URL(object): constructor arguments is below. Args: - scheme: The text name of the scheme. - host: The host portion of the network location - port: The port part of the network location. If ``None`` or no port is - passed, the port will default to the default port of the scheme, if - it is known. See the ``SCHEME_PORT_MAP`` and - :func:`register_default_port` for more info. - path: A tuple of strings representing the slash-separated parts of the - path. - query: The query parameters, as a dictionary or as an sequence of - key-value pairs. - fragment: The fragment part of the URL. - rooted: A rooted URL is one which indicates an absolute path. - This is True on any URL that includes a host, or any relative URL - that starts with a slash. - userinfo: The username or colon-separated username:password pair. - uses_netloc: Indicates whether ``://`` (the "netloc separator") will - appear to separate the scheme from the *path* in cases where no - host is present. - Setting this to ``True`` is a non-spec-compliant affordance for the - common practice of having URIs that are *not* URLs (cannot have a - 'host' part) but nevertheless use the common ``://`` idiom that - most people associate with URLs; e.g. ``message:`` URIs like - ``message://message-id`` being equivalent to ``message:message-id``. - This may be inferred based on the scheme depending on whether - :func:`register_scheme` has been used to register the scheme and - should not be passed directly unless you know the scheme works like - this and you know it has not been registered. - - All of these parts are also exposed as read-only attributes of :class:`URL` - instances, along with several useful methods. + scheme (Optional[Text]): The text name of the scheme. + host (Optional[Text]): The host portion of the network location + port (Optional[int]): The port part of the network location. If + ``None`` or no port is passed, the port will default to + the default port of the scheme, if it is known. See the + ``SCHEME_PORT_MAP`` and :func:`register_default_port` + for more info. + path (Iterable[Text]): A tuple of strings representing the + slash-separated parts of the path. + query (Sequence[Tuple[Text, Optional[Text]]]): The query parameters, as + a dictionary or as an sequence of key-value pairs. + fragment (Text): The fragment part of the URL. + rooted (bool): A rooted URL is one which indicates an absolute path. + This is True on any URL that includes a host, or any relative URL + that starts with a slash. + userinfo (Text): The username or colon-separated + username:password pair. + uses_netloc (Optional[bool]): Indicates whether ``://`` (the "netloc + separator") will appear to separate the scheme from the *path* in + cases where no host is present. Setting this to ``True`` is a + non-spec-compliant affordance for the common practice of having URIs + that are *not* URLs (cannot have a 'host' part) but nevertheless use + the common ``://`` idiom that most people associate with URLs; + e.g. ``message:`` URIs like ``message://message-id`` being + equivalent to ``message:message-id``. This may be inferred based on + the scheme depending on whether :func:`register_scheme` has been + used to register the scheme and should not be passed directly unless + you know the scheme works like this and you know it has not been + registered. + + All of these parts are also exposed as read-only attributes of + URL instances, along with several useful methods. .. _RFC 3986: https://tools.ietf.org/html/rfc3986 .. _RFC 3987: https://tools.ietf.org/html/rfc3987 @@ -1185,9 +1187,9 @@ def authority(self, with_password=False, **kw): u'user:pass@localhost:8080' Args: - with_password: Whether the return value of this method include the - password in the URL, if it is set. - Defaults to False. + with_password (bool): Whether the return value of this + method include the password in the URL, if it is + set. Defaults to False. Returns: Text: The authority (network location and user information) portion @@ -1296,29 +1298,32 @@ def replace( the value on the current URL. Args: - scheme: The text name of the scheme. - host: The host portion of the network location. - path: A tuple of strings representing the slash-separated parts of - the path. - query: The query parameters, as a dictionary or as an sequence of - key-value pairs. - fragment: The fragment part of the URL. - port: The port part of the network location. - rooted: Whether or not the path begins with a slash. - userinfo: The username or colon-separated username:password pair. - uses_netloc: Indicates whether ``://`` (the "netloc separator") - will appear to separate the scheme from the *path* in cases - where no host is present. - Setting this to ``True`` is a non-spec-compliant affordance for - the common practice of having URIs that are *not* URLs (cannot - have a 'host' part) but nevertheless use the common ``://`` - idiom that most people associate with URLs; e.g. ``message:`` - URIs like ``message://message-id`` being equivalent to - ``message:message-id``. - This may be inferred based on the scheme depending on whether - :func:`register_scheme` has been used to register the scheme - and should not be passed directly unless you know the scheme - works like this and you know it has not been registered. + scheme (Optional[Text]): The text name of the scheme. + host (Optional[Text]): The host portion of the network location. + path (Iterable[Text]): A tuple of strings representing the + slash-separated parts of the path. + query (Sequence[Tuple[Text, Optional[Text]]]): The query + parameters, as a dictionary or as an sequence of key-value + pairs. + fragment (Text): The fragment part of the URL. + port (Optional[int]): The port part of the network location. + rooted (Optional[bool]): Whether or not the path begins with a + slash. + userinfo (Text): The username or colon-separated username:password + pair. + uses_netloc (bool): Indicates whether ``://`` (the "netloc + separator") will appear to separate the scheme from the *path* + in cases where no host is present. Setting this to ``True`` is + a non-spec-compliant affordance for the common practice of + having URIs that are *not* URLs (cannot have a 'host' part) but + nevertheless use the common ``://`` idiom that most people + associate with URLs; e.g. ``message:`` URIs like + ``message://message-id`` being equivalent to + ``message:message-id``. This may be inferred based on the + scheme depending on whether :func:`register_scheme` has been + used to register the scheme and should not be passed directly + unless you know the scheme works like this and you know it has + not been registered. Returns: URL: A copy of the current :class:`URL`, with new values for @@ -1358,7 +1363,7 @@ def from_text(cls, text): sure to decode those bytestrings. Args: - text: A valid URL string. + text (Text): A valid URL string. Returns: URL: The structured object version of the parsed string. @@ -1464,14 +1469,15 @@ def normalize( name. Args: - scheme: Convert the scheme to lowercase - host: Convert the host to lowercase - path: Normalize the path (see above for details) - query: Normalize the query string - fragment: Normalize the fragment - userinfo: Normalize the userinfo - percents: Encode isolated percent signs for any percent-encoded - fields which are being normalized (defaults to `True`). + scheme (bool): Convert the scheme to lowercase + host (bool): Convert the host to lowercase + path (bool): Normalize the path (see above for details) + query (bool): Normalize the query string + fragment (bool): Normalize the fragment + userinfo (bool): Normalize the userinfo + percents (bool): Encode isolated percent signs for any + percent-encoded fields which are being normalized + (defaults to True). >>> url = URL.from_text(u'Http://example.COM/a/../b/./c%2f?%61%') >>> print(url.normalize().to_text()) @@ -1531,9 +1537,9 @@ def child(self, *segments): u'http://localhost/a/b/c/d?x=y' Args: - segments: Additional parts to be joined and added to the path, like - :func:`os.path.join`. Special characters in segments will be - percent encoded. + segments (Text): Additional parts to be joined and added to + the path, like :func:`os.path.join`. Special characters + in segments will be percent encoded. Returns: URL: A copy of the current URL with the extra path segments. @@ -1556,7 +1562,7 @@ def sibling(self, segment): sibling of this URL path. Args: - segment: A single path segment. + segment (Text): A single path segment. Returns: URL: A copy of the current URL with the last path segment @@ -1855,11 +1861,11 @@ def add(self, name, value=None): URL.from_text(u'https://example.com/?x=y&x=z') Args: - name: The name of the query parameter to add. + name (Text): The name of the query parameter to add. The part before the ``=``. - value: The value of the query parameter to add. - The part after the ``=``. - Defaults to ``None``, meaning no value. + value (Optional[Text]): The value of the query parameter to add. + The part after the ``=``. Defaults to ``None``, meaning no + value. Returns: URL: A new :class:`URL` instance with the parameter added. @@ -1878,11 +1884,11 @@ def set(self, name, value=None): URL.from_text(u'https://example.com/?x=z') Args: - name: The name of the query parameter to set. + name (Text): The name of the query parameter to set. The part before the ``=``. - value: The value of the query parameter to set. - The part after the ``=``. - Defaults to ``None``, meaning no value. + value (Optional[Text]): The value of the query parameter to set. + The part after the ``=``. Defaults to ``None``, meaning no + value. Returns: URL: A new :class:`URL` instance with the parameter set. @@ -1909,7 +1915,7 @@ def get(self, name): list is always returned, and this method raises no exceptions. Args: - name: The name of the query parameter to get. + name (Text): The name of the query parameter to get. Returns: List[Optional[Text]]: A list of all the values associated with the @@ -1930,11 +1936,12 @@ def remove( parameter is not already set. Args: - name: The name of the query parameter to remove. - value: Optional value to additionally filter on. + name (Text): The name of the query parameter to remove. + value (Text): Optional value to additionally filter on. Setting this removes query parameters which match both name and value. - limit: Optional maximum number of parameters to remove. + limit (Optional[int]): Optional maximum number of parameters to + remove. Returns: URL: A new :class:`URL` instance with the parameter removed. @@ -1994,10 +2001,9 @@ class DecodedURL(object): special characters encoded with codecs other than UTF-8. Args: - url: A :class:`URL` object to wrap. - lazy: Set to True to avoid pre-decode all parts of the URL to check for - validity. - Defaults to False. + url (URL): A :class:`URL` object to wrap. + lazy (bool): Set to True to avoid pre-decode all parts of the URL to + check for validity. Defaults to False. .. note:: @@ -2028,10 +2034,9 @@ def from_text(cls, text, lazy=False): Make a `DecodedURL` instance from any text string containing a URL. Args: - text: Text containing the URL - lazy: Whether to pre-decode all parts of the URL to check for - validity. - Defaults to True. + text (Text): Text containing the URL + lazy (bool): Whether to pre-decode all parts of the URL to check for + validity. Defaults to True. """ _url = URL.from_text(text) return cls(_url, lazy=lazy) @@ -2381,16 +2386,16 @@ def parse(url, decoded=True, lazy=False): https://github.com/python-hyper/hyperlink Args: - url: A text string representation of a URL. + url (str): A text string representation of a URL. - decoded: Whether or not to return a :class:`DecodedURL`, + decoded (bool): Whether or not to return a :class:`DecodedURL`, which automatically handles all encoding/decoding/quoting/unquoting for all the various accessors of parts of the URL, or a :class:`URL`, which has the same API, but requires handling of special characters for different parts of the URL. - lazy: In the case of `decoded=True`, this controls + lazy (bool): In the case of `decoded=True`, this controls whether the URL is decoded immediately or as accessed. The default, `lazy=False`, checks all encoded parts of the URL for decodability. From bbbdebe69f130d5831661d120cf384e61f9a3b4b Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sat, 27 Jun 2020 17:00:15 -0700 Subject: [PATCH 277/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 8aeea44d..dae214e4 100644 --- a/tox.ini +++ b/tox.ini @@ -318,7 +318,7 @@ description = build documentation basepython = {[default]basepython} deps = - Sphinx==2.4.4 + Sphinx==3.1.1 sphinx-rtd-theme==0.5.0 commands = From e253ff20ac1c952fde2d9f72be56b89d7d019f27 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sun, 5 Jul 2020 03:56:32 -0700 Subject: [PATCH 278/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index dae214e4..1f926087 100644 --- a/tox.ini +++ b/tox.ini @@ -318,7 +318,7 @@ description = build documentation basepython = {[default]basepython} deps = - Sphinx==3.1.1 + Sphinx==3.1.2 sphinx-rtd-theme==0.5.0 commands = From 3c5277dd4e84addc5b342c62182716b09babb28a Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Mon, 6 Jul 2020 10:08:58 -0700 Subject: [PATCH 279/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 1f926087..162ea394 100644 --- a/tox.ini +++ b/tox.ini @@ -247,7 +247,7 @@ basepython = {[default]basepython} skip_install = True deps = - coverage==5.1 + coverage==5.2 setenv = {[default]setenv} From 50f946da8e43366d319b8f0f09b0ea531f704a7f Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Thu, 9 Jul 2020 17:24:32 -0700 Subject: [PATCH 280/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 162ea394..0b3f0175 100644 --- a/tox.ini +++ b/tox.ini @@ -48,7 +48,7 @@ deps = {[default]deps} # In Python 2, we need to pull in typing, mock - py{26,27,py2}: typing==3.7.4.1 + py{26,27,py2}: typing==3.7.4.2 py{26,27,py2}: mock==3.0.5 # rq.filter: <4 # For pytest From 7404f2426964f0a951031f5c92e2bf3adebf07ff Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Mon, 13 Jul 2020 16:21:30 -0700 Subject: [PATCH 281/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 0b3f0175..3e955758 100644 --- a/tox.ini +++ b/tox.ini @@ -48,7 +48,7 @@ deps = {[default]deps} # In Python 2, we need to pull in typing, mock - py{26,27,py2}: typing==3.7.4.2 + py{26,27,py2}: typing==3.7.4.3 py{26,27,py2}: mock==3.0.5 # rq.filter: <4 # For pytest From 7c4eeae844e9bb5b98bffa63424952cd83f06096 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Wed, 15 Jul 2020 19:15:53 -0700 Subject: [PATCH 282/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 3e955758..2fc070f1 100644 --- a/tox.ini +++ b/tox.ini @@ -276,7 +276,7 @@ skip_install = True deps = {[testenv:coverage_report]deps} - codecov==2.1.7 + codecov==2.1.8 passenv = # See https://github.com/codecov/codecov-python/blob/master/README.md#using-tox From dd4813aa493fa94ed0e8c9f3663dcb51b5139ec3 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Fri, 24 Jul 2020 05:08:15 -0700 Subject: [PATCH 283/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 2fc070f1..3b9ebba7 100644 --- a/tox.ini +++ b/tox.ini @@ -247,7 +247,7 @@ basepython = {[default]basepython} skip_install = True deps = - coverage==5.2 + coverage==5.2.1 setenv = {[default]setenv} From 2a56774f7c65cc1cd43e2e1718d90a53efa688cc Mon Sep 17 00:00:00 2001 From: Mahmoud Hashemi Date: Mon, 3 Aug 2020 23:31:09 -0700 Subject: [PATCH 284/419] bump version for v20.0.0 release --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 2e771699..4c20cbe2 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ __author__ = "Mahmoud Hashemi and Glyph Lefkowitz" -__version__ = "19.0.1dev" +__version__ = "20.0.0" __contact__ = "mahmoud@hatnote.com" __url__ = "https://github.com/python-hyper/hyperlink" __license__ = "MIT" From 039511b027502edd2cca50672a4ef3b3624a7729 Mon Sep 17 00:00:00 2001 From: Mahmoud Hashemi Date: Mon, 3 Aug 2020 23:34:25 -0700 Subject: [PATCH 285/419] finalize v20.0.0 changelog --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b23dfb44..a8868a7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Hyperlink Changelog -## dev (not yet released) +## 20.0.0 + +*(August 3, 2020)* * CPython 3.7 and 3.8 and PyPy3 added to test matrix * Hyperlink now has type hints and they are now exported per From 4487046042caa565c924e5172a8fcab0aafd6f8c Mon Sep 17 00:00:00 2001 From: Mahmoud Hashemi Date: Mon, 3 Aug 2020 23:35:40 -0700 Subject: [PATCH 286/419] fix pypi badge typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e1936ca1..017f9eb8 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Documentation - PyPI + PyPI Calendar Versioning From b1db333d679ed99cb9a9ac51f352d8aee134b44a Mon Sep 17 00:00:00 2001 From: Mahmoud Hashemi Date: Mon, 3 Aug 2020 23:36:35 -0700 Subject: [PATCH 287/419] bump docs version for 20.0 --- docs/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index b6e0155c..62c22532 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -64,8 +64,8 @@ copyright = u'2018, Mahmoud Hashemi' author = u'Mahmoud Hashemi' -version = '19.0' -release = '19.0.0' +version = '20.0' +release = '20.0.0' if os.name != 'nt': today_fmt = '%B %d, %Y' From 3ba3c878a2dfc67dffa3136c8a64c749847394c1 Mon Sep 17 00:00:00 2001 From: Mahmoud Hashemi Date: Mon, 3 Aug 2020 23:37:33 -0700 Subject: [PATCH 288/419] bump version for v20.0.1dev --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4c20cbe2..7b57b830 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ __author__ = "Mahmoud Hashemi and Glyph Lefkowitz" -__version__ = "20.0.0" +__version__ = "20.0.1dev" __contact__ = "mahmoud@hatnote.com" __url__ = "https://github.com/python-hyper/hyperlink" __license__ = "MIT" From 4393561d71ecc11d579cdf741ff1c223bbc33ea6 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Mon, 3 Aug 2020 23:37:45 -0700 Subject: [PATCH 289/419] [requires.io] dependency update --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 2e771699..7b57b830 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ __author__ = "Mahmoud Hashemi and Glyph Lefkowitz" -__version__ = "19.0.1dev" +__version__ = "20.0.1dev" __contact__ = "mahmoud@hatnote.com" __url__ = "https://github.com/python-hyper/hyperlink" __license__ = "MIT" From dd1e3c755928fb48fa58a12394d7ec57f1650f12 Mon Sep 17 00:00:00 2001 From: Mahmoud Hashemi Date: Tue, 4 Aug 2020 20:33:16 -0700 Subject: [PATCH 290/419] bump version for v20.0.1 release --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 7b57b830..157586f1 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ __author__ = "Mahmoud Hashemi and Glyph Lefkowitz" -__version__ = "20.0.1dev" +__version__ = "20.0.1" __contact__ = "mahmoud@hatnote.com" __url__ = "https://github.com/python-hyper/hyperlink" __license__ = "MIT" From 43aede180661b3379319fb2eab595b16aee1d72d Mon Sep 17 00:00:00 2001 From: Mahmoud Hashemi Date: Tue, 4 Aug 2020 20:36:54 -0700 Subject: [PATCH 291/419] changelog and docs version bump for v20.0.1 --- CHANGELOG.md | 8 ++++++++ docs/conf.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8868a7a..2371d814 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Hyperlink Changelog +## 20.0.1 + +*(August 4, 2020)* + +Rerelease to fix packaging metadata around conditional requirements. +See [issue #133](https://github.com/python-hyper/hyperlink/issues/133) +for more details. + ## 20.0.0 *(August 3, 2020)* diff --git a/docs/conf.py b/docs/conf.py index 62c22532..c4f1d68e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -65,7 +65,7 @@ author = u'Mahmoud Hashemi' version = '20.0' -release = '20.0.0' +release = '20.0.1' if os.name != 'nt': today_fmt = '%B %d, %Y' From 00ec5525a34cd8ae36e6425bc69f47564bcd839c Mon Sep 17 00:00:00 2001 From: Mahmoud Hashemi Date: Tue, 4 Aug 2020 20:37:54 -0700 Subject: [PATCH 292/419] bump version for v20.0.2dev --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 157586f1..7e94a2ff 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ __author__ = "Mahmoud Hashemi and Glyph Lefkowitz" -__version__ = "20.0.1" +__version__ = "20.0.2dev" __contact__ = "mahmoud@hatnote.com" __url__ = "https://github.com/python-hyper/hyperlink" __license__ = "MIT" From 58e4418599c95728ccd79d0e979d8d63b381c63b Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sat, 8 Aug 2020 02:30:58 -0700 Subject: [PATCH 293/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 3b9ebba7..ec3cf899 100644 --- a/tox.ini +++ b/tox.ini @@ -318,7 +318,7 @@ description = build documentation basepython = {[default]basepython} deps = - Sphinx==3.1.2 + Sphinx==3.2.0 sphinx-rtd-theme==0.5.0 commands = From 5a06bea720863d6e992b778240dba5c9f20d83d9 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sat, 8 Aug 2020 02:30:59 -0700 Subject: [PATCH 294/419] [requires.io] dependency update --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 7b57b830..7e94a2ff 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ __author__ = "Mahmoud Hashemi and Glyph Lefkowitz" -__version__ = "20.0.1dev" +__version__ = "20.0.2dev" __contact__ = "mahmoud@hatnote.com" __url__ = "https://github.com/python-hyper/hyperlink" __license__ = "MIT" From 59489c462bcdcf5a8f78ff61dec7a7aeaff31ce3 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Tue, 11 Aug 2020 08:24:38 -0700 Subject: [PATCH 295/419] [requires.io] dependency update From c0e6977c36479d680c8dcf4de62bf897d4549cd1 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Fri, 14 Aug 2020 04:15:14 -0700 Subject: [PATCH 296/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index ec3cf899..ecb7d430 100644 --- a/tox.ini +++ b/tox.ini @@ -318,7 +318,7 @@ description = build documentation basepython = {[default]basepython} deps = - Sphinx==3.2.0 + Sphinx==3.2.1 sphinx-rtd-theme==0.5.0 commands = From 59186f319901776fcc84e4c0b5cb4317931c03bd Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Fri, 14 Aug 2020 10:34:18 -0700 Subject: [PATCH 297/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index ecb7d430..66b277de 100644 --- a/tox.ini +++ b/tox.ini @@ -58,7 +58,7 @@ deps = # For code coverage {[testenv:coverage_report]deps} py{26,27,34,py2}: pytest-cov==2.8.1 # rq.filter: <2.9 - py{35,36,37,38,39,py3}: pytest-cov==2.10.0 + py{35,36,37,38,39,py3}: pytest-cov==2.10.1 # For hypothesis. Note Python 3.4 isn't supported by hypothesis. py{26,27,py2}: hypothesis==4.43.9 # rq.filter: <4.44 From a0fa2d5dcc03c2ba286b4d17b567a666ffb0755d Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Fri, 21 Aug 2020 09:57:25 -0700 Subject: [PATCH 298/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 66b277de..9ae457fe 100644 --- a/tox.ini +++ b/tox.ini @@ -276,7 +276,7 @@ skip_install = True deps = {[testenv:coverage_report]deps} - codecov==2.1.8 + codecov==2.1.9 passenv = # See https://github.com/codecov/codecov-python/blob/master/README.md#using-tox From 0a8b810009d37d3e58dc575dafe5f3bdf1e1c41f Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sat, 22 Aug 2020 14:29:49 -0700 Subject: [PATCH 299/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 9ae457fe..ca25c5a4 100644 --- a/tox.ini +++ b/tox.ini @@ -125,7 +125,7 @@ deps = mccabe==0.6.1 pep8-naming==0.11.1 pycodestyle==2.6.0 - pydocstyle==5.0.2 + pydocstyle==5.1.0 # pin pyflakes pending a release with https://github.com/PyCQA/pyflakes/pull/455 git+git://github.com/PyCQA/pyflakes@ffe9386#egg=pyflakes From ac896805ccc193f6e288f0cea737b0ee303946a2 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Wed, 26 Aug 2020 07:29:06 -0700 Subject: [PATCH 300/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index ca25c5a4..860fc6f9 100644 --- a/tox.ini +++ b/tox.ini @@ -89,7 +89,7 @@ basepython = {[default]basepython} skip_install = True deps = - black==19.10b0 + black==20.8b0 setenv = BLACK_LINT_ARGS=--check From cfb4fc5c42ee137159e244ba9db3296c489a6f82 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Wed, 26 Aug 2020 08:54:05 -0700 Subject: [PATCH 301/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 860fc6f9..901645a9 100644 --- a/tox.ini +++ b/tox.ini @@ -89,7 +89,7 @@ basepython = {[default]basepython} skip_install = True deps = - black==20.8b0 + black==20.8b1 setenv = BLACK_LINT_ARGS=--check From 0843a8a5cb5492ec4c551d748d502ce938eaf357 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez=20Vega?= Date: Fri, 28 Aug 2020 09:50:01 -0700 Subject: [PATCH 302/419] Install IDNA tables file. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 7e94a2ff..97646a82 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ url=__url__, packages=find_packages(where="src"), package_dir={"": "src"}, - package_data=dict(hyperlink=["py.typed"]), + package_data=dict(hyperlink=["py.typed", "idna-tables-properties.csv.gz"]), zip_safe=False, license=__license__, platforms="any", From 533415dfd61f56f247f1bc1bf3345971caa43128 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez=20Vega?= Date: Fri, 28 Aug 2020 10:19:04 -0700 Subject: [PATCH 303/419] Try moving comment out of rq.filter line to make requests.io happy --- tox.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 7ec3b2fb..6fe4f8b1 100644 --- a/tox.ini +++ b/tox.ini @@ -247,7 +247,8 @@ basepython = {[default]basepython} skip_install = True deps = - coverage==4.5.4 # rq.filter: <5 # coverage 5.0 drops Python 3.4 support + # coverage 5.0 drops Python 3.4 support + coverage==4.5.4 # rq.filter: <5 setenv = {[default]setenv} From 2943bdf09056ab46710bdc5dfdd46193ab601416 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sat, 29 Aug 2020 13:59:11 -0700 Subject: [PATCH 304/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index be4fdc43..fcf27f2e 100644 --- a/tox.ini +++ b/tox.ini @@ -125,7 +125,7 @@ deps = mccabe==0.6.1 pep8-naming==0.11.1 pycodestyle==2.6.0 - pydocstyle==5.1.0 + pydocstyle==5.1.1 # pin pyflakes pending a release with https://github.com/PyCQA/pyflakes/pull/455 git+git://github.com/PyCQA/pyflakes@ffe9386#egg=pyflakes From 280ed3c6482def530ff969a70223e233a1e2f75d Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Mon, 31 Aug 2020 05:11:56 -0700 Subject: [PATCH 305/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index fcf27f2e..4a2a45ba 100644 --- a/tox.ini +++ b/tox.ini @@ -337,7 +337,7 @@ basepython = {[default]basepython} deps = {[testenv:docs]deps} - sphinx-autobuild==0.7.1 + sphinx-autobuild==2020.9.1 commands = sphinx-autobuild \ From f37c756b8f2d18e09b0a7b0ebb25faea78159390 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Mon, 21 Sep 2020 08:47:09 -0700 Subject: [PATCH 306/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 4a2a45ba..6b2471ac 100644 --- a/tox.ini +++ b/tox.ini @@ -360,7 +360,7 @@ basepython = {[default]basepython} skip_install = True deps = - check-manifest==0.42 + check-manifest==0.43 readme-renderer==26.0 twine==3.2.0 From 7401329bbe12524d538c7a395245d5d0165a9c13 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Fri, 2 Oct 2020 17:55:18 -0700 Subject: [PATCH 307/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 6b2471ac..024801e7 100644 --- a/tox.ini +++ b/tox.ini @@ -121,7 +121,7 @@ skip_install = True deps = flake8-bugbear==20.1.4 - flake8==3.8.3 + flake8==3.8.4 mccabe==0.6.1 pep8-naming==0.11.1 pycodestyle==2.6.0 From 7aca4b5a4549ef6c6d39ef9b1752ffb90b19f55f Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sat, 3 Oct 2020 02:06:38 -0700 Subject: [PATCH 308/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 024801e7..2c906ab0 100644 --- a/tox.ini +++ b/tox.ini @@ -360,7 +360,7 @@ basepython = {[default]basepython} skip_install = True deps = - check-manifest==0.43 + check-manifest==0.44 readme-renderer==26.0 twine==3.2.0 From a729742b2614d1028e4186a6aac6436e4b6d7d47 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Fri, 9 Oct 2020 05:41:58 -0700 Subject: [PATCH 309/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 2c906ab0..a64a544f 100644 --- a/tox.ini +++ b/tox.ini @@ -277,7 +277,7 @@ skip_install = True deps = {[testenv:coverage_report]deps} - codecov==2.1.9 + codecov==2.1.10 passenv = # See https://github.com/codecov/codecov-python/blob/master/README.md#using-tox From 113d9d5253bcb4a6d596dae762305fcf80151f1f Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Fri, 9 Oct 2020 09:54:42 -0700 Subject: [PATCH 310/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index a64a544f..d8b7bc8d 100644 --- a/tox.ini +++ b/tox.ini @@ -184,7 +184,7 @@ description = run Mypy (static type checker) basepython = {[default]basepython} deps = - mypy==0.782 + mypy==0.790 {[default]deps} From 2859273155a659ef0e402550faa30040b5a0dc3c Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Fri, 9 Oct 2020 11:16:08 -0700 Subject: [PATCH 311/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index d8b7bc8d..a789647c 100644 --- a/tox.ini +++ b/tox.ini @@ -361,7 +361,7 @@ skip_install = True deps = check-manifest==0.44 - readme-renderer==26.0 + readme-renderer==27.0 twine==3.2.0 commands = From 2b016fde09f6c578e75121d8c5072bfe652e7840 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Tue, 20 Oct 2020 17:43:51 -0700 Subject: [PATCH 312/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index a789647c..a8d9fb81 100644 --- a/tox.ini +++ b/tox.ini @@ -361,7 +361,7 @@ skip_install = True deps = check-manifest==0.44 - readme-renderer==27.0 + readme-renderer==28.0 twine==3.2.0 commands = From 433621c20ea08bec7a4d7fc0f93864a0428f87a0 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sun, 1 Nov 2020 12:08:05 -0800 Subject: [PATCH 313/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index a8d9fb81..f7b1d55f 100644 --- a/tox.ini +++ b/tox.ini @@ -360,7 +360,7 @@ basepython = {[default]basepython} skip_install = True deps = - check-manifest==0.44 + check-manifest==0.45 readme-renderer==28.0 twine==3.2.0 From 7f00957b79e68b1871c03dbc7e711d882e052d68 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Tue, 3 Nov 2020 09:52:09 -0800 Subject: [PATCH 314/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index f7b1d55f..1edc8e1b 100644 --- a/tox.ini +++ b/tox.ini @@ -319,7 +319,7 @@ description = build documentation basepython = {[default]basepython} deps = - Sphinx==3.2.1 + Sphinx==3.3.0 sphinx-rtd-theme==0.5.0 commands = From b6c20e1c8944b591435b44638118698aa769f774 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Thu, 12 Nov 2020 17:51:47 -0800 Subject: [PATCH 315/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 1edc8e1b..83f5c971 100644 --- a/tox.ini +++ b/tox.ini @@ -319,7 +319,7 @@ description = build documentation basepython = {[default]basepython} deps = - Sphinx==3.3.0 + Sphinx==3.3.1 sphinx-rtd-theme==0.5.0 commands = From 2607c743abba37c1eb666f30e2e135710eace957 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Mon, 23 Nov 2020 17:24:02 -0800 Subject: [PATCH 316/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 83f5c971..a3fbe69f 100644 --- a/tox.ini +++ b/tox.ini @@ -120,7 +120,7 @@ basepython = {[default]basepython} skip_install = True deps = - flake8-bugbear==20.1.4 + flake8-bugbear==20.11.1 flake8==3.8.4 mccabe==0.6.1 pep8-naming==0.11.1 From 33ef55adbec7e3f6ef2944ef128449038671e08e Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Thu, 17 Dec 2020 08:02:33 -0800 Subject: [PATCH 317/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index a3fbe69f..4e060853 100644 --- a/tox.ini +++ b/tox.ini @@ -277,7 +277,7 @@ skip_install = True deps = {[testenv:coverage_report]deps} - codecov==2.1.10 + codecov==2.1.11 passenv = # See https://github.com/codecov/codecov-python/blob/master/README.md#using-tox From 80b7afecf2e94412c5f8f7294500934c3785cca0 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sun, 20 Dec 2020 04:53:00 -0800 Subject: [PATCH 318/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 4e060853..f547908e 100644 --- a/tox.ini +++ b/tox.ini @@ -319,7 +319,7 @@ description = build documentation basepython = {[default]basepython} deps = - Sphinx==3.3.1 + Sphinx==3.4.0 sphinx-rtd-theme==0.5.0 commands = From ec2d79d5401ebc74b73e2766438dda974b817cb3 Mon Sep 17 00:00:00 2001 From: Tom Most Date: Tue, 22 Dec 2020 11:50:31 -0800 Subject: [PATCH 319/419] Reformat with Black --- src/hyperlink/hypothesis.py | 3 ++- src/hyperlink/test/common.py | 32 +++++++++++++++---------------- src/hyperlink/test/test_common.py | 8 ++------ 3 files changed, 20 insertions(+), 23 deletions(-) diff --git a/src/hyperlink/hypothesis.py b/src/hyperlink/hypothesis.py index 6228ad3e..4ab987eb 100644 --- a/src/hyperlink/hypothesis.py +++ b/src/hyperlink/hypothesis.py @@ -78,7 +78,8 @@ def idna_characters(): ) with open_gzip(dataFileName) as dataFile: reader = csv_reader( - (line.decode("utf-8") for line in dataFile), delimiter=",", + (line.decode("utf-8") for line in dataFile), + delimiter=",", ) next(reader) # Skip header row for row in reader: diff --git a/src/hyperlink/test/common.py b/src/hyperlink/test/common.py index 1eec0db1..ad3bd04a 100644 --- a/src/hyperlink/test/common.py +++ b/src/hyperlink/test/common.py @@ -16,26 +16,26 @@ def assertRaises( # type: ignore[override] ): # type: (...) -> Any """Fail unless an exception of class expected_exception is raised - by callableObj when invoked with arguments args and keyword - arguments kwargs. If a different type of exception is - raised, it will not be caught, and the test case will be - deemed to have suffered an error, exactly as for an - unexpected exception. + by callableObj when invoked with arguments args and keyword + arguments kwargs. If a different type of exception is + raised, it will not be caught, and the test case will be + deemed to have suffered an error, exactly as for an + unexpected exception. - If called with callableObj omitted or None, will return a - context object used like this:: + If called with callableObj omitted or None, will return a + context object used like this:: - with self.assertRaises(SomeException): - do_something() + with self.assertRaises(SomeException): + do_something() - The context manager keeps a reference to the exception as - the 'exception' attribute. This allows you to inspect the - exception after the assertion:: + The context manager keeps a reference to the exception as + the 'exception' attribute. This allows you to inspect the + exception after the assertion:: - with self.assertRaises(SomeException) as cm: - do_something() - the_exception = cm.exception - self.assertEqual(the_exception.error_code, 3) + with self.assertRaises(SomeException) as cm: + do_something() + the_exception = cm.exception + self.assertEqual(the_exception.error_code, 3) """ context = _AssertRaisesContext(expected_exception, self) if callableObj is None: diff --git a/src/hyperlink/test/test_common.py b/src/hyperlink/test/test_common.py index 6827d0b9..dc5e5bb8 100644 --- a/src/hyperlink/test/test_common.py +++ b/src/hyperlink/test/test_common.py @@ -7,15 +7,11 @@ class _ExpectedException(Exception): - """An exception used to test HyperlinkTestCase.assertRaises. - - """ + """An exception used to test HyperlinkTestCase.assertRaises.""" class _UnexpectedException(Exception): - """An exception used to test HyperlinkTestCase.assertRaises. - - """ + """An exception used to test HyperlinkTestCase.assertRaises.""" class TestHyperlink(TestCase): From 43e80fcd0f0adaf0462db69ad71fa47d09b5b401 Mon Sep 17 00:00:00 2001 From: Tom Most Date: Tue, 22 Dec 2020 13:18:56 -0800 Subject: [PATCH 320/419] Bump pyflakes to 2.2.0 This release includes https://github.com/PyCQA/pyflakes/pull/455 --- tox.ini | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 4e060853..e9c6f3f9 100644 --- a/tox.ini +++ b/tox.ini @@ -126,8 +126,7 @@ deps = pep8-naming==0.11.1 pycodestyle==2.6.0 pydocstyle==5.1.1 - # pin pyflakes pending a release with https://github.com/PyCQA/pyflakes/pull/455 - git+git://github.com/PyCQA/pyflakes@ffe9386#egg=pyflakes + pyflakes==2.2.0 commands = flake8 {posargs:setup.py src/{env:PY_MODULE}} From 07c5b0d5eb3608a35755bae5c173b8d46d763468 Mon Sep 17 00:00:00 2001 From: Tom Most Date: Tue, 22 Dec 2020 13:18:56 -0800 Subject: [PATCH 321/419] Bump pyflakes to 2.2.0 This release includes https://github.com/PyCQA/pyflakes/pull/455 --- tox.ini | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 4e060853..e9c6f3f9 100644 --- a/tox.ini +++ b/tox.ini @@ -126,8 +126,7 @@ deps = pep8-naming==0.11.1 pycodestyle==2.6.0 pydocstyle==5.1.1 - # pin pyflakes pending a release with https://github.com/PyCQA/pyflakes/pull/455 - git+git://github.com/PyCQA/pyflakes@ffe9386#egg=pyflakes + pyflakes==2.2.0 commands = flake8 {posargs:setup.py src/{env:PY_MODULE}} From 22bf59225091c988754ab47ce20119607bd51c8b Mon Sep 17 00:00:00 2001 From: Tom Most Date: Tue, 22 Dec 2020 13:19:31 -0800 Subject: [PATCH 322/419] Mark Python 3.9 supported --- CHANGELOG.md | 4 ++++ setup.py | 1 + tox.ini | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2371d814..a1754331 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Hyperlink Changelog +## Next + +* CPython 3.9 added to text matrix + ## 20.0.1 *(August 4, 2020)* diff --git a/setup.py b/setup.py index 97646a82..16e3815e 100644 --- a/setup.py +++ b/setup.py @@ -47,6 +47,7 @@ "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", "Programming Language :: Python :: Implementation :: PyPy", "License :: OSI Approved :: MIT License", ], diff --git a/tox.ini b/tox.ini index e9c6f3f9..4d42ed13 100644 --- a/tox.ini +++ b/tox.ini @@ -2,7 +2,7 @@ envlist = flake8, mypy, black - test-py{26,27,34,35,36,37,38,py2,py3} + test-py{26,27,34,35,36,37,38,39,py2,py3} coverage_report docs packaging From 58a99a14d70de8c632c3ca9a7340558a406980cd Mon Sep 17 00:00:00 2001 From: Tom Most Date: Tue, 22 Dec 2020 13:53:29 -0800 Subject: [PATCH 323/419] Add Github Actions configuration This was shamelessly ripped from https://github.com/twisted/klein/blob/7256d04caf3860e6f3703c02440f4088b545fd18/.github/workflows/cicd.yml with minor modifications. --- .github/workflows/cicd.yml | 240 +++++++++++++++++++++++++++++++++++++ 1 file changed, 240 insertions(+) create mode 100644 .github/workflows/cicd.yml diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml new file mode 100644 index 00000000..51994870 --- /dev/null +++ b/.github/workflows/cicd.yml @@ -0,0 +1,240 @@ +# Docs: +# https://help.github.com/en/actions/automating-your-workflow-with-github-actions + + + +name: CI/CD + + +on: + push: + branches: ["master"] + pull_request: + branches: ["master"] + + +jobs: + + info: + + name: Workflow information + runs-on: ubuntu-latest + timeout-minutes: 1 + + steps: + + - name: Print GitHub Context + env: + GITHUB_CONTEXT: ${{ toJson(github) }} + run: echo "${GITHUB_CONTEXT}"; + + - name: Print Job Context + env: + JOB_CONTEXT: ${{ toJson(job) }} + run: echo "${JOB_CONTEXT}"; + + - name: Print Steps Context + env: + STEPS_CONTEXT: ${{ toJson(steps) }} + run: echo "${STEPS_CONTEXT}"; + + - name: Print Runner Context + env: + RUNNER_CONTEXT: ${{ toJson(runner) }} + run: echo "${RUNNER_CONTEXT}"; + + - name: Print Strategy Context + env: + STRATEGY_CONTEXT: ${{ toJson(strategy) }} + run: echo "${STRATEGY_CONTEXT}"; + + - name: Print Matrix Context + env: + MATRIX_CONTEXT: ${{ toJson(matrix) }} + run: echo "${MATRIX_CONTEXT}"; + + + flake8: + + name: Flake8 (linter) + + runs-on: ubuntu-latest + timeout-minutes: 5 + + steps: + + - name: Checkout source code + uses: actions/checkout@v2 + + - name: Install Python + uses: actions/setup-python@v1 + with: + python-version: "3.9" + + - name: Install Tox + run: pip install tox; + + - name: Run Flake8 + run: tox -e flake8; + + + black: + + name: Black (linter) + + runs-on: ubuntu-latest + timeout-minutes: 5 + + steps: + + - name: Checkout source code + uses: actions/checkout@v2 + + - name: Install Python + uses: actions/setup-python@v1 + with: + python-version: "3.9" + + - name: Install Tox + run: pip install tox; + + - name: Run Black + run: tox -e black; + + + mypy: + name: Mypy (static type checker) + + runs-on: ubuntu-latest + timeout-minutes: 5 + + steps: + + - name: Checkout source code + uses: actions/checkout@v2 + + - name: Install Python + uses: actions/setup-python@v1 + with: + python-version: "3.9" + + - name: Install Tox + run: pip install tox; + + - name: Run Mypy + run: tox -e mypy; + + + docs: + + name: Build documentation + + runs-on: ubuntu-latest + timeout-minutes: 5 + + steps: + + - name: Checkout source code + uses: actions/checkout@v2 + + - name: Install Python + uses: actions/setup-python@v1 + with: + python-version: "3.9" + + - name: Install Tox + run: pip install tox; + + - name: Build documentation + run: tox -e docs; + + + packaging: + name: Packaging + + runs-on: ubuntu-latest + timeout-minutes: 5 + + steps: + + - name: Checkout source code + uses: actions/checkout@v2 + + - name: Install Python + uses: actions/setup-python@v1 + with: + python-version: "3.9" + + - name: Install Tox + run: pip install tox; + + - name: Check packaging + run: tox -e packaging; + + + unit: + name: Unit Tests using Python ${{ matrix.python }} on Ubuntu + + needs: [flake8, black, mypy, docs, packaging] + + runs-on: ubuntu-latest + timeout-minutes: 30 + strategy: + matrix: + python: ["2.7", "3.4", "3.5", "3.6", "3.7", "3.8", "3.9", "pypy-2.7", "pypy-3.7"] + + steps: + + - name: Checkout source code + uses: actions/checkout@v2 + + - name: Install Python + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python }} + + - name: Install Tox + run: pip install tox; + + - name: Run unit tests + shell: bash + # This hairy shell code is here to map the Python versions + # specified above to the equivalents used in Tox environments. + run: | + set -eux + py="${{ matrix.python }}"; + if [[ $py =~ pypy ]]; then # PyPy + py_test="pypy${py:5:1}"; # Add "pypy" prefix, select major version + else # CPython + py_test="py${py/./}"; # Add "py" prefix, remove "." + fi; + env_test="test-${py_test},codecov"; + echo "Test environment: ${env_test}"; + ln -s ".tox/${env_test}" testenv; # Fixed location for upload step below + tox -e "${env_test}"; + + # Use the latest supported Python version for combining coverage to + # prevent parsing errors in older versions when looking at modern code. + - uses: "actions/setup-python@v2" + with: + python-version: "3.9" + + - name: "Combine coverage" + run: | + set -eux + pip install coverage[toml]; + coverage combine; + coverage xml; + env: + COVERAGE_FILE: .tox/coverage + + - name: "Upload coverage to Codecov" + uses: "codecov/codecov-action@v1" + with: + env_vars: GITHUB_REF,GITHUB_COMMIT,GITHUB_USER,GITHUB_WORKFLOW + fail_ci_if_error: true + env: + GITHUB_REF: ${{ github.ref }} + GITHUB_COMMIT: ${{ github.sha }} + GITHUB_USER: ${{ github.actor }} + GITHUB_WORKFLOW: ${{ github.workflow }} From 039cc282ebb929d28b4ad3c3ad6882a94929194d Mon Sep 17 00:00:00 2001 From: Tom Most Date: Tue, 22 Dec 2020 17:07:11 -0800 Subject: [PATCH 324/419] Fix typo --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1754331..7d8995fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## Next -* CPython 3.9 added to text matrix +* CPython 3.9 added to test matrix ## 20.0.1 From f62cf498991d8448f113fdf94445c2dac74c67b9 Mon Sep 17 00:00:00 2001 From: Tom Most Date: Tue, 22 Dec 2020 17:07:32 -0800 Subject: [PATCH 325/419] Remove redundant codecov run --- .github/workflows/cicd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 51994870..7a370087 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -208,7 +208,7 @@ jobs: else # CPython py_test="py${py/./}"; # Add "py" prefix, remove "." fi; - env_test="test-${py_test},codecov"; + env_test="test-${py_test}"; echo "Test environment: ${env_test}"; ln -s ".tox/${env_test}" testenv; # Fixed location for upload step below tox -e "${env_test}"; From 301bc18e5aa7f5e6ba30e4784928089a3245d54a Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Thu, 24 Dec 2020 03:15:28 -0800 Subject: [PATCH 326/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index f547908e..8050d5a8 100644 --- a/tox.ini +++ b/tox.ini @@ -362,7 +362,7 @@ skip_install = True deps = check-manifest==0.45 readme-renderer==28.0 - twine==3.2.0 + twine==3.3.0 commands = check-manifest From 1255f955734def4fb7c9629eca712153a82a7d9a Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Thu, 24 Dec 2020 07:36:21 -0800 Subject: [PATCH 327/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 8050d5a8..507f714f 100644 --- a/tox.ini +++ b/tox.ini @@ -319,7 +319,7 @@ description = build documentation basepython = {[default]basepython} deps = - Sphinx==3.4.0 + Sphinx==3.4.1 sphinx-rtd-theme==0.5.0 commands = From 91fee33af37aa10eecb04718783453f7d4db9297 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sat, 26 Dec 2020 17:30:27 -0800 Subject: [PATCH 328/419] [requires.io] dependency update --- tox.ini | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 507f714f..579a3d99 100644 --- a/tox.ini +++ b/tox.ini @@ -126,8 +126,7 @@ deps = pep8-naming==0.11.1 pycodestyle==2.6.0 pydocstyle==5.1.1 - # pin pyflakes pending a release with https://github.com/PyCQA/pyflakes/pull/455 - git+git://github.com/PyCQA/pyflakes@ffe9386#egg=pyflakes + pyflakes==2.2.0 commands = flake8 {posargs:setup.py src/{env:PY_MODULE}} From ad88c47c93dcf951d54ebe6c1d5d067eeac16392 Mon Sep 17 00:00:00 2001 From: Glyph Date: Mon, 28 Dec 2020 00:06:08 -0800 Subject: [PATCH 329/419] make hyperlink handle + like an HTML form post by default --- src/hyperlink/_url.py | 70 +++++++++++++++---- src/hyperlink/test/test_decoded_url.py | 16 +++++ .../test/test_scheme_registration.py | 12 +++- 3 files changed, 84 insertions(+), 14 deletions(-) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index 4fb84133..6e42f013 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -467,9 +467,13 @@ def _encode_userinfo_part(text, maximal=True): ) # As of Mar 11, 2017, there were 44 netloc schemes, and 13 non-netloc +NO_QUERY_PLUS_SCHEMES = set() -def register_scheme(text, uses_netloc=True, default_port=None): - # type: (Text, bool, Optional[int]) -> None + +def register_scheme( + text, uses_netloc=True, default_port=None, query_plus_is_space=True +): + # type: (Text, bool, Optional[int], bool) -> None """Registers new scheme information, resulting in correct port and slash behavior from the URL object. There are dozens of standard schemes preregistered, so this function is mostly meant for @@ -485,6 +489,8 @@ def register_scheme(text, uses_netloc=True, default_port=None): not. Defaults to True. default_port: The default port, if any, for netloc-using schemes. + query_plus_is_space: If true, a "+" in the query string should be + decoded as a space by DecodedURL. .. _file an issue: https://github.com/mahmoud/hyperlink/issues """ @@ -510,6 +516,9 @@ def register_scheme(text, uses_netloc=True, default_port=None): else: raise ValueError("uses_netloc expected bool, not: %r" % uses_netloc) + if not query_plus_is_space: + NO_QUERY_PLUS_SCHEMES.add(text) + return @@ -1969,6 +1978,16 @@ def remove( _EMPTY_URL = URL() +def _replace_plus(text): + # type: (Text) -> Text + return text.replace("+", "%20") + + +def _no_op(text): + # type: (Text) -> Text + return text + + class DecodedURL(object): """ :class:`DecodedURL` is a type designed to act as a higher-level @@ -1998,6 +2017,9 @@ class DecodedURL(object): lazy: Set to True to avoid pre-decode all parts of the URL to check for validity. Defaults to False. + query_plus_is_space: + characters in the query string should be treated + as spaces when decoding. If unspecified, the default is taken from + the scheme. .. note:: @@ -2012,9 +2034,12 @@ class DecodedURL(object): .. versionadded:: 18.0.0 """ - def __init__(self, url=_EMPTY_URL, lazy=False): - # type: (URL, bool) -> None + def __init__(self, url=_EMPTY_URL, lazy=False, query_plus_is_space=None): + # type: (URL, bool, Optional[bool]) -> None self._url = url + if query_plus_is_space is None: + query_plus_is_space = url.scheme not in NO_QUERY_PLUS_SCHEMES + self._query_plus_is_space = query_plus_is_space if not lazy: # cache the following, while triggering any decoding # issues with decodable fields @@ -2022,8 +2047,8 @@ def __init__(self, url=_EMPTY_URL, lazy=False): return @classmethod - def from_text(cls, text, lazy=False): - # type: (Text, bool) -> DecodedURL + def from_text(cls, text, lazy=False, query_plus_is_space=None): + # type: (Text, bool, Optional[bool]) -> DecodedURL """\ Make a `DecodedURL` instance from any text string containing a URL. @@ -2034,7 +2059,7 @@ def from_text(cls, text, lazy=False): Defaults to True. """ _url = URL.from_text(text) - return cls(_url, lazy=lazy) + return cls(_url, lazy=lazy, query_plus_is_space=query_plus_is_space) @property def encoded_url(self): @@ -2059,6 +2084,14 @@ def to_iri(self): "Passthrough to :meth:`~hyperlink.URL.to_iri()`" return self._url.to_iri() + def _clone(self, url): + # type: (URL) -> DecodedURL + return self.__class__( + url, + # TODO: propagate laziness? + query_plus_is_space=self._query_plus_is_space, + ) + def click(self, href=u""): # type: (Union[Text, URL, DecodedURL]) -> DecodedURL """Return a new DecodedURL wrapping the result of @@ -2066,7 +2099,9 @@ def click(self, href=u""): """ if isinstance(href, DecodedURL): href = href._url - return self.__class__(self._url.click(href=href)) + return self._clone( + self._url.click(href=href), + ) def sibling(self, segment): # type: (Text) -> DecodedURL @@ -2074,7 +2109,9 @@ def sibling(self, segment): return a new `DecodedURL` wrapping the result of :meth:`~hyperlink.URL.sibling()` """ - return self.__class__(self._url.sibling(_encode_reserved(segment))) + return self._clone( + self._url.sibling(_encode_reserved(segment)), + ) def child(self, *segments): # type: (Text) -> DecodedURL @@ -2085,7 +2122,7 @@ def child(self, *segments): if not segments: return self new_segs = [_encode_reserved(s) for s in segments] - return self.__class__(self._url.child(*new_segs)) + return self._clone(self._url.child(*new_segs)) def normalize( self, @@ -2101,7 +2138,7 @@ def normalize( """Return a new `DecodedURL` wrapping the result of :meth:`~hyperlink.URL.normalize()` """ - return self.__class__( + return self._clone( self._url.normalize( scheme, host, path, query, fragment, userinfo, percents ) @@ -2148,11 +2185,18 @@ def path(self): def query(self): # type: () -> QueryPairs if not hasattr(self, "_query"): + if self._query_plus_is_space: + predecode = _replace_plus + else: + predecode = _no_op + self._query = cast( QueryPairs, tuple( tuple( - _percent_decode(x, raise_subencoding_exc=True) + _percent_decode( + predecode(x), raise_subencoding_exc=True + ) if x is not None else None for x in (k, v) @@ -2248,7 +2292,7 @@ def replace( userinfo=userinfo_text, uses_netloc=uses_netloc, ) - return self.__class__(url=new_url) + return self._clone(url=new_url) def get(self, name): # type: (Text) -> List[Optional[Text]] diff --git a/src/hyperlink/test/test_decoded_url.py b/src/hyperlink/test/test_decoded_url.py index 7104bea5..235cd915 100644 --- a/src/hyperlink/test/test_decoded_url.py +++ b/src/hyperlink/test/test_decoded_url.py @@ -210,3 +210,19 @@ def test_click_decoded_url(self): assert clicked.host == durl.host assert clicked.path == durl_dest.path assert clicked.path == ("tëst",) + + def test_decode_plus(self): + # type: () -> None + durl = DecodedURL.from_text("/x+y%2B?a=b+c%2B") + assert durl.path == ("x+y+",) + assert durl.get("a") == ["b c+"] + assert durl.query == (("a", "b c+"),) + + def test_decode_nonplussed(self): + # type: () -> None + durl = DecodedURL.from_text( + "/x+y%2B?a=b+c%2B", query_plus_is_space=False + ) + assert durl.path == ("x+y+",) + assert durl.get("a") == ["b+c+"] + assert durl.query == (("a", "b+c+"),) diff --git a/src/hyperlink/test/test_scheme_registration.py b/src/hyperlink/test/test_scheme_registration.py index f98109a3..b43c91e3 100644 --- a/src/hyperlink/test/test_scheme_registration.py +++ b/src/hyperlink/test/test_scheme_registration.py @@ -5,7 +5,7 @@ from .. import _url from .common import HyperlinkTestCase -from .._url import register_scheme, URL +from .._url import register_scheme, URL, DecodedURL class TestSchemeRegistration(HyperlinkTestCase): @@ -70,3 +70,13 @@ def test_register_invalid_port(self): # type: () -> None with self.assertRaises(ValueError): register_scheme("nope", default_port=cast(bool, object())) + + def test_register_no_quote_plus_scheme(self): + # type: () -> None + register_scheme("keepplus", query_plus_is_space=False) + plus_is_not_space = DecodedURL.from_text( + "keepplus://example.com/?q=a+b" + ) + plus_is_space = DecodedURL.from_text("https://example.com/?q=a+b") + assert plus_is_not_space.get("q") == ["a+b"] + assert plus_is_space.get("q") == ["a b"] From b69be9e34a57fc4f799e1e93e6c4f6c6ba95fc5a Mon Sep 17 00:00:00 2001 From: Tom Most Date: Mon, 28 Dec 2020 14:38:59 -0800 Subject: [PATCH 330/419] Test URL round-tripping of + in query --- src/hyperlink/test/test_url.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/hyperlink/test/test_url.py b/src/hyperlink/test/test_url.py index 159d6a58..37c91726 100644 --- a/src/hyperlink/test/test_url.py +++ b/src/hyperlink/test/test_url.py @@ -133,6 +133,8 @@ "https://example.com/?a=%23", # hash in query param value "https://example.com/?a=%26", # ampersand in query param value "https://example.com/?a=%3D", # equals in query param value + "https://example.com/?foo+bar=baz", # plus in query param name + "https://example.com/?foo=bar+baz", # plus in query param value # double-encoded percent sign in all percent-encodable positions: "http://(%2525):(%2525)@example.com/(%2525)/?(%2525)=(%2525)#(%2525)", # colon in first part of schemeless relative url From a9eba07c7223fe34ad828712963689b66ceeaf6a Mon Sep 17 00:00:00 2001 From: Tom Most Date: Mon, 28 Dec 2020 21:43:47 -0800 Subject: [PATCH 331/419] Don't force encode + Before: >>> URL(scheme='https', host='foo', query={'f+o o': 'b+a r'}).to_text() 'https://foo/?f%2Bo o=b%2Ba r' After: >>> URL(scheme='https', host='foo', query={'f+o o': 'b+a r'}).to_text() 'https://foo/?f+o o=b+a r' If spaces pass unencoded, surely + can. --- src/hyperlink/_url.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index 6e42f013..be69baf6 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -183,7 +183,7 @@ def __nonzero__(self): _SCHEMELESS_PATH_DELIMS = _ALL_DELIMS - _SCHEMELESS_PATH_SAFE _FRAGMENT_SAFE = _UNRESERVED_CHARS | _PATH_SAFE | set(u"/?") _FRAGMENT_DELIMS = _ALL_DELIMS - _FRAGMENT_SAFE -_QUERY_VALUE_SAFE = _UNRESERVED_CHARS | _FRAGMENT_SAFE - set(u"&+") +_QUERY_VALUE_SAFE = _UNRESERVED_CHARS | _FRAGMENT_SAFE - set(u"&") _QUERY_VALUE_DELIMS = _ALL_DELIMS - _QUERY_VALUE_SAFE _QUERY_KEY_SAFE = _UNRESERVED_CHARS | _QUERY_VALUE_SAFE - set(u"=") _QUERY_KEY_DELIMS = _ALL_DELIMS - _QUERY_KEY_SAFE @@ -931,9 +931,9 @@ class URL(object): https://example.com/hello/world The constructor runs basic type checks. All strings are expected - to be decoded (:class:`unicode` in Python 2). All arguments are - optional, defaulting to appropriately empty values. A full list of - constructor arguments is below. + to be text (:class:`str` in Python 3, :class:`unicode` in Python 2). All + arguments are optional, defaulting to appropriately empty values. A full + list of constructor arguments is below. Args: scheme: The text name of the scheme. @@ -943,9 +943,9 @@ class URL(object): it is known. See the ``SCHEME_PORT_MAP`` and :func:`register_default_port` for more info. path: A tuple of strings representing the slash-separated parts of the - path. + path, each percent-encoded. query: The query parameters, as a dictionary or as an sequence of - key-value pairs. + percent-encoded key-value pairs. fragment: The fragment part of the URL. rooted: A rooted URL is one which indicates an absolute path. This is True on any URL that includes a host, or any relative URL From 4d7f64370a157b1d79db59a77e0b53f8e23eb8a0 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Mon, 4 Jan 2021 05:08:10 -0800 Subject: [PATCH 332/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 579a3d99..6bdb7bc4 100644 --- a/tox.ini +++ b/tox.ini @@ -318,7 +318,7 @@ description = build documentation basepython = {[default]basepython} deps = - Sphinx==3.4.1 + Sphinx==3.4.2 sphinx-rtd-theme==0.5.0 commands = From 4533c5a2c197a1fadfd7794a9e7eb7b561d68651 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Mon, 4 Jan 2021 05:22:40 -0800 Subject: [PATCH 333/419] [requires.io] dependency update From 31f37e24a6ebe528f43a1454199cb16934ea4dde Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Mon, 4 Jan 2021 06:46:22 -0800 Subject: [PATCH 334/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 6bdb7bc4..06336ffd 100644 --- a/tox.ini +++ b/tox.ini @@ -359,7 +359,7 @@ basepython = {[default]basepython} skip_install = True deps = - check-manifest==0.45 + check-manifest==0.46 readme-renderer==28.0 twine==3.3.0 From cdfe3c853dc14244db32cdbdf108e8ea0efe6254 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Mon, 4 Jan 2021 17:27:35 -0800 Subject: [PATCH 335/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 06336ffd..ede9e20a 100644 --- a/tox.ini +++ b/tox.ini @@ -319,7 +319,7 @@ basepython = {[default]basepython} deps = Sphinx==3.4.2 - sphinx-rtd-theme==0.5.0 + sphinx-rtd-theme==0.5.1 commands = sphinx-build \ From 39b1912f547f8ec0aeb8ca5e32987e87aa80c1b5 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Thu, 7 Jan 2021 09:14:23 -0800 Subject: [PATCH 336/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index ede9e20a..f9842dc0 100644 --- a/tox.ini +++ b/tox.ini @@ -318,7 +318,7 @@ description = build documentation basepython = {[default]basepython} deps = - Sphinx==3.4.2 + Sphinx==3.4.3 sphinx-rtd-theme==0.5.1 commands = From eae9223fafccfc4b32f8309bfe2b6817c3a88331 Mon Sep 17 00:00:00 2001 From: Mahmoud Hashemi Date: Thu, 7 Jan 2021 21:48:11 -0800 Subject: [PATCH 337/419] bump version for v21.0.0 release (and add py3.9 classifier) --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 97646a82..0daa11d4 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ __author__ = "Mahmoud Hashemi and Glyph Lefkowitz" -__version__ = "20.0.2dev" +__version__ = "21.0.0" __contact__ = "mahmoud@hatnote.com" __url__ = "https://github.com/python-hyper/hyperlink" __license__ = "MIT" @@ -47,6 +47,7 @@ "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", "Programming Language :: Python :: Implementation :: PyPy", "License :: OSI Approved :: MIT License", ], From cd875e6bbb9b6260c34a3aac3215632fa601158f Mon Sep 17 00:00:00 2001 From: Mahmoud Hashemi Date: Thu, 7 Jan 2021 21:59:56 -0800 Subject: [PATCH 338/419] bump docs version, write v21.0.0 changelog --- CHANGELOG.md | 13 +++++++++++++ docs/conf.py | 4 ++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2371d814..ab7f7223 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Hyperlink Changelog +## 21.0.0 + +*(January 7, 2021)* + +* Update plus sign (`+`) handling to work with/like HTML form encoding + (`POST`) by default, fixes [#129][i129], and associated roundtripping ([#146][i146]). +* Package IDNA tables. ([#134][i134]) +* Long overdue dependency bumps + +[i129]: https://github.com/python-hyper/hyperlink/issues/129 +[i134]: https://github.com/python-hyper/hyperlink/issues/134 +[i146]: https://github.com/python-hyper/hyperlink/issues/146 + ## 20.0.1 *(August 4, 2020)* diff --git a/docs/conf.py b/docs/conf.py index c4f1d68e..baee58ab 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -64,8 +64,8 @@ copyright = u'2018, Mahmoud Hashemi' author = u'Mahmoud Hashemi' -version = '20.0' -release = '20.0.1' +version = '21.0' +release = '21.0.0' if os.name != 'nt': today_fmt = '%B %d, %Y' From a451f3dcad448220a27301703d0a8bc5bf99f78a Mon Sep 17 00:00:00 2001 From: Mahmoud Hashemi Date: Thu, 7 Jan 2021 22:01:07 -0800 Subject: [PATCH 339/419] bump version for v21.0.1dev --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 0daa11d4..f057fb8a 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ __author__ = "Mahmoud Hashemi and Glyph Lefkowitz" -__version__ = "21.0.0" +__version__ = "21.0.1dev" __contact__ = "mahmoud@hatnote.com" __url__ = "https://github.com/python-hyper/hyperlink" __license__ = "MIT" From 26e24d0f6576ce4e8a1e590674e9a04c6416466d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Thu, 14 Jan 2021 09:59:40 -0800 Subject: [PATCH 340/419] Update basepython to 3.9 to match CI config --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index c01f8996..1c30942c 100644 --- a/tox.ini +++ b/tox.ini @@ -12,7 +12,7 @@ skip_missing_interpreters = {tty:True:False} [default] -basepython = python3.8 +basepython = python3.9 deps = idna==2.9 From 1c2bf35618408c2e9de9343bee7d3cdd3b2053e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Thu, 14 Jan 2021 10:12:40 -0800 Subject: [PATCH 341/419] No 3.4. Fix pypy IDs. --- .github/workflows/cicd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 7a370087..a899570b 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -181,7 +181,7 @@ jobs: timeout-minutes: 30 strategy: matrix: - python: ["2.7", "3.4", "3.5", "3.6", "3.7", "3.8", "3.9", "pypy-2.7", "pypy-3.7"] + python: ["2.7", "3.5", "3.6", "3.7", "3.8", "3.9", "pypy2", "pypy3"] steps: From 02075c6c34ca0ec22b358647d3b9281ea1320ca7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Thu, 14 Jan 2021 11:16:59 -0800 Subject: [PATCH 342/419] Sync up a bit with Klein --- tox.ini | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 1c30942c..dda1f299 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,7 @@ [tox] envlist = - flake8, mypy, black + flake8, black, mypy test-py{26,27,34,35,36,37,38,39,py2,py3} coverage_report docs @@ -32,6 +32,8 @@ setenv = description = run tests basepython = + py: python + py26: python2.6 py27: python2.7 py34: python3.4 @@ -40,6 +42,7 @@ basepython = py37: python3.7 py38: python3.8 py39: python3.9 + py310: python3.10 pypy2: pypy pypy3: pypy3 @@ -292,7 +295,7 @@ passenv = setenv = {[testenv:coverage_report]setenv} - COVERAGE_XML={envlogdir}/coverage_report.xml + COVERAGE_XML={envlogdir}/coverage.xml commands = # Note documentation for CI variables in passenv above @@ -367,3 +370,21 @@ commands = check-manifest pip wheel --wheel-dir "{envtmpdir}/dist" --no-deps {toxinidir} twine check "{envtmpdir}/dist/"* + + +## +# Print dependencies +## + +[testenv:dependencies] + +description = print dependencies + +basepython = {[default]basepython} + +recreate = true + +deps = + +commands = + pip freeze --exclude={env:PY_MODULE} From ef5819c0560db391f8f7c888e53769dcd88de704 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Thu, 14 Jan 2021 11:33:38 -0800 Subject: [PATCH 343/419] Try to clean up test workflow. --- .github/workflows/cicd.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index a899570b..880d4568 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -204,14 +204,20 @@ jobs: set -eux py="${{ matrix.python }}"; if [[ $py =~ pypy ]]; then # PyPy - py_test="pypy${py:5:1}"; # Add "pypy" prefix, select major version + py_test="${py}"; else # CPython py_test="py${py/./}"; # Add "py" prefix, remove "." fi; env_test="test-${py_test}"; echo "Test environment: ${env_test}"; - ln -s ".tox/${env_test}" testenv; # Fixed location for upload step below - tox -e "${env_test}"; + tar cvzf pytest-logs.tgz ".tox/${env_test}/log"; + + - name: Upload pytest log artifact + if: failure() + uses: actions/upload-artifact@v1 + with: + name: pytest-logs + path: pytest-logs.tgz # Use the latest supported Python version for combining coverage to # prevent parsing errors in older versions when looking at modern code. From 304d382b1c445a3ab8c0150da312327199e7baad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Thu, 14 Jan 2021 11:35:34 -0800 Subject: [PATCH 344/419] Remove merge turd --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 012b9ba1..50f34c6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,6 @@ [i129]: https://github.com/python-hyper/hyperlink/issues/129 [i134]: https://github.com/python-hyper/hyperlink/issues/134 [i146]: https://github.com/python-hyper/hyperlink/issues/146 ->>>>>>> master ## 20.0.1 From 1d125a5c37efda5c43e86920ba2839cfcdd3bcfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Thu, 14 Jan 2021 11:37:37 -0800 Subject: [PATCH 345/419] Whoops don't forget tox --- .github/workflows/cicd.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 880d4568..9e713a32 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -210,6 +210,7 @@ jobs: fi; env_test="test-${py_test}"; echo "Test environment: ${env_test}"; + tox -e "${env_test}"; tar cvzf pytest-logs.tgz ".tox/${env_test}/log"; - name: Upload pytest log artifact From dc89c77a1b453f4b5df15274af5fe9ad3089ed13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Thu, 14 Jan 2021 11:54:44 -0800 Subject: [PATCH 346/419] =?UTF-8?q?Try=20to=20cope=20with=20coverage=204?= =?UTF-8?q?=E2=9E=9C5=20DB=20format=20change?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/cicd.yml | 3 ++- tox.ini | 26 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 9e713a32..7064009a 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -229,7 +229,8 @@ jobs: - name: "Combine coverage" run: | set -eux - pip install coverage[toml]; + # pip install coverage[toml]; + pip install coverage==4.5.4; # coverage 5.0 drops Python 3.4 support coverage combine; coverage xml; env: diff --git a/tox.ini b/tox.ini index dda1f299..15677d66 100644 --- a/tox.ini +++ b/tox.ini @@ -263,6 +263,32 @@ commands = - coverage html +## +# Coverage combine (for CI) +## + +[testenv:coverage_combine] + +description = combine coverage + +depends = {[coverage_report]depends} + +basepython = {[default]basepython} + +skip_install = True + +deps = + # coverage 5.0 drops Python 3.4 support + py{26,27,34,py2}: coverage==4.5.4 # rq.filter: <5 + py{35,36,37,38,39,py3}: coverage==5.3.1 + +setenv = {[coverage_report]setenv} + +commands = + coverage combine + coverage xml + + ## # Codecov ## From e4cbd63a6ca56a444e4ac09d511473d446c6ebe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Thu, 14 Jan 2021 11:58:13 -0800 Subject: [PATCH 347/419] More CI --- .github/workflows/cicd.yml | 7 +------ tox.ini | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 7064009a..3eff014a 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -227,12 +227,7 @@ jobs: python-version: "3.9" - name: "Combine coverage" - run: | - set -eux - # pip install coverage[toml]; - pip install coverage==4.5.4; # coverage 5.0 drops Python 3.4 support - coverage combine; - coverage xml; + run: tox -e coverage_combine; env: COVERAGE_FILE: .tox/coverage diff --git a/tox.ini b/tox.ini index 15677d66..a464ea0e 100644 --- a/tox.ini +++ b/tox.ini @@ -282,7 +282,7 @@ deps = py{26,27,34,py2}: coverage==4.5.4 # rq.filter: <5 py{35,36,37,38,39,py3}: coverage==5.3.1 -setenv = {[coverage_report]setenv} +passenv = COVERAGE_FILE commands = coverage combine From 0d1205e18521b91f4a1663fc757623f060b08b78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Thu, 14 Jan 2021 12:03:22 -0800 Subject: [PATCH 348/419] Run combine with unit tests step so we can throw in the python version slug --- .github/workflows/cicd.yml | 8 +++----- tox.ini | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 3eff014a..c2036f31 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -212,6 +212,9 @@ jobs: echo "Test environment: ${env_test}"; tox -e "${env_test}"; tar cvzf pytest-logs.tgz ".tox/${env_test}/log"; + tox -e "coverage_combine-${env_test}"; + env: + COVERAGE_FILE: .tox/coverage - name: Upload pytest log artifact if: failure() @@ -226,11 +229,6 @@ jobs: with: python-version: "3.9" - - name: "Combine coverage" - run: tox -e coverage_combine; - env: - COVERAGE_FILE: .tox/coverage - - name: "Upload coverage to Codecov" uses: "codecov/codecov-action@v1" with: diff --git a/tox.ini b/tox.ini index a464ea0e..8c144f53 100644 --- a/tox.ini +++ b/tox.ini @@ -15,7 +15,7 @@ skip_missing_interpreters = {tty:True:False} basepython = python3.9 deps = - idna==2.9 + idna==2.9 # rq.filter: <3 setenv = PY_MODULE=hyperlink From 883a33d7920770d4f11c40207e387a559a4ef320 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Thu, 14 Jan 2021 12:07:43 -0800 Subject: [PATCH 349/419] More coverage CI --- .github/workflows/cicd.yml | 4 +--- tox.ini | 9 +++++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index c2036f31..063ea5ec 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -212,9 +212,7 @@ jobs: echo "Test environment: ${env_test}"; tox -e "${env_test}"; tar cvzf pytest-logs.tgz ".tox/${env_test}/log"; - tox -e "coverage_combine-${env_test}"; - env: - COVERAGE_FILE: .tox/coverage + tox -e "coverage_xml-${py_test}"; - name: Upload pytest log artifact if: failure() diff --git a/tox.ini b/tox.ini index 8c144f53..e2c46e25 100644 --- a/tox.ini +++ b/tox.ini @@ -264,12 +264,12 @@ commands = ## -# Coverage combine (for CI) +# Coverage XML ## -[testenv:coverage_combine] +[testenv:coverage_xml] -description = combine coverage +description = generate coverage XML depends = {[coverage_report]depends} @@ -282,7 +282,8 @@ deps = py{26,27,34,py2}: coverage==4.5.4 # rq.filter: <5 py{35,36,37,38,39,py3}: coverage==5.3.1 -passenv = COVERAGE_FILE +setenv = + {[testenv:coverage_report]setenv} commands = coverage combine From b571b3a97b12806131079f5e6140cb3a68caca2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Thu, 14 Jan 2021 12:12:29 -0800 Subject: [PATCH 350/419] =?UTF-8?q?Argh,=20can't=20do=20version=20slugs=20?= =?UTF-8?q?except=20in=20the=20default=20test=20env=E2=80=A6=20let's=20do?= =?UTF-8?q?=20the=20XML=20dance=20there.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/cicd.yml | 3 +-- tox.ini | 28 +--------------------------- 2 files changed, 2 insertions(+), 29 deletions(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 063ea5ec..b5b7e9eb 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -208,11 +208,10 @@ jobs: else # CPython py_test="py${py/./}"; # Add "py" prefix, remove "." fi; - env_test="test-${py_test}"; + env_test="test-${py_test}-coverage_xml"; echo "Test environment: ${env_test}"; tox -e "${env_test}"; tar cvzf pytest-logs.tgz ".tox/${env_test}/log"; - tox -e "coverage_xml-${py_test}"; - name: Upload pytest log artifact if: failure() diff --git a/tox.ini b/tox.ini index e2c46e25..e453f7be 100644 --- a/tox.ini +++ b/tox.ini @@ -77,6 +77,7 @@ passenv = CI commands = pytest --cov={env:PY_MODULE} --cov-report=term-missing:skip-covered --doctest-modules {posargs:src/{env:PY_MODULE}} + coverage_xml: coverage xml ## @@ -263,33 +264,6 @@ commands = - coverage html -## -# Coverage XML -## - -[testenv:coverage_xml] - -description = generate coverage XML - -depends = {[coverage_report]depends} - -basepython = {[default]basepython} - -skip_install = True - -deps = - # coverage 5.0 drops Python 3.4 support - py{26,27,34,py2}: coverage==4.5.4 # rq.filter: <5 - py{35,36,37,38,39,py3}: coverage==5.3.1 - -setenv = - {[testenv:coverage_report]setenv} - -commands = - coverage combine - coverage xml - - ## # Codecov ## From 9ad7c091dc28eb592697f2b1416037c514221033 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Fri, 22 Jan 2021 04:01:14 -0800 Subject: [PATCH 351/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index e453f7be..4c1ae163 100644 --- a/tox.ini +++ b/tox.ini @@ -187,7 +187,7 @@ description = run Mypy (static type checker) basepython = {[default]basepython} deps = - mypy==0.790 + mypy==0.800 {[default]deps} From 6ea21a7324dd5e1ea72c9cc4b6903342c9e228fe Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sun, 14 Feb 2021 00:24:04 -0800 Subject: [PATCH 352/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 4c1ae163..a0da91a0 100644 --- a/tox.ini +++ b/tox.ini @@ -322,7 +322,7 @@ description = build documentation basepython = {[default]basepython} deps = - Sphinx==3.4.3 + Sphinx==3.5.0 sphinx-rtd-theme==0.5.1 commands = From fbc37bd308653f7d6acc886c88fbc84856c5b981 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sun, 14 Feb 2021 00:37:43 -0800 Subject: [PATCH 353/419] [requires.io] dependency update From 4577a3925306605d4f585bd84cd6fcad79938ab4 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Tue, 16 Feb 2021 07:10:55 -0800 Subject: [PATCH 354/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index a0da91a0..1f33d059 100644 --- a/tox.ini +++ b/tox.ini @@ -322,7 +322,7 @@ description = build documentation basepython = {[default]basepython} deps = - Sphinx==3.5.0 + Sphinx==3.5.1 sphinx-rtd-theme==0.5.1 commands = From b62f7f0a360ccf07cb9cda8f3482eb74ddd3a396 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Fri, 19 Feb 2021 12:14:23 -0800 Subject: [PATCH 355/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 1f33d059..ab0ffdf2 100644 --- a/tox.ini +++ b/tox.ini @@ -187,7 +187,7 @@ description = run Mypy (static type checker) basepython = {[default]basepython} deps = - mypy==0.800 + mypy==0.812 {[default]deps} From 4694332809f68dac0731c2870f22f30829d52e67 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Mon, 22 Feb 2021 22:01:39 -0800 Subject: [PATCH 356/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index ab0ffdf2..b506a48e 100644 --- a/tox.ini +++ b/tox.ini @@ -364,7 +364,7 @@ skip_install = True deps = check-manifest==0.46 - readme-renderer==28.0 + readme-renderer==29.0 twine==3.3.0 commands = From 51c8d28389e3a6ce1bf2c6333b9545d3542900f1 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Wed, 3 Mar 2021 07:33:42 -0800 Subject: [PATCH 357/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index b506a48e..058d8a3d 100644 --- a/tox.ini +++ b/tox.ini @@ -124,7 +124,7 @@ basepython = {[default]basepython} skip_install = True deps = - flake8-bugbear==20.11.1 + flake8-bugbear==21.3.1 flake8==3.8.4 mccabe==0.6.1 pep8-naming==0.11.1 From 994ad7a1cadb806aa2f2bc74c3c35e7a8d3d26b9 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sat, 6 Mar 2021 04:32:13 -0800 Subject: [PATCH 358/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 058d8a3d..50306f08 100644 --- a/tox.ini +++ b/tox.ini @@ -322,7 +322,7 @@ description = build documentation basepython = {[default]basepython} deps = - Sphinx==3.5.1 + Sphinx==3.5.2 sphinx-rtd-theme==0.5.1 commands = From 0bc4ecc60b7dd65dba1829f3973651524eb6054f Mon Sep 17 00:00:00 2001 From: Damien Carol Date: Sun, 7 Mar 2021 18:37:51 +0100 Subject: [PATCH 359/419] Update conf.py --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index baee58ab..f8a4fb98 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -61,7 +61,7 @@ # General information about the project. project = u'hyperlink' -copyright = u'2018, Mahmoud Hashemi' +copyright = u'2021, Mahmoud Hashemi' author = u'Mahmoud Hashemi' version = '21.0' From e5db41af63a90984d0a1226aee0f17d2ace8745d Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Mon, 8 Mar 2021 10:16:04 -0800 Subject: [PATCH 360/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 50306f08..75cf2e85 100644 --- a/tox.ini +++ b/tox.ini @@ -124,7 +124,7 @@ basepython = {[default]basepython} skip_install = True deps = - flake8-bugbear==21.3.1 + flake8-bugbear==21.3.2 flake8==3.8.4 mccabe==0.6.1 pep8-naming==0.11.1 From b2c80f34595001d758c31e8178695f5e0edb435d Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sun, 14 Mar 2021 06:54:31 -0700 Subject: [PATCH 361/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 75cf2e85..75996614 100644 --- a/tox.ini +++ b/tox.ini @@ -340,7 +340,7 @@ basepython = {[default]basepython} deps = {[testenv:docs]deps} - sphinx-autobuild==2020.9.1 + sphinx-autobuild==2021.3.14 commands = sphinx-autobuild \ From 1a819a24974a7f3b8f6fdc5bd4a3c812e2157505 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sun, 14 Mar 2021 10:26:11 -0700 Subject: [PATCH 362/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 75996614..69050998 100644 --- a/tox.ini +++ b/tox.ini @@ -130,7 +130,7 @@ deps = pep8-naming==0.11.1 pycodestyle==2.6.0 pydocstyle==5.1.1 - pyflakes==2.2.0 + pyflakes==2.3.0 commands = flake8 {posargs:setup.py src/{env:PY_MODULE}} From 8426a94aeea75fb207072bc82f01f69e72ee3f2e Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sun, 14 Mar 2021 12:00:07 -0700 Subject: [PATCH 363/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 69050998..79bf2bc7 100644 --- a/tox.ini +++ b/tox.ini @@ -128,7 +128,7 @@ deps = flake8==3.8.4 mccabe==0.6.1 pep8-naming==0.11.1 - pycodestyle==2.6.0 + pycodestyle==2.7.0 pydocstyle==5.1.1 pyflakes==2.3.0 From 06722c6f917a03ee753200ce98253440c72fa899 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sun, 14 Mar 2021 17:10:58 -0700 Subject: [PATCH 364/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 79bf2bc7..b84daa51 100644 --- a/tox.ini +++ b/tox.ini @@ -125,7 +125,7 @@ skip_install = True deps = flake8-bugbear==21.3.2 - flake8==3.8.4 + flake8==3.9.0 mccabe==0.6.1 pep8-naming==0.11.1 pycodestyle==2.7.0 From f822713c1db5457187fd50fc61f225821db15705 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Mon, 15 Mar 2021 09:53:44 -0700 Subject: [PATCH 365/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index b84daa51..f97b1540 100644 --- a/tox.ini +++ b/tox.ini @@ -365,7 +365,7 @@ skip_install = True deps = check-manifest==0.46 readme-renderer==29.0 - twine==3.3.0 + twine==3.4.0 commands = check-manifest From 240731fb88724c2a7c3bc4b86cfc214fefed8cc6 Mon Sep 17 00:00:00 2001 From: David Euresti Date: Tue, 16 Mar 2021 09:42:44 -0700 Subject: [PATCH 366/419] Fix some small type issues. Use @overload to have a better return type to parse. Use Iterable for QueryParameters to support generators and views. Fixes #156 --- src/hyperlink/_url.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index be69baf6..340e1c11 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -43,6 +43,7 @@ TypeVar, Union, cast, + TYPE_CHECKING, ) from unicodedata import normalize from ._socket import inet_pton @@ -65,7 +66,7 @@ QueryParameters = Union[ Mapping[Text, Optional[Text]], QueryPairs, - Sequence[Tuple[Text, Optional[Text]]], + Iterable[Tuple[Text, Optional[Text]]], ] T = TypeVar("T") @@ -2415,6 +2416,35 @@ def __dir__(self): # # End Twisted Compat Code +if TYPE_CHECKING: + # Add some overloads so that parse gives a better return value. + # Literal is not available in all pythons so we only bring it in for mypy. + # Also to remain compatible with 2.7 we use pass instead of ... + from typing import Literal, overload + + @overload + def parse(url): + # type: (Text) -> DecodedURL + pass + + + @overload + def parse(url, decoded, lazy=False): + # type: (Text, Literal[True], bool) -> DecodedURL + pass + + + @overload + def parse(url, decoded, lazy=False): + # type: (Text, Literal[False], bool) -> URL + pass + + @overload + def parse(url, decoded=True, lazy=False): + # type: (Text, bool, bool) -> Union[URL, DecodedURL] + pass + + def parse(url, decoded=True, lazy=False): # type: (Text, bool, bool) -> Union[URL, DecodedURL] """ From 5eb389652aea6923749c5acafdba2722e9eef676 Mon Sep 17 00:00:00 2001 From: David Euresti Date: Tue, 16 Mar 2021 10:07:53 -0700 Subject: [PATCH 367/419] Fix mypy --- src/hyperlink/_url.py | 41 ++++++++++++++++------------------------- 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index 340e1c11..37190ad3 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -44,6 +44,7 @@ Union, cast, TYPE_CHECKING, + overload, ) from unicodedata import normalize from ._socket import inet_pton @@ -2415,35 +2416,25 @@ def __dir__(self): # # End Twisted Compat Code - +# Add some overloads so that parse gives a better return value. +# Literal is not available in all pythons so we only bring it in for mypy. if TYPE_CHECKING: - # Add some overloads so that parse gives a better return value. - # Literal is not available in all pythons so we only bring it in for mypy. - # Also to remain compatible with 2.7 we use pass instead of ... - from typing import Literal, overload - - @overload - def parse(url): - # type: (Text) -> DecodedURL - pass - - - @overload - def parse(url, decoded, lazy=False): - # type: (Text, Literal[True], bool) -> DecodedURL - pass + from typing import Literal +@overload +def parse(url, decoded, lazy=False): + # type: (Text, Literal[False], bool) -> URL + """Passing decoded=False returns URL.""" - @overload - def parse(url, decoded, lazy=False): - # type: (Text, Literal[False], bool) -> URL - pass - - @overload - def parse(url, decoded=True, lazy=False): - # type: (Text, bool, bool) -> Union[URL, DecodedURL] - pass +@overload +def parse(url, decoded=True, lazy=False): + # type: (Text, Literal[True], bool) -> DecodedURL + """Passing decoded=True (or the default value) returns DecodedURL.""" +@overload +def parse(url, decoded=True, lazy=False): + # type: (Text, bool, bool) -> Union[URL, DecodedURL] + """If decoded is not a literal we don't know the return type.""" def parse(url, decoded=True, lazy=False): # type: (Text, bool, bool) -> Union[URL, DecodedURL] From 5e44454ef0c332a3eb2919a91a7bd9f0b74f05d1 Mon Sep 17 00:00:00 2001 From: David Euresti Date: Tue, 16 Mar 2021 10:10:21 -0700 Subject: [PATCH 368/419] Fix lint --- src/hyperlink/_url.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index 37190ad3..006ac381 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -70,6 +70,9 @@ Iterable[Tuple[Text, Optional[Text]]], ] T = TypeVar("T") +# Literal is not available in all pythons so we only bring it in for mypy. +if TYPE_CHECKING: + from typing import Literal # from boltons.typeutils @@ -2416,26 +2419,26 @@ def __dir__(self): # # End Twisted Compat Code -# Add some overloads so that parse gives a better return value. -# Literal is not available in all pythons so we only bring it in for mypy. -if TYPE_CHECKING: - from typing import Literal +# Add some overloads so that parse gives a better return value. @overload def parse(url, decoded, lazy=False): # type: (Text, Literal[False], bool) -> URL """Passing decoded=False returns URL.""" + @overload def parse(url, decoded=True, lazy=False): # type: (Text, Literal[True], bool) -> DecodedURL """Passing decoded=True (or the default value) returns DecodedURL.""" + @overload def parse(url, decoded=True, lazy=False): # type: (Text, bool, bool) -> Union[URL, DecodedURL] """If decoded is not a literal we don't know the return type.""" + def parse(url, decoded=True, lazy=False): # type: (Text, bool, bool) -> Union[URL, DecodedURL] """ From bc27543168347b9acd863e0ea5ef2146f8768d2e Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Tue, 16 Mar 2021 18:59:08 -0700 Subject: [PATCH 369/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index f97b1540..cd729b55 100644 --- a/tox.ini +++ b/tox.ini @@ -365,7 +365,7 @@ skip_install = True deps = check-manifest==0.46 readme-renderer==29.0 - twine==3.4.0 + twine==3.4.1 commands = check-manifest From 4a96c8463136ffaa9289f069cc336d54543e1928 Mon Sep 17 00:00:00 2001 From: Glyph Date: Wed, 17 Mar 2021 23:34:28 -0700 Subject: [PATCH 370/419] don't unquote percent-encoded + in to_iri since it may be ambiguous --- src/hyperlink/_url.py | 2 +- src/hyperlink/test/test_decoded_url.py | 28 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/hyperlink/_url.py b/src/hyperlink/_url.py index be69baf6..ee1229f9 100644 --- a/src/hyperlink/_url.py +++ b/src/hyperlink/_url.py @@ -225,7 +225,7 @@ def _make_quote_map(safe_chars): _QUERY_KEY_QUOTE_MAP = _make_quote_map(_QUERY_KEY_SAFE) _QUERY_KEY_DECODE_MAP = _make_decode_map(_QUERY_KEY_DELIMS) _QUERY_VALUE_QUOTE_MAP = _make_quote_map(_QUERY_VALUE_SAFE) -_QUERY_VALUE_DECODE_MAP = _make_decode_map(_QUERY_VALUE_DELIMS) +_QUERY_VALUE_DECODE_MAP = _make_decode_map(_QUERY_VALUE_DELIMS | set("+")) _FRAGMENT_QUOTE_MAP = _make_quote_map(_FRAGMENT_SAFE) _FRAGMENT_DECODE_MAP = _make_decode_map(_FRAGMENT_DELIMS) _UNRESERVED_QUOTE_MAP = _make_quote_map(_UNRESERVED_CHARS) diff --git a/src/hyperlink/test/test_decoded_url.py b/src/hyperlink/test/test_decoded_url.py index 235cd915..44489510 100644 --- a/src/hyperlink/test/test_decoded_url.py +++ b/src/hyperlink/test/test_decoded_url.py @@ -36,6 +36,34 @@ def test_durl_basic(self): assert durl.user == "user" assert durl.userinfo == ("user", "\0\0\0\0") + def test_roundtrip_iri_parameter_values(self): + """ + .to_iri() should never modify the application-level data of a query + parameter. + """ + # type: () -> None + for value in ["hello", "goodbye", "+", "/", ":", "?"]: + self.assertEqual( + DecodedURL(DecodedURL().set("test", value).to_iri()).get( + "test" + ), + [value], + ) + + def test_roundtrip_uri_parameter_values(self): + """ + .to_uri() should never modify the application-level data of a query + parameter. + """ + # type: () -> None + for value in ["hello", "goodbye", "+", "/", ":", "?"]: + self.assertEqual( + DecodedURL(DecodedURL().set("test", value).to_uri()).get( + "test" + ), + [value], + ) + def test_passthroughs(self): # type: () -> None From 4f0e397193258cf276da05882758bb7d97e4e8fe Mon Sep 17 00:00:00 2001 From: Glyph Date: Thu, 18 Mar 2021 00:42:11 -0700 Subject: [PATCH 371/419] mypy comment syntax fix --- src/hyperlink/test/test_decoded_url.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hyperlink/test/test_decoded_url.py b/src/hyperlink/test/test_decoded_url.py index 44489510..48452579 100644 --- a/src/hyperlink/test/test_decoded_url.py +++ b/src/hyperlink/test/test_decoded_url.py @@ -37,11 +37,11 @@ def test_durl_basic(self): assert durl.userinfo == ("user", "\0\0\0\0") def test_roundtrip_iri_parameter_values(self): + # type: () -> None """ .to_iri() should never modify the application-level data of a query parameter. """ - # type: () -> None for value in ["hello", "goodbye", "+", "/", ":", "?"]: self.assertEqual( DecodedURL(DecodedURL().set("test", value).to_iri()).get( @@ -51,11 +51,11 @@ def test_roundtrip_iri_parameter_values(self): ) def test_roundtrip_uri_parameter_values(self): + # type: () -> None """ .to_uri() should never modify the application-level data of a query parameter. """ - # type: () -> None for value in ["hello", "goodbye", "+", "/", ":", "?"]: self.assertEqual( DecodedURL(DecodedURL().set("test", value).to_uri()).get( From 033c64efaa7fe39be04dc15ed731d98fde8f66a2 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Thu, 18 Mar 2021 20:19:17 -0700 Subject: [PATCH 372/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index cd729b55..af3da71a 100644 --- a/tox.ini +++ b/tox.ini @@ -129,7 +129,7 @@ deps = mccabe==0.6.1 pep8-naming==0.11.1 pycodestyle==2.7.0 - pydocstyle==5.1.1 + pydocstyle==6.0.0 pyflakes==2.3.0 commands = From cc12e4b9a058ee1fa49259017307605b0f973daa Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sat, 20 Mar 2021 07:32:34 -0700 Subject: [PATCH 373/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index af3da71a..0b2144a2 100644 --- a/tox.ini +++ b/tox.ini @@ -322,7 +322,7 @@ description = build documentation basepython = {[default]basepython} deps = - Sphinx==3.5.2 + Sphinx==3.5.3 sphinx-rtd-theme==0.5.1 commands = From 196a307e0f184051a8e7280bff6432e6c4bff919 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Wed, 24 Mar 2021 09:59:54 -0700 Subject: [PATCH 374/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 0b2144a2..715b808e 100644 --- a/tox.ini +++ b/tox.ini @@ -130,7 +130,7 @@ deps = pep8-naming==0.11.1 pycodestyle==2.7.0 pydocstyle==6.0.0 - pyflakes==2.3.0 + pyflakes==2.3.1 commands = flake8 {posargs:setup.py src/{env:PY_MODULE}} From 521a562465c95442cc65b9c6193568072acc6b67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Fri, 26 Mar 2021 14:37:37 -0700 Subject: [PATCH 375/419] Remove Python 2.7 and Python 3.5 from the Windows build matrix. --- .appveyor.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 4a09c1ec..19f589e5 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -6,12 +6,6 @@ environment: matrix: - - PYTHON: "C:\\Python27-x64" - TOX_ENV: "test-py27,codecov" - - - PYTHON: "C:\\Python35-x64" - TOX_ENV: "test-py35,codecov" - - PYTHON: "C:\\Python36-x64" TOX_ENV: "test-py36,codecov" From a562f871bd9cbf64996589e25dccfb6ace24a65f Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Thu, 1 Apr 2021 11:09:00 -0700 Subject: [PATCH 376/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 715b808e..ce844545 100644 --- a/tox.ini +++ b/tox.ini @@ -124,7 +124,7 @@ basepython = {[default]basepython} skip_install = True deps = - flake8-bugbear==21.3.2 + flake8-bugbear==21.4.1 flake8==3.9.0 mccabe==0.6.1 pep8-naming==0.11.1 From 24eb21725a96e64b407dfb5dc6570a5e05f10a2a Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Thu, 1 Apr 2021 13:23:28 -0700 Subject: [PATCH 377/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index ce844545..e8ed7bcd 100644 --- a/tox.ini +++ b/tox.ini @@ -124,7 +124,7 @@ basepython = {[default]basepython} skip_install = True deps = - flake8-bugbear==21.4.1 + flake8-bugbear==21.4.2 flake8==3.9.0 mccabe==0.6.1 pep8-naming==0.11.1 From bb2cdfd198b4ca7366a8c1bb9b79b55b01d56008 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Thu, 1 Apr 2021 19:38:22 -0700 Subject: [PATCH 378/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index e8ed7bcd..fc979480 100644 --- a/tox.ini +++ b/tox.ini @@ -124,7 +124,7 @@ basepython = {[default]basepython} skip_install = True deps = - flake8-bugbear==21.4.2 + flake8-bugbear==21.4.3 flake8==3.9.0 mccabe==0.6.1 pep8-naming==0.11.1 From ad440312d86df6e6af4affbeeccf28fc96d56fc2 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Mon, 5 Apr 2021 09:50:51 -0700 Subject: [PATCH 379/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index fc979480..34c197aa 100644 --- a/tox.ini +++ b/tox.ini @@ -323,7 +323,7 @@ basepython = {[default]basepython} deps = Sphinx==3.5.3 - sphinx-rtd-theme==0.5.1 + sphinx-rtd-theme==0.5.2 commands = sphinx-build \ From 87a18397575f94a7fd09c417db4708b508d3a788 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Tue, 6 Apr 2021 15:03:02 -0700 Subject: [PATCH 380/419] Exclude `if TYPE_CHECKING` and `...` from coverage. --- .coveragerc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.coveragerc b/.coveragerc index 7dcb567b..c7166536 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,3 +1,9 @@ [run] branch = True omit = */flycheck_* + +[report] +precision = 2 +exclude_lines = + if TYPE_CHECKING + \s*\.\.\.$ From 8f833b60314c1a32238f6b1693cd2cdb9a9dd8fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Tue, 6 Apr 2021 15:54:20 -0700 Subject: [PATCH 381/419] Nix Travis --- .travis.yml | 39 --------------------------------------- 1 file changed, 39 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 80b3525c..00000000 --- a/.travis.yml +++ /dev/null @@ -1,39 +0,0 @@ -cache: pip - -language: python - - -matrix: - include: - - python: "3.8" - env: TOXENV=flake8 - - python: "3.8" - env: TOXENV=mypy - - python: "2.7" - env: TOXENV=test-py27,codecov - - python: "3.4" - env: TOXENV=test-py34,codecov - - python: "3.5" - env: TOXENV=test-py35,codecov - - python: "3.6" - env: TOXENV=test-py36,codecov - - python: "3.7" - env: TOXENV=test-py37,codecov - - python: "3.8" - env: TOXENV=test-py38,codecov - - python: "pypy" - env: TOXENV=test-pypy2,codecov - - python: "pypy3" - env: TOXENV=test-pypy3,codecov - - python: "2.7" - env: TOXENV=packaging-py27 - - python: "3.8" - env: TOXENV=docs - - -install: - - pip install tox - - -script: - - tox From 8bfb957ee9a93ce8c97c54f943fc6992d9c6e893 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sun, 11 Apr 2021 05:10:55 -0700 Subject: [PATCH 382/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 34c197aa..fbec70a3 100644 --- a/tox.ini +++ b/tox.ini @@ -322,7 +322,7 @@ description = build documentation basepython = {[default]basepython} deps = - Sphinx==3.5.3 + Sphinx==3.5.4 sphinx-rtd-theme==0.5.2 commands = From f3b5a7e46c40b9c55b7e7646f006118819d64354 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sun, 11 Apr 2021 05:39:34 -0700 Subject: [PATCH 383/419] [requires.io] dependency update From 07d9f6a902d49afa8e990fe3040b9de19556daf9 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Thu, 15 Apr 2021 21:47:25 -0700 Subject: [PATCH 384/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index fbec70a3..93b73f58 100644 --- a/tox.ini +++ b/tox.ini @@ -125,7 +125,7 @@ skip_install = True deps = flake8-bugbear==21.4.3 - flake8==3.9.0 + flake8==3.9.1 mccabe==0.6.1 pep8-naming==0.11.1 pycodestyle==2.7.0 From f1a9b38b6f9daac492bf6b0836f325adcc28f79c Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sun, 25 Apr 2021 15:26:36 -0700 Subject: [PATCH 385/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 93b73f58..7aad07d7 100644 --- a/tox.ini +++ b/tox.ini @@ -93,7 +93,7 @@ basepython = {[default]basepython} skip_install = True deps = - black==20.8b1 + black==21.4b0 setenv = BLACK_LINT_ARGS=--check From 4742babed656b0ac0e0d3be04eea9923e43f09c5 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Tue, 27 Apr 2021 07:56:38 -0700 Subject: [PATCH 386/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 7aad07d7..ef125046 100644 --- a/tox.ini +++ b/tox.ini @@ -93,7 +93,7 @@ basepython = {[default]basepython} skip_install = True deps = - black==21.4b0 + black==21.4b1 setenv = BLACK_LINT_ARGS=--check From 9157e05a34e3f6a49475412cbcb454868c2434f8 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Wed, 28 Apr 2021 08:42:15 -0700 Subject: [PATCH 387/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index ef125046..f88d9e7e 100644 --- a/tox.ini +++ b/tox.ini @@ -93,7 +93,7 @@ basepython = {[default]basepython} skip_install = True deps = - black==21.4b1 + black==21.4b2 setenv = BLACK_LINT_ARGS=--check From fe3b2289865d34be5245e3d7d69c536cd0612859 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sat, 1 May 2021 11:17:02 -0700 Subject: [PATCH 388/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index f88d9e7e..7fa43c2d 100644 --- a/tox.ini +++ b/tox.ini @@ -51,7 +51,7 @@ deps = {[default]deps} # In Python 2, we need to pull in typing, mock - py{26,27,py2}: typing==3.7.4.3 + py{26,27,py2}: typing==3.10.0.0 py{26,27,py2}: mock==3.0.5 # rq.filter: <4 # For pytest From b7664ad515a002d9743c0c641c578e9542780dc4 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Tue, 4 May 2021 18:49:29 -0700 Subject: [PATCH 389/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 7fa43c2d..69e97e66 100644 --- a/tox.ini +++ b/tox.ini @@ -93,7 +93,7 @@ basepython = {[default]basepython} skip_install = True deps = - black==21.4b2 + black==21.5b0 setenv = BLACK_LINT_ARGS=--check From d41f5f6e480589c3b887c9ea46b46afc515bac58 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sat, 8 May 2021 13:37:06 -0700 Subject: [PATCH 390/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 69e97e66..766f57f2 100644 --- a/tox.ini +++ b/tox.ini @@ -125,7 +125,7 @@ skip_install = True deps = flake8-bugbear==21.4.3 - flake8==3.9.1 + flake8==3.9.2 mccabe==0.6.1 pep8-naming==0.11.1 pycodestyle==2.7.0 From 0d8b20cfec8605a3fd68709ba13c2c4bd500e4c0 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sat, 8 May 2021 20:51:23 -0700 Subject: [PATCH 391/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 766f57f2..6751753e 100644 --- a/tox.ini +++ b/tox.ini @@ -322,7 +322,7 @@ description = build documentation basepython = {[default]basepython} deps = - Sphinx==3.5.4 + Sphinx==4.0.0 sphinx-rtd-theme==0.5.2 commands = From 3f0b64dc9503fea1ae7cc9daa04562c6db7b7d92 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Mon, 10 May 2021 08:52:48 -0700 Subject: [PATCH 392/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 6751753e..d8945d47 100644 --- a/tox.ini +++ b/tox.ini @@ -93,7 +93,7 @@ basepython = {[default]basepython} skip_install = True deps = - black==21.5b0 + black==21.5b1 setenv = BLACK_LINT_ARGS=--check From c82e64f9f65de48edae7577c4d64f191275a794c Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Mon, 10 May 2021 11:05:51 -0700 Subject: [PATCH 393/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index d8945d47..e3aff935 100644 --- a/tox.ini +++ b/tox.ini @@ -322,7 +322,7 @@ description = build documentation basepython = {[default]basepython} deps = - Sphinx==4.0.0 + Sphinx==4.0.1 sphinx-rtd-theme==0.5.2 commands = From 826ba4cdeca82e0b2e7330d4bd6dfdc200e39496 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Mon, 17 May 2021 12:22:23 -0700 Subject: [PATCH 394/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index e3aff935..d03bf681 100644 --- a/tox.ini +++ b/tox.ini @@ -129,7 +129,7 @@ deps = mccabe==0.6.1 pep8-naming==0.11.1 pycodestyle==2.7.0 - pydocstyle==6.0.0 + pydocstyle==6.1.0 pyflakes==2.3.1 commands = From 5cc451d0461da20f94b785cc13ac149539850e04 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Mon, 17 May 2021 15:46:27 -0700 Subject: [PATCH 395/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index d03bf681..52af8f6c 100644 --- a/tox.ini +++ b/tox.ini @@ -129,7 +129,7 @@ deps = mccabe==0.6.1 pep8-naming==0.11.1 pycodestyle==2.7.0 - pydocstyle==6.1.0 + pydocstyle==6.1.1 pyflakes==2.3.1 commands = From af2bea19bb10f5515baa5b46f3ff98580badd0c0 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Thu, 20 May 2021 06:52:51 -0700 Subject: [PATCH 396/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 52af8f6c..02eaa0ee 100644 --- a/tox.ini +++ b/tox.ini @@ -322,7 +322,7 @@ description = build documentation basepython = {[default]basepython} deps = - Sphinx==4.0.1 + Sphinx==4.0.2 sphinx-rtd-theme==0.5.2 commands = From d32fab1ba0955a99d23221646b1ca705f0b8f8fc Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Mon, 31 May 2021 07:43:41 -0700 Subject: [PATCH 397/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 02eaa0ee..b60afa08 100644 --- a/tox.ini +++ b/tox.ini @@ -93,7 +93,7 @@ basepython = {[default]basepython} skip_install = True deps = - black==21.5b1 + black==21.5b2 setenv = BLACK_LINT_ARGS=--check From 1cf766e89b694b94b84a1891e20f9014f150349c Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Tue, 8 Jun 2021 11:26:41 -0700 Subject: [PATCH 398/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index b60afa08..de6ec0ea 100644 --- a/tox.ini +++ b/tox.ini @@ -187,7 +187,7 @@ description = run Mypy (static type checker) basepython = {[default]basepython} deps = - mypy==0.812 + mypy==0.900 {[default]deps} From 3af06c0dcbf4df5735f330b934573405e1aa6602 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Tue, 8 Jun 2021 19:00:40 -0700 Subject: [PATCH 399/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index de6ec0ea..b1238f45 100644 --- a/tox.ini +++ b/tox.ini @@ -187,7 +187,7 @@ description = run Mypy (static type checker) basepython = {[default]basepython} deps = - mypy==0.900 + mypy==0.901 {[default]deps} From bfa30e3a0dfea83e53314173641d71f9316256a1 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Thu, 10 Jun 2021 07:56:42 -0700 Subject: [PATCH 400/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index b1238f45..60fee3c7 100644 --- a/tox.ini +++ b/tox.ini @@ -187,7 +187,7 @@ description = run Mypy (static type checker) basepython = {[default]basepython} deps = - mypy==0.901 + mypy==0.902 {[default]deps} From 3dd6dd9e38b31a424826670168f924da3bb5d240 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Thu, 10 Jun 2021 19:45:38 -0700 Subject: [PATCH 401/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 60fee3c7..43970776 100644 --- a/tox.ini +++ b/tox.ini @@ -93,7 +93,7 @@ basepython = {[default]basepython} skip_install = True deps = - black==21.5b2 + black==21.6b0 setenv = BLACK_LINT_ARGS=--check From 190a325665e3e3dc0b63d2d57b7bb5a8124e3d9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilfredo=20Sa=CC=81nchez?= Date: Thu, 10 Jun 2021 23:07:37 -0700 Subject: [PATCH 402/419] Fix mypy error that includes this message: src/hyperlink/test/test_hypothesis.py:19:1: note: Hint: "python3 -m pip install types-mock" --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index 43970776..592949c7 100644 --- a/tox.ini +++ b/tox.ini @@ -188,6 +188,7 @@ basepython = {[default]basepython} deps = mypy==0.902 + types-mock==0.1.1 {[default]deps} From 01d3a888cf4ffcad485de290cbfcd301b00503f7 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sat, 12 Jun 2021 18:10:36 -0700 Subject: [PATCH 403/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 592949c7..c9674616 100644 --- a/tox.ini +++ b/tox.ini @@ -188,7 +188,7 @@ basepython = {[default]basepython} deps = mypy==0.902 - types-mock==0.1.1 + types-mock==0.1.2 {[default]deps} From 694ddaf2835943f3564b142c2c0d5707c065e3c1 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Thu, 17 Jun 2021 08:36:20 -0700 Subject: [PATCH 404/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index c9674616..e4606716 100644 --- a/tox.ini +++ b/tox.ini @@ -188,7 +188,7 @@ basepython = {[default]basepython} deps = mypy==0.902 - types-mock==0.1.2 + types-mock==0.1.3 {[default]deps} From bf6ada3af750f345425164655039f8afcadf3191 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Tue, 22 Jun 2021 11:51:33 -0700 Subject: [PATCH 405/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index e4606716..1b0146bb 100644 --- a/tox.ini +++ b/tox.ini @@ -187,7 +187,7 @@ description = run Mypy (static type checker) basepython = {[default]basepython} deps = - mypy==0.902 + mypy==0.910 types-mock==0.1.3 {[default]deps} From abffc890b2551f088e9e12ba8a16c14662258123 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Tue, 22 Jun 2021 12:01:27 -0700 Subject: [PATCH 406/419] [requires.io] dependency update From b3e6d2c9acc3f1e06bb0789b46bbd0eda7ae9ca9 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Mon, 5 Jul 2021 09:31:45 -0700 Subject: [PATCH 407/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 1b0146bb..3198f79c 100644 --- a/tox.ini +++ b/tox.ini @@ -323,7 +323,7 @@ description = build documentation basepython = {[default]basepython} deps = - Sphinx==4.0.2 + Sphinx==4.0.3 sphinx-rtd-theme==0.5.2 commands = From 63b5dd58cd2ca76ba4138a57f4beb13fcc32752b Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Wed, 7 Jul 2021 04:31:00 -0700 Subject: [PATCH 408/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 3198f79c..176a5564 100644 --- a/tox.ini +++ b/tox.ini @@ -127,7 +127,7 @@ deps = flake8-bugbear==21.4.3 flake8==3.9.2 mccabe==0.6.1 - pep8-naming==0.11.1 + pep8-naming==0.12.0 pycodestyle==2.7.0 pydocstyle==6.1.1 pyflakes==2.3.1 From 6c9bb98737d7bec5bafe3086917f4a8ef454fff2 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sun, 11 Jul 2021 10:40:33 -0700 Subject: [PATCH 409/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 176a5564..e04be276 100644 --- a/tox.ini +++ b/tox.ini @@ -323,7 +323,7 @@ description = build documentation basepython = {[default]basepython} deps = - Sphinx==4.0.3 + Sphinx==4.1.0 sphinx-rtd-theme==0.5.2 commands = From ccb1a1e730a2b69373be882964d9287803904e2d Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sun, 11 Jul 2021 11:16:38 -0700 Subject: [PATCH 410/419] [requires.io] dependency update From 191bb70555558c2702161aad0be576860b912c62 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Wed, 14 Jul 2021 10:37:05 -0700 Subject: [PATCH 411/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index e04be276..7381f9d1 100644 --- a/tox.ini +++ b/tox.ini @@ -323,7 +323,7 @@ description = build documentation basepython = {[default]basepython} deps = - Sphinx==4.1.0 + Sphinx==4.1.1 sphinx-rtd-theme==0.5.2 commands = From 534628749733660453565f8bc24e2c0bb59474f0 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Fri, 16 Jul 2021 07:58:40 -0700 Subject: [PATCH 412/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 7381f9d1..cff25909 100644 --- a/tox.ini +++ b/tox.ini @@ -93,7 +93,7 @@ basepython = {[default]basepython} skip_install = True deps = - black==21.6b0 + black==21.7b0 setenv = BLACK_LINT_ARGS=--check From f6d2a7d7266e441bc250671151a54e56a5994063 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Tue, 20 Jul 2021 09:28:00 -0700 Subject: [PATCH 413/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index cff25909..898da80f 100644 --- a/tox.ini +++ b/tox.ini @@ -366,7 +366,7 @@ skip_install = True deps = check-manifest==0.46 readme-renderer==29.0 - twine==3.4.1 + twine==3.4.2 commands = check-manifest From 5b743cbdd665e41d983d780f74bc0a57cdc264eb Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Mon, 26 Jul 2021 10:42:02 -0700 Subject: [PATCH 414/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 898da80f..be2f329b 100644 --- a/tox.ini +++ b/tox.ini @@ -323,7 +323,7 @@ description = build documentation basepython = {[default]basepython} deps = - Sphinx==4.1.1 + Sphinx==4.1.2 sphinx-rtd-theme==0.5.2 commands = From ba4680d4bd7cafed989f4b994bb7278fd36ba9ce Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Mon, 2 Aug 2021 11:16:50 -0700 Subject: [PATCH 415/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index be2f329b..8e20f64f 100644 --- a/tox.ini +++ b/tox.ini @@ -281,7 +281,7 @@ skip_install = True deps = {[testenv:coverage_report]deps} - codecov==2.1.11 + codecov==2.1.12 passenv = # See https://github.com/codecov/codecov-python/blob/master/README.md#using-tox From f8f376d5de2753c2ed08ba779b8dfcaca6e57ad3 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sat, 7 Aug 2021 19:57:12 -0700 Subject: [PATCH 416/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 8e20f64f..230738cb 100644 --- a/tox.ini +++ b/tox.ini @@ -127,7 +127,7 @@ deps = flake8-bugbear==21.4.3 flake8==3.9.2 mccabe==0.6.1 - pep8-naming==0.12.0 + pep8-naming==0.12.1 pycodestyle==2.7.0 pydocstyle==6.1.1 pyflakes==2.3.1 From 20ced639ae57923b64e3b5b20b62f1dccd040427 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sun, 8 Aug 2021 02:30:18 -0700 Subject: [PATCH 417/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 230738cb..823de73c 100644 --- a/tox.ini +++ b/tox.ini @@ -188,7 +188,7 @@ basepython = {[default]basepython} deps = mypy==0.910 - types-mock==0.1.3 + types-mock==0.1.4 {[default]deps} From f452e7fb0a09c014f13d97df33fa8cf636ab48a0 Mon Sep 17 00:00:00 2001 From: "requires.io" Date: Sun, 8 Aug 2021 11:30:11 -0700 Subject: [PATCH 418/419] [requires.io] dependency update --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 823de73c..8865d178 100644 --- a/tox.ini +++ b/tox.ini @@ -188,7 +188,7 @@ basepython = {[default]basepython} deps = mypy==0.910 - types-mock==0.1.4 + types-mock==0.1.5 {[default]deps} From 978f2e645578aad856fe5e03e2e2c017c8c0f7ca Mon Sep 17 00:00:00 2001 From: Mahmoud Hashemi Date: Fri, 20 Mar 2026 19:29:01 +0000 Subject: [PATCH 419/419] tox: add Python 3.11, 3.12, 3.13 to test matrix --- tox.ini | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tox.ini b/tox.ini index 8865d178..2165a835 100644 --- a/tox.ini +++ b/tox.ini @@ -43,6 +43,9 @@ basepython = py38: python3.8 py39: python3.9 py310: python3.10 + py311: python3.11 + py312: python3.12 + py313: python3.13 pypy2: pypy pypy3: pypy3 @@ -56,16 +59,16 @@ deps = # For pytest py{26,27,34,py2}: pytest==4.6.11 # rq.filter: <5 - py{35,36,37,38,39,py3}: pytest==5.2.4 + py{35,36,37,38,39,310,311,312,313,py3}: pytest==5.2.4 # For code coverage {[testenv:coverage_report]deps} py{26,27,34,py2}: pytest-cov==2.8.1 # rq.filter: <2.9 - py{35,36,37,38,39,py3}: pytest-cov==2.10.1 + py{35,36,37,38,39,310,311,312,313,py3}: pytest-cov==2.10.1 # For hypothesis. Note Python 3.4 isn't supported by hypothesis. py{26,27,py2}: hypothesis==4.43.9 # rq.filter: <4.44 - py{35,36,37,38,39,py3}: hypothesis==5.8.6 + py{35,36,37,38,39,310,311,312,313,py3}: hypothesis==5.8.6 setenv = {[default]setenv} @@ -244,7 +247,7 @@ ignore_missing_imports = True description = generate coverage report -depends = test-py{26,27,34,35,36,37,38,39,py2,py3} +depends = test-py{26,27,34,35,36,37,38,39,310,311,312,313,py2,py3} basepython = {[default]basepython}