From c27f599e37e7f77e70d4093afd6deba893114463 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 11 Mar 2020 14:43:28 +0100 Subject: [PATCH] Fix type specifiers in models --- fbchat/_exception.py | 8 ++++---- fbchat/_listen.py | 4 ++-- fbchat/_models/_attachment.py | 20 +++++++++---------- fbchat/_models/_common.py | 12 +++++++----- fbchat/_models/_file.py | 36 +++++++++++++++++----------------- fbchat/_models/_location.py | 19 ++++++++++-------- fbchat/_models/_message.py | 16 +++++++-------- fbchat/_models/_plan.py | 10 +++++----- fbchat/_models/_quick_reply.py | 16 +++++++-------- fbchat/_models/_sticker.py | 20 ++++++++++--------- fbchat/_session.py | 2 +- fbchat/_threads/_group.py | 21 ++++++++++---------- fbchat/_threads/_page.py | 18 +++++++++-------- fbchat/_threads/_user.py | 24 ++++++++++++----------- fbchat/_util.py | 7 +++++++ 15 files changed, 126 insertions(+), 107 deletions(-) diff --git a/fbchat/_exception.py b/fbchat/_exception.py index b35466b..3f9d038 100644 --- a/fbchat/_exception.py +++ b/fbchat/_exception.py @@ -1,7 +1,7 @@ import attr import requests -from typing import Any +from typing import Any, Optional # Not frozen, since that doesn't work in PyPy @attr.s(slots=True, auto_exc=True) @@ -20,7 +20,7 @@ class HTTPError(FacebookError): """Base class for errors with the HTTP(s) connection to Facebook.""" #: The returned HTTP status code, if relevant - status_code = attr.ib(None, type=int) + status_code = attr.ib(None, type=Optional[int]) def __str__(self): if not self.status_code: @@ -58,7 +58,7 @@ class ExternalError(FacebookError): #: The error message that Facebook returned (Possibly in the user's own language) description = attr.ib(type=str) #: The error code that Facebook returned - code = attr.ib(None, type=int) + code = attr.ib(None, type=Optional[int]) def __str__(self): if self.code: @@ -73,7 +73,7 @@ class GraphQLError(ExternalError): # TODO: Handle multiple errors #: Query debug information - debug_info = attr.ib(None, type=str) + debug_info = attr.ib(None, type=Optional[str]) def __str__(self): if self.debug_info: diff --git a/fbchat/_listen.py b/fbchat/_listen.py index 9cb399d..a39bf1c 100644 --- a/fbchat/_listen.py +++ b/fbchat/_listen.py @@ -5,7 +5,7 @@ import requests from ._common import log, kw_only from . import _util, _exception, _session, _graphql, _events -from typing import Iterable, Optional, Mapping +from typing import Iterable, Optional, Mapping, List HOST = "edge-chat.facebook.com" @@ -118,7 +118,7 @@ class Listener: _mqtt = attr.ib(factory=mqtt_factory, type=paho.mqtt.client.Client) _sync_token = attr.ib(None, type=Optional[str]) _sequence_id = attr.ib(None, type=Optional[int]) - _tmp_events = attr.ib(factory=list, type=Iterable[_events.Event]) + _tmp_events = attr.ib(factory=list, type=List[_events.Event]) def __attrs_post_init__(self): # Configure callbacks diff --git a/fbchat/_models/_attachment.py b/fbchat/_models/_attachment.py index 315771f..1bce65d 100644 --- a/fbchat/_models/_attachment.py +++ b/fbchat/_models/_attachment.py @@ -3,7 +3,7 @@ from . import Image from .._common import attrs_default from .. import _util -from typing import Sequence +from typing import Optional, Sequence @attrs_default @@ -11,7 +11,7 @@ class Attachment: """Represents a Facebook attachment.""" #: The attachment ID - id = attr.ib(None, type=str) + id = attr.ib(None, type=Optional[str]) @attrs_default @@ -24,21 +24,21 @@ class ShareAttachment(Attachment): """Represents a shared item (e.g. URL) attachment.""" #: ID of the author of the shared post - author = attr.ib(None, type=str) + author = attr.ib(None, type=Optional[str]) #: Target URL - url = attr.ib(None, type=str) + url = attr.ib(None, type=Optional[str]) #: Original URL if Facebook redirects the URL - original_url = attr.ib(None, type=str) + original_url = attr.ib(None, type=Optional[str]) #: Title of the attachment - title = attr.ib(None, type=str) + title = attr.ib(None, type=Optional[str]) #: Description of the attachment - description = attr.ib(None, type=str) + description = attr.ib(None, type=Optional[str]) #: Name of the source - source = attr.ib(None, type=str) + source = attr.ib(None, type=Optional[str]) #: The attached image - image = attr.ib(None, type=Image) + image = attr.ib(None, type=Optional[Image]) #: URL of the original image if Facebook uses ``safe_image`` - original_image_url = attr.ib(None, type=str) + original_image_url = attr.ib(None, type=Optional[str]) #: List of additional attachments attachments = attr.ib(factory=list, type=Sequence[Attachment]) diff --git a/fbchat/_models/_common.py b/fbchat/_models/_common.py index a99e011..6828151 100644 --- a/fbchat/_models/_common.py +++ b/fbchat/_models/_common.py @@ -4,6 +4,8 @@ import enum from .._common import attrs_default from .. import _util +from typing import Optional + class ThreadLocation(enum.Enum): """Used to specify where a thread is located (inbox, pending, archived, other).""" @@ -21,11 +23,11 @@ class ThreadLocation(enum.Enum): @attrs_default class ActiveStatus: #: Whether the user is active now - active = attr.ib(None, type=bool) + active = attr.ib(type=bool) #: Datetime when the user was last active - last_active = attr.ib(None, type=datetime.datetime) + last_active = attr.ib(None, type=Optional[datetime.datetime]) #: Whether the user is playing Messenger game now - in_game = attr.ib(None, type=bool) + in_game = attr.ib(None, type=Optional[bool]) @classmethod def _from_orca_presence(cls, data): @@ -42,9 +44,9 @@ class Image: #: URL to the image url = attr.ib(type=str) #: Width of the image - width = attr.ib(None, type=int) + width = attr.ib(None, type=Optional[int]) #: Height of the image - height = attr.ib(None, type=int) + height = attr.ib(None, type=Optional[int]) @classmethod def _from_uri(cls, data): diff --git a/fbchat/_models/_file.py b/fbchat/_models/_file.py index 0aec26c..984e245 100644 --- a/fbchat/_models/_file.py +++ b/fbchat/_models/_file.py @@ -4,7 +4,7 @@ from . import Image, Attachment from .._common import attrs_default from .. import _util -from typing import Set +from typing import Set, Optional @attrs_default @@ -12,13 +12,13 @@ class FileAttachment(Attachment): """Represents a file that has been sent as a Facebook attachment.""" #: URL where you can download the file - url = attr.ib(None, type=str) + url = attr.ib(None, type=Optional[str]) #: Size of the file in bytes - size = attr.ib(None, type=int) + size = attr.ib(None, type=Optional[int]) #: Name of the file - name = attr.ib(None, type=str) + name = attr.ib(None, type=Optional[str]) #: Whether Facebook determines that this file may be harmful - is_malicious = attr.ib(None, type=bool) + is_malicious = attr.ib(None, type=Optional[bool]) @classmethod def _from_graphql(cls, data, size=None): @@ -36,13 +36,13 @@ class AudioAttachment(Attachment): """Represents an audio file that has been sent as a Facebook attachment.""" #: Name of the file - filename = attr.ib(None, type=str) + filename = attr.ib(None, type=Optional[str]) #: URL of the audio file - url = attr.ib(None, type=str) + url = attr.ib(None, type=Optional[str]) #: Duration of the audio clip as a timedelta - duration = attr.ib(None, type=datetime.timedelta) + duration = attr.ib(None, type=Optional[datetime.timedelta]) #: Audio type - audio_type = attr.ib(None, type=str) + audio_type = attr.ib(None, type=Optional[str]) @classmethod def _from_graphql(cls, data): @@ -63,13 +63,13 @@ class ImageAttachment(Attachment): """ #: The extension of the original image (e.g. ``png``) - original_extension = attr.ib(None, type=str) + original_extension = attr.ib(None, type=Optional[str]) #: Width of original image - width = attr.ib(None, converter=lambda x: None if x is None else int(x), type=int) + width = attr.ib(None, converter=_util.int_or_none, type=Optional[int]) #: Height of original image - height = attr.ib(None, converter=lambda x: None if x is None else int(x), type=int) + height = attr.ib(None, converter=_util.int_or_none, type=Optional[int]) #: Whether the image is animated - is_animated = attr.ib(None, type=bool) + is_animated = attr.ib(None, type=Optional[bool]) #: A set, containing variously sized / various types of previews of the image previews = attr.ib(factory=set, type=Set[Image]) @@ -113,15 +113,15 @@ class VideoAttachment(Attachment): """Represents a video that has been sent as a Facebook attachment.""" #: Size of the original video in bytes - size = attr.ib(None, type=int) + size = attr.ib(None, type=Optional[int]) #: Width of original video - width = attr.ib(None, type=int) + width = attr.ib(None, type=Optional[int]) #: Height of original video - height = attr.ib(None, type=int) + height = attr.ib(None, type=Optional[int]) #: Length of video as a timedelta - duration = attr.ib(None, type=datetime.timedelta) + duration = attr.ib(None, type=Optional[datetime.timedelta]) #: URL to very compressed preview video - preview_url = attr.ib(None, type=str) + preview_url = attr.ib(None, type=Optional[str]) #: A set, containing variously sized previews of the video previews = attr.ib(factory=set, type=Set[Image]) diff --git a/fbchat/_models/_location.py b/fbchat/_models/_location.py index 07ee607..7dd7557 100644 --- a/fbchat/_models/_location.py +++ b/fbchat/_models/_location.py @@ -1,8 +1,11 @@ import attr +import datetime from . import Image, Attachment from .._common import attrs_default from .. import _util, _exception +from typing import Optional + @attrs_default class LocationAttachment(Attachment): @@ -12,15 +15,15 @@ class LocationAttachment(Attachment): """ #: Latitude of the location - latitude = attr.ib(None, type=float) + latitude = attr.ib(None, type=Optional[float]) #: Longitude of the location - longitude = attr.ib(None, type=float) + longitude = attr.ib(None, type=Optional[float]) #: Image showing the map of the location - image = attr.ib(None, type=Image) + image = attr.ib(None, type=Optional[Image]) #: URL to Bing maps with the location - url = attr.ib(None, type=str) + url = attr.ib(None, type=Optional[str]) # Address of the location - address = attr.ib(None, type=str) + address = attr.ib(None, type=Optional[str]) @classmethod def _from_graphql(cls, data): @@ -51,11 +54,11 @@ class LiveLocationAttachment(LocationAttachment): """Represents a live user location.""" #: Name of the location - name = attr.ib(None) + name = attr.ib(None, type=Optional[str]) #: Datetime when live location expires - expires_at = attr.ib(None) + expires_at = attr.ib(None, type=Optional[datetime.datetime]) #: True if live location is expired - is_expired = attr.ib(None) + is_expired = attr.ib(None, type=Optional[bool]) @classmethod def _from_pull(cls, data): diff --git a/fbchat/_models/_message.py b/fbchat/_models/_message.py index f129fed..eab7855 100644 --- a/fbchat/_models/_message.py +++ b/fbchat/_models/_message.py @@ -255,31 +255,31 @@ class MessageData(Message): #: Datetime of when the message was sent created_at = attr.ib(type=datetime.datetime) #: The actual message - text = attr.ib(None, type=str) + text = attr.ib(None, type=Optional[str]) #: A list of `Mention` objects mentions = attr.ib(factory=list, type=Sequence[Mention]) #: Size of a sent emoji - emoji_size = attr.ib(None, type=EmojiSize) + emoji_size = attr.ib(None, type=Optional[EmojiSize]) #: Whether the message is read - is_read = attr.ib(None, type=bool) + is_read = attr.ib(None, type=Optional[bool]) #: A list of people IDs who read the message, works only with `Client.fetch_thread_messages` read_by = attr.ib(factory=list, type=bool) #: A dictionary with user's IDs as keys, and their reaction as values reactions = attr.ib(factory=dict, type=Mapping[str, str]) #: A `Sticker` - sticker = attr.ib(None, type=_sticker.Sticker) + sticker = attr.ib(None, type=Optional[_sticker.Sticker]) #: A list of attachments attachments = attr.ib(factory=list, type=Sequence[_attachment.Attachment]) #: A list of `QuickReply` quick_replies = attr.ib(factory=list, type=Sequence[_quick_reply.QuickReply]) #: Whether the message is unsent (deleted for everyone) - unsent = attr.ib(False, type=bool) + unsent = attr.ib(False, type=Optional[bool]) #: Message ID you want to reply to - reply_to_id = attr.ib(None, type=str) + reply_to_id = attr.ib(None, type=Optional[str]) #: Replied message - replied_to = attr.ib(None, type="MessageData") + replied_to = attr.ib(None, type=Optional["MessageData"]) #: Whether the message was forwarded - forwarded = attr.ib(False, type=bool) + forwarded = attr.ib(False, type=Optional[bool]) @staticmethod def _get_forwarded_from_tags(tags): diff --git a/fbchat/_models/_plan.py b/fbchat/_models/_plan.py index 8d5289f..a5d0a8c 100644 --- a/fbchat/_models/_plan.py +++ b/fbchat/_models/_plan.py @@ -4,7 +4,7 @@ import enum from .._common import attrs_default from .. import _exception, _util, _session -from typing import Mapping, Sequence +from typing import Mapping, Sequence, Optional class GuestStatus(enum.Enum): @@ -132,13 +132,13 @@ class PlanData(Plan): #: Plan title title = attr.ib(type=str) #: Plan location name - location = attr.ib(None, converter=lambda x: x or "", type=str) + location = attr.ib(None, converter=lambda x: x or "", type=Optional[str]) #: Plan location ID - location_id = attr.ib(None, converter=lambda x: x or "", type=str) + location_id = attr.ib(None, converter=lambda x: x or "", type=Optional[str]) #: ID of the plan creator - author_id = attr.ib(None, type=str) + author_id = attr.ib(None, type=Optional[str]) #: `User` ids mapped to their `GuestStatus` - guests = attr.ib(None, type=Mapping[str, GuestStatus]) + guests = attr.ib(None, type=Optional[Mapping[str, GuestStatus]]) @property def going(self) -> Sequence[str]: diff --git a/fbchat/_models/_quick_reply.py b/fbchat/_models/_quick_reply.py index 8d41db7..a3c7edc 100644 --- a/fbchat/_models/_quick_reply.py +++ b/fbchat/_models/_quick_reply.py @@ -2,7 +2,7 @@ import attr from . import Attachment from .._common import attrs_default -from typing import Any +from typing import Any, Optional @attrs_default @@ -24,9 +24,9 @@ class QuickReplyText(QuickReply): """Represents a text quick reply.""" #: Title of the quick reply - title = attr.ib(None, type=str) - #: URL of the quick reply image (optional) - image_url = attr.ib(None, type=str) + title = attr.ib(None, type=Optional[str]) + #: URL of the quick reply image + image_url = attr.ib(None, type=Optional[str]) #: Type of the quick reply _type = "text" @@ -43,8 +43,8 @@ class QuickReplyLocation(QuickReply): class QuickReplyPhoneNumber(QuickReply): """Represents a phone number quick reply (Doesn't work on mobile).""" - #: URL of the quick reply image (optional) - image_url = attr.ib(None, type=str) + #: URL of the quick reply image + image_url = attr.ib(None, type=Optional[str]) #: Type of the quick reply _type = "user_phone_number" @@ -53,8 +53,8 @@ class QuickReplyPhoneNumber(QuickReply): class QuickReplyEmail(QuickReply): """Represents an email quick reply (Doesn't work on mobile).""" - #: URL of the quick reply image (optional) - image_url = attr.ib(None, type=str) + #: URL of the quick reply image + image_url = attr.ib(None, type=Optional[str]) #: Type of the quick reply _type = "user_email" diff --git a/fbchat/_models/_sticker.py b/fbchat/_models/_sticker.py index e6fa829..d549414 100644 --- a/fbchat/_models/_sticker.py +++ b/fbchat/_models/_sticker.py @@ -2,34 +2,36 @@ import attr from . import Image, Attachment from .._common import attrs_default +from typing import Optional + @attrs_default class Sticker(Attachment): """Represents a Facebook sticker that has been sent to a thread as an attachment.""" #: The sticker-pack's ID - pack = attr.ib(None, type=str) + pack = attr.ib(None, type=Optional[str]) #: Whether the sticker is animated is_animated = attr.ib(False, type=bool) # If the sticker is animated, the following should be present #: URL to a medium spritemap - medium_sprite_image = attr.ib(None, type=str) + medium_sprite_image = attr.ib(None, type=Optional[str]) #: URL to a large spritemap - large_sprite_image = attr.ib(None, type=str) + large_sprite_image = attr.ib(None, type=Optional[str]) #: The amount of frames present in the spritemap pr. row - frames_per_row = attr.ib(None, type=int) + frames_per_row = attr.ib(None, type=Optional[int]) #: The amount of frames present in the spritemap pr. column - frames_per_col = attr.ib(None, type=int) + frames_per_col = attr.ib(None, type=Optional[int]) #: The total amount of frames in the spritemap - frame_count = attr.ib(None, type=int) + frame_count = attr.ib(None, type=Optional[int]) #: The frame rate the spritemap is intended to be played in - frame_rate = attr.ib(None, type=int) + frame_rate = attr.ib(None, type=Optional[int]) #: The sticker's image - image = attr.ib(None, type=Image) + image = attr.ib(None, type=Optional[Image]) #: The sticker's label/name - label = attr.ib(None, type=str) + label = attr.ib(None, type=Optional[str]) @classmethod def _from_graphql(cls, data): diff --git a/fbchat/_session.py b/fbchat/_session.py index 26df79f..be0d580 100644 --- a/fbchat/_session.py +++ b/fbchat/_session.py @@ -157,7 +157,7 @@ class Session: _session = attr.ib(factory=session_factory, type=requests.Session) _counter = attr.ib(0, type=int) _client_id = attr.ib(factory=client_id_factory, type=str) - _logout_h = attr.ib(None, type=str) + _logout_h = attr.ib(None, type=Optional[str]) @property def user(self): diff --git a/fbchat/_threads/_group.py b/fbchat/_threads/_group.py index d80c5fb..891ec04 100644 --- a/fbchat/_threads/_group.py +++ b/fbchat/_threads/_group.py @@ -4,7 +4,8 @@ from ._abc import ThreadABC from . import _user from .._common import attrs_default from .. import _util, _session, _graphql, _models -from typing import Sequence, Iterable, Set, Mapping + +from typing import Sequence, Iterable, Set, Mapping, Optional @attrs_default @@ -179,31 +180,31 @@ class GroupData(Group): """ #: The group's picture - photo = attr.ib(None, type="_models.Image") + photo = attr.ib(None, type=Optional["_models.Image"]) #: The name of the group - name = attr.ib(None, type=str) + name = attr.ib(None, type=Optional[str]) #: When the group was last active / when the last message was sent - last_active = attr.ib(None, type=datetime.datetime) + last_active = attr.ib(None, type=Optional[datetime.datetime]) #: Number of messages in the group - message_count = attr.ib(None, type=int) + message_count = attr.ib(None, type=Optional[int]) #: Set `Plan` - plan = attr.ib(None, type="_models.PlanData") + plan = attr.ib(None, type=Optional["_models.PlanData"]) #: The group thread's participant user ids participants = attr.ib(factory=set, type=Set[str]) #: A dictionary, containing user nicknames mapped to their IDs nicknames = attr.ib(factory=dict, type=Mapping[str, str]) #: The groups's message color - color = attr.ib(None, type=str) + color = attr.ib(None, type=Optional[str]) #: The groups's default emoji - emoji = attr.ib(None, type=str) + emoji = attr.ib(None, type=Optional[str]) # User ids of thread admins admins = attr.ib(factory=set, type=Set[str]) # True if users need approval to join - approval_mode = attr.ib(None, type=bool) + approval_mode = attr.ib(None, type=Optional[bool]) # Set containing user IDs requesting to join approval_requests = attr.ib(factory=set, type=Set[str]) # Link for joining group - join_link = attr.ib(None, type=str) + join_link = attr.ib(None, type=Optional[str]) @classmethod def _from_graphql(cls, session, data): diff --git a/fbchat/_threads/_page.py b/fbchat/_threads/_page.py index daae1d9..28ebe24 100644 --- a/fbchat/_threads/_page.py +++ b/fbchat/_threads/_page.py @@ -4,6 +4,8 @@ from ._abc import ThreadABC from .._common import attrs_default from .. import _session, _models +from typing import Optional + @attrs_default class Page(ThreadABC): @@ -39,21 +41,21 @@ class PageData(Page): #: The name of the page name = attr.ib(type=str) #: When the thread was last active / when the last message was sent - last_active = attr.ib(None, type=datetime.datetime) + last_active = attr.ib(None, type=Optional[datetime.datetime]) #: Number of messages in the thread - message_count = attr.ib(None, type=int) + message_count = attr.ib(None, type=Optional[int]) #: Set `Plan` - plan = attr.ib(None, type="_models.PlanData") + plan = attr.ib(None, type=Optional["_models.PlanData"]) #: The page's custom URL - url = attr.ib(None, type=str) + url = attr.ib(None, type=Optional[str]) #: The name of the page's location city - city = attr.ib(None, type=str) + city = attr.ib(None, type=Optional[str]) #: Amount of likes the page has - likes = attr.ib(None, type=int) + likes = attr.ib(None, type=Optional[int]) #: Some extra information about the page - sub_title = attr.ib(None, type=str) + sub_title = attr.ib(None, type=Optional[str]) #: The page's category - category = attr.ib(None, type=str) + category = attr.ib(None, type=Optional[str]) @classmethod def _from_graphql(cls, session, data): diff --git a/fbchat/_threads/_user.py b/fbchat/_threads/_user.py index e09a92f..888dc76 100644 --- a/fbchat/_threads/_user.py +++ b/fbchat/_threads/_user.py @@ -4,6 +4,8 @@ from ._abc import ThreadABC from .._common import log, attrs_default from .. import _util, _session, _models +from typing import Optional + GENDERS = { # For standard requests @@ -111,27 +113,27 @@ class UserData(User): #: The users first name first_name = attr.ib(type=str) #: The users last name - last_name = attr.ib(None, type=str) + last_name = attr.ib(None, type=Optional[str]) #: Datetime when the thread was last active / when the last message was sent - last_active = attr.ib(None, type=datetime.datetime) + last_active = attr.ib(None, type=Optional[datetime.datetime]) #: Number of messages in the thread - message_count = attr.ib(None, type=int) + message_count = attr.ib(None, type=Optional[int]) #: Set `Plan` - plan = attr.ib(None, type="_models.PlanData") + plan = attr.ib(None, type=Optional["_models.PlanData"]) #: The profile URL. ``None`` for Messenger-only users - url = attr.ib(None, type=str) + url = attr.ib(None, type=Optional[str]) #: The user's gender - gender = attr.ib(None, type=str) + gender = attr.ib(None, type=Optional[str]) #: From 0 to 1. How close the client is to the user - affinity = attr.ib(None, type=float) + affinity = attr.ib(None, type=Optional[float]) #: The user's nickname - nickname = attr.ib(None, type=str) + nickname = attr.ib(None, type=Optional[str]) #: The clients nickname, as seen by the user - own_nickname = attr.ib(None, type=str) + own_nickname = attr.ib(None, type=Optional[str]) #: The message color - color = attr.ib(None, type=str) + color = attr.ib(None, type=Optional[str]) #: The default emoji - emoji = attr.ib(None, type=str) + emoji = attr.ib(None, type=Optional[str]) @staticmethod def _get_other_user(data): diff --git a/fbchat/_util.py b/fbchat/_util.py index f5b1420..2c46fba 100644 --- a/fbchat/_util.py +++ b/fbchat/_util.py @@ -20,6 +20,13 @@ USER_AGENTS = [ ] +def int_or_none(inp: Any) -> Optional[int]: + try: + return int(inp) + except Exception: + return None + + def get_limits(limit: Optional[int], max_limit: int) -> Iterable[int]: """Helper that generates limits based on a max limit.""" if limit is None: