blob: 8dae9fa163f6cdb2af3ecd2c36c036e9f0b33be9 [file]
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import copy
from typing import Any, Optional
from urllib import parse
from urllib.parse import urlencode, urlunparse
from dubbo.constants import common_constants
__all__ = ["URL", "create_url"]
def create_url(url: str, encoded: bool = False) -> "URL":
"""
Creates a URL object from a URL string.
This function takes a URL string and converts it into a URL object.
If the 'encoded' parameter is set to True, the URL string will be decoded before being converted.
:param url: The URL string to be converted into a URL object.
:type url: str
:param encoded: Determines if the URL string should be decoded before being converted. Defaults to False.
:type encoded: bool
:return: A URL object.
:rtype: URL
:raises ValueError: If the URL format is invalid.
"""
# If the URL is encoded, decode it
if encoded:
url = parse.unquote(url)
if common_constants.PROTOCOL_SEPARATOR not in url:
raise ValueError("Invalid URL format: missing protocol")
parsed_url = parse.urlparse(url)
if not parsed_url.scheme:
raise ValueError("Invalid URL format: missing scheme.")
return URL(
parsed_url.scheme,
parsed_url.hostname or "",
parsed_url.port,
parsed_url.username or "",
parsed_url.password or "",
parsed_url.path.lstrip("/"),
{k: v[0] for k, v in parse.parse_qs(parsed_url.query).items()},
)
class URL:
"""
URL - Uniform Resource Locator.
"""
__slots__ = [
"_scheme",
"_host",
"_port",
"_location",
"_username",
"_password",
"_path",
"_parameters",
"_attributes",
]
def __init__(
self,
scheme: str,
host: str,
port: Optional[int] = None,
username: str = "",
password: str = "",
path: str = "",
parameters: Optional[dict[str, str]] = None,
attributes: Optional[dict[str, Any]] = None,
):
"""
Initialize the URL object.
:param scheme: The scheme of the URL (e.g., 'http', 'https').
:type scheme: str
:param host: The host of the URL.
:type host: str
:param port: The port number of the URL, defaults to None.
:type port: int, optional
:param username: The username for authentication, defaults to an empty string.
:type username: str, optional
:param password: The password for authentication, defaults to an empty string.
:type password: str, optional
:param path: The path of the URL, defaults to an empty string.
:type path: str, optional
:param parameters: The query parameters of the URL as a dictionary, defaults to None.
:type parameters: Dict[str, str], optional
:param attributes: Additional attributes of the URL as a dictionary, defaults to None.
:type attributes: Dict[str, Any], optional
"""
self._scheme = scheme
self._host = host
self._port = port
self._location = f"{host}:{port}" if port else host
self._username = username
self._password = password
self._path = path
self._parameters = parameters or {}
self._attributes = attributes or {}
@property
def scheme(self) -> str:
"""
Get or set the scheme of the URL.
:return: The scheme of the URL.
:rtype: str
"""
return self._scheme
@scheme.setter
def scheme(self, value: str):
self._scheme = value
@property
def host(self) -> str:
"""
Get or set the host of the URL.
:return: The host of the URL.
:rtype: str
"""
return self._host
@host.setter
def host(self, value: str):
self._host = value
self._location = f"{self.host}:{self.port}" if self.port else self.host
@property
def port(self) -> Optional[int]:
"""
Get or set the port of the URL.
:return: The port of the URL.
:rtype: int, optional
"""
return self._port
@port.setter
def port(self, value: int):
if value > 0:
self._port = value
self._location = f"{self.host}:{self.port}"
@property
def location(self) -> str:
"""
Get or set the location (host:port) of the URL.
:return: The location of the URL.
:rtype: str
"""
return self._location
@location.setter
def location(self, value: str):
try:
values = value.split(":")
self.host = values[0]
if len(values) == 2:
self.port = int(values[1])
except Exception as e:
raise ValueError(f"Invalid location: {value}") from e
@property
def username(self) -> str:
"""
Get or set the username for authentication.
:return: The username.
:rtype: str
"""
return self._username
@username.setter
def username(self, value: str):
self._username = value
@property
def password(self) -> str:
"""
Get or set the password for authentication.
:return: The password.
:rtype: str
"""
return self._password
@password.setter
def password(self, value: str):
self._password = value
@property
def path(self) -> str:
"""
Get or set the path of the URL.
:return: The path of the URL.
:rtype: str
"""
return self._path
@path.setter
def path(self, value: str):
self._path = value.lstrip("/")
@property
def parameters(self) -> dict[str, str]:
"""
Get the query parameters of the URL.
:return: The query parameters as a dictionary.
:rtype: Dict[str, str]
"""
return self._parameters
@property
def attributes(self) -> dict[str, Any]:
"""
Get the additional attributes of the URL.
:return: The attributes as a dictionary.
:rtype: Dict[str, Any]
"""
return self._attributes
def to_str(
self,
contain_ip: bool = True,
contain_user: bool = True,
contain_path: bool = True,
contain_parameters: bool = True,
encode: bool = False,
) -> str:
"""
Converts the URL to a string.
:param contain_ip: Determines if the URL should contain the IP address. Defaults to True.
:type contain_ip: bool
:param contain_user: Determines if the URL should contain the username. Defaults to True.
:type contain_user: bool
:param contain_path: Determines if the URL should contain the path. Defaults to True.
:type contain_path: bool
:param contain_parameters: Determines if the URL should contain the parameters. Defaults to True.
:param encode: Determines if the URL should be encoded. Defaults to False.
:type encode: bool
:return: The URL string.
:rtype: str
"""
# Construct the scheme part
scheme = ""
netloc = ""
if contain_ip:
scheme = self.scheme
# Construct the netloc part
if contain_user and self.username and self.password:
netloc = f"{self.username}:{self.password}@{self.location}"
else:
netloc = self.location
# Construct the path part
path = self.path if contain_path else ""
# Construct the query part
query = urlencode(self.parameters) if contain_parameters else ""
# Construct the URL
url = ""
if scheme or netloc or path or query:
url = urlunparse((scheme, netloc, path, "", query, ""))
if encode:
url = parse.quote(url, safe="")
return url
def copy(self) -> "URL":
"""
Copy the URL object.
:return: A shallow copy of the URL object.
:rtype: URL
"""
return copy.copy(self)
def deepcopy(self) -> "URL":
"""
Deep copy the URL object.
:return: A deep copy of the URL object.
:rtype: URL
"""
return copy.deepcopy(self)
def __eq__(self, other: Any) -> bool:
if not isinstance(other, URL):
return False
return (
self.scheme == other.scheme
and self.host == other.host
and self.port == other.port
and self.username == other.username
and self.password == other.password
and self.path == other.path
and self.parameters == other.parameters
and self.attributes == other.attributes
)
def __copy__(self) -> "URL":
return URL(
self.scheme,
self.host,
self.port,
self.username,
self.password,
self.path,
self.parameters.copy(),
self.attributes.copy(),
)
def __deepcopy__(self, memo) -> "URL":
return URL(
self.scheme,
self.host,
self.port,
self.username,
self.password,
self.path,
copy.deepcopy(self.parameters, memo),
copy.deepcopy(self.attributes, memo),
)
def __str__(self) -> str:
return self.to_str()
def __repr__(self) -> str:
return self.to_str()