Repository: aio-libs/yarl

Issue:
Passwords without users
Currently, yarl refuses to set an password if a user isn't set.  That's a bit unfortunate because that’s exactly what you need if you’re building redis URLs: https://redis-py.readthedocs.io/en/latest/#redis.StrictRedis.from_url

Comments:

Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index 5b9a667..49b78a2 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -10,6 +10,8 @@ CHANGES
 
 * Unsafe encoding for QS fixed. Encode `;` char in value param (#104)
 
+* Process passwords without user names (#95)
+
 0.12.0 (2017-06-26)
 -------------------
 
diff --git a/yarl/__init__.py b/yarl/__init__.py
index 41a3089..e1151c0 100644
--- a/yarl/__init__.py
+++ b/yarl/__init__.py
@@ -176,8 +176,11 @@ class URL:
                     netloc += ':{}'.format(val.port)
                 if val.username:
                     user = _quote(val.username)
-                    if val.password:
-                        user += ':' + _quote(val.password)
+                else:
+                    user = ''
+                if val.password:
+                    user += ':' + _quote(val.password)
+                if user:
                     netloc = user + '@' + netloc
 
             path = _quote(val[2], safe='+@:', protected='/+', strict=strict)
@@ -380,7 +383,10 @@ class URL:
 
         """
         # not .username
-        return self._val.username
+        ret = self._val.username
+        if not ret:
+            return None
+        return ret
 
     @cached_property
     def user(self):
@@ -590,7 +596,7 @@ class URL:
             ret = ret + ':' + str(port)
         if password:
             if not user:
-                raise ValueError("Non-empty password requires non-empty user")
+                user = ''
             user = user + ':' + password
         if user:
             ret = user + '@' + ret


Test patch:
diff --git a/tests/test_url.py b/tests/test_url.py
index b5d365e..d2582ff 100644
--- a/tests/test_url.py
+++ b/tests/test_url.py
@@ -77,6 +77,11 @@ def test_raw_user_non_ascii():
     assert '%D0%B2%D0%B0%D1%81%D1%8F' == url.raw_user
 
 
+def test_no_user():
+    url = URL('http://example.com')
+    assert url.user is None
+
+
 def test_user_non_ascii():
     url = URL('http://вася@example.com')
     assert 'вася' == url.user
@@ -97,6 +102,11 @@ def test_password_non_ascii():
     assert 'пароль' == url.password
 
 
+def test_password_without_user():
+    url = URL('http://:password@example.com')
+    assert 'password' == url.password
+
+
 def test_raw_host():
     url = URL('http://example.com')
     assert "example.com" == url.raw_host
diff --git a/tests/test_url_update_netloc.py b/tests/test_url_update_netloc.py
index 9790dd4..46bdab0 100644
--- a/tests/test_url_update_netloc.py
+++ b/tests/test_url_update_netloc.py
@@ -90,8 +90,10 @@ def test_with_password_invalid_type():
 
 def test_with_password_and_empty_user():
     url = URL('http://example.com')
-    with pytest.raises(ValueError):
-        assert str(url.with_password('pass'))
+    url2 = url.with_password('pass')
+    assert url2.password == 'pass'
+    assert url2.user is None
+    assert str(url2) == 'http://:pass@example.com'
 
 
 def test_from_str_with_host_ipv4():