From 4430a838c96775f969a9d39803f33f8ba5bc9414 Mon Sep 17 00:00:00 2001 From: EGsagon Date: Wed, 10 Apr 2024 09:26:01 +0200 Subject: [PATCH] Some dosctring updates --- src/phub/consts.py | 2 +- src/phub/core.py | 169 +++++++++++++++++++++-------------- src/phub/errors.py | 42 +++------ src/phub/literals.py | 29 +++++- src/phub/objects/account.py | 5 +- src/phub/objects/image.py | 6 +- src/phub/objects/playlist.py | 2 +- src/phub/objects/query.py | 14 +-- src/phub/objects/user.py | 6 +- src/phub/objects/video.py | 2 +- src/phub/utils.py | 16 ++-- 11 files changed, 163 insertions(+), 130 deletions(-) diff --git a/src/phub/consts.py b/src/phub/consts.py index f95815a..3d2f6aa 100644 --- a/src/phub/consts.py +++ b/src/phub/consts.py @@ -52,7 +52,7 @@ IFRAME = '' # Supported languages -LANGUAGES = [ 'cn', 'de', 'fr', 'it', 'pt', 'pl', 'rt', 'nl', 'cz', 'jp' ] +LANGUAGES = [ 'en', 'cn', 'de', 'fr', 'it', 'pt', 'pl', 'rt', 'nl', 'cz', 'jp' ] FEED_CLASS_TO_CONST = { 'stream_videos_uploaded': 'Section.VIDEO', diff --git a/src/phub/core.py b/src/phub/core.py index d5745aa..ad88efa 100644 --- a/src/phub/core.py +++ b/src/phub/core.py @@ -28,23 +28,27 @@ class Client: ''' def __init__(self, - username: str = None, + email: str = None, password: str = None, *, - language: str = 'en,en-US', - delay: int = 0, + language: literals.language = 'en,en-US', + delay: int | float = 0, proxies: dict = None, login: bool = True) -> None: ''' - Initialise a new client. + Initialises a new client. Args: - username (str): Optional account username/address to connect to. - password (str): Optional account password to connect to. - language (str): Language locale (fr, en, ru, etc.) - delay (float): Minimum delay between requests. + email (str): Account email address. + password (str): Account password. + language (str): Client language (fr, en, ru, etc.) + delay (int | float): Minimum delay between requests. proxies (dict): Dictionary of proxies for the requests. - login (bool): Whether to automatically log in after initialization. + login (bool): Whether to directly log in after initialization. + + Raises: + LoginFailed: If Pornhub refuses the authentification. + The reason will be passed as the error body. ''' logger.debug('Initialised new Client %s', self) @@ -54,7 +58,7 @@ def __init__(self, self.proxies = proxies self.language = {'Accept-Language': language} - self.credentials = {'email': username, + self.credentials = {'email': email, 'password': password} self.delay = delay @@ -68,12 +72,13 @@ def __init__(self, # Automatic login if login and self.account: - logger.debug('Automatic login triggered') self.login() def reset(self) -> None: ''' Reset the client requests session. + This is useful if you are keeping the client running + for a long time and can help with Pornhub rate limit. ''' # Initialise session @@ -92,19 +97,23 @@ def call(self, throw: bool = True, silent: bool = False) -> requests.Response: ''' - Send a request. + Used internally to send a request or an API call. Args: - func (str): URL or PH function to call. - method (str): Request method. - data (dict): Optional data to send to the server. - headers (dict): Request optional headers. + func (str): URL or PH function to fetch or call. + method (str): Request method (GET, POST, PUT, ...). + data (dict): Optional data to send to the server. + headers (dict): Additional request headers. timeout (float): Request maximum response time. - throw (bool): Whether to raise an error when a request explicitly fails. - silent (bool): Make the call logging one level deeper. + throw (bool): Whether to raise an error when a request explicitly fails. + silent (bool): Whether to supress this call from logs. Returns: - requests.Response: The fetched response. + Response: The fetched response. + + Raises: + ConnectionError: If the request was blocked by Pornhub. + HTTPError: If the request failed, for any reason. ''' logger.log(logging.DEBUG if silent else logging.INFO, 'Fetching %s', func or '/') @@ -168,6 +177,10 @@ def login(self, Returns: bool: Whether the login was successful. + + Raises: + ClientAlreadyLogged: If ``force`` was False and client was already logged. + LoginFailed: If the login failed, for a reason passed in the error body. ''' logger.debug('Attempting login') @@ -203,10 +216,10 @@ def login(self, def get(self, video: str | Video) -> Video: ''' - Fetch a Pornhub video. + Get a Pornhub video. Args: - video (str): Video full URL, partial URL or viewkey. + video (str | Video): Video URL or viewkey. Returns: Video: The corresponding video object. @@ -238,10 +251,10 @@ def get(self, video: str | Video) -> Video: def get_user(self, user: str | User) -> User: ''' - Get a specific user. + Get a Pornhub user. Args: - user (str): user URL or name. + user (str | User): user URL or name. Returns: User: The corresponding user object. @@ -265,13 +278,16 @@ def search_hubtraffic(self, It is condidered to be much faster but has less filters. Args: - query (str): The query to search. - category (str): A category the video is in. - sort (str): Sorting type. - period (str): When using sort, specify the search period. + query (str): The query to search. + category (str): A category the video is in. + sort (str): Sorting type. + period (str): When using sort, specify the search period. Returns: - Query: Initialised query. + Query: Initialised query response. + + Raises: + AssertionError: If one or more filters don't match their literals. ''' literals.ass('category', category, literals.category) @@ -304,16 +320,20 @@ def search(self, Performs searching on Pornhub. Args: - query (str): The query to search. - production (str): Production type. - category (str): A category the video is in. + query (str): The query to search. + production (str): Production type. + category (str): A category the video is in. exclude_category (str | list): One or more categories to exclude. - hd (bool): Whether to search only HD videos. - sort (str): Sorting type. - period (str): When using sort, specify the search period. + hd (bool): Whether to search only HD videos. + sort (str): Sorting type. + period (str): When using sort, specify the search period. Returns: Query: Initialised query. + + Raises: + AssertioneError: If the query is empty. + AssertioneError: If one or more filters don't match their literals. ''' query = query.strip() @@ -341,49 +361,63 @@ def search(self, query_repr = query ) - def get_playlist(self, pl: str | int | Playlist) -> Playlist: + def get_playlist(self, playlist: str | int | Playlist) -> Playlist: ''' - Initializes a Playlist object. + Get a Pornhub playlist. Args: - pl (str | int | Playlist): The playlist url or id + playlist (str | int | Playlist): The playlist url or id Returns: - Playlist object + Playlist: The corresponding playlist object. + + Raises: + TypeError: If the playlist argument is invalid. ''' - assert isinstance(pl, str | int | Playlist), 'Invalid type' + if not isinstance(playlist, str | int | Playlist): + raise TypeError(f'Invalid type: {type(playlist)} should be str, int or Playlist.') - if isinstance(pl, Playlist): - pl = pl.url + if isinstance(playlist, Playlist): + playlist = playlist.url - if isinstance(pl, str) and 'playlist' in pl: - pl = pl.split('/')[-1] + if isinstance(playlist, str) and 'playlist' in playlist: + playlist = playlist.split('/')[-1] - return Playlist(self, pl) + return Playlist(self, playlist) def search_user(self, - username: str = None, - country: str = None, - city: str = None, - min_age: int = None, - max_age: int = None, - gender: literals.gender = None, - orientation: literals.orientation = None, - offers: literals.offers = None, - relation: literals.relation = None, - sort: literals.sort_user = None, - is_online: bool = None, - is_model: bool = None, - is_staff: bool = None, - has_avatar: bool = None, - has_videos: bool = None, - has_photos: bool = None, - has_playlists: bool = None - ) -> queries.UserQuery: + username: str = None, country: str = None, + city: str = None, min_age: int = None, + max_age: int = None, gender: literals.gender = None, + orientation: literals.orientation = None, offers: literals.offers = None, + relation: literals.relation = None, sort: literals.sort_user = None, + is_online: bool = None, is_model: bool = None, + is_staff: bool = None, has_avatar: bool = None, + has_videos: bool = None, has_photos: bool = None, + has_playlists: bool = None) -> queries.UserQuery: ''' Search for users in the community. + Args: + username (str): Filters users with a name query. + country (str): Filter users with a country. + city (str): Filters users with a city. + min_age (int): Filters users with a minimal age. + max_age (int): Filters users with a maximal age. + gender (gender): Filters users with a gender. + orientation (orientation): Filters users with an orientation. + offers (offers): Filters users with what content they do on their channel. + relation (relation): Filters users with their relation. + sort (sort_user): Response sort type. + is_online (bool): Get only online or offline users. + is_model (bool): Get only model users. + is_staff (bool): Get only Pornhub staff members. + has_avatar (bool): Get only users with a custom avatar. + has_videos (bool): Get only users that have posted videos. + has_photos (bool): Get only users that have posted photos. + has_playlists (bool): Get only users that have posted playlists. + Returns: UserQuery: Initialised query. ''' @@ -414,7 +448,7 @@ def search_user(self, def _clear_granted_token(self) -> None: ''' - Clear the granted token cache. + Removes the current granted token stored, if any. ''' if '_granted_token' in self.__dict__: @@ -423,13 +457,14 @@ def _clear_granted_token(self) -> None: @cached_property def _granted_token(self) -> str: ''' - Get a granted token after having - authentified the account. + Get a granted token for the account. Used internally + for making API calls. + + Raises: + AssertionError: If the client is not logged in ''' assert self.logged, 'Client must be logged in' - self._token_controller = True - page = self.call('').text return consts.re.get_token(page) diff --git a/src/phub/errors.py b/src/phub/errors.py index 20c8ea1..f6aa477 100644 --- a/src/phub/errors.py +++ b/src/phub/errors.py @@ -4,14 +4,13 @@ class ClientAlreadyLogged(Exception): ''' - The Client already has initiated - a login call which was successful. + The Client is already logged in. ''' class LoginFailed(Exception): ''' The Client failed to connect. - Given credentials might be wrong. + Input credentials might be wrong. ''' class RegexError(Exception): @@ -21,53 +20,32 @@ class RegexError(Exception): class URLError(Exception): ''' - Provided URL is invalid. + The provided URL is invalid. ''' class ParsingError(Exception): ''' - The parser failed to properly - fetch data. + The parser failed to properly fetch data. ''' class MaxRetriesExceeded(Exception): ''' - A module failed its job after too - many retries. You might want to - try again after a little time. + A process failed after too many retries. - You can also use: attr:`Client.delay` - to slow down requests. + Note: You can use: attr:`Client.delay` to slow down requests. ''' class UserNotFound(Exception): ''' - User wasn't found. This either happens - because the user does not exist, or - its name or URL is wrong. + User could not be foundn either because + it does not exists, or its name/URL is wrong. ''' class NoResult(Exception): ''' A query failed to retrieve an item at a specific index. Most of the time, - this is raised when you go too far in - a fetching loop: - - .. code-block:: python - - query = ... - for i in range(1000): - print(query[i]) - - Instead, use: - - .. code-block:: python - - query = ... - for item in query[:1000]: - print(item) - + this is the equivalent of a StopIteration signal. ''' class InvalidCategory(Exception): @@ -88,7 +66,7 @@ class VideoError(Exception): ''' Pornhub refused to serve video data because of some internal error (mostly because the video - is not available anymore). + is not available). ''' # EOF \ No newline at end of file diff --git a/src/phub/literals.py b/src/phub/literals.py index c3710c9..765f27d 100644 --- a/src/phub/literals.py +++ b/src/phub/literals.py @@ -4,6 +4,9 @@ from typing import Literal, Iterable, Type +# Language locales +language = Literal['en', 'cn', 'de', 'fr', 'it', 'pt', 'pl', 'rt', 'nl', 'cz', 'jp'] + # Production type production = Literal['homemade', 'professional'] @@ -79,7 +82,7 @@ class map: ''' - Map literals values to Pornhub arg representation. + This object maps PHUB literals to their Pornhub URL value. ''' sort = { @@ -179,9 +182,15 @@ class map: 'month': 'monthly' } -def _craft_list(args: Iterable[category]) -> str: +def _craft_list(args: Iterable[str]) -> str: ''' Craft an item list list into a url-valid list. + + Args: + args (Iterable[str]): A list of items. + + Returns: + str: A dash separated URL-valid list. ''' if isinstance(args, str): @@ -190,9 +199,15 @@ def _craft_list(args: Iterable[category]) -> str: if args: return '-'.join(list(args)) -def _craft_boolean(b: bool | None) -> str: +def _craft_boolean(b: bool | None) -> int | None: ''' Craft a boolean into a url-valid int. + + Args: + b (bool): Initial boolean value. + + Returns: + int: URL-valid representation of b. ''' if b is not None: @@ -201,6 +216,14 @@ def _craft_boolean(b: bool | None) -> str: def ass(name: str, item: str | list[str], literal: Type) -> None: ''' Assert one or multiple items are part of a literal. + + Args: + name (str): The literal name, used for error bodies. + item (str | list[str]): The object(s) to assert. + literal (Type): The literal that should match the item(s). + + Raises: + AssertionError: If the item is invalid. ''' if not item: return diff --git a/src/phub/objects/account.py b/src/phub/objects/account.py index a437c13..466d617 100644 --- a/src/phub/objects/account.py +++ b/src/phub/objects/account.py @@ -34,7 +34,6 @@ def __new__(cls, client: Client) -> Self | None: ''' if all(client.credentials.values()): - logger.info('Creating new account object') return object.__new__(cls) def __init__(self, client: Client) -> None: @@ -55,9 +54,7 @@ def __init__(self, client: Client) -> None: # Save data keys so far, so we can make a difference with the # cached property ones. self.loaded_keys = list(self.__dict__.keys()) + ['loaded_keys'] - - logger.info('Account object %s created', self) - + def __repr__(self) -> str: status = 'logged-out' if self.name is None else f'name={self.name}' diff --git a/src/phub/objects/image.py b/src/phub/objects/image.py index e9b7c4e..0a7f7ea 100644 --- a/src/phub/objects/image.py +++ b/src/phub/objects/image.py @@ -28,10 +28,10 @@ def __init__(self, Initialise a new image object. Args: - client (Client): Parent client. - url (str): The image URL. + client (Client): Parent client. + url (str): The image URL. sizes (list[dict]): Image sizes/resolutions/servers. - name (str): Image name. + name (str): Image name. ''' self.url = url diff --git a/src/phub/objects/playlist.py b/src/phub/objects/playlist.py index a007944..5e6a068 100644 --- a/src/phub/objects/playlist.py +++ b/src/phub/objects/playlist.py @@ -29,7 +29,7 @@ def __init__(self, client: Client, pid: str) -> None: Args: client (Client): Parent client to use. - pid (str): The playlist id. + pid (str): The playlist id. ''' # Initialise diff --git a/src/phub/objects/query.py b/src/phub/objects/query.py index c7d0549..f205398 100644 --- a/src/phub/objects/query.py +++ b/src/phub/objects/query.py @@ -80,11 +80,11 @@ def __init__(self, Initialise a new query. Args: - client (Client): The parent client. - func (str): The URL function. - args (dict): Arguments. + client (Client): The parent client. + func (str): The URL function. + args (dict): Arguments. container_hint (Callable): An hint function to help determine where should the target container be. - query_repr (str): Indication for the query representation. + query_repr (str): Indication for the query representation. ''' self.client = client @@ -143,9 +143,9 @@ def sample(self, Get a sample of the query. Args: - max (int): Maximum amount of items to fetch. - filter (Callable): A filter function that decides whether to keep each QueryItems. - watched (bool): Whether videos should have been watched by the account or not. + max (int): Maximum amount of items to fetch. + filter (Callable): A filter function that decides whether to keep each QueryItems. + watched (bool): Whether videos should have been watched by the account or not. free_premium (bool): Whether videos should be free premium or not. Returns: diff --git a/src/phub/objects/user.py b/src/phub/objects/user.py index 2f46dca..b025df2 100644 --- a/src/phub/objects/user.py +++ b/src/phub/objects/user.py @@ -38,8 +38,8 @@ def __init__(self, client: Client, name: str, url: str, type: str = None) -> Non Args: client (Client): The client parent. - name (str): The username. - url (str): The user page URL. + name (str): The username. + url (str): The user page URL. ''' self.client = client @@ -122,7 +122,7 @@ def get(cls, client: Client, user: str) -> Self: Args: client (Client): The parent client. - user (str): Username or URL. + user (str): Username or URL. ''' if consts.re.is_url(user): diff --git a/src/phub/objects/video.py b/src/phub/objects/video.py index e1083b3..0d5cbc0 100644 --- a/src/phub/objects/video.py +++ b/src/phub/objects/video.py @@ -36,7 +36,7 @@ def __init__(self, client: Client, url: str) -> None: Args: client (Client): The parent client. - url (str): The video URL. + url (str): The video URL. ''' if not consts.re.is_video_url(url): diff --git a/src/phub/utils.py b/src/phub/utils.py index 7849e88..9a212a1 100644 --- a/src/phub/utils.py +++ b/src/phub/utils.py @@ -34,7 +34,7 @@ def concat(*args) -> str: Concatenate multiple URL or file path parts. Args: - *args: A variable number of arguments, each can be a str or a pathlib.Path object. + args: A variable number of arguments, each can be a str or a pathlib.Path object. Returns: str: The concatenated path as a string. @@ -100,7 +100,7 @@ def serialize(object_: object, recursive: bool = False) -> object: Simple serializer for PHUB objects. Args: - object_ (Any): The object to serialize. + object_ (Any): The object to serialize. recursive (bool): Whether to allow serializing PHUB objects inside the object. Returns: @@ -140,10 +140,10 @@ def dictify(object_: object, Dictify an object. Args: - object_ (Any): The object to serialize. - keys (list[str]): A list of keys to dictify. - all_ (list[str]): The total list of serializeable object keys. - recursive (bool): Whether to allow serializing PHUB objects inside the object. + object_ (Any): The object to serialize. + keys (list[str]): A list of keys to dictify. + all_ (list[str]): The total list of serializeable object keys. + recursive (bool): Whether to allow serializing PHUB objects inside the object. Returns: dict: The object dictified. @@ -160,7 +160,7 @@ def suppress(gen: Iterable, errs: Exception | tuple[Exception] = errors.VideoErr Set up a generator to bypass items that throw errors. Args: - gen (Iterable): The iterable to suppress. + gen (Iterable): The iterable to suppress. errs (Exception): The errors that fall under the suppression rule. Returns @@ -228,7 +228,7 @@ def head(client: object, url: str) -> str | bool: Args: client (Client): A client containing an initialised session. - url (str): The url of the request. + url (str): The url of the request. Returns: str | bool: The redirect URL if success, False otherwise.