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)
 ==================
 

Reply via email to