Repository: aio-libs/yarl

Issue:
Allow scheme replacement for relative urls
`URL('path/to').with_scheme('http')` should work.
Now it produces `scheme replacement is not allowed for relative URLs`

Discussion:
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/aio-libs/yarl/pull/1138?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aio-libs) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 95.42%. Comparing base [(`0ee0104`)](https://app.codecov.io/gh/aio-libs/yarl/commit/0ee0104ec400e578e53a138498dbad076660645c?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aio-libs) to head [(`35746f0`)](https://app.codecov.io/gh/aio-libs/yarl/commit/35746f08b98dc48246729a48ca5bcc5d6dfcade8?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aio-libs).

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##           master    #1138   +/-   ##
=======================================
  Coverage   95.41%   95.42%           
=======================================
  Files          30       30           
  Lines        4756     4762    +6     
  Branches      422      422           
=======================================
+ Hits         4538     4544    +6     
  Misses        192      192           
  Partials       26       26           
```

| [Flag](https://app.codecov.io/gh/aio-libs/yarl/pull/1138/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aio-libs) | Coverage Δ | |
|---|---|---|
| [CI-GHA](https://app.codecov.io/gh/aio-libs/yarl/pull/1138/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aio-libs) | `95.38% <100.00%> (+<0.01%)` | :arrow_up: |
| [MyPy](https://app.codecov.io/gh/aio-libs/yarl/pull/1138/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aio-libs) | `41.21% <50.00%> (+0.01%)` | :arrow_up: |
| [OS-Linux](https://app.codecov.io/gh/aio-libs/yarl/pull/1138/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aio-libs) | `99.39% <100.00%> (+<0.01%)` | :arrow_up: |
| [OS-Windows](https://app.codecov.io/gh/aio-libs/yarl/pull/1138/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aio-libs) | `99.49% <100.00%> (+<0.01%)` | :arrow_up: |
| [OS-macOS](https://app.codecov.io/gh/aio-libs/yarl/pull/1138/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aio-libs) | `99.10% <100.00%> (+<0.01%)` | :arrow_up: |
| [Py-3.10.11](https://app.codecov.io/gh/aio-libs/yarl/pull/1138/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aio-libs) | `99.00% <100.00%> (+<0.01%)` | :arrow_up: |
| [Py-3.10.14](https://app.codecov.io/gh/aio-libs/yarl/pull/1138/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aio-libs) | `99.23% <100.00%> (+<0.01%)` | :arrow_up: |
| [Py-3.11.9](https://app.codecov.io/gh/aio-libs/yarl/pull/1138/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aio-libs) | `99.23% <100.00%> (+<0.01%)` | :arrow_up: |
| [Py-3.12.5](https://app.codecov.io/gh/aio-libs/yarl/pull/1138/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aio-libs) | `99.23% <100.00%> (+<0.01%)` | :arrow_up: |
| [Py-3.13.0-rc.1](https://app.codecov.io/gh/aio-libs/yarl/pull/1138/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aio-libs) | `99.23% <100.00%> (+<0.01%)` | :arrow_up: |
| [Py-3.8.10](https://app.codecov.io/gh/aio-libs/yarl/pull/1138/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aio-libs) | `98.95% <100.00%> (+<0.01%)` | :arrow_up: |
| [Py-3.8.18](https://app.codecov.io/gh/aio-libs/yarl/pull/1138/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aio-libs) | `99.18% <100.00%> (+<0.01%)` | :arrow_up: |
| [Py-3.9.13](https://app.codecov.io/gh/aio-libs/yarl/pull/1138/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aio-libs) | `98.95% <100.00%> (+<0.01%)` | :arrow_up: |
| [Py-3.9.19](https://app.codecov.io/gh/aio-libs/yarl/pull/1138/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aio-libs) | `99.18% <100.00%> (+<0.01%)` | :arrow_up: |
| [Py-pypy7.3.11](https://app.codecov.io/gh/aio-libs/yarl/pull/1138/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aio-libs) | `99.24% <100.00%> (+<0.01%)` | :arrow_up: |
| [Py-pypy7.3.16](https://app.codecov.io/gh/aio-libs/yarl/pull/1138/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aio-libs) | `99.24% <100.00%> (+<0.01%)` | :arrow_up: |
| [Py-pypy7.3.17](https://app.codecov.io/gh/aio-libs/yarl/pull/1138/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aio-libs) | `?` | |
| [VM-macos-latest](https://app.codecov.io/gh/aio-libs/yarl/pull/1138/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aio-libs) | `99.10% <100.00%> (+<0.01%)` | :arrow_up: |
| [VM-ubuntu-latest](https://app.codecov.io/gh/aio-libs/yarl/pull/1138/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aio-libs) | `99.39% <100.00%> (+<0.01%)` | :arrow_up: |
| [VM-windows-latest](https://app.codecov.io/gh/aio-libs/yarl/pull/1138/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aio-libs) | `99.49% <100.00%> (+<0.01%)` | :arrow_up: |
| [pytest](https://app.codecov.io/gh/aio-libs/yarl/pull/1138/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aio-libs) | `99.39% <100.00%> (+<0.01%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aio-libs#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/aio-libs/yarl/pull/1138?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aio-libs).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aio-libs).


Patch:
diff --git a/CHANGES/1138.bugfix.rst b/CHANGES/1138.bugfix.rst
new file mode 100644
index 0000000..cfee185
--- /dev/null
+++ b/CHANGES/1138.bugfix.rst
@@ -0,0 +1,1 @@
+Allow scheme replacement for relative URLs if the scheme does not require a host -- by :user:`bdraco`.
diff --git a/CHANGES/280.bugfix.rst b/CHANGES/280.bugfix.rst
new file mode 120000
index 0000000..a2d4ea9
--- /dev/null
+++ b/CHANGES/280.bugfix.rst
@@ -0,0 +1,1 @@
+1138.bugfix.rst
\ No newline at end of file
diff --git a/yarl/_url.py b/yarl/_url.py
index b6a4530..ae9fc17 100644
--- a/yarl/_url.py
+++ b/yarl/_url.py
@@ -1049,8 +1049,12 @@ class URL:
         # N.B. doesn't cleanup query/fragment
         if not isinstance(scheme, str):
             raise TypeError("Invalid scheme type")
-        if not self.absolute:
-            raise ValueError("scheme replacement is not allowed for relative URLs")
+        if not self.absolute and scheme in SCHEME_REQUIRES_HOST:
+            msg = (
+                "scheme replacement is not allowed for "
+                f"relative URLs for the {scheme} scheme"
+            )
+            raise ValueError(msg)
         return URL(self._val._replace(scheme=scheme.lower()), encoded=True)
 
     def with_user(self, user: Union[str, None]) -> "URL":


Test patch:
diff --git a/tests/test_url_update_netloc.py b/tests/test_url_update_netloc.py
index b266e60..295d620 100644
--- a/tests/test_url_update_netloc.py
+++ b/tests/test_url_update_netloc.py
@@ -16,8 +16,13 @@ def test_with_scheme_uppercased():
 
 
 def test_with_scheme_for_relative_url():
-    with pytest.raises(ValueError):
-        URL("path/to").with_scheme("http")
+    """Test scheme can be set for relative URL."""
+    msg = "scheme replacement is not allowed for " "relative URLs for the http scheme"
+    with pytest.raises(ValueError, match=msg):
+        assert URL("path/to").with_scheme("http")
+
+    expected = URL("file:///absolute/path")
+    assert expected.with_scheme("file") == expected
 
 
 def test_with_scheme_invalid_type():