Skip to content

Commit

Permalink
created url_parser and removed duplication
Browse files Browse the repository at this point in the history
  • Loading branch information
ahmedsobeh committed Jun 28, 2024
1 parent d116aa6 commit 21ae3ff
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 225 deletions.
3 changes: 2 additions & 1 deletion valkey/_parsers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from .hiredis import _AsyncHiredisParser, _HiredisParser
from .resp2 import _AsyncRESP2Parser, _RESP2Parser
from .resp3 import _AsyncRESP3Parser, _RESP3Parser

from url_parser import parse_url
__all__ = [
"AsyncCommandsParser",
"_AsyncHiredisParser",
Expand All @@ -17,4 +17,5 @@
"_HiredisParser",
"_RESP2Parser",
"_RESP3Parser",
"parse_url",
]
85 changes: 85 additions & 0 deletions valkey/_parsers/url_parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
from valkey.asyncio.connection import ConnectKwargs, UnixDomainSocketConnection, SSLConnection
from urllib.parse import ParseResult, parse_qs, unquote, urlparse
from types import MappingProxyType
from typing import (
Callable,
Mapping,
Optional,
)


def to_bool(value) -> Optional[bool]:
if value is None or value == "":
return None
if isinstance(value, str) and value.upper() in FALSE_STRINGS:
return False
return bool(value)


FALSE_STRINGS = ("0", "F", "FALSE", "N", "NO")

URL_QUERY_ARGUMENT_PARSERS: Mapping[str, Callable[..., object]] = MappingProxyType(
{
"db": int,
"socket_timeout": float,
"socket_connect_timeout": float,
"socket_keepalive": to_bool,
"retry_on_timeout": to_bool,
"max_connections": int,
"health_check_interval": int,
"ssl_check_hostname": to_bool,
"timeout": float,
}
)


def parse_url(url: str) -> ConnectKwargs:
parsed: ParseResult = urlparse(url)
kwargs: ConnectKwargs = {}

for name, value_list in parse_qs(parsed.query).items():
if value_list and len(value_list) > 0:
value = unquote(value_list[0])
parser = URL_QUERY_ARGUMENT_PARSERS.get(name)
if parser:
try:
kwargs[name] = parser(value)
except (TypeError, ValueError):
raise ValueError(f"Invalid value for `{name}` in connection URL.")
else:
kwargs[name] = value

if parsed.username:
kwargs["username"] = unquote(parsed.username)
if parsed.password:
kwargs["password"] = unquote(parsed.password)

# We only support valkey://, valkeys:// and unix:// schemes.
if parsed.scheme == "unix":
if parsed.path:
kwargs["path"] = unquote(parsed.path)
kwargs["connection_class"] = UnixDomainSocketConnection

elif parsed.scheme in ("valkey", "valkeys"):
if parsed.hostname:
kwargs["host"] = unquote(parsed.hostname)
if parsed.port:
kwargs["port"] = int(parsed.port)

# If there's a path argument, use it as the db argument if a
# querystring value wasn't specified
if parsed.path and "db" not in kwargs:
try:
kwargs["db"] = int(unquote(parsed.path).replace("/", ""))
except (AttributeError, ValueError):
pass

if parsed.scheme == "valkeys":
kwargs["connection_class"] = SSLConnection
else:
valid_schemes = "valkey://, valkeys://, unix://"
raise ValueError(
f"Valkey URL must specify one of the following schemes ({valid_schemes})"
)

return kwargs
77 changes: 1 addition & 76 deletions valkey/asyncio/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@
import weakref
from abc import abstractmethod
from itertools import chain
from types import MappingProxyType
from .._parsers.url_parser import parse_url
from typing import (
Any,
Callable,
Iterable,
List,
Mapping,
Expand All @@ -25,7 +24,6 @@
TypeVar,
Union,
)
from urllib.parse import ParseResult, parse_qs, unquote, urlparse

# the functionality is available in 3.11.x but has a major issue before
# 3.11.3. See https://github.com/redis/redis-py/issues/2633
Expand Down Expand Up @@ -986,31 +984,8 @@ def _error_message(self, exception: BaseException) -> str:
)


FALSE_STRINGS = ("0", "F", "FALSE", "N", "NO")


def to_bool(value) -> Optional[bool]:
if value is None or value == "":
return None
if isinstance(value, str) and value.upper() in FALSE_STRINGS:
return False
return bool(value)


URL_QUERY_ARGUMENT_PARSERS: Mapping[str, Callable[..., object]] = MappingProxyType(
{
"db": int,
"socket_timeout": float,
"socket_connect_timeout": float,
"socket_keepalive": to_bool,
"retry_on_timeout": to_bool,
"max_connections": int,
"health_check_interval": int,
"ssl_check_hostname": to_bool,
"timeout": float,
}
)


class ConnectKwargs(TypedDict, total=False):
username: str
Expand All @@ -1022,56 +997,6 @@ class ConnectKwargs(TypedDict, total=False):
path: str


def parse_url(url: str) -> ConnectKwargs:
parsed: ParseResult = urlparse(url)
kwargs: ConnectKwargs = {}

for name, value_list in parse_qs(parsed.query).items():
if value_list and len(value_list) > 0:
value = unquote(value_list[0])
parser = URL_QUERY_ARGUMENT_PARSERS.get(name)
if parser:
try:
kwargs[name] = parser(value)
except (TypeError, ValueError):
raise ValueError(f"Invalid value for `{name}` in connection URL.")
else:
kwargs[name] = value

if parsed.username:
kwargs["username"] = unquote(parsed.username)
if parsed.password:
kwargs["password"] = unquote(parsed.password)

# We only support valkey://, valkeys:// and unix:// schemes.
if parsed.scheme == "unix":
if parsed.path:
kwargs["path"] = unquote(parsed.path)
kwargs["connection_class"] = UnixDomainSocketConnection

elif parsed.scheme in ("valkey", "valkeys"):
if parsed.hostname:
kwargs["host"] = unquote(parsed.hostname)
if parsed.port:
kwargs["port"] = int(parsed.port)

# If there's a path argument, use it as the db argument if a
# querystring value wasn't specified
if parsed.path and "db" not in kwargs:
try:
kwargs["db"] = int(unquote(parsed.path).replace("/", ""))
except (AttributeError, ValueError):
pass

if parsed.scheme == "valkeys":
kwargs["connection_class"] = SSLConnection
else:
valid_schemes = "valkey://, valkeys://, unix://"
raise ValueError(
f"Valkey URL must specify one of the following schemes ({valid_schemes})"
)

return kwargs


_CP = TypeVar("_CP", bound="ConnectionPool")
Expand Down
Loading

0 comments on commit 21ae3ff

Please sign in to comment.