diff --git a/py3xui/api/__init__.py b/py3xui/api/__init__.py index 002d902..917ae35 100644 --- a/py3xui/api/__init__.py +++ b/py3xui/api/__init__.py @@ -1,3 +1,4 @@ +# pylint: disable=missing-module-docstring from py3xui.api.api_client import ClientApi from py3xui.api.api_database import DatabaseApi from py3xui.api.api_inbound import InboundApi diff --git a/py3xui/api/api.py b/py3xui/api/api.py index 34efb0e..34f8a56 100644 --- a/py3xui/api/api.py +++ b/py3xui/api/api.py @@ -1,5 +1,7 @@ """This module provides classes to interact with the XUI API.""" +from __future__ import annotations + from py3xui.api import ClientApi, DatabaseApi, InboundApi from py3xui.utils import Logger, env @@ -7,6 +9,44 @@ class Api: + """This class provides a high-level interface to interact with the XUI API. + Access to the client, inbound, and database APIs is provided through this class. + + Args: + host (str): The XUI host URL. + username (str): The XUI username. + password (str): The XUI password. + skip_login (bool): Skip the login process. Default is False. + + Attributes and Properties: + client (ClientApi): The client API. + inbound (InboundApi): The inbound API. + database (DatabaseApi): The database API. + + Public Methods: + login: Logs into the XUI API. + from_env: Creates an instance of the API from environment variables. + + Examples: + ```python + import py3xui + + # It's recommended to use environment variables for the credentials. + os.environ["XUI_HOST"] = "https://xui.example.com" + os.environ["XUI_USERNAME"] = "username" + os.environ["XUI_PASSWORD"] = "password" + + api = py3xui.Api.from_env() + + # Alternatively, you can provide the credentials directly. + api = py3xui.Api("https://xui.example.com", "username", "password") + + # Some examples of using the API. + inbounds: list[py3xui.Inbound] = api.inbound.get_list() + client: py3xui.Client = api.client.get_by_email("email") + ``` + """ + def __init__(self, host: str, username: str, password: str, skip_login: bool = False): self.client = ClientApi(host, username, password) self.inbound = InboundApi(host, username, password) @@ -15,13 +55,43 @@ def __init__(self, host: str, username: str, password: str, skip_login: bool = F self.login() @classmethod - def from_env(cls, skip_login: bool = False): + def from_env(cls, skip_login: bool = False) -> Api: + """Creates an instance of the API from environment variables. + Following environment variables should be set: + - XUI_HOST: The XUI host URL. + - XUI_USERNAME: The XUI username. + - XUI_PASSWORD: The XUI password. + + Args: + skip_login (bool): Skip the login process. Default is False. + + Returns: + Api: The API instance. + + Examples: + ```python + import py3xui + + api = py3xui.Api.from_env() + ``` + """ host = env.xui_host() username = env.xui_username() password = env.xui_password() return cls(host, username, password, skip_login) def login(self) -> None: + """Logs into the XUI API and sets the session cookie for the client, inbound, and + database APIs. + + Examples: + ```python + import py3xui + + api = py3xui.Api.from_env(skip_login=True) + api.login() + ``` + """ self.client.login() self.inbound.session = self.client.session self.database.session = self.client.session diff --git a/py3xui/api/api_base.py b/py3xui/api/api_base.py index b465921..dcd3f88 100644 --- a/py3xui/api/api_base.py +++ b/py3xui/api/api_base.py @@ -1,3 +1,5 @@ +"""This module contains the base class for the XUI API.""" + from time import sleep from typing import Any, Callable @@ -20,6 +22,32 @@ class ApiFields: class BaseApi: + """Base class for the XUI API. Contains common methods for making requests. + + Arguments: + host (str): The host of the XUI API. + username (str): The username for the XUI API. + password (str): The password for the XUI API. + + Attributes and Properties: + host (str): The host of the XUI API. + username (str): The username for the XUI API. + password (str): The password for the XUI API. + max_retries (int): The maximum number of retries for a request. + session (str): The session cookie for the XUI API. + + Public Methods: + login: Logs into the XUI API. + + Private Methods: + _check_response: Checks the response from the XUI API. + _url: Returns the URL for the XUI API. + _request_with_retry: Makes a request to the XUI API with retries. + _post: Makes a POST request to the XUI API. + _get: Makes a GET request to the XUI API. + + """ + def __init__(self, host: str, username: str, password: str): self._host = host.rstrip("/") self._username = username @@ -29,33 +57,65 @@ def __init__(self, host: str, username: str, password: str): @property def host(self) -> str: + """The host of the XUI API. + + Returns: + str: The host of the XUI API.""" return self._host @property def username(self) -> str: + """The username for the XUI API. + + Returns: + str: The username for the XUI API.""" return self._username @property def password(self) -> str: + """The password for the XUI API. + + Returns: + str: The password for the XUI API.""" return self._password @property def max_retries(self) -> int: + """The maximum number of retries for a request. + + Returns: + int: The maximum number of retries for a request.""" return self._max_retries @max_retries.setter def max_retries(self, value: int) -> None: + """Sets the maximum number of retries for a request. + + Arguments: + value (int): The maximum number of retries for a request.""" self._max_retries = value @property def session(self) -> str | None: + """The session cookie for the XUI API. + + Returns: + str | None: The session cookie for the XUI API.""" return self._session @session.setter def session(self, value: str | None) -> None: + """Sets the session cookie for the XUI API. + + Arguments: + value (str | None): The session cookie for the XUI API.""" self._session = value def login(self) -> None: + """Logs into the XUI API and sets the session cookie if successful. + + Raises: + ValueError: If the login is unsuccessful.""" endpoint = "login" headers: dict[str, str] = {} @@ -71,6 +131,14 @@ def login(self) -> None: self.session = cookie def _check_response(self, response: requests.Response) -> None: + """Checks the response from the XUI API using the success field. + + Arguments: + response (requests.Response): The response from the XUI API. + + Raises: + ValueError: If the response status is not successful. + """ response_json = response.json() status = response_json.get(ApiFields.SUCCESS) @@ -79,6 +147,13 @@ def _check_response(self, response: requests.Response) -> None: raise ValueError(f"Response status is not successful, message: {message}") def _url(self, endpoint: str) -> str: + """Returns the URL for the XUI API (adds the endpoint to the host URL). + + Arguments: + endpoint (str): The endpoint for the XUI API. + + Returns: + str: The URL for the XUI API.""" return f"{self._host}/{endpoint}" def _request_with_retry( @@ -88,6 +163,20 @@ def _request_with_retry( headers: dict[str, str], **kwargs: Any, ) -> requests.Response: + """Makes a request to the XUI API with retries. + + Arguments: + method (Callable[..., requests.Response]): The request method to use. + url (str): The URL for the XUI API. + headers (dict[str, str]): The headers for the request. + **kwargs (Any): Additional keyword arguments for the request. + + Returns: + requests.Response: The response from the XUI API. + + Raises: + requests.exceptions.RequestException: If the request fails. + requests.exceptions.RetryError: If the maximum number of retries is exceeded.""" logger.debug("%s request to %s...", method.__name__.upper(), url) for retry in range(1, self.max_retries + 1): try: @@ -114,7 +203,26 @@ def _request_with_retry( def _post( self, url: str, headers: dict[str, str], data: dict[str, Any], **kwargs ) -> requests.Response: + """Makes a POST request to the XUI API. + + Arguments: + url (str): The URL for the XUI API. + headers (dict[str, str]): The headers for the request. + data (dict[str, Any]): The data for the request. + **kwargs (Any): Additional keyword arguments for the request. + + Returns: + requests.Response: The response from the XUI API.""" return self._request_with_retry(requests.post, url, headers, json=data, **kwargs) def _get(self, url: str, headers: dict[str, str], **kwargs) -> requests.Response: + """Makes a GET request to the XUI API. + + Arguments: + url (str): The URL for the XUI API. + headers (dict[str, str]): The headers for the request. + **kwargs (Any): Additional keyword arguments for the request. + + Returns: + requests.Response: The response from the XUI API.""" return self._request_with_retry(requests.get, url, headers, **kwargs) diff --git a/py3xui/api/api_client.py b/py3xui/api/api_client.py index 3ba7af8..37265bb 100644 --- a/py3xui/api/api_client.py +++ b/py3xui/api/api_client.py @@ -14,7 +14,7 @@ def get_by_email(self, email: str) -> Client | None: This endpoint provides details such as traffic statistics and other relevant information related to the client. - `Source documentation `_ + [Source documentation](https://documenter.getpostman.com/view/16802678/2s9YkgD5jm#9d0e5cd5-e6ac-4d72-abca-76cf75af5f00) Args: email (str): The email of the client to retrieve. @@ -22,11 +22,13 @@ def get_by_email(self, email: str) -> Client | None: Returns: Client | None: The client object if found, otherwise None. - Examples:: + Examples: + ```python import py3xui api = py3xui.Api.from_env() - client: py3xui.Client = api.get_by_email("email") + client: py3xui.Client = api.client.get_by_email("email") + ``` """ # pylint: disable=line-too-long endpoint = f"panel/api/inbounds/getClientTraffics/{email}" @@ -47,7 +49,7 @@ def get_ips(self, email: str) -> str | None: """This route is used to retrieve the IP records associated with a specific client identified by their email. - `Source documentation `_ + [Source documentation](https://documenter.getpostman.com/view/16802678/2s9YkgD5jm#06f1214c-dbb0-49f2-81b5-8e924abd19a9) Args: email (str): The email of the client to retrieve. @@ -55,12 +57,13 @@ def get_ips(self, email: str) -> str | None: Returns: str | None: The client IPs if found, otherwise None. - Examples:: + Examples: + ```python import py3xui api = py3xui.Api.from_env() - ips = api.get_ips("email") - + ips = api.client.get_ips("email") + ``` """ # pylint: disable=line-too-long endpoint = f"panel/api/inbounds/clientIps/{email}" headers = {"Accept": "application/json"}