diff --git a/docs/ref/zipline.md b/docs/ref/zipline.md index 5bb0be6..ba45086 100644 --- a/docs/ref/zipline.md +++ b/docs/ref/zipline.md @@ -1,3 +1,3 @@ # API Reference -::: pyzipline.zipline +::: pyzipline.zipline.ZiplineApi diff --git a/mkdocs.yml b/mkdocs.yml index 3003882..7e8802e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -21,11 +21,14 @@ plugins: - git-authors - search - social - # - mkdocstrings: - # default_handler: python - # handlers: - # python: - # paths: [pyzipline] + - mkdocstrings: + default_handler: python + handlers: + python: + paths: [pyzipline] + options: + docstring_options: + ignore_imit_summary: true markdown_extensions: - abbr diff --git a/poetry.lock b/poetry.lock index 21f301d..63df4d1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -598,6 +598,20 @@ gitdb = ">=4.0.1,<5" [package.extras] test = ["black", "coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest", "pytest-cov", "pytest-instafail", "pytest-subtests", "pytest-sugar"] +[[package]] +name = "griffe" +version = "0.38.1" +description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." +optional = false +python-versions = ">=3.8" +files = [ + {file = "griffe-0.38.1-py3-none-any.whl", hash = "sha256:334c79d3b5964ade65c05dfcaf53518c576dedd387aaba5c9fd71212f34f1483"}, + {file = "griffe-0.38.1.tar.gz", hash = "sha256:bd68d7da7f3d87bc57eb9962b250db123efd9bbcc06c11c1a91b6e583b2a9361"}, +] + +[package.dependencies] +colorama = ">=0.4" + [[package]] name = "idna" version = "3.6" @@ -882,6 +896,7 @@ Markdown = ">=3.3" MarkupSafe = ">=1.1" mkdocs = ">=1.4" mkdocs-autorefs = ">=0.3.1" +mkdocstrings-python = {version = ">=0.5.2", optional = true, markers = "extra == \"python\""} platformdirs = ">=2.2.0" pymdown-extensions = ">=6.3" @@ -890,6 +905,21 @@ crystal = ["mkdocstrings-crystal (>=0.3.4)"] python = ["mkdocstrings-python (>=0.5.2)"] python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] +[[package]] +name = "mkdocstrings-python" +version = "1.7.5" +description = "A Python handler for mkdocstrings." +optional = false +python-versions = ">=3.8" +files = [ + {file = "mkdocstrings_python-1.7.5-py3-none-any.whl", hash = "sha256:5f6246026353f0c0785135db70c3fe9a5d9318990fc7ceb11d62097b8ffdd704"}, + {file = "mkdocstrings_python-1.7.5.tar.gz", hash = "sha256:c7d143728257dbf1aa550446555a554b760dcd40a763f077189d298502b800be"}, +] + +[package.dependencies] +griffe = ">=0.37" +mkdocstrings = ">=0.20" + [[package]] name = "multidict" version = "6.0.4" @@ -1633,4 +1663,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.10 || ^3.11 || ^3.12" -content-hash = "4a37211d89e134df27183ed799df55662694232bcc6aef27f99f47c131186940" +content-hash = "3f27e66de6ea095d7574340c1233c4c5ff13409f22d3dcb2a43829acd676be30" diff --git a/pyproject.toml b/pyproject.toml index b3cd94d..07b88fc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ optional = true [tool.poetry.group.docs.dependencies] mkdocs = "1.5.3" -mkdocstrings = "0.24.0" +mkdocstrings = {extras = ["python"], version = "0.24.0"} mkdocs-git-authors-plugin = "0.7.2" mkdocs-git-revision-date-localized-plugin = "1.2.2" mkdocs-material = {extras = ["imaging"], version = "^9.5.2"} diff --git a/pyzipline/errors.py b/pyzipline/errors.py index af2d594..f334681 100644 --- a/pyzipline/errors.py +++ b/pyzipline/errors.py @@ -1,9 +1,3 @@ -class KwargConflict(Exception): - """ - Raised when the keyword arguments passed to a function conflict. - """ - pass - class HTTPFailure(Exception): """ Raised when an HTTP request fails. @@ -15,3 +9,9 @@ class PyZiplineError(Exception): Raised when an error occurs in the PyZipline library. """ pass + +class FeatureDisabledError(Exception): + """ + Raised when a feature is disabled on the Zipline instance. + """ + pass diff --git a/pyzipline/models.py b/pyzipline/models.py index e766d65..b4e17be 100644 --- a/pyzipline/models.py +++ b/pyzipline/models.py @@ -13,10 +13,11 @@ class Embed: ): """Embed object used for checking embeds - :param color: String of the embed's color - :param title: String of the embed's title - :param siteName: String of the embed's site name - :param description: String of the embed's description + Args: + color (str): String of the embed's color + title (str): String of the embed's title + siteName (str): String of the embed's site name + description (str): String of the embed's description """ self.color = color @@ -46,19 +47,20 @@ class File: ): """File object used for uploading files to Zipline - :param createdAt: Datetime object of when the file was created - :param id: Integer ID of the file - :param mimetype: String of the file's mimetype - :param views: Integer of the number of views the file has - :param name: String of the file's name - :param size: Integer of the file's size in bytes - :param favorite: Boolean of whether the file is favorited - :param originalName: (optional) String of the file's original name - :param url: (optional) String of the file's URL - :param maxViews: (optional) Integer of the file's maximum number of views - :param expiredAt: (optional) Datetime object of when the file will expire - :param thumbnail: (optional) String of the file's thumbnail URL - :param folderId: (optional) Integer of the file's folder ID + Args: + createdAt (datetime.datetime): Datetime object of when the file was created + id (int): ID of the file + mimetype (str): String of the file's mimetype + views (int): Integer of the number of views the file has + name (str): String of the file's name + size (int): Integer of the file's size in bytes + favorite (bool): Boolean of whether the file is favorited + originalName (str = None): (optional) String of the file's original name + url (str = None): (optional) String of the file's URL + maxViews (int = None): (optional) Integer of the file's maximum number of views + expiredAt (datetime.datetime = None): (optional) Datetime object of when the file will expire + thumbnail (str = None): (optional) String of the file's thumbnail URL + folderId (int = None): (optional) Integer of the file's folder ID """ self.createdAt = createdAt @@ -78,19 +80,18 @@ class File: class Result: - def __init__(self, status_code: int, message: str = '', data: List[Dict] = None): + def __init__(self, success: bool, status_code: int, message: str = '', data: List[Dict] = None): """Result returned from low-level RestAdapter - :param status_code: Standard HTTP Status code - :type status_code: int - :param message: Human readable result - :type message: str - :param data: Python List of Dictionaries (or maybe just a single Dictionary on error) - :type data: Union[List[Dict], Dict] + Args: + success (bool): Boolean of whether the request was successful + status_code (int): Standard HTTP Status code + message (str = ''): Human readable result + data (List[Dict] = None): Python List of Dictionaries (or maybe just a single Dictionary on error) """ - - self.status_code = int(status_code) - self.message = str(message) + self.success = success + self.status_code = status_code + self.message = message self.data = data if data else [] @@ -107,20 +108,14 @@ class Invite: ): """Invite object used for managing invites - :param id: Integer ID of the invite - :type id: int - :param code: String of the invite's code - :type code: str - :param createdAt: Datetime object of when the invite was created - :type createdAt: datetime - :param expiredAt: Datetime object of when the invite will expire - :type expiredAt: datetime - :param used: Boolean of whether the invite has been used - :type used: bool - :param createdById: Integer ID of the user who created the invite - :type createdById: int + Args: + id (int): Integer ID of the invite + code (str): String of the invite's code + createdAt (datetime): Datetime object of when the invite was created + expiredAt (datetime): Datetime object of when the invite will expire + used (bool): Boolean of whether the invite has been used + createdById (int): Integer ID of the user who created the invite """ - self.id = id self.code = code self.createdAt = createdAt @@ -144,22 +139,15 @@ class OAuth: ): """OAuth object used for managing OAuth - :param id: Integer ID of the OAuth - :type id: int - :param provider: String of the OAuth's provider, one of 'DISCORD', 'GITHUB', 'GOOGLE' - :type provider: str - :param userId: Integer ID of the user who owns the OAuth - :type userId: int - :param providerId: String of the OAuth's provider ID - :type providerId: str - :param username: String of the OAuth's connected account's username - :type username: str - :param token: String of the OAuth's access token - :type token: str - :param refresh: String of the OAuth's refresh token - :type refresh: str + Args: + id (int): Integer ID of the OAuth + provider (str): String of the OAuth's provider, one of 'DISCORD', 'GITHUB', 'GOOGLE' + userId (int): Integer ID of the user who owns the OAuth + providerId (str): String of the OAuth's provider ID + username (str): String of the OAuth's connected account's username + token (str): String of the OAuth's access token + refresh (str): String of the OAuth's refresh token """ - self.id = id self.provider = provider self.userId = userId @@ -190,34 +178,21 @@ class User: ): """User object used for managing users - :param id: Integer ID of the user - :type id: int - :param uuid: String of the user's UUID - :type uuid: str - :param username: String of the user's username - :type username: str - :param avatar: String of the user's avatar, base64 encoded - :type avatar: str - :param token: String of the user's token - :type token: str - :param administrator: Boolean of whether the user is an administrator - :type administrator: bool - :param superAdmin: Boolean of whether the user is a super administrator - :type superAdmin: bool - :param systemTheme: String of the user's system theme - :type systemTheme: str - :param embed: Embed object of the user's embed - :type embed: Embed - :param totpSecret: String of the user's TOTP secret - :type totpSecret: str - :param domains: List of Strings of the user's domains - :type domains: List[str] - :param oauth: (optional) List of OAuth objects - :type oauth: Union[List[OAuth], None] - :param ratelimit: (optional) Datetime object of when the user's ratelimit expires - :type ratelimit: Union[datetime, None] + Args: + id (int): Integer ID of the user + uuid (str): String of the user's UUID + username (str): String of the user's username + avatar (str): String of the user's avatar, base64 encoded + token (str): String of the user's token + administrator (bool): Boolean of whether the user is an administrator + superAdmin (bool): Boolean of whether the user is a super administrator + systemTheme (str): String of the user's system theme + embed (Embed): Embed object of the user's embed + totpSecret (str): String of the user's TOTP secret + domains (List[str]): List of Strings of the user's domains + oauth (List[OAuth] = None): (optional) List of OAuth objects + ratelimit (datetime = None): (optional) Datetime object of when the user's ratelimit expires """ - self.id = id self.uuid = uuid self.username = username diff --git a/pyzipline/rest_adapter.py b/pyzipline/rest_adapter.py index d3d3a9f..74aa3ce 100644 --- a/pyzipline/rest_adapter.py +++ b/pyzipline/rest_adapter.py @@ -12,17 +12,15 @@ class RestAdapter: def __init__(self, hostname: str, token: str = '', ssl: bool = True, enforced_signing: bool = True, logger: logging.Logger = None): """Constructor for RestAdapter - :param hostname: The hostname of your Zipline instance, WITHOUT https or http. - :type hostname: str - :param token: (optional) String used for authentication when making requests. - :param token: str - :param ssl: (optional) Normally set to True, but if your Zipline instance doesn't use SSL/TLS, set this to False. - :type ssl: bool - :param enforced_signing: (optional) Normally set to True, but if having SSL/TLS cert validation issues, can turn off with False. - :type enforced_signing: bool - :param logger: (optional) If your app has a logger, pass it in here. - :type logger: logging.Logger - :raise KwargConflict: Raised when the keyword arguments passed to a function conflict. + Args: + hostname (str): The hostname of your Zipline instance, WITHOUT https or http. + token (str = None): String used for authentication when making requests. + ssl (bool = True): Normally set to True, but if your Zipline instance doesn't use SSL/TLS, set this to False. + enforced_signing (bool = True): Normally set to True, but if having SSL/TLS cert validation issues, can turn off with False. + logger (logging.Logger = None) If your app has a logger, pass it in here. + + Raises: + ValueError: Raised when the keyword arguments passed to the class constructor conflict. """ self._url = f"http{'s' if ssl else ''}://{hostname}/api/" self._token = token @@ -31,7 +29,7 @@ class RestAdapter: self._logger = logger or logging.getLogger(__name__) if ssl is False and enforced_signing is True: - raise KwargConflict("Cannot enforce signing without SSL") + raise ValueError("Cannot enforce signing without SSL") if not ssl and not enforced_signing: disable_warnings() @@ -39,18 +37,19 @@ class RestAdapter: def _do(self, http_method: str, endpoint: str, params: Dict = None, data: Dict = None) -> Result: """Internal method to make a request to the Zipline server. You shouldn't use this directly. - :param http_method: The HTTP method to use (GET, POST, DELETE) - :type http_method: str - :param endpoint: The endpoint to make the request to. - :type endpoint: str - :param params: (optional) Python dictionary of query parameters to send with the request. - :type params: Dict - :param data: (optional) Python dictionary of data to send with the request. - :type data: Dict - :raise HTTPFailure: Raised when an HTTP request fails. - :raise PyZiplineError: Raised when an error occurs in the PyZipline library. - :return: Result object - :rtype: Result""" + Args: + http_method (str): The HTTP method to use (GET, POST, DELETE) + endpoint (str): The endpoint to make the request to. + params (Dict = None): Python dictionary of query parameters to send with the request. + data (Dict = None): Python dictionary of data to send with the request. + + Returns: + Result: A Result object containing the status code, message, and data from the request. + + Raises: + HTTPError: Raised when an HTTP request fails. + PyZiplineError: Raised when an error occurs in the PyZipline library. + """ full_url = self._url + endpoint headers = {'Authorization': self._token} @@ -71,51 +70,46 @@ class RestAdapter: self._logger.error(msg=log_line_post.format(False, None, e)) raise PyZiplineError("Could not decode response from Zipline server") from e - # If status_code in 200-299 range, return success Result with data, otherwise raise exception + # If status_code in 200-299 range, return success Result with data, otherwise return failed Result with message is_success = 299 >= response.status_code >= 200 - log_line = log_line_post.format(is_success, response.status_code, response.reason) - if is_success: - self._logger.debug(msg=log_line_post.format(is_success, response.status_code, response.reason)) - return Result(status_code=response.status_code, message=response.reason, data=data_out) - - self._logger.error(msg=log_line) - - raise PyZiplineError(f"{response.status_code}: {response.reason}") + self._logger.debug(msg=log_line_post.format(is_success, response.status_code, response.reason)) + return Result(success=is_success, status_code=response.status_code, message=response.reason, data=data_out) def get(self, endpoint: str, params: Dict = None) -> Result: """Make a GET request to the Zipline server. You should almost never have to use this directly. - :param endpoint: The endpoint to make the request to. - :type endpoint: str - :param params: (optional) Python dictionary of query parameters to send with the request. - :type params: Dict - :return: Result object - :rtype: Result""" + Args: + endpoint (str): The endpoint to make the request to. + params (Dict = None): Python dictionary of query parameters to send with the request. + + Returns: + Result: A Result object containing the status code, message, and data from the request. + """ return self._do(http_method='GET', endpoint=endpoint, params=params) def post(self, endpoint: str, params: Dict = None, data: Dict = None) -> Result: """Make a POST request to the Zipline server. You should almost never have to use this directly. - :param endpoint: The endpoint to make the request to. - :type endpoint: str - :param params: (optional) Python dictionary of query parameters to send with the request. - :type params: Dict - :param data: (optional) Python dictionary of data to send with the request. - :type data: Dict - :return: Result object - :rtype: Result""" + Args: + endpoint (str): The endpoint to make the request to. + params (Dict = None): Python dictionary of query parameters to send with the request. + data (Dict = None): Python dictionary of data to send with the request. + + Returns: + Result: A Result object containing the status code, message, and data from the request. + """ return self._do(http_method='POST', endpoint=endpoint, params=params, data=data) def delete(self, endpoint: str, params: Dict = None, data: Dict = None) -> Result: """Make a DELETE request to the Zipline server. You should almost never have to use this directly. - :param endpoint: The endpoint to make the request to. - :type endpoint: str - :param params: (optional) Python dictionary of query parameters to send with the request. - :type params: Dict - :param data: (optional) Python dictionary of data to send with the request. - :type data: Dict - :return: Result object - :rtype: Result""" + Args: + endpoint (str): The endpoint to make the request to. + params (Dict = None): Python dictionary of query parameters to send with the request. + data (Dict = None): Python dictionary of data to send with the request. + + Returns: + Result: A Result object containing the status code, message, and data from the request. + """ return self._do(http_method='DELETE', endpoint=endpoint, params=params, data=data) diff --git a/pyzipline/utils.py b/pyzipline/utils.py index 308eca9..edb46ea 100644 --- a/pyzipline/utils.py +++ b/pyzipline/utils.py @@ -3,9 +3,10 @@ from datetime import datetime def convert_str_to_datetime(date_string: str) -> datetime: """Converts a string to a datetime object - :param date_string: String to convert - :type date_string: str - :return: Datetime object - :rtype: datetime + Args: + date_string (str): String to convert + + Returns: + datetime.datetime: Datetime object """ return datetime.strptime(date_string, '%Y-%m-%dT%H:%M:%S.%fZ') diff --git a/pyzipline/zipline.py b/pyzipline/zipline.py index ff3e671..cebe5d9 100644 --- a/pyzipline/zipline.py +++ b/pyzipline/zipline.py @@ -1,9 +1,20 @@ import logging from pyzipline.rest_adapter import RestAdapter -from pyzipline.errors import PyZiplineError +from pyzipline.errors import PyZiplineError, FeatureDisabledError from pyzipline.models import * class ZiplineApi: + """Represents an instance of the Zipline API. + + All API requests should be made through this class. + + Args: + hostname (str): The hostname of your Zipline instance, WITHOUT https or http. + token (str = None): String used for authentication when making requests. + ssl (bool = True): Normally set to True, but if your Zipline instance doesn't use SSL/TLS, set this to False. + enforced_signing (bool = True): Normally set to True, but if having SSL/TLS cert validation issues, can turn off with False. + logger (logging.Logger = None): If your app has a logger, pass it in here. + """ def __init__( self, hostname: str, @@ -12,25 +23,16 @@ class ZiplineApi: enforced_signing: bool = True, logger: logging.Logger = None ): - """Constructor for ZiplineApi. - - All API requests should be made through this class. - - Args: - hostname (str): The hostname of your Zipline instance, WITHOUT https or http. - token (str = None): String used for authentication when making requests. - ssl (bool = True): Normally set to True, but if your Zipline instance doesn't use SSL/TLS, set this to False. - enforced_signing (bool = True): Normally set to True, but if having SSL/TLS cert validation issues, can turn off with False. - logger (logging.Logger = None): If your app has a logger, pass it in here. - """ self._rest_adapter = RestAdapter(hostname=hostname, token=token, ssl=ssl, enforced_signing=enforced_signing, logger=logger) def get_user(self, user_id: int) -> User: """Get a user by ID - user_id (int): Integer ID of the user to retrieve - :return: The :class:`pyzipline.models.User` object matching the ID - :rtype: :class:`pyzipline.models.User` + Args: + user_id (int): Integer ID of the user to retrieve + + Returns: + :class:`pyzipline.models.User`: The user with the given ID """ result = self._rest_adapter.get(endpoint=f"user/{user_id}") return User(**result.data) @@ -38,8 +40,35 @@ class ZiplineApi: def get_self(self) -> User: """Get the currently authenticated user - :return: `pyzipline.models.User`object matching the authenticated user - :rtype: `pyzipline.models.User` + Returns: + :class:`pyzipline.models.User`: The currently authenticated user """ result = self._rest_adapter.get(endpoint=f"user") return User(**result.data) + + def check_user_exists(self, username: str, invite: str = None) -> bool: + """Check if a user exists by username + + Args: + username (str): Username to check + invite (str = None): Invite code to use, only required if registration without invites is disabled + + Raises: + FeatureDisabledError: Raised when registration or invites are disabled on the Zipline instance + PyZiplineError: Raised + + Returns: + bool: True if user exists, False if not + """ + params = {'username': username} if invite is None else {'username': username, 'code': invite} + result: Result = self._rest_adapter.get(endpoint=f"user/check", params=params) + if result.status_code == 200: + return bool(result.data['success']) + elif result.message == 'user resistration is disabled' or result.message == 'invites are disabled': + raise FeatureDisabledError(result.message) + elif result.message == 'username already exists': + return True + elif result.message == 'invalid invite code': + raise ValueError(result.message) + else: + raise PyZiplineError(result.message)