Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-yarl for openSUSE:Factory checked in at 2023-04-27 19:59:04 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-yarl (Old) and /work/SRC/openSUSE:Factory/.python-yarl.new.1533 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-yarl" Thu Apr 27 19:59:04 2023 rev:24 rq:1082898 version:1.9.2 Changes: -------- --- /work/SRC/openSUSE:Factory/python-yarl/python-yarl.changes 2023-04-22 21:58:48.572624245 +0200 +++ /work/SRC/openSUSE:Factory/.python-yarl.new.1533/python-yarl.changes 2023-04-27 19:59:04.897271987 +0200 @@ -1,0 +2,16 @@ +Wed Apr 26 07:02:00 UTC 2023 - Daniel Garcia <daniel.gar...@suse.com> + +- Update to version 1.9.2 + Fix regression with truediv and absolute URLs with empty paths + causing the raw path to lack the leading /. ((#854)_) + +------------------------------------------------------------------- +Mon Apr 24 09:13:21 UTC 2023 - Adrian Schröter <adr...@suse.de> + +- update to version 1.9.1 + * Marked tests that fail on older Python patch releases + (< 3.7.10, < 3.8.8 and < 3.9.2) as expected to fail due to missing + a security fix for CVE-2021-23336. ((#850)_) +- Delete support-python-311.patch, not needed anymore + +------------------------------------------------------------------- Old: ---- support-python-311.patch yarl-1.8.2.tar.gz New: ---- yarl-1.9.2.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-yarl.spec ++++++ --- /var/tmp/diff_new_pack.XF1TFg/_old 2023-04-27 19:59:05.401274949 +0200 +++ /var/tmp/diff_new_pack.XF1TFg/_new 2023-04-27 19:59:05.405274973 +0200 @@ -18,14 +18,12 @@ %{?sle15_python_module_pythons} Name: python-yarl -Version: 1.8.2 +Version: 1.9.2 Release: 0 Summary: Yet another URL library License: Apache-2.0 URL: https://github.com/aio-libs/yarl/ Source: https://files.pythonhosted.org/packages/source/y/yarl/yarl-%{version}.tar.gz -# PATCH-FIX-OPENSUSE Workaround until gh#aio-libs/yarl#803 is fixed -Patch0: support-python-311.patch BuildRequires: %{python_module Cython} BuildRequires: %{python_module devel >= 3.7} BuildRequires: %{python_module idna >= 2.0} ++++++ yarl-1.8.2.tar.gz -> yarl-1.9.2.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yarl-1.8.2/CHANGES.rst new/yarl-1.9.2/CHANGES.rst --- old/yarl-1.8.2/CHANGES.rst 2022-12-03 04:07:42.000000000 +0100 +++ new/yarl-1.9.2/CHANGES.rst 2023-04-25 19:43:45.000000000 +0200 @@ -14,6 +14,54 @@ .. towncrier release notes start +1.9.2 (2023-04-25) +================== + +Bugfixes +-------- + +- Fix regression with truediv and absolute URLs with empty paths causing the raw path to lack the leading ``/``. (`#854 <https://github.com/aio-libs/yarl/issues/854>`_) + + +1.9.1 (2023-04-21) +================== + +Bugfixes +-------- + +- Marked tests that fail on older Python patch releases (< 3.7.10, < 3.8.8 and < 3.9.2) as expected to fail due to missing a security fix for CVE-2021-23336. (`#850 <https://github.com/aio-libs/yarl/issues/850>`_) + + +1.9.0 (2023-04-19) +================== + +This release was never published to PyPI, due to issues with the build process. + +Features +-------- + +- Added ``URL.joinpath(*elements)``, to create a new URL appending multiple path elements. (`#704 <https://github.com/aio-libs/yarl/issues/704>`_) +- Made :py:meth:`URL.__truediv__` return ``NotImplemented`` if called with an unsupported type â by :user:`michaeljpeters`. (`#832 <https://github.com/aio-libs/yarl/issues/832>`_) + + +Bugfixes +-------- + +- Path normalisation for absolute URLs no longer raises a ValueError exception + when `..` segments would otherwise go beyond the URL path root. (`#536 <https://github.com/aio-libs/yarl/issues/536>`_) +- Fixed an issue with update_query() not getting rid of the query when argument is None. (`#792 <https://github.com/aio-libs/yarl/issues/792>`_) +- Added some input restrictions on with_port() function to prevent invalid boolean inputs or out of valid port inputs; handled incorrect 0 port representation. (`#793 <https://github.com/aio-libs/yarl/issues/793>`_) +- Made :py:meth:`URL.build` raise a :py:exc:`TypeError` if the ``host`` argument is :py:data:`None` â by :user:`paulpapacz`. (`#808 <https://github.com/aio-libs/yarl/issues/808>`_) +- Fixed an issue with ``update_query()`` getting rid of the query when the argument + is empty but not ``None``. (`#845 <https://github.com/aio-libs/yarl/issues/845>`_) + + +Misc +---- + +- `#220 <https://github.com/aio-libs/yarl/issues/220>`_ + + 1.8.2 (2022-12-03) ================== diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yarl-1.8.2/LICENSE new/yarl-1.9.2/LICENSE --- old/yarl-1.8.2/LICENSE 2022-12-03 04:07:42.000000000 +0100 +++ new/yarl-1.9.2/LICENSE 2023-04-25 19:43:45.000000000 +0200 @@ -1,4 +1,193 @@ - Copyright 2016-2021, Andrew Svetlov and aio-libs team + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yarl-1.8.2/PKG-INFO new/yarl-1.9.2/PKG-INFO --- old/yarl-1.8.2/PKG-INFO 2022-12-03 04:07:53.682745500 +0100 +++ new/yarl-1.9.2/PKG-INFO 2023-04-25 19:43:54.632388000 +0200 @@ -1,11 +1,11 @@ Metadata-Version: 2.1 Name: yarl -Version: 1.8.2 +Version: 1.9.2 Summary: Yet another URL library Home-page: https://github.com/aio-libs/yarl/ Author: Andrew Svetlov Author-email: andrew.svet...@gmail.com -License: Apache 2 +License: Apache-2.0 Classifier: License :: OSI Approved :: Apache Software License Classifier: Intended Audience :: Developers Classifier: Programming Language :: Python @@ -23,6 +23,8 @@ yarl ==== +The module provides handy URL class for URL parsing and changing. + .. image:: https://github.com/aio-libs/yarl/workflows/CI/badge.svg :target: https://github.com/aio-libs/yarl/actions?query=workflow%3ACI :align: right @@ -240,6 +242,54 @@ .. towncrier release notes start +1.9.2 (2023-04-25) +================== + +Bugfixes +-------- + +- Fix regression with truediv and absolute URLs with empty paths causing the raw path to lack the leading ``/``. (`#854 <https://github.com/aio-libs/yarl/issues/854>`_) + + +1.9.1 (2023-04-21) +================== + +Bugfixes +-------- + +- Marked tests that fail on older Python patch releases (< 3.7.10, < 3.8.8 and < 3.9.2) as expected to fail due to missing a security fix for CVE-2021-23336. (`#850 <https://github.com/aio-libs/yarl/issues/850>`_) + + +1.9.0 (2023-04-19) +================== + +This release was never published to PyPI, due to issues with the build process. + +Features +-------- + +- Added ``URL.joinpath(*elements)``, to create a new URL appending multiple path elements. (`#704 <https://github.com/aio-libs/yarl/issues/704>`_) +- Made :py``(?P=rendered_text)`` return ``NotImplemented`` if called with an unsupported type â by ``(?P=rendered_text)``. (`#832 <https://github.com/aio-libs/yarl/issues/832>`_) + + +Bugfixes +-------- + +- Path normalisation for absolute URLs no longer raises a ValueError exception + when `..` segments would otherwise go beyond the URL path root. (`#536 <https://github.com/aio-libs/yarl/issues/536>`_) +- Fixed an issue with update_query() not getting rid of the query when argument is None. (`#792 <https://github.com/aio-libs/yarl/issues/792>`_) +- Added some input restrictions on with_port() function to prevent invalid boolean inputs or out of valid port inputs; handled incorrect 0 port representation. (`#793 <https://github.com/aio-libs/yarl/issues/793>`_) +- Made :py``(?P=rendered_text)`` raise a :py``(?P=rendered_text)`` if the ``host`` argument is :py``(?P=rendered_text)`` â by ``(?P=rendered_text)``. (`#808 <https://github.com/aio-libs/yarl/issues/808>`_) +- Fixed an issue with ``update_query()`` getting rid of the query when the argument + is empty but not ``None``. (`#845 <https://github.com/aio-libs/yarl/issues/845>`_) + + +Misc +---- + +- `#220 <https://github.com/aio-libs/yarl/issues/220>`_ + + 1.8.2 (2022-12-03) ================== diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yarl-1.8.2/README.rst new/yarl-1.9.2/README.rst --- old/yarl-1.8.2/README.rst 2022-12-03 04:07:42.000000000 +0100 +++ new/yarl-1.9.2/README.rst 2023-04-25 19:43:45.000000000 +0200 @@ -1,6 +1,8 @@ yarl ==== +The module provides handy URL class for URL parsing and changing. + .. image:: https://github.com/aio-libs/yarl/workflows/CI/badge.svg :target: https://github.com/aio-libs/yarl/actions?query=workflow%3ACI :align: right diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yarl-1.8.2/docs/api.rst new/yarl-1.9.2/docs/api.rst --- old/yarl-1.8.2/docs/api.rst 2022-12-03 04:07:42.000000000 +0100 +++ new/yarl-1.9.2/docs/api.rst 2023-04-25 19:43:45.000000000 +0200 @@ -817,6 +817,30 @@ >>> url URL('http://example.com/path/%D1%81%D1%8E%D0%B4%D0%B0') +.. method:: URL.joinpath(*other, encoded=False) + + Construct a new URL by with all ``other`` elements appended to + *path*, and cleaned up *query* and *fragment* parts. + + Passing ``encoded=True`` parameter prevents path element auto-encoding, the caller is + responsible for taking care of URL correctness. + + .. doctest:: + + >>> url = URL('http://example.com/path?arg#frag').joinpath('to', 'subpath') + >>> url + URL('http://example.com/path/to/subpath') + >>> url.parts + ('/', 'path', 'to', 'subpath') + >>> url = URL('http://example.com/path?arg#frag').joinpath('ÑÑда') + >>> url + URL('http://example.com/path/%D1%81%D1%8E%D0%B4%D0%B0') + >>> url = URL('http://example.com/path').joinpath('%D1%81%D1%8E%D0%B4%D0%B0', encoded=True) + >>> url + URL('http://example.com/path/%D1%81%D1%8E%D0%B4%D0%B0') + + .. versionadded:: 1.9 + .. method:: URL.join(url) Construct a full (âabsoluteâ) URL by combining a âbase URLâ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yarl-1.8.2/setup.py new/yarl-1.9.2/setup.py --- old/yarl-1.8.2/setup.py 2022-12-03 04:07:42.000000000 +0100 +++ new/yarl-1.9.2/setup.py 2023-04-25 19:43:45.000000000 +0200 @@ -70,7 +70,7 @@ author="Andrew Svetlov", author_email="andrew.svet...@gmail.com", url="https://github.com/aio-libs/yarl/", - license="Apache 2", + license="Apache-2.0", packages=["yarl"], install_requires=install_requires, python_requires=">=3.7", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yarl-1.8.2/tests/test_normalize_path.py new/yarl-1.9.2/tests/test_normalize_path.py --- old/yarl-1.8.2/tests/test_normalize_path.py 2022-12-03 04:07:42.000000000 +0100 +++ new/yarl-1.9.2/tests/test_normalize_path.py 2023-04-25 19:43:45.000000000 +0200 @@ -19,6 +19,10 @@ ("../path/to", "path/to"), ("path/../to", "to"), ("path/../../to", "to"), + # absolute path root / is maintained; tests based on two + # tests from web-platform-tests project's urltestdata.json + ("/foo/../../../ton", "/ton"), + ("/foo/../../../..bar", "/..bar"), # Non-ASCII characters ("μονοÏάÏι/../../να/á´É´Éª/á´á´á´ á´", "να/á´É´Éª/á´á´á´ á´"), ("μονοÏάÏι/../../να/ð¦ðð/ðð ðð/.", "να/ð¦ðð/ðð ðð/"), diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yarl-1.8.2/tests/test_update_query.py new/yarl-1.9.2/tests/test_update_query.py --- old/yarl-1.8.2/tests/test_update_query.py 2022-12-03 04:07:42.000000000 +0100 +++ new/yarl-1.9.2/tests/test_update_query.py 2023-04-25 19:43:45.000000000 +0200 @@ -40,6 +40,17 @@ url.update_query("a", "b") +def test_update_query_with_none_arg(): + url = URL("http://example.com/?foo=bar&baz=foo") + expected_url = URL("http://example.com/") + assert url.update_query(None) == expected_url + + +def test_update_query_with_empty_dict(): + url = URL("http://example.com/?foo=bar&baz=foo") + assert url.update_query({}) == url + + def test_with_query_list_of_pairs(): url = URL("http://example.com") assert str(url.with_query([("a", "1")])) == "http://example.com/?a=1" @@ -48,7 +59,7 @@ def test_with_query_list_non_pairs(): url = URL("http://example.com") with pytest.raises(ValueError): - url.with_query(["a=1", "b=2" "c=3"]) + url.with_query(["a=1", "b=2", "c=3"]) def test_with_query_kwargs(): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yarl-1.8.2/tests/test_url.py new/yarl-1.9.2/tests/test_url.py --- old/yarl-1.8.2/tests/test_url.py 2022-12-03 04:07:42.000000000 +0100 +++ new/yarl-1.9.2/tests/test_url.py 2023-04-25 19:43:45.000000000 +0200 @@ -431,7 +431,7 @@ def test_raw_parts_for_relative_double_path(): url = URL("path/to") - assert url.raw_parts == url.raw_parts + assert ("path", "to") == url.raw_parts def test_parts_for_empty_url(): @@ -690,23 +690,27 @@ def test_div_root(): - url = URL("http://example.com") - assert str(url / "path" / "to") == "http://example.com/path/to" + url = URL("http://example.com") / "path" / "to" + assert str(url) == "http://example.com/path/to" + assert url.raw_path == "/path/to" def test_div_root_with_slash(): - url = URL("http://example.com/") - assert str(url / "path" / "to") == "http://example.com/path/to" + url = URL("http://example.com/") / "path" / "to" + assert str(url) == "http://example.com/path/to" + assert url.raw_path == "/path/to" def test_div(): - url = URL("http://example.com/path") - assert str(url / "to") == "http://example.com/path/to" + url = URL("http://example.com/path") / "to" + assert str(url) == "http://example.com/path/to" + assert url.raw_path == "/path/to" def test_div_with_slash(): - url = URL("http://example.com/path/") - assert str(url / "to") == "http://example.com/path/to" + url = URL("http://example.com/path/") / "to" + assert str(url) == "http://example.com/path/to" + assert url.raw_path == "/path/to" def test_div_path_starting_from_slash_is_forbidden(): @@ -715,6 +719,12 @@ url / "/to/others" +def test_div_bad_type(): + url = URL("http://example.com/path/") + with pytest.raises(TypeError): + url / 3 + + def test_div_cleanup_query_and_fragment(): url = URL("http://example.com/path?a=1#frag") assert str(url / "to") == "http://example.com/path/to" @@ -767,6 +777,111 @@ assert url.raw_path == "/path/to" +# joinpath + + +@pytest.mark.parametrize( + "base,to_join,expected", + [ + pytest.param("", ("path", "to"), "http://example.com/path/to", id="root"), + pytest.param( + "/", ("path", "to"), "http://example.com/path/to", id="root-with-slash" + ), + pytest.param("/path", ("to",), "http://example.com/path/to", id="path"), + pytest.param( + "/path/", ("to",), "http://example.com/path/to", id="path-with-slash" + ), + pytest.param( + "/path?a=1#frag", + ("to",), + "http://example.com/path/to", + id="cleanup-query-and-fragment", + ), + ], +) +def test_joinpath(base, to_join, expected): + url = URL(f"http://example.com{base}") + assert str(url.joinpath(*to_join)) == expected + + +@pytest.mark.parametrize( + "url,to_join,expected", + [ + pytest.param(URL(), ("a",), ("a",), id="empty-url"), + pytest.param(URL("a"), ("b",), ("a", "b"), id="relative-path"), + pytest.param(URL("a"), ("b", "", "c"), ("a", "b", "c"), id="empty-element"), + pytest.param(URL("/a"), ("b"), ("/", "a", "b"), id="absolute-path"), + ], +) +def test_joinpath_relative(url, to_join, expected): + assert url.joinpath(*to_join).raw_parts == expected + + +@pytest.mark.parametrize( + "url,to_join,encoded,e_path,e_raw_path,e_parts,e_raw_parts", + [ + pytest.param( + "http://example.com/ÑÑда", + ("ÑÑда",), + False, + "/ÑÑда/ÑÑда", + "/%D1%81%D1%8E%D0%B4%D0%B0/%D1%82%D1%83%D0%B4%D0%B0", + ("/", "ÑÑда", "ÑÑда"), + ("/", "%D1%81%D1%8E%D0%B4%D0%B0", "%D1%82%D1%83%D0%B4%D0%B0"), + id="non-ascii", + ), + pytest.param( + "http://example.com/path", + ("%cf%80",), + False, + "/path/%cf%80", + "/path/%25cf%2580", + ("/", "path", "%cf%80"), + ("/", "path", "%25cf%2580"), + id="percent-encoded", + ), + pytest.param( + "http://example.com/path", + ("%cf%80",), + True, + "/path/Ï", + "/path/%cf%80", + ("/", "path", "Ï"), + ("/", "path", "%cf%80"), + id="encoded-percent-encoded", + ), + ], +) +def test_joinpath_encoding( + url, to_join, encoded, e_path, e_raw_path, e_parts, e_raw_parts +): + joined = URL(url).joinpath(*to_join, encoded=encoded) + assert joined.path == e_path + assert joined.raw_path == e_raw_path + assert joined.parts == e_parts + assert joined.raw_parts == e_raw_parts + + +@pytest.mark.parametrize( + "to_join,expected", + [ + pytest.param(("path:abc@123",), "/base/path:abc@123", id="with-colon-and-at"), + pytest.param(("..", "path", ".", "to"), "/path/to", id="with-dots"), + ], +) +def test_joinpath_edgecases(to_join, expected): + url = URL("http://example.com/base").joinpath(*to_join) + assert url.raw_path == expected + + +def test_joinpath_path_starting_from_slash_is_forbidden(): + url = URL("http://example.com/path/") + with pytest.raises( + ValueError, match="Appending path .* starting from slash is forbidden" + ): + assert url.joinpath("/to/others") + + # with_path diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yarl-1.8.2/tests/test_url_build.py new/yarl-1.9.2/tests/test_url_build.py --- old/yarl-1.8.2/tests/test_url_build.py 2022-12-03 04:07:42.000000000 +0100 +++ new/yarl-1.9.2/tests/test_url_build.py 2023-04-25 19:43:45.000000000 +0200 @@ -239,6 +239,11 @@ URL.build(scheme="http", host="example.com", path="path_without_leading_slash") +def test_build_with_none_host(): + with pytest.raises(TypeError, match="NoneType is illegal for.*host"): + URL.build(scheme="http", host=None) + + def test_build_with_none_path(): with pytest.raises(TypeError): URL.build(scheme="http", host="example.com", path=None) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yarl-1.8.2/tests/test_url_parsing.py new/yarl-1.9.2/tests/test_url_parsing.py --- old/yarl-1.8.2/tests/test_url_parsing.py 2022-12-03 04:07:42.000000000 +0100 +++ new/yarl-1.9.2/tests/test_url_parsing.py 2023-04-25 19:43:45.000000000 +0200 @@ -73,8 +73,8 @@ assert u.fragment == "" def test_not_a_scheme2(self): - u = URL("37signals:book") - assert u.scheme == "37signals" + u = URL("signals37:book") + assert u.scheme == "signals37" assert u.host is None assert u.path == "book" assert u.query_string == "" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yarl-1.8.2/tests/test_url_query.py new/yarl-1.9.2/tests/test_url_query.py --- old/yarl-1.8.2/tests/test_url_query.py 2022-12-03 04:07:42.000000000 +0100 +++ new/yarl-1.9.2/tests/test_url_query.py 2023-04-25 19:43:45.000000000 +0200 @@ -1,36 +1,71 @@ -from urllib.parse import urlencode +from typing import List, Tuple +from urllib.parse import parse_qs, urlencode +import pytest from multidict import MultiDict, MultiDictProxy from yarl import URL -# query - - -def test_query_spaces(): - url = URL("http://example.com?a+b=c+d") - assert url.query == MultiDict({"a b": "c d"}) - - -def test_query_empty(): - url = URL("http://example.com") - assert isinstance(url.query, MultiDictProxy) - assert url.query == MultiDict() - - -def test_query(): - url = URL("http://example.com?a=1&b=2") - assert url.query == MultiDict([("a", "1"), ("b", "2")]) - - -def test_query_repeated_args(): - url = URL("http://example.com?a=1&b=2&a=3") - assert url.query == MultiDict([("a", "1"), ("b", "2"), ("a", "3")]) - - -def test_query_empty_arg(): - url = URL("http://example.com?a") - assert url.query == MultiDict([("a", "")]) +# ======================================== +# Basic chars in query values +# ======================================== + +URLS_WITH_BASIC_QUERY_VALUES: List[Tuple[URL, MultiDict]] = [ + # Empty strings, keys and values + ( + URL("http://example.com"), + MultiDict(), + ), + ( + URL("http://example.com?a="), + MultiDict([("a", "")]), + ), + # ASCII chars + ( + URL("http://example.com?a+b=c+d"), + MultiDict({"a b": "c d"}), + ), + ( + URL("http://example.com?a=1&b=2"), + MultiDict([("a", "1"), ("b", "2")]), + ), + ( + URL("http://example.com?a=1&b=2&a=3"), + MultiDict([("a", "1"), ("b", "2"), ("a", "3")]), + ), + # Non-ASCI BMP chars + ( + URL("http://example.com?клÑÑ=знаÑ"), + MultiDict({"клÑÑ": "знаÑ"}), + ), + ( + URL("http://example.com?foo=á´É´Éªá´á´á´ á´"), + MultiDict({"foo": "á´É´Éªá´á´á´ á´"}), + ), + # Non-BMP chars + ( + URL("http://example.com?bar=ð¦ðððð ðð"), + MultiDict({"bar": "ð¦ðððð ðð"}), + ), +] + + +@pytest.mark.parametrize( + "original_url, expected_query", + URLS_WITH_BASIC_QUERY_VALUES, +) +def test_query_basic_parsing(original_url, expected_query): + assert isinstance(original_url.query, MultiDictProxy) + assert original_url.query == expected_query + + +@pytest.mark.parametrize( + "original_url, expected_query", + URLS_WITH_BASIC_QUERY_VALUES, +) +def test_query_basic_update_query(original_url, expected_query): + new_url = original_url.update_query({}) + assert new_url == original_url def test_query_dont_unqoute_twice(): @@ -42,20 +77,97 @@ assert url.query["url"] == sample_url -def test_query_nonascii(): - url = URL("http://example.com?клÑÑ=знаÑ") - assert url.query == MultiDict({"клÑÑ": "знаÑ"}) - - -# query separators - - -def test_ampersand_as_separator(): - u = URL("http://127.0.0.1/?a=1&b=2") - assert len(u.query) == 2 +# ======================================== +# Reserved chars in query values +# ======================================== + +# See https://github.com/python/cpython#87133, which introduced a new +# `separator` keyword argument to `urllib.parse.parse_qs` (among others). +# If the name doesn't exist as a variable in the function bytecode, the +# test is expected to fail. +_SEMICOLON_XFAIL = pytest.mark.xfail( + condition="separator" not in parse_qs.__code__.co_varnames, + reason=( + "Python versions < 3.7.10, < 3.8.8 and < 3.9.2 lack a fix for " + 'CVE-2021-23336 dropping ";" as a valid query parameter separator, ' + "making this test fail." + ), + strict=True, +) + + +URLS_WITH_RESERVED_CHARS_IN_QUERY_VALUES = [ + # Ampersand + (URL("http://127.0.0.1/?a=10&b=20"), 2, "10"), + (URL("http://127.0.0.1/?a=10%26b=20"), 1, "10&b=20"), + (URL("http://127.0.0.1/?a=10%3Bb=20"), 1, "10;b=20"), + # Semicolon, which is *not* a query parameter separator as of RFC3986 + (URL("http://127.0.0.1/?a=10;b=20"), 1, "10;b=20"), + (URL("http://127.0.0.1/?a=10%26b=20"), 1, "10&b=20"), + (URL("http://127.0.0.1/?a=10%3Bb=20"), 1, "10;b=20"), +] +URLS_WITH_RESERVED_CHARS_IN_QUERY_VALUES_W_XFAIL = [ + # Ampersand + *URLS_WITH_RESERVED_CHARS_IN_QUERY_VALUES[:3], + # Semicolon, which is *not* a query parameter separator as of RFC3986 + # Mark the first of these as expecting to fail on old Python patch releases. + pytest.param(*URLS_WITH_RESERVED_CHARS_IN_QUERY_VALUES[3], marks=_SEMICOLON_XFAIL), + *URLS_WITH_RESERVED_CHARS_IN_QUERY_VALUES[4:], +] + + +@pytest.mark.parametrize( + "original_url, expected_query_len, expected_value_a", + URLS_WITH_RESERVED_CHARS_IN_QUERY_VALUES_W_XFAIL, +) +def test_query_separators_from_parsing( + original_url, + expected_query_len, + expected_value_a, +): + assert len(original_url.query) == expected_query_len + assert original_url.query["a"] == expected_value_a + + +@pytest.mark.parametrize( + "original_url, expected_query_len, expected_value_a", + URLS_WITH_RESERVED_CHARS_IN_QUERY_VALUES_W_XFAIL, +) +def test_query_separators_from_update_query( + original_url, + expected_query_len, + expected_value_a, +): + new_url = original_url.update_query({"c": expected_value_a}) + assert new_url.query["a"] == expected_value_a + assert new_url.query["c"] == expected_value_a + + +@pytest.mark.parametrize( + "original_url, expected_query_len, expected_value_a", + URLS_WITH_RESERVED_CHARS_IN_QUERY_VALUES, +) +def test_query_separators_from_with_query( + original_url, + expected_query_len, + expected_value_a, +): + new_url = original_url.with_query({"c": expected_value_a}) + assert new_url.query["c"] == expected_value_a + + +@pytest.mark.parametrize( + "original_url, expected_query_len, expected_value_a", + URLS_WITH_RESERVED_CHARS_IN_QUERY_VALUES, +) +def test_query_from_empty_update_query( + original_url, + expected_query_len, + expected_value_a, +): + new_url = original_url.update_query({}) + assert new_url.query["a"] == original_url.query["a"] -def test_ampersand_as_value(): - u = URL("http://127.0.0.1/?a=1%26b=2") - assert len(u.query) == 1 - assert u.query["a"] == "1&b=2" + if "b" in original_url.query: + assert new_url.query["b"] == original_url.query["b"] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yarl-1.8.2/tests/test_url_update_netloc.py new/yarl-1.9.2/tests/test_url_update_netloc.py --- old/yarl-1.8.2/tests/test_url_update_netloc.py 2022-12-03 04:07:42.000000000 +0100 +++ new/yarl-1.9.2/tests/test_url_update_netloc.py 2023-04-25 19:43:45.000000000 +0200 @@ -191,6 +191,11 @@ assert str(url.with_port(8888)) == "http://example.com:8888" +def test_with_port_with_no_port(): + url = URL("http://example.com") + assert str(url.with_port(None)) == "http://example.com" + + def test_with_port_ipv6(): url = URL("http://[::1]:8080/") assert str(url.with_port(80)) == "http://[::1]:80/" @@ -214,3 +219,10 @@ def test_with_port_invalid_type(): with pytest.raises(TypeError): URL("http://example.com").with_port("123") + with pytest.raises(TypeError): + URL("http://example.com").with_port(True) + + +def test_with_port_invalid_range(): + with pytest.raises(ValueError): + URL("http://example.com").with_port(-1) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yarl-1.8.2/yarl/__init__.py new/yarl-1.9.2/yarl/__init__.py --- old/yarl-1.8.2/yarl/__init__.py 2022-12-03 04:07:42.000000000 +0100 +++ new/yarl-1.9.2/yarl/__init__.py 2023-04-25 19:43:45.000000000 +0200 @@ -1,5 +1,5 @@ from ._url import URL, cache_clear, cache_configure, cache_info -__version__ = "1.8.2" +__version__ = "1.9.2" __all__ = ("URL", "cache_clear", "cache_configure", "cache_info") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yarl-1.8.2/yarl/__init__.pyi new/yarl-1.9.2/yarl/__init__.pyi --- old/yarl-1.8.2/yarl/__init__.pyi 2022-12-03 04:07:42.000000000 +0100 +++ new/yarl-1.9.2/yarl/__init__.pyi 2023-04-25 19:43:45.000000000 +0200 @@ -96,6 +96,7 @@ def with_name(self, name: str) -> URL: ... def with_suffix(self, suffix: str) -> URL: ... def join(self, url: URL) -> URL: ... + def joinpath(self, *url: str) -> URL: ... def human_repr(self) -> str: ... # private API @classmethod diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yarl-1.8.2/yarl/_quoting.py new/yarl-1.9.2/yarl/_quoting.py --- old/yarl-1.8.2/yarl/_quoting.py 2022-12-03 04:07:42.000000000 +0100 +++ new/yarl-1.9.2/yarl/_quoting.py 2023-04-25 19:43:45.000000000 +0200 @@ -11,8 +11,8 @@ if not NO_EXTENSIONS: # pragma: no branch try: - from ._quoting_c import _Quoter, _Unquoter # type: ignore[misc] + from ._quoting_c import _Quoter, _Unquoter # type: ignore[assignment] except ImportError: # pragma: no cover - from ._quoting_py import _Quoter, _Unquoter # type: ignore[misc] + from ._quoting_py import _Quoter, _Unquoter # type: ignore[assignment] else: - from ._quoting_py import _Quoter, _Unquoter # type: ignore[misc] + from ._quoting_py import _Quoter, _Unquoter # type: ignore[assignment] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yarl-1.8.2/yarl/_quoting_c.c new/yarl-1.9.2/yarl/_quoting_c.c --- old/yarl-1.8.2/yarl/_quoting_c.c 2022-12-03 04:07:53.000000000 +0100 +++ new/yarl-1.9.2/yarl/_quoting_c.c 2023-04-25 19:43:54.000000000 +0200 @@ -1,4 +1,4 @@ -/* Generated by Cython 0.29.32 */ +/* Generated by Cython 0.29.34 */ #ifndef PY_SSIZE_T_CLEAN #define PY_SSIZE_T_CLEAN @@ -9,8 +9,8 @@ #elif PY_VERSION_HEX < 0x02060000 || (0x03000000 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x03030000) #error Cython requires Python 2.6+ or Python 3.3+. #else -#define CYTHON_ABI "0_29_32" -#define CYTHON_HEX_VERSION 0x001D20F0 +#define CYTHON_ABI "0_29_34" +#define CYTHON_HEX_VERSION 0x001D22F0 #define CYTHON_FUTURE_DIVISION 1 #include <stddef.h> #ifndef offsetof @@ -87,7 +87,7 @@ #undef CYTHON_USE_EXC_INFO_STACK #define CYTHON_USE_EXC_INFO_STACK 0 #ifndef CYTHON_UPDATE_DESCRIPTOR_DOC - #define CYTHON_UPDATE_DESCRIPTOR_DOC (PYPY_VERSION_HEX >= 0x07030900) + #define CYTHON_UPDATE_DESCRIPTOR_DOC 0 #endif #elif defined(PYSTON_VERSION) #define CYTHON_COMPILING_IN_PYPY 0 @@ -203,7 +203,7 @@ #undef CYTHON_USE_PYLONG_INTERNALS #define CYTHON_USE_PYLONG_INTERNALS 0 #elif !defined(CYTHON_USE_PYLONG_INTERNALS) - #define CYTHON_USE_PYLONG_INTERNALS 1 + #define CYTHON_USE_PYLONG_INTERNALS (PY_VERSION_HEX < 0x030C00A5) #endif #ifndef CYTHON_USE_PYLIST_INTERNALS #define CYTHON_USE_PYLIST_INTERNALS 1 @@ -242,7 +242,7 @@ #define CYTHON_USE_TP_FINALIZE (PY_VERSION_HEX >= 0x030400a1) #endif #ifndef CYTHON_USE_DICT_VERSIONS - #define CYTHON_USE_DICT_VERSIONS (PY_VERSION_HEX >= 0x030600B1) + #define CYTHON_USE_DICT_VERSIONS ((PY_VERSION_HEX >= 0x030600B1) && (PY_VERSION_HEX < 0x030C00A5)) #endif #if PY_VERSION_HEX >= 0x030B00A4 #undef CYTHON_USE_EXC_INFO_STACK @@ -552,11 +552,11 @@ #endif #if PY_VERSION_HEX > 0x03030000 && defined(PyUnicode_KIND) #define CYTHON_PEP393_ENABLED 1 - #if defined(PyUnicode_IS_READY) - #define __Pyx_PyUnicode_READY(op) (likely(PyUnicode_IS_READY(op)) ?\ - 0 : _PyUnicode_Ready((PyObject *)(op))) + #if PY_VERSION_HEX >= 0x030C0000 + #define __Pyx_PyUnicode_READY(op) (0) #else - #define __Pyx_PyUnicode_READY(op) (0) + #define __Pyx_PyUnicode_READY(op) (likely(PyUnicode_IS_READY(op)) ?\ + 0 : _PyUnicode_Ready((PyObject *)(op))) #endif #define __Pyx_PyUnicode_GET_LENGTH(u) PyUnicode_GET_LENGTH(u) #define __Pyx_PyUnicode_READ_CHAR(u, i) PyUnicode_READ_CHAR(u, i) @@ -565,14 +565,14 @@ #define __Pyx_PyUnicode_DATA(u) PyUnicode_DATA(u) #define __Pyx_PyUnicode_READ(k, d, i) PyUnicode_READ(k, d, i) #define __Pyx_PyUnicode_WRITE(k, d, i, ch) PyUnicode_WRITE(k, d, i, ch) - #if defined(PyUnicode_IS_READY) && defined(PyUnicode_GET_SIZE) - #if CYTHON_COMPILING_IN_CPYTHON && PY_VERSION_HEX >= 0x03090000 - #define __Pyx_PyUnicode_IS_TRUE(u) (0 != (likely(PyUnicode_IS_READY(u)) ? PyUnicode_GET_LENGTH(u) : ((PyCompactUnicodeObject *)(u))->wstr_length)) - #else - #define __Pyx_PyUnicode_IS_TRUE(u) (0 != (likely(PyUnicode_IS_READY(u)) ? PyUnicode_GET_LENGTH(u) : PyUnicode_GET_SIZE(u))) - #endif + #if PY_VERSION_HEX >= 0x030C0000 + #define __Pyx_PyUnicode_IS_TRUE(u) (0 != PyUnicode_GET_LENGTH(u)) #else - #define __Pyx_PyUnicode_IS_TRUE(u) (0 != PyUnicode_GET_LENGTH(u)) + #if CYTHON_COMPILING_IN_CPYTHON && PY_VERSION_HEX >= 0x03090000 + #define __Pyx_PyUnicode_IS_TRUE(u) (0 != (likely(PyUnicode_IS_READY(u)) ? PyUnicode_GET_LENGTH(u) : ((PyCompactUnicodeObject *)(u))->wstr_length)) + #else + #define __Pyx_PyUnicode_IS_TRUE(u) (0 != (likely(PyUnicode_IS_READY(u)) ? PyUnicode_GET_LENGTH(u) : PyUnicode_GET_SIZE(u))) + #endif #endif #else #define CYTHON_PEP393_ENABLED 0 @@ -1319,18 +1319,18 @@ /* GetModuleGlobalName.proto */ #if CYTHON_USE_DICT_VERSIONS -#define __Pyx_GetModuleGlobalName(var, name) {\ +#define __Pyx_GetModuleGlobalName(var, name) do {\ static PY_UINT64_T __pyx_dict_version = 0;\ static PyObject *__pyx_dict_cached_value = NULL;\ (var) = (likely(__pyx_dict_version == __PYX_GET_DICT_VERSION(__pyx_d))) ?\ (likely(__pyx_dict_cached_value) ? __Pyx_NewRef(__pyx_dict_cached_value) : __Pyx_GetBuiltinName(name)) :\ __Pyx__GetModuleGlobalName(name, &__pyx_dict_version, &__pyx_dict_cached_value);\ -} -#define __Pyx_GetModuleGlobalNameUncached(var, name) {\ +} while(0) +#define __Pyx_GetModuleGlobalNameUncached(var, name) do {\ PY_UINT64_T __pyx_dict_version;\ PyObject *__pyx_dict_cached_value;\ (var) = __Pyx__GetModuleGlobalName(name, &__pyx_dict_version, &__pyx_dict_cached_value);\ -} +} while(0) static PyObject *__Pyx__GetModuleGlobalName(PyObject *name, PY_UINT64_T *dict_version, PyObject **dict_cached_value); #else #define __Pyx_GetModuleGlobalName(var, name) (var) = __Pyx__GetModuleGlobalName(name) @@ -1450,12 +1450,20 @@ /* TypeImport.proto */ #ifndef __PYX_HAVE_RT_ImportType_proto #define __PYX_HAVE_RT_ImportType_proto +#if __STDC_VERSION__ >= 201112L +#include <stdalign.h> +#endif +#if __STDC_VERSION__ >= 201112L || __cplusplus >= 201103L +#define __PYX_GET_STRUCT_ALIGNMENT(s) alignof(s) +#else +#define __PYX_GET_STRUCT_ALIGNMENT(s) sizeof(void*) +#endif enum __Pyx_ImportType_CheckSize { __Pyx_ImportType_CheckSize_Error = 0, __Pyx_ImportType_CheckSize_Warn = 1, __Pyx_ImportType_CheckSize_Ignore = 2 }; -static PyTypeObject *__Pyx_ImportType(PyObject* module, const char *module_name, const char *class_name, size_t size, enum __Pyx_ImportType_CheckSize check_size); +static PyTypeObject *__Pyx_ImportType(PyObject* module, const char *module_name, const char *class_name, size_t size, size_t alignment, enum __Pyx_ImportType_CheckSize check_size); #endif /* CLineInTraceback.proto */ @@ -4551,7 +4559,7 @@ * def __setstate_cython__(self, __pyx_state): * __pyx_unpickle__Quoter__set_state(self, __pyx_state) # <<<<<<<<<<<<<< */ - if (!(likely(PyTuple_CheckExact(__pyx_v___pyx_state))||((__pyx_v___pyx_state) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "tuple", Py_TYPE(__pyx_v___pyx_state)->tp_name), 0))) __PYX_ERR(1, 17, __pyx_L1_error) + if (!(likely(PyTuple_CheckExact(__pyx_v___pyx_state))||((__pyx_v___pyx_state) == Py_None)||((void)PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "tuple", Py_TYPE(__pyx_v___pyx_state)->tp_name), 0))) __PYX_ERR(1, 17, __pyx_L1_error) __pyx_t_1 = __pyx_f_4yarl_10_quoting_c___pyx_unpickle__Quoter__set_state(__pyx_v_self, ((PyObject*)__pyx_v___pyx_state)); if (unlikely(!__pyx_t_1)) __PYX_ERR(1, 17, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_1); __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; @@ -4658,7 +4666,7 @@ * self._qs = qs * self._quoter = _Quoter() */ - if (!(likely(PyUnicode_CheckExact(__pyx_v_unsafe))||((__pyx_v_unsafe) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "unicode", Py_TYPE(__pyx_v_unsafe)->tp_name), 0))) __PYX_ERR(0, 278, __pyx_L1_error) + if (!(likely(PyUnicode_CheckExact(__pyx_v_unsafe))||((__pyx_v_unsafe) == Py_None)||((void)PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "unicode", Py_TYPE(__pyx_v_unsafe)->tp_name), 0))) __PYX_ERR(0, 278, __pyx_L1_error) __pyx_t_1 = __pyx_v_unsafe; __Pyx_INCREF(__pyx_t_1); __Pyx_GIVEREF(__pyx_t_1); @@ -6344,7 +6352,7 @@ * def __setstate_cython__(self, __pyx_state): * __pyx_unpickle__Unquoter__set_state(self, __pyx_state) # <<<<<<<<<<<<<< */ - if (!(likely(PyTuple_CheckExact(__pyx_v___pyx_state))||((__pyx_v___pyx_state) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "tuple", Py_TYPE(__pyx_v___pyx_state)->tp_name), 0))) __PYX_ERR(1, 17, __pyx_L1_error) + if (!(likely(PyTuple_CheckExact(__pyx_v___pyx_state))||((__pyx_v___pyx_state) == Py_None)||((void)PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "tuple", Py_TYPE(__pyx_v___pyx_state)->tp_name), 0))) __PYX_ERR(1, 17, __pyx_L1_error) __pyx_t_1 = __pyx_f_4yarl_10_quoting_c___pyx_unpickle__Unquoter__set_state(__pyx_v_self, ((PyObject*)__pyx_v___pyx_state)); if (unlikely(!__pyx_t_1)) __PYX_ERR(1, 17, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_1); __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; @@ -6590,7 +6598,7 @@ * return __pyx_result * cdef __pyx_unpickle__Quoter__set_state(_Quoter __pyx_result, tuple __pyx_state): */ - if (!(likely(PyTuple_CheckExact(__pyx_v___pyx_state))||((__pyx_v___pyx_state) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "tuple", Py_TYPE(__pyx_v___pyx_state)->tp_name), 0))) __PYX_ERR(1, 9, __pyx_L1_error) + if (!(likely(PyTuple_CheckExact(__pyx_v___pyx_state))||((__pyx_v___pyx_state) == Py_None)||((void)PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "tuple", Py_TYPE(__pyx_v___pyx_state)->tp_name), 0))) __PYX_ERR(1, 9, __pyx_L1_error) __pyx_t_4 = __pyx_f_4yarl_10_quoting_c___pyx_unpickle__Quoter__set_state(((struct __pyx_obj_4yarl_10_quoting_c__Quoter *)__pyx_v___pyx_result), ((PyObject*)__pyx_v___pyx_state)); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 9, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_4); __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; @@ -7017,7 +7025,7 @@ * return __pyx_result * cdef __pyx_unpickle__Unquoter__set_state(_Unquoter __pyx_result, tuple __pyx_state): */ - if (!(likely(PyTuple_CheckExact(__pyx_v___pyx_state))||((__pyx_v___pyx_state) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "tuple", Py_TYPE(__pyx_v___pyx_state)->tp_name), 0))) __PYX_ERR(1, 9, __pyx_L1_error) + if (!(likely(PyTuple_CheckExact(__pyx_v___pyx_state))||((__pyx_v___pyx_state) == Py_None)||((void)PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "tuple", Py_TYPE(__pyx_v___pyx_state)->tp_name), 0))) __PYX_ERR(1, 9, __pyx_L1_error) __pyx_t_4 = __pyx_f_4yarl_10_quoting_c___pyx_unpickle__Unquoter__set_state(((struct __pyx_obj_4yarl_10_quoting_c__Unquoter *)__pyx_v___pyx_result), ((PyObject*)__pyx_v___pyx_state)); if (unlikely(!__pyx_t_4)) __PYX_ERR(1, 9, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_4); __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; @@ -7135,7 +7143,7 @@ } __pyx_t_1 = __Pyx_GetItemInt_Tuple(__pyx_v___pyx_state, 3, long, 1, __Pyx_PyInt_From_long, 0, 0, 1); if (unlikely(!__pyx_t_1)) __PYX_ERR(1, 12, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_1); - if (!(likely(PyUnicode_CheckExact(__pyx_t_1))||((__pyx_t_1) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "unicode", Py_TYPE(__pyx_t_1)->tp_name), 0))) __PYX_ERR(1, 12, __pyx_L1_error) + if (!(likely(PyUnicode_CheckExact(__pyx_t_1))||((__pyx_t_1) == Py_None)||((void)PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "unicode", Py_TYPE(__pyx_t_1)->tp_name), 0))) __PYX_ERR(1, 12, __pyx_L1_error) __Pyx_GIVEREF(__pyx_t_1); __Pyx_GOTREF(__pyx_v___pyx_result->_unsafe); __Pyx_DECREF(__pyx_v___pyx_result->_unsafe); @@ -7993,7 +8001,7 @@ } static CYTHON_SMALL_CODE int __Pyx_InitGlobals(void) { - if (__Pyx_InitStrings(__pyx_string_tab) < 0) __PYX_ERR(0, 1, __pyx_L1_error); + if (__Pyx_InitStrings(__pyx_string_tab) < 0) __PYX_ERR(0, 1, __pyx_L1_error) __pyx_int_2 = PyInt_FromLong(2); if (unlikely(!__pyx_int_2)) __PYX_ERR(0, 1, __pyx_L1_error) __pyx_int_41310077 = PyInt_FromLong(41310077L); if (unlikely(!__pyx_int_41310077)) __PYX_ERR(0, 1, __pyx_L1_error) __pyx_int_81650385 = PyInt_FromLong(81650385L); if (unlikely(!__pyx_int_81650385)) __PYX_ERR(0, 1, __pyx_L1_error) @@ -8098,9 +8106,9 @@ __Pyx_GOTREF(__pyx_t_1); __pyx_ptype_7cpython_4type_type = __Pyx_ImportType(__pyx_t_1, __Pyx_BUILTIN_MODULE_NAME, "type", #if defined(PYPY_VERSION_NUM) && PYPY_VERSION_NUM < 0x050B0000 - sizeof(PyTypeObject), + sizeof(PyTypeObject), __PYX_GET_STRUCT_ALIGNMENT(PyTypeObject), #else - sizeof(PyHeapTypeObject), + sizeof(PyHeapTypeObject), __PYX_GET_STRUCT_ALIGNMENT(PyHeapTypeObject), #endif __Pyx_ImportType_CheckSize_Warn); if (!__pyx_ptype_7cpython_4type_type) __PYX_ERR(2, 9, __pyx_L1_error) @@ -8301,7 +8309,7 @@ Py_INCREF(__pyx_b); __pyx_cython_runtime = PyImport_AddModule((char *) "cython_runtime"); if (unlikely(!__pyx_cython_runtime)) __PYX_ERR(0, 1, __pyx_L1_error) Py_INCREF(__pyx_cython_runtime); - if (PyObject_SetAttrString(__pyx_m, "__builtins__", __pyx_b) < 0) __PYX_ERR(0, 1, __pyx_L1_error); + if (PyObject_SetAttrString(__pyx_m, "__builtins__", __pyx_b) < 0) __PYX_ERR(0, 1, __pyx_L1_error) /*--- Initialize various global constants etc. ---*/ if (__Pyx_InitGlobals() < 0) __PYX_ERR(0, 1, __pyx_L1_error) #if PY_MAJOR_VERSION < 3 && (__PYX_DEFAULT_STRING_ENCODING_IS_ASCII || __PYX_DEFAULT_STRING_ENCODING_IS_DEFAULT) @@ -8433,7 +8441,7 @@ __pyx_t_1 = PyNumber_Add(__pyx_t_3, __pyx_kp_u__14); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 16, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_1); __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - if (!(likely(PyUnicode_CheckExact(__pyx_t_1))||((__pyx_t_1) == Py_None)||(PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "unicode", Py_TYPE(__pyx_t_1)->tp_name), 0))) __PYX_ERR(0, 16, __pyx_L1_error) + if (!(likely(PyUnicode_CheckExact(__pyx_t_1))||((__pyx_t_1) == Py_None)||((void)PyErr_Format(PyExc_TypeError, "Expected %.16s, got %.200s", "unicode", Py_TYPE(__pyx_t_1)->tp_name), 0))) __PYX_ERR(0, 16, __pyx_L1_error) __Pyx_XGOTREF(__pyx_v_4yarl_10_quoting_c_UNRESERVED); __Pyx_DECREF_SET(__pyx_v_4yarl_10_quoting_c_UNRESERVED, ((PyObject*)__pyx_t_1)); __Pyx_GIVEREF(__pyx_t_1); @@ -8743,9 +8751,7 @@ PyGILState_STATE state; if (nogil) state = PyGILState_Ensure(); -#ifdef _MSC_VER - else state = (PyGILState_STATE)-1; -#endif + else state = (PyGILState_STATE)0; #endif __Pyx_PyThreadState_assign __Pyx_ErrFetch(&old_exc, &old_val, &old_tb); @@ -9110,13 +9116,7 @@ } PyErr_SetObject(type, value); if (tb) { -#if CYTHON_COMPILING_IN_PYPY - PyObject *tmp_type, *tmp_value, *tmp_tb; - PyErr_Fetch(&tmp_type, &tmp_value, &tmp_tb); - Py_INCREF(tb); - PyErr_Restore(tmp_type, tmp_value, tb); - Py_XDECREF(tmp_tb); -#else +#if CYTHON_FAST_THREAD_STATE PyThreadState *tstate = __Pyx_PyThreadState_Current; PyObject* tmp_tb = tstate->curexc_traceback; if (tb != tmp_tb) { @@ -9124,6 +9124,12 @@ tstate->curexc_traceback = tb; Py_XDECREF(tmp_tb); } +#else + PyObject *tmp_type, *tmp_value, *tmp_tb; + PyErr_Fetch(&tmp_type, &tmp_value, &tmp_tb); + Py_INCREF(tb); + PyErr_Restore(tmp_type, tmp_value, tb); + Py_XDECREF(tmp_tb); #endif } bad: @@ -9676,7 +9682,7 @@ return __Pyx_PyFunction_FastCall(func, NULL, 0); } #endif -#ifdef __Pyx_CyFunction_USED +#if defined(__Pyx_CyFunction_USED) && defined(NDEBUG) if (likely(PyCFunction_Check(func) || __Pyx_CyFunction_Check(func))) #else if (likely(PyCFunction_Check(func))) @@ -10236,13 +10242,15 @@ #ifndef __PYX_HAVE_RT_ImportType #define __PYX_HAVE_RT_ImportType static PyTypeObject *__Pyx_ImportType(PyObject *module, const char *module_name, const char *class_name, - size_t size, enum __Pyx_ImportType_CheckSize check_size) + size_t size, size_t alignment, enum __Pyx_ImportType_CheckSize check_size) { PyObject *result = 0; char warning[200]; Py_ssize_t basicsize; + Py_ssize_t itemsize; #ifdef Py_LIMITED_API PyObject *py_basicsize; + PyObject *py_itemsize; #endif result = PyObject_GetAttrString(module, class_name); if (!result) @@ -10255,6 +10263,7 @@ } #ifndef Py_LIMITED_API basicsize = ((PyTypeObject *)result)->tp_basicsize; + itemsize = ((PyTypeObject *)result)->tp_itemsize; #else py_basicsize = PyObject_GetAttrString(result, "__basicsize__"); if (!py_basicsize) @@ -10264,8 +10273,23 @@ py_basicsize = 0; if (basicsize == (Py_ssize_t)-1 && PyErr_Occurred()) goto bad; + py_itemsize = PyObject_GetAttrString(result, "__itemsize__"); + if (!py_itemsize) + goto bad; + itemsize = PyLong_AsSsize_t(py_itemsize); + Py_DECREF(py_itemsize); + py_itemsize = 0; + if (itemsize == (Py_ssize_t)-1 && PyErr_Occurred()) + goto bad; #endif - if ((size_t)basicsize < size) { + if (itemsize) { + if (size % alignment) { + alignment = size % alignment; + } + if (itemsize < (Py_ssize_t)alignment) + itemsize = (Py_ssize_t)alignment; + } + if ((size_t)(basicsize + itemsize) < size) { PyErr_Format(PyExc_ValueError, "%.200s.%.200s size changed, may indicate binary incompatibility. " "Expected %zd from C header, got %zd from PyObject", @@ -10295,7 +10319,7 @@ /* CLineInTraceback */ #ifndef CYTHON_CLINE_IN_TRACEBACK -static int __Pyx_CLineForTraceback(CYTHON_NCP_UNUSED PyThreadState *tstate, int c_line) { +static int __Pyx_CLineForTraceback(CYTHON_UNUSED PyThreadState *tstate, int c_line) { PyObject *use_cline; PyObject *ptype, *pvalue, *ptraceback; #if CYTHON_COMPILING_IN_CPYTHON diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yarl-1.8.2/yarl/_url.py new/yarl-1.9.2/yarl/_url.py --- old/yarl-1.8.2/yarl/_url.py 2022-12-03 04:07:42.000000000 +0100 +++ new/yarl-1.9.2/yarl/_url.py 2023-04-25 19:43:45.000000000 +0200 @@ -2,6 +2,7 @@ import math import warnings from collections.abc import Mapping, Sequence +from contextlib import suppress from ipaddress import ip_address from urllib.parse import SplitResult, parse_qsl, quote, urljoin, urlsplit, urlunsplit @@ -51,6 +52,30 @@ raise AttributeError("cached property is read-only") +def _normalize_path_segments(segments): + """Drop '.' and '..' from a sequence of str segments""" + + resolved_path = [] + + for seg in segments: + if seg == "..": + # ignore any .. segments that would otherwise cause an + # IndexError when popped from resolved_path if + # resolving for rfc3986 + with suppress(IndexError): + resolved_path.pop() + elif seg != ".": + resolved_path.append(seg) + + if segments and segments[-1] in (".", ".."): + # do some post-processing here. + # if the last segment was a relative dir, + # then we need to append the trailing '/' + resolved_path.append("") + + return resolved_path + + @rewrite_module class URL: # Don't derive from str @@ -215,12 +240,13 @@ if ( scheme is None or authority is None + or host is None or path is None or query_string is None or fragment is None ): raise TypeError( - 'NoneType is illegal for "scheme", "authority", "path", ' + 'NoneType is illegal for "scheme", "authority", "host", "path", ' '"query_string", and "fragment" args, use empty string instead.' ) @@ -315,25 +341,9 @@ return self._val > other._val def __truediv__(self, name): - name = self._PATH_QUOTER(name) - if name.startswith("/"): - raise ValueError( - f"Appending path {name!r} starting from slash is forbidden" - ) - path = self._val.path - if path == "/": - new_path = "/" + name - elif not path and not self.is_absolute(): - new_path = name - else: - parts = path.rstrip("/").split("/") - parts.append(name) - new_path = "/".join(parts) - if self.is_absolute(): - new_path = self._normalize_path(new_path) - return URL( - self._val._replace(path=new_path, query="", fragment=""), encoded=True - ) + if not type(name) is str: + return NotImplemented + return self._make_child((name,)) def __mod__(self, query): return self.update_query(query) @@ -701,34 +711,51 @@ "Path in a URL with authority should start with a slash ('/') if set" ) + def _make_child(self, segments, encoded=False): + """add segments to self._val.path, accounting for absolute vs relative paths""" + parsed = [] + for seg in reversed(segments): + if not seg: + continue + if seg[0] == "/": + raise ValueError( + f"Appending path {seg!r} starting from slash is forbidden" + ) + seg = seg if encoded else self._PATH_QUOTER(seg) + if "/" in seg: + parsed += ( + sub for sub in reversed(seg.split("/")) if sub and sub != "." + ) + elif seg != ".": + parsed.append(seg) + parsed.reverse() + old_path = self._val.path + if old_path: + parsed = [*old_path.rstrip("/").split("/"), *parsed] + if self.is_absolute(): + parsed = _normalize_path_segments(parsed) + if parsed and parsed[0] != "": + # inject a leading slash when adding a path to an absolute URL + # where there was none before + parsed = ["", *parsed] + new_path = "/".join(parsed) + return URL( + self._val._replace(path=new_path, query="", fragment=""), encoded=True + ) + @classmethod def _normalize_path(cls, path): - # Drop '.' and '..' from path + # Drop '.' and '..' from str path - segments = path.split("/") - resolved_path = [] + prefix = "" + if path.startswith("/"): + # preserve the "/" root element of absolute paths, copying it to the + # normalised output as per sections 5.2.4 and 6.2.2.3 of rfc3986. + prefix = "/" + path = path[1:] - for seg in segments: - if seg == "..": - try: - resolved_path.pop() - except IndexError: - # ignore any .. segments that would otherwise cause an - # IndexError when popped from resolved_path if - # resolving for rfc3986 - pass - elif seg == ".": - continue - else: - resolved_path.append(seg) - - if segments[-1] in (".", ".."): - # do some post-processing here. - # if the last segment was a relative dir, - # then we need to append the trailing '/' - resolved_path.append("") - - return "/".join(resolved_path) + segments = path.split("/") + return prefix + "/".join(_normalize_path_segments(segments)) @classmethod def _encode_host(cls, host, human=False): @@ -761,7 +788,7 @@ ret = cls._encode_host(host) else: ret = host - if port: + if port is not None: ret = ret + ":" + str(port) if password is not None: if not user: @@ -869,8 +896,11 @@ """ # N.B. doesn't cleanup query/fragment - if port is not None and not isinstance(port, int): - raise TypeError(f"port should be int or None, got {type(port)}") + if port is not None: + if isinstance(port, bool) or not isinstance(port, int): + raise TypeError(f"port should be int or None, got {type(port)}") + if port < 0 or port > 65535: + raise ValueError(f"port must be between 0 and 65535, got {port}") if not self.is_absolute(): raise ValueError("port replacement is not allowed for relative URLs") val = self._val @@ -932,7 +962,7 @@ raise ValueError("Either kwargs or single query parameter must be present") if query is None: - query = "" + query = None elif isinstance(query, Mapping): quoter = self._QUERY_PART_QUOTER query = "&".join(self._query_seq_pairs(quoter, query.items())) @@ -974,7 +1004,7 @@ """ # N.B. doesn't cleanup query/fragment - new_query = self._get_str_query(*args, **kwargs) + new_query = self._get_str_query(*args, **kwargs) or "" return URL( self._val._replace(path=self._val.path, query=new_query), encoded=True ) @@ -982,11 +1012,15 @@ def update_query(self, *args, **kwargs): """Return a new URL with query part updated.""" s = self._get_str_query(*args, **kwargs) - new_query = MultiDict(parse_qsl(s, keep_blank_values=True)) - query = MultiDict(self.query) - query.update(new_query) + query = None + if s is not None: + new_query = MultiDict(parse_qsl(s, keep_blank_values=True)) + query = MultiDict(self.query) + query.update(new_query) - return URL(self._val._replace(query=self._get_str_query(query)), encoded=True) + return URL( + self._val._replace(query=self._get_str_query(query) or ""), encoded=True + ) def with_fragment(self, fragment): """Return a new URL with fragment replaced. @@ -1077,6 +1111,10 @@ raise TypeError("url should be URL") return URL(urljoin(str(self), str(url)), encoded=True) + def joinpath(self, *other, encoded=False): + """Return a new URL with the elements in other appended to the path.""" + return self._make_child(other, encoded=encoded) + def human_repr(self): """Return decoded human readable string for URL representation.""" user = _human_quote(self.user, "#/:?@") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yarl-1.8.2/yarl.egg-info/PKG-INFO new/yarl-1.9.2/yarl.egg-info/PKG-INFO --- old/yarl-1.8.2/yarl.egg-info/PKG-INFO 2022-12-03 04:07:53.000000000 +0100 +++ new/yarl-1.9.2/yarl.egg-info/PKG-INFO 2023-04-25 19:43:54.000000000 +0200 @@ -1,11 +1,11 @@ Metadata-Version: 2.1 Name: yarl -Version: 1.8.2 +Version: 1.9.2 Summary: Yet another URL library Home-page: https://github.com/aio-libs/yarl/ Author: Andrew Svetlov Author-email: andrew.svet...@gmail.com -License: Apache 2 +License: Apache-2.0 Classifier: License :: OSI Approved :: Apache Software License Classifier: Intended Audience :: Developers Classifier: Programming Language :: Python @@ -23,6 +23,8 @@ yarl ==== +The module provides handy URL class for URL parsing and changing. + .. image:: https://github.com/aio-libs/yarl/workflows/CI/badge.svg :target: https://github.com/aio-libs/yarl/actions?query=workflow%3ACI :align: right @@ -240,6 +242,54 @@ .. towncrier release notes start +1.9.2 (2023-04-25) +================== + +Bugfixes +-------- + +- Fix regression with truediv and absolute URLs with empty paths causing the raw path to lack the leading ``/``. (`#854 <https://github.com/aio-libs/yarl/issues/854>`_) + + +1.9.1 (2023-04-21) +================== + +Bugfixes +-------- + +- Marked tests that fail on older Python patch releases (< 3.7.10, < 3.8.8 and < 3.9.2) as expected to fail due to missing a security fix for CVE-2021-23336. (`#850 <https://github.com/aio-libs/yarl/issues/850>`_) + + +1.9.0 (2023-04-19) +================== + +This release was never published to PyPI, due to issues with the build process. + +Features +-------- + +- Added ``URL.joinpath(*elements)``, to create a new URL appending multiple path elements. (`#704 <https://github.com/aio-libs/yarl/issues/704>`_) +- Made :py``(?P=rendered_text)`` return ``NotImplemented`` if called with an unsupported type â by ``(?P=rendered_text)``. (`#832 <https://github.com/aio-libs/yarl/issues/832>`_) + + +Bugfixes +-------- + +- Path normalisation for absolute URLs no longer raises a ValueError exception + when `..` segments would otherwise go beyond the URL path root. (`#536 <https://github.com/aio-libs/yarl/issues/536>`_) +- Fixed an issue with update_query() not getting rid of the query when argument is None. (`#792 <https://github.com/aio-libs/yarl/issues/792>`_) +- Added some input restrictions on with_port() function to prevent invalid boolean inputs or out of valid port inputs; handled incorrect 0 port representation. (`#793 <https://github.com/aio-libs/yarl/issues/793>`_) +- Made :py``(?P=rendered_text)`` raise a :py``(?P=rendered_text)`` if the ``host`` argument is :py``(?P=rendered_text)`` â by ``(?P=rendered_text)``. (`#808 <https://github.com/aio-libs/yarl/issues/808>`_) +- Fixed an issue with ``update_query()`` getting rid of the query when the argument + is empty but not ``None``. (`#845 <https://github.com/aio-libs/yarl/issues/845>`_) + + +Misc +---- + +- `#220 <https://github.com/aio-libs/yarl/issues/220>`_ + + 1.8.2 (2022-12-03) ==================