From 51913db293d04242e881cf67e92e58f74c5b2df9 Mon Sep 17 00:00:00 2001 From: ikaroskun Date: Tue, 19 Mar 2024 11:01:25 +0800 Subject: [PATCH 1/2] feat(OAuth): :sparkles: add parameters for oauth --- pyfacebook/api/graph.py | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/pyfacebook/api/graph.py b/pyfacebook/api/graph.py index 54d2d86..c984788 100644 --- a/pyfacebook/api/graph.py +++ b/pyfacebook/api/graph.py @@ -54,6 +54,9 @@ def __init__( instagram_business_id: Optional[str] = None, authorization_url: Optional[str] = None, access_token_url: Optional[str] = None, + redirect_uri: Optional[str] = None, + scope: Optional[List[str]] = None, + state: Optional[str] = None, ): self.app_id = app_id self.app_secret = app_secret @@ -77,6 +80,9 @@ def __init__( self.access_token_url = ( access_token_url if access_token_url else self.EXCHANGE_ACCESS_TOKEN_URL ) + self.redirect_uri = redirect_uri if redirect_uri else self.DEFAULT_REDIRECT_URI + self.scope = scope if scope else self.DEFAULT_SCOPE + self.state = state if state else self.STATE if version is None: # default version is last new. @@ -498,11 +504,13 @@ def _get_oauth_session( self, redirect_uri: Optional[str] = None, scope: Optional[List[str]] = None, + state: Optional[str] = None, **kwargs, ) -> OAuth2Session: """ :param redirect_uri: The URL that you want to redirect the person logging in back to. :param scope: A list of permission string to request from the person using your app. + :param state: A CSRF token that will be passed to the redirect URL. :param kwargs: Additional parameters for oauth. :return: OAuth Session """ @@ -511,15 +519,17 @@ def _get_oauth_session( raise LibraryError({"message": "OAuth need your app credentials"}) if redirect_uri is None: - redirect_uri = self.DEFAULT_REDIRECT_URI + redirect_uri = self.redirect_uri if scope is None: - scope = self.DEFAULT_SCOPE + scope = self.scope + if state is None: + state = self.state session = OAuth2Session( client_id=self.app_id, scope=scope, redirect_uri=redirect_uri, - state=self.STATE, + state=state, **kwargs, ) session = facebook_compliance_fix(session) @@ -529,6 +539,7 @@ def get_authorization_url( self, redirect_uri: Optional[str] = None, scope: Optional[List[str]] = None, + state: Optional[str] = None, **kwargs, ) -> Tuple[str, str]: """ @@ -538,11 +549,12 @@ def get_authorization_url( :param redirect_uri: The URL that you want to redirect the person logging in back to. Note: Your redirect uri need be set to `Valid OAuth redirect URIs` items in App Dashboard. :param scope: A list of permission string to request from the person using your app. + :param state: A CSRF token that will be passed to the redirect URL. :param kwargs: Additional parameters for oauth. :return: URL to do oauth and state """ session = self._get_oauth_session( - redirect_uri=redirect_uri, scope=scope, **kwargs + redirect_uri=redirect_uri, scope=scope, state=state, **kwargs ) authorization_url, state = session.authorization_url(url=self.authorization_url) return authorization_url, state @@ -552,17 +564,19 @@ def exchange_user_access_token( response: str, redirect_uri: Optional[str] = None, scope: Optional[List[str]] = None, + state: Optional[str] = None, **kwargs, ) -> dict: """ :param response: The redirect response url for authorize redirect - :param scope: A list of permission string to request from the person using your app. :param redirect_uri: Url for your redirect. + :param scope: A list of permission string to request from the person using your app. + :param state: A CSRF token that will be passed to the redirect URL. :param kwargs: Additional parameters for oauth. :return: """ session = self._get_oauth_session( - redirect_uri=redirect_uri, scope=scope, **kwargs + redirect_uri=redirect_uri, scope=scope, state=state, **kwargs ) session.fetch_token( @@ -769,17 +783,19 @@ def exchange_user_access_token( response: str, redirect_uri: Optional[str] = None, scope: Optional[List[str]] = None, + state: Optional[str] = None, **kwargs, ) -> dict: """ :param response: The redirect response url for authorize redirect - :param scope: A list of permission string to request from the person using your app. :param redirect_uri: Url for your redirect. + :param scope: A list of permission string to request from the person using your app. + :param state: A CSRF token that will be passed to the redirect URL. :param kwargs: Additional parameters for oauth. :return: """ session = self._get_oauth_session( - redirect_uri=redirect_uri, scope=scope, **kwargs + redirect_uri=redirect_uri, scope=scope, state=state, **kwargs ) session.fetch_token( From 3e397307b47bad90c0a12e7dfac25eff6c7d8028 Mon Sep 17 00:00:00 2001 From: ikaroskun Date: Tue, 19 Mar 2024 11:04:38 +0800 Subject: [PATCH 2/2] style(code): :art: format code with black 24 --- examples/get_facebook_feed.py | 1 + examples/publish_facebook_post.py | 1 + examples/publish_instagram_media.py | 1 + examples/server_sent_events.py | 1 + get_token.py | 1 + pyfacebook/api/base_client.py | 3 ++- pyfacebook/api/base_resource.py | 1 + pyfacebook/api/facebook/client.py | 1 + pyfacebook/api/facebook/resource/page.py | 1 + pyfacebook/api/facebook/resource/user.py | 1 + pyfacebook/api/graph.py | 1 + .../api/instagram_basic/resource/user.py | 1 + .../api/instagram_business/resource/comment.py | 1 + .../api/instagram_business/resource/media.py | 1 + pyfacebook/models/album.py | 12 +++--------- pyfacebook/models/application.py | 4 +--- pyfacebook/models/base.py | 1 + pyfacebook/models/comment.py | 4 +--- pyfacebook/models/conversation.py | 1 + pyfacebook/models/event.py | 8 ++------ pyfacebook/models/live_video.py | 13 ++++--------- pyfacebook/models/page.py | 1 + pyfacebook/models/photo.py | 16 ++++------------ pyfacebook/models/post.py | 10 ++++------ pyfacebook/models/video.py | 8 ++------ pyfacebook/ratelimit.py | 18 +++++++++--------- tests/facebook/test_photo.py | 1 + tests/test_basic_display_api.py | 1 + tests/test_graph.py | 1 + tests/test_server_sent_events.py | 1 + 30 files changed, 52 insertions(+), 64 deletions(-) diff --git a/examples/get_facebook_feed.py b/examples/get_facebook_feed.py index d5e23c4..f9c5669 100644 --- a/examples/get_facebook_feed.py +++ b/examples/get_facebook_feed.py @@ -3,6 +3,7 @@ Refer: https://developers.facebook.com/docs/graph-api/reference/page/feed#read """ + import json import os diff --git a/examples/publish_facebook_post.py b/examples/publish_facebook_post.py index 3556f5b..c55f8eb 100644 --- a/examples/publish_facebook_post.py +++ b/examples/publish_facebook_post.py @@ -3,6 +3,7 @@ Refer: https://developers.facebook.com/docs/graph-api/reference/page/feed#publish """ + import os from pyfacebook import GraphAPI diff --git a/examples/publish_instagram_media.py b/examples/publish_instagram_media.py index 3cbce18..8e06862 100644 --- a/examples/publish_instagram_media.py +++ b/examples/publish_instagram_media.py @@ -3,6 +3,7 @@ Refer: https://developers.facebook.com/docs/instagram-api/reference/ig-user/media """ + import os from pyfacebook import GraphAPI diff --git a/examples/server_sent_events.py b/examples/server_sent_events.py index 5ef609b..d851b7f 100644 --- a/examples/server_sent_events.py +++ b/examples/server_sent_events.py @@ -1,6 +1,7 @@ """ A demo for sample streaming api. """ + import json import logging diff --git a/get_token.py b/get_token.py index 9d40e1c..801615a 100644 --- a/get_token.py +++ b/get_token.py @@ -2,6 +2,7 @@ Utility to get your access tokens. Refer: https://developers.facebook.com/docs/facebook-login/access-tokens/refreshing """ + import webbrowser import click diff --git a/pyfacebook/api/base_client.py b/pyfacebook/api/base_client.py index e936c5a..a3f9898 100644 --- a/pyfacebook/api/base_client.py +++ b/pyfacebook/api/base_client.py @@ -1,6 +1,7 @@ """ Base client for API. """ + import inspect from pyfacebook import GraphAPI, BasicDisplayAPI @@ -23,4 +24,4 @@ def __new__(cls, *args, **kwargs): class BaseBasicDisplayApi(BasicDisplayAPI, BaseApi): - ... + pass diff --git a/pyfacebook/api/base_resource.py b/pyfacebook/api/base_resource.py index 56ae342..f322381 100644 --- a/pyfacebook/api/base_resource.py +++ b/pyfacebook/api/base_resource.py @@ -1,6 +1,7 @@ """ Resource base class """ + from pyfacebook.api.graph import GraphAPI diff --git a/pyfacebook/api/facebook/client.py b/pyfacebook/api/facebook/client.py index 5a3697b..3092030 100644 --- a/pyfacebook/api/facebook/client.py +++ b/pyfacebook/api/facebook/client.py @@ -1,6 +1,7 @@ """ Client for facebook graph api """ + from pyfacebook.api.base_client import BaseApi from pyfacebook.api.facebook import resource as rs diff --git a/pyfacebook/api/facebook/resource/page.py b/pyfacebook/api/facebook/resource/page.py index 0fc963f..c206af7 100644 --- a/pyfacebook/api/facebook/resource/page.py +++ b/pyfacebook/api/facebook/resource/page.py @@ -1,6 +1,7 @@ """ Apis for page. """ + from typing import Dict, Optional, Union import pyfacebook.utils.constant as const diff --git a/pyfacebook/api/facebook/resource/user.py b/pyfacebook/api/facebook/resource/user.py index 64a613e..194d726 100644 --- a/pyfacebook/api/facebook/resource/user.py +++ b/pyfacebook/api/facebook/resource/user.py @@ -1,6 +1,7 @@ """ Apis for User. """ + from typing import Dict, Optional, Union import pyfacebook.utils.constant as const diff --git a/pyfacebook/api/graph.py b/pyfacebook/api/graph.py index c984788..508706e 100644 --- a/pyfacebook/api/graph.py +++ b/pyfacebook/api/graph.py @@ -1,6 +1,7 @@ """ This module contains the GraphAPI class, its subclass BasicDisplayAPI and the class ServerSentEventAPI. """ + import hashlib import hmac import logging diff --git a/pyfacebook/api/instagram_basic/resource/user.py b/pyfacebook/api/instagram_basic/resource/user.py index 7db4ed1..408af6c 100644 --- a/pyfacebook/api/instagram_basic/resource/user.py +++ b/pyfacebook/api/instagram_basic/resource/user.py @@ -1,6 +1,7 @@ """ Apis for basic user """ + from typing import Optional, Union import pyfacebook.utils.constant as const diff --git a/pyfacebook/api/instagram_business/resource/comment.py b/pyfacebook/api/instagram_business/resource/comment.py index 0fecd9e..a427b9f 100644 --- a/pyfacebook/api/instagram_business/resource/comment.py +++ b/pyfacebook/api/instagram_business/resource/comment.py @@ -1,6 +1,7 @@ """ Apis for comment. """ + from typing import Dict, Optional, Union import pyfacebook.utils.constant as const diff --git a/pyfacebook/api/instagram_business/resource/media.py b/pyfacebook/api/instagram_business/resource/media.py index d101a1d..c8247c5 100644 --- a/pyfacebook/api/instagram_business/resource/media.py +++ b/pyfacebook/api/instagram_business/resource/media.py @@ -1,6 +1,7 @@ """ Apis for media. """ + from typing import Dict, Optional, Union import pyfacebook.utils.constant as const diff --git a/pyfacebook/models/album.py b/pyfacebook/models/album.py index 66b2cc6..baf185a 100644 --- a/pyfacebook/models/album.py +++ b/pyfacebook/models/album.py @@ -24,25 +24,19 @@ class Album(BaseModel): backdated_time_granularity: Optional[str] = field() can_upload: Optional[bool] = field() count: Optional[int] = field() - cover_photo: Optional[ - dict - ] = ( + cover_photo: Optional[dict] = ( field() ) # TODO Refer: https://developers.facebook.com/docs/graph-api/reference/photo/ created_time: Optional[str] = field() description: Optional[str] = field() - event: Optional[ - dict - ] = ( + event: Optional[dict] = ( field() ) # TODO Refer: https://developers.facebook.com/docs/graph-api/reference/event/ _from: Optional[dict] = field(metadata=config(field_name="from")) link: Optional[str] = field() location: Optional[str] = field() name: Optional[str] = field(repr=True) - place: Optional[ - dict - ] = ( + place: Optional[dict] = ( field() ) # TODO Refer: https://developers.facebook.com/docs/graph-api/reference/place/ privacy: Optional[str] = field() diff --git a/pyfacebook/models/application.py b/pyfacebook/models/application.py index 0d790f2..d9b4e38 100644 --- a/pyfacebook/models/application.py +++ b/pyfacebook/models/application.py @@ -68,9 +68,7 @@ class Application(BaseModel): auth_referral_friend_perms: Optional[List[str]] = field() auth_referral_response_type: Optional[str] = field() auth_referral_user_perms: Optional[List[str]] = field() - business: Optional[ - dict - ] = ( + business: Optional[dict] = ( field() ) # TODO Refer: https://developers.facebook.com/docs/marketing-api/reference/business/ canvas_fluid_height: Optional[bool] = field() diff --git a/pyfacebook/models/base.py b/pyfacebook/models/base.py index 6375ba8..f8d2870 100644 --- a/pyfacebook/models/base.py +++ b/pyfacebook/models/base.py @@ -1,6 +1,7 @@ """ Base model """ + from copy import deepcopy from dataclasses import dataclass, field as base_field from typing import ( diff --git a/pyfacebook/models/comment.py b/pyfacebook/models/comment.py index 7001fb8..9b0ed07 100644 --- a/pyfacebook/models/comment.py +++ b/pyfacebook/models/comment.py @@ -49,9 +49,7 @@ class Comment(BaseModel): object: Optional[dict] = field() # TODO parent: Optional["Comment"] = field() permalink_url: Optional[str] = field() - private_reply_conversation: Optional[ - dict - ] = ( + private_reply_conversation: Optional[dict] = ( field() ) # TODO Refer: https://developers.facebook.com/docs/graph-api/reference/conversation user_likes: Optional[bool] = field() diff --git a/pyfacebook/models/conversation.py b/pyfacebook/models/conversation.py index 6696b5d..7da82d0 100644 --- a/pyfacebook/models/conversation.py +++ b/pyfacebook/models/conversation.py @@ -3,6 +3,7 @@ Refer: https://developers.facebook.com/docs/graph-api/reference/conversation """ + from dataclasses import dataclass from typing import List, Optional diff --git a/pyfacebook/models/event.py b/pyfacebook/models/event.py index cbba19c..2ebd196 100644 --- a/pyfacebook/models/event.py +++ b/pyfacebook/models/event.py @@ -52,14 +52,10 @@ class Event(BaseModel): online_event_format: Optional[str] = field() online_event_third_party_url: Optional[str] = field() owner: Optional[dict] = field() - parent_group: Optional[ - dict - ] = ( + parent_group: Optional[dict] = ( field() ) # TODO Refer: https://developers.facebook.com/docs/graph-api/reference/group/ - place: Optional[ - dict - ] = ( + place: Optional[dict] = ( field() ) # TODO Refer: https://developers.facebook.com/docs/graph-api/reference/place/ scheduled_publish_time: Optional[str] = field() diff --git a/pyfacebook/models/live_video.py b/pyfacebook/models/live_video.py index 0e5611b..280b653 100644 --- a/pyfacebook/models/live_video.py +++ b/pyfacebook/models/live_video.py @@ -3,6 +3,7 @@ Refer: https://developers.facebook.com/docs/graph-api/reference/live-video/ """ + from dataclasses import dataclass from typing import List, Optional @@ -24,9 +25,7 @@ class VideoCopyrightRule(BaseModel): condition_groups: Optional[List[dict]] = field() # TODO copyrights: Optional[List[str]] = field() created_date: Optional[str] = field() - creator: Optional[ - dict - ] = ( + creator: Optional[dict] = ( field() ) # TODO Refer: https://developers.facebook.com/docs/graph-api/reference/user/ is_in_migration: Optional[bool] = field() @@ -44,9 +43,7 @@ class VideoCopyright(BaseModel): id: Optional[str] = field(repr=True, compare=True) content_category: Optional[str] = field() copyright_content_id: Optional[str] = field() - creator: Optional[ - dict - ] = ( + creator: Optional[dict] = ( field() ) # TODO Refer: https://developers.facebook.com/docs/graph-api/reference/user/ excluded_ownership_segments: Optional[List[dict]] = field() # TODO @@ -134,9 +131,7 @@ class LiveVideo(BaseModel): stream_url: Optional[str] = field() targeting: Optional[dict] = field() # TODO title: Optional[str] = field(repr=True) - video: Optional[ - dict - ] = ( + video: Optional[dict] = ( field() ) # TODO Refer: https://developers.facebook.com/docs/graph-api/reference/video/ diff --git a/pyfacebook/models/page.py b/pyfacebook/models/page.py index d7d8962..65df807 100644 --- a/pyfacebook/models/page.py +++ b/pyfacebook/models/page.py @@ -3,6 +3,7 @@ Refer: https://developers.facebook.com/docs/graph-api/reference/page """ + from dataclasses import dataclass from typing import List, Optional diff --git a/pyfacebook/models/photo.py b/pyfacebook/models/photo.py index d9f7fb1..e0861a6 100644 --- a/pyfacebook/models/photo.py +++ b/pyfacebook/models/photo.py @@ -34,9 +34,7 @@ class Photo(BaseModel): """ id: Optional[str] = field(repr=True, compare=True) - album: Optional[ - dict - ] = ( + album: Optional[dict] = ( field() ) # TODO Refer: https://developers.facebook.com/docs/graph-api/reference/album/ alt_text: Optional[str] = field() @@ -47,9 +45,7 @@ class Photo(BaseModel): can_delete: Optional[bool] = field() can_tag: Optional[bool] = field() created_time: Optional[str] = field() - event: Optional[ - dict - ] = ( + event: Optional[dict] = ( field() ) # TODO Refer: https://developers.facebook.com/docs/graph-api/reference/event/ _from: Optional[dict] = field(metadata=config(field_name="from")) @@ -60,14 +56,10 @@ class Photo(BaseModel): name: Optional[str] = field(repr=True) name_tags: Optional[List[EntityAtTextRange]] = field() page_story_id: Optional[str] = field() - place: Optional[ - dict - ] = ( + place: Optional[dict] = ( field() ) # TODO Refer: https://developers.facebook.com/docs/graph-api/reference/place/ - target: Optional[ - dict - ] = ( + target: Optional[dict] = ( field() ) # TODO Refer: https://developers.facebook.com/docs/graph-api/reference/profile/ updated_time: Optional[str] = field() diff --git a/pyfacebook/models/post.py b/pyfacebook/models/post.py index c25bdde..39297ae 100644 --- a/pyfacebook/models/post.py +++ b/pyfacebook/models/post.py @@ -57,9 +57,7 @@ class Post(BaseModel): actions: Optional[List[PostAction]] = field() admin_creator: Optional[dict] = field() # TODO allowed_advertising_objectives: Optional[List[str]] = field() - application: Optional[ - dict - ] = ( + application: Optional[dict] = ( field() ) # TODO https://developers.facebook.com/docs/graph-api/reference/application/ backdated_time: Optional[str] = field() @@ -69,9 +67,9 @@ class Post(BaseModel): comments_mirroring_domain: Optional[str] = field() coordinates: Optional[List[dict]] = field() # TODO created_time: Optional[str] = field() - event: Optional[ - dict - ] = field() # TODO https://developers.facebook.com/docs/graph-api/reference/event/ + event: Optional[dict] = ( + field() + ) # TODO https://developers.facebook.com/docs/graph-api/reference/event/ expanded_height: Optional[int] = field() expanded_width: Optional[int] = field() feed_targeting: Optional[dict] = field() # TODO diff --git a/pyfacebook/models/video.py b/pyfacebook/models/video.py index 835346a..8c19b78 100644 --- a/pyfacebook/models/video.py +++ b/pyfacebook/models/video.py @@ -57,9 +57,7 @@ class Video(BaseModel): description: Optional[str] = field(repr=True) embed_html: Optional[str] = field() embeddable: Optional[bool] = field() - event: Optional[ - dict - ] = ( + event: Optional[dict] = ( field() ) # TODO Refer: https://developers.facebook.com/docs/graph-api/reference/event/ format: Optional[List[VideoFormat]] = field() @@ -74,9 +72,7 @@ class Video(BaseModel): live_status: Optional[str] = field() music_video_copyright: Optional[dict] = field() # TODO permalink_url: Optional[str] = field() - place: Optional[ - dict - ] = ( + place: Optional[dict] = ( field() ) # TODO Refer: https://developers.facebook.com/docs/graph-api/reference/place/ post_views: Optional[int] = field() diff --git a/pyfacebook/ratelimit.py b/pyfacebook/ratelimit.py index 09c0150..f2598e7 100644 --- a/pyfacebook/ratelimit.py +++ b/pyfacebook/ratelimit.py @@ -37,13 +37,13 @@ class RateLimitHeader(object): cpu_time: int = 0 # IG basic display has returned this, but have no docs for this. acc_id_util_pct: int = 0 # only for X-Ad-Account-Usage type: Optional[str] = None # only for Business Use Case Rate Limits - estimated_time_to_regain_access: Optional[ - str - ] = None # only for Business Use Case Rate Limits + estimated_time_to_regain_access: Optional[str] = ( + None # only for Business Use Case Rate Limits + ) reset_time_duration: Optional[int] = None # only for X-Ad-Account-Usage - ads_api_access_tier: Optional[ - str - ] = None # for X-Ad-Account-Usage and Business Use Case Rate Limits + ads_api_access_tier: Optional[str] = ( + None # for X-Ad-Account-Usage and Business Use Case Rate Limits + ) def max_percent(self): return max( @@ -141,9 +141,9 @@ def set_limit(self, headers: CaseInsensitiveDict): if business_usage is not None: for business_id, items in business_usage.items(): for item in items: - self.resources["business"][business_id][ - item["type"] - ] = RateLimitHeader(**item) + self.resources["business"][business_id][item["type"]] = ( + RateLimitHeader(**item) + ) ad_account_usage = self.parse_headers(headers, "x-ad-account-usage") if ad_account_usage is not None: diff --git a/tests/facebook/test_photo.py b/tests/facebook/test_photo.py index 974595d..d18a2d4 100644 --- a/tests/facebook/test_photo.py +++ b/tests/facebook/test_photo.py @@ -1,6 +1,7 @@ """ Tests for photo api """ + import pytest import responses diff --git a/tests/test_basic_display_api.py b/tests/test_basic_display_api.py index 42f4abe..f889491 100644 --- a/tests/test_basic_display_api.py +++ b/tests/test_basic_display_api.py @@ -1,6 +1,7 @@ """ tests for ig basic display api """ + import pytest import responses diff --git a/tests/test_graph.py b/tests/test_graph.py index e3c0e97..1335525 100644 --- a/tests/test_graph.py +++ b/tests/test_graph.py @@ -1,6 +1,7 @@ """ tests for base graph api. """ + import pytest import requests import responses diff --git a/tests/test_server_sent_events.py b/tests/test_server_sent_events.py index 52141dd..f37787c 100644 --- a/tests/test_server_sent_events.py +++ b/tests/test_server_sent_events.py @@ -1,6 +1,7 @@ """ Tests for ServerSentEventAPI """ + import random from unittest.mock import patch