Repository: samuelcolvin/pydantic
Pull request #2888 (2021-06-08 06:55:58)

Accept empty query/fragment URL parts
<!-- Thank you for your contribution! -->
<!-- Unless your change is trivial, please create an issue to discuss the change before creating a PR -->
<!-- See https://pydantic-docs.helpmanual.io/contributing/ for help on Contributing -->

## Change Summary

Fixes the URL validation to accept empty query or fragments.

## Related issue number

fix https://github.com/samuelcolvin/pydantic/issues/1807

## Checklist

* [x] Unit tests for the changes exist
* [x] Tests pass on CI and coverage remains at 100%
* [x] Documentation reflects the changes where applicable
* [x] `changes/<pull request or issue id>-<github username>.md` file added describing change
  (see [changes/README.md](https://github.com/samuelcolvin/pydantic/blob/master/changes/README.md) for details)
* [x] My PR is ready to review, **please add a comment including the phrase "please review" to assign reviewers**


xavier: please review
PrettyWood: Hi @xavier and thanks for your contribution.
Your PR fixes https://github.com/samuelcolvin/pydantic/issues/1807 ;)
Please update the description accordingly
xavier: please review
llgerard: I am running into this too.

Patch:
diff --git a/changes/1807-xavier.md b/changes/1807-xavier.md
new file mode 100644
index 000000000..59f1f3f6f
--- /dev/null
+++ b/changes/1807-xavier.md
@@ -0,0 +1,1 @@
+Accept empty query/fragment URL parts
diff --git a/changes/2913-gbdlin.md b/changes/2913-gbdlin.md
new file mode 100644
index 000000000..bfd75dbcc
--- /dev/null
+++ b/changes/2913-gbdlin.md
@@ -0,0 +1,1 @@
+add a dark mode to _pydantic_ documentation
diff --git a/mkdocs.yml b/mkdocs.yml
index b0493e31d..31eb6003b 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -7,8 +7,21 @@ theme:
   name: 'material'
   custom_dir: 'docs/theme'
   palette:
+  - media: "(prefers-color-scheme: light)"
+    scheme: default
     primary: pink
     accent: pink
+    toggle:
+      icon: material/lightbulb-outline
+      name: "Switch to dark mode"
+  - media: "(prefers-color-scheme: dark)"
+    scheme: slate
+    primary: pink
+    accent: pink
+    toggle:
+      icon: material/lightbulb
+      name: "Switch to light mode"
+
   logo: 'logo-white.svg'
   favicon: 'favicon.png'
 
diff --git a/pydantic/networks.py b/pydantic/networks.py
index b5f6ff2df..557edc835 100644
--- a/pydantic/networks.py
+++ b/pydantic/networks.py
@@ -92,8 +92,8 @@ def url_regex() -> Pattern[str]:
             r')?'
             r'(?::(?P<port>\d+))?'  # port
             r'(?P<path>/[^\s?#]*)?'  # path
-            r'(?:\?(?P<query>[^\s#]+))?'  # query
-            r'(?:#(?P<fragment>\S+))?',  # fragment
+            r'(?:\?(?P<query>[^\s#]*))?'  # query
+            r'(?:#(?P<fragment>[^\s#]*))?',  # fragment
             re.IGNORECASE,
         )
     return _url_regex_cache


Test patch:
diff --git a/tests/test_networks.py b/tests/test_networks.py
index b24701dff..4e7fe29fd 100644
--- a/tests/test_networks.py
+++ b/tests/test_networks.py
@@ -70,6 +70,14 @@ except ImportError:
         'http://twitter.com/@handle/',
         'http://11.11.11.11.example.com/action',
         'http://abc.11.11.11.11.example.com/action',
+        'http://example#',
+        'http://example/#',
+        'http://example/#fragment',
+        'http://example/?#',
+        'http://example.org/path#',
+        'http://example.org/path#fragment',
+        'http://example.org/path?query#',
+        'http://example.org/path?query#fragment',
         'file://localhost/foo/bar',
     ],
 )
@@ -115,6 +123,18 @@ def test_any_url_success(value):
         ),
         ('http://[192.168.1.1]:8329', 'value_error.url.host', 'URL host invalid', None),
         ('http://example.com:99999', 'value_error.url.port', 'URL port invalid, port cannot exceed 65535', None),
+        (
+            'http://example##',
+            'value_error.url.extra',
+            "URL invalid, extra characters found after valid URL: '#'",
+            {'extra': '#'},
+        ),
+        (
+            'http://example/##',
+            'value_error.url.extra',
+            "URL invalid, extra characters found after valid URL: '#'",
+            {'extra': '#'},
+        ),
         ('file:///foo/bar', 'value_error.url.host', 'URL host invalid', None),
     ],
 )