17 | {% endblock %}
18 |
--------------------------------------------------------------------------------
/facebook/version.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # Copyright 2015 Mobolic
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License. You may obtain
7 | # a copy of the License at
8 | #
9 | # https://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 | # License for the specific language governing permissions and limitations
15 | # under the License.
16 |
17 | __version__ = "3.0.0-alpha"
18 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | =======================
2 | Facebook SDK for Python
3 | =======================
4 |
5 | .. toctree::
6 | :maxdepth: 2
7 |
8 | install
9 | integration
10 | support
11 | api
12 | changes
13 |
14 | This client library is designed to support the `Facebook Graph API`_ and the
15 | official `Facebook JavaScript SDK`_, which is the canonical way to implement
16 | Facebook authentication. You can read more about the Graph API by accessing its
17 | `official documentation`_.
18 |
19 | .. _Facebook Graph API: https://developers.facebook.com/docs/reference/api/
20 | .. _Facebook JavaScript SDK: https://developers.facebook.com/docs/reference/javascript/
21 | .. _official documentation: https://developers.facebook.com/docs/reference/api/
22 |
--------------------------------------------------------------------------------
/docs/support.rst:
--------------------------------------------------------------------------------
1 | =====================
2 | Support & Development
3 | =====================
4 |
5 | Mailing List
6 | ============
7 |
8 | Questions about the SDK should be sent to its `Google Group`_.
9 |
10 | .. _Google Group: https://groups.google.com/group/pythonforfacebook
11 |
12 | Reporting Bugs
13 | ==============
14 |
15 | Bugs with the SDK should be reported on the `issue tracker at Github`_. Bugs
16 | with Facebook's Graph API should be reported on `Facebook's bugtracker`_.
17 |
18 | .. _issue tracker at Github: https://github.com/mobolic/facebook-sdk/issues
19 | .. _Facebook's bugtracker: https://developers.facebook.com/x/bugs/
20 |
21 | Security Issues
22 | ---------------
23 |
24 | Security issues with the SDK that would adversely affect users if reported
25 | publicly should be sent through private email to the project maintainer at
26 | martey @ marteydodoo.com (GPG key ID is 0x2cd700988f74c455).
27 |
--------------------------------------------------------------------------------
/CONTRIBUTING.rst:
--------------------------------------------------------------------------------
1 | Contributing
2 | ============
3 |
4 | Use Github Pull Requests
5 | ------------------------
6 |
7 | All potential code changes should be submitted as pull requests on Github. A
8 | pull request should only include commits directly applicable to its change
9 | (e.g. a pull request that adds a new feature should not include PEP8 changes in
10 | an unrelated area of the code).
11 |
12 | Code Style
13 | ----------
14 |
15 | Code *must* be compliant with `PEP 8`_. Use the latest version of `pep8`_ or
16 | `flake8`_ to catch issues.
17 |
18 | Git commit messages should include `a summary and proper line wrapping`_.
19 |
20 | .. _PEP 8: https://www.python.org/dev/peps/pep-0008/
21 | .. _pep8: https://pypi.python.org/pypi/pep8
22 | .. _flake8: https://pypi.python.org/pypi/flake8
23 | .. _a summary and proper line wrapping: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
24 |
25 | Update Tests and Documentation
26 | ------------------------------
27 |
28 | All non-trivial changes should include full test coverage. Please review
29 | the package's documentation to ensure that it is up to date with any changes.
30 |
--------------------------------------------------------------------------------
/docs/install.rst:
--------------------------------------------------------------------------------
1 | ============
2 | Installation
3 | ============
4 |
5 | The SDK currently supports Python 2.7, 3.3, 3.4, and 3.5. The `requests`_
6 | package is required.
7 |
8 | We recommend using `pip`_ and `virtualenv`_ to install the SDK. Please note
9 | that the SDK's Python package is called **facebook-sdk**: ::
10 |
11 | Installing from Git
12 | ===================
13 |
14 | For the newest features, you should install the SDK directly from Git.
15 |
16 | .. code-block:: shell
17 |
18 | virtualenv facebookenv
19 | source facebookenv/bin/activate
20 | pip install -e git+https://github.com/mobolic/facebook-sdk.git#egg=facebook-sdk
21 |
22 | Installing a Released Version
23 | =============================
24 |
25 | If your application requires maximum stability, you will want to use a version
26 | of the SDK that has been officially released.
27 |
28 | .. code-block:: shell
29 |
30 | virtualenv facebookenv
31 | source facebookenv/bin/activate
32 | pip install facebook-sdk
33 |
34 | .. _requests: https://pypi.python.org/pypi/requests
35 | .. _pip: https://pip.pypa.io/
36 | .. _virtualenv: https://virtualenv.pypa.io/
37 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from setuptools import setup
3 |
4 | exec(open("facebook/version.py").read())
5 |
6 | setup(
7 | name='facebook-sdk',
8 | version=__version__,
9 | description='This client library is designed to support the Facebook '
10 | 'Graph API and the official Facebook JavaScript SDK, which '
11 | 'is the canonical way to implement Facebook authentication.',
12 | author='Facebook',
13 | maintainer='Martey Dodoo',
14 | maintainer_email='martey+facebook-sdk@mobolic.com',
15 | url='https://github.com/mobolic/facebook-sdk',
16 | license='Apache',
17 | packages=["facebook"],
18 | long_description=open("README.rst").read(),
19 | classifiers=[
20 | 'License :: OSI Approved :: Apache Software License',
21 | 'Programming Language :: Python',
22 | 'Programming Language :: Python :: 2.7',
23 | 'Programming Language :: Python :: 3',
24 | 'Programming Language :: Python :: 3.3',
25 | 'Programming Language :: Python :: 3.4',
26 | 'Programming Language :: Python :: 3.5'
27 | ],
28 | install_requires=[
29 | 'requests',
30 | ],
31 | )
32 |
--------------------------------------------------------------------------------
/examples/tornado/schema.sql:
--------------------------------------------------------------------------------
1 | -- Copyright 2010 Facebook
2 | --
3 | -- Licensed under the Apache License, Version 2.0 (the "License"); you may
4 | -- not use this file except in compliance with the License. You may obtain
5 | -- a copy of the License at
6 | --
7 | -- https://www.apache.org/licenses/LICENSE-2.0
8 | --
9 | -- Unless required by applicable law or agreed to in writing, software
10 | -- distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 | -- WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 | -- License for the specific language governing permissions and limitations
13 | -- under the License.
14 |
15 | -- To create the database:
16 | -- CREATE DATABASE example;
17 | -- GRANT ALL PRIVILEGES ON example.* TO 'example'@'localhost' IDENTIFIED BY 'example';
18 | --
19 | -- To reload the tables:
20 | -- mysql --user=example --password=example --database=example < schema.sql
21 |
22 | SET SESSION storage_engine = "InnoDB";
23 | SET SESSION time_zone = "+0:00";
24 | ALTER DATABASE CHARACTER SET "utf8";
25 |
26 | DROP TABLE IF EXISTS users;
27 | CREATE TABLE users (
28 | id VARCHAR(25) NOT NULL PRIMARY KEY,
29 | name VARCHAR(256) NOT NULL,
30 | profile_url VARCHAR(512) NOT NULL,
31 | access_token VARCHAR(512) NOT NULL,
32 | updated TIMESTAMP NOT NULL
33 | );
34 |
--------------------------------------------------------------------------------
/examples/tornado/example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Facebook Example
6 |
7 |
8 |
9 |
10 | {% if current_user %}
11 |
12 |
Hello, {{ escape(current_user.name) }}
13 | {% end %}
14 |
15 |
16 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | ===================
2 | Facebook Python SDK
3 | ===================
4 |
5 | This client library is designed to support the `Facebook Graph API`_ and the
6 | official `Facebook JavaScript SDK`_, which is the canonical way to implement
7 | Facebook authentication. You can read more about the Graph API by accessing its
8 | `official documentation`_.
9 |
10 | .. _Facebook Graph API: https://developers.facebook.com/docs/reference/api/
11 | .. _Facebook JavaScript SDK: https://developers.facebook.com/docs/reference/javascript/
12 | .. _official documentation: https://developers.facebook.com/docs/reference/api/
13 |
14 | Licensing
15 | =========
16 |
17 | This library uses the `Apache License, version 2.0`_. Please see the library's
18 | individual files for more information.
19 |
20 | .. _Apache License, version 2.0: https://www.apache.org/licenses/LICENSE-2.0
21 |
22 | Reporting Issues
23 | ================
24 |
25 | If you have bugs or other issues specifically pertaining to this library, file
26 | them `here`_. Bugs with the Graph API should be filed on `Facebook's
27 | bugtracker`_.
28 |
29 | .. _here: https://github.com/mobolic/facebook-sdk/issues
30 | .. _Facebook's bugtracker: https://developers.facebook.com/bugs/
31 |
32 |
33 | Support & Discussion
34 | ====================
35 |
36 | Documentation is available at https://facebook-sdk.readthedocs.org/en/latest/.
37 |
38 | Have a question? Need help? Visit the library's `Google Group`_.
39 |
40 | .. _Google Group: https://groups.google.com/group/pythonforfacebook
41 |
--------------------------------------------------------------------------------
/examples/get_posts.py:
--------------------------------------------------------------------------------
1 | """
2 | A simple example script to get all posts on a user's timeline.
3 | Originally created by Mitchell Stewart.
4 |
5 | """
6 | import facebook
7 | import requests
8 |
9 |
10 | def some_action(post):
11 | """ Here you might want to do something with each post. E.g. grab the
12 | post's message (post['message']) or the post's picture (post['picture']).
13 | In this implementation we just print the post's created time.
14 | """
15 | print(post['created_time'])
16 |
17 |
18 | # You'll need an access token here to do anything. You can get a temporary one
19 | # here: https://developers.facebook.com/tools/explorer/
20 | access_token = ''
21 | # Look at Bill Gates's profile for this example by using his Facebook id.
22 | user = 'BillGates'
23 |
24 | graph = facebook.GraphAPI(access_token)
25 | profile = graph.get_object(user)
26 | posts = graph.get_connections(profile['id'], 'posts')
27 |
28 | # Wrap this block in a while loop so we can keep paginating requests until
29 | # finished.
30 | while True:
31 | try:
32 | # Perform some action on each post in the collection we receive from
33 | # Facebook.
34 | [some_action(post=post) for post in posts['data']]
35 | # Attempt to make a request to the next page of data, if it exists.
36 | posts = requests.get(posts['paging']['next']).json()
37 | except KeyError:
38 | # When there are no more pages (['paging']['next']), break from the
39 | # loop and end the script.
40 | break
41 |
--------------------------------------------------------------------------------
/docs/changes.rst:
--------------------------------------------------------------------------------
1 | =========
2 | Changelog
3 | =========
4 |
5 | Version 3.0.0 (unreleased)
6 | ========================
7 | - Remove support for Python 2.6.
8 | - Add support for Graph API version 2.8.
9 | - Remove support for Graph API version 2.1.
10 | - Change default Graph API version to 2.2.
11 | - Add support for requests' sessions.
12 |
13 | Version 2.0.0 (2016-08-08)
14 | ==============================
15 | - Add support for Graph API versions 2.6 and 2.7.
16 | - Remove support for Graph API version 2.0 and FQL.
17 | - Change default Graph API version to 2.1.
18 | - Fix bug with debug_access_token method not working when the
19 | GraphAPI object's access token was set (#276).
20 | - Allow offline generation of application access tokens.
21 |
22 | Version 1.0.0 (2016-04-01)
23 | ==========================
24 |
25 | - Python 3 support.
26 | - More comprehensive test coverage.
27 | - Full Unicode support.
28 | - Better exception handling.
29 | - Vastly improved documentation.
30 |
31 | Version 0.4.0 (2012-10-15)
32 | ==========================
33 |
34 | - Add support for deleting application requests.
35 | - Fix minor documentation error in README.
36 | - Verify signed request parsing succeeded when creating OAuth token.
37 | - Convert README to ReStructuredText.
38 |
39 | Version 0.3.2 (2012-07-28)
40 | ==========================
41 |
42 | - Add support for state parameters in auth dialog URLs.
43 | - Fixes bug with Unicode app secrets.
44 | - Add optional timeout support for faster API requests.
45 | - Random PEP8 compliance fixes.
46 |
47 | Version 0.3.1 (2012-05-16)
48 | ==========================
49 |
50 | - Minor documentation updates.
51 | - Removes the develop branch in favor of named feature branches.
52 |
--------------------------------------------------------------------------------
/examples/flask/app/templates/base.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | {{ app_name }}
14 |
15 |
16 |
17 |
18 |
25 |
26 |
27 |
50 |
51 |
52 | {% block content %} {% endblock %}
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/examples/flask/app/views.py:
--------------------------------------------------------------------------------
1 | from facebook import get_user_from_cookie, GraphAPI
2 | from flask import g, render_template, redirect, request, session, url_for
3 |
4 | from app import app, db
5 | from models import User
6 |
7 | # Facebook app details
8 | FB_APP_ID = ''
9 | FB_APP_NAME = ''
10 | FB_APP_SECRET = ''
11 |
12 |
13 | @app.route('/')
14 | def index():
15 | # If a user was set in the get_current_user function before the request,
16 | # the user is logged in.
17 | if g.user:
18 | return render_template('index.html', app_id=FB_APP_ID,
19 | app_name=FB_APP_NAME, user=g.user)
20 | # Otherwise, a user is not logged in.
21 | return render_template('login.html', app_id=FB_APP_ID, name=FB_APP_NAME)
22 |
23 |
24 | @app.route('/logout')
25 | def logout():
26 | """Log out the user from the application.
27 |
28 | Log out the user from the application by removing them from the
29 | session. Note: this does not log the user out of Facebook - this is done
30 | by the JavaScript SDK.
31 | """
32 | session.pop('user', None)
33 | return redirect(url_for('index'))
34 |
35 |
36 | @app.before_request
37 | def get_current_user():
38 | """Set g.user to the currently logged in user.
39 |
40 | Called before each request, get_current_user sets the global g.user
41 | variable to the currently logged in user. A currently logged in user is
42 | determined by seeing if it exists in Flask's session dictionary.
43 |
44 | If it is the first time the user is logging into this application it will
45 | create the user and insert it into the database. If the user is not logged
46 | in, None will be set to g.user.
47 | """
48 |
49 | # Set the user in the session dictionary as a global g.user and bail out
50 | # of this function early.
51 | if session.get('user'):
52 | g.user = session.get('user')
53 | return
54 |
55 | # Attempt to get the short term access token for the current user.
56 | result = get_user_from_cookie(cookies=request.cookies, app_id=FB_APP_ID,
57 | app_secret=FB_APP_SECRET)
58 |
59 | # If there is no result, we assume the user is not logged in.
60 | if result:
61 | # Check to see if this user is already in our database.
62 | user = User.query.filter(User.id == result['uid']).first()
63 |
64 | if not user:
65 | # Not an existing user so get info
66 | graph = GraphAPI(result['access_token'])
67 | profile = graph.get_object('me')
68 | if 'link' not in profile:
69 | profile['link'] = ""
70 |
71 | # Create the user and insert it into the database
72 | user = User(id=str(profile['id']), name=profile['name'],
73 | profile_url=profile['link'],
74 | access_token=result['access_token'])
75 | db.session.add(user)
76 | elif user.access_token != result['access_token']:
77 | # If an existing user, update the access token
78 | user.access_token = result['access_token']
79 |
80 | # Add the user to the current session
81 | session['user'] = dict(name=user.name, profile_url=user.profile_url,
82 | id=user.id, access_token=user.access_token)
83 |
84 | # Commit changes to the database and set the user as a global g.user
85 | db.session.commit()
86 | g.user = session.get('user', None)
87 |
--------------------------------------------------------------------------------
/examples/tornado/example.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # Copyright 2010 Facebook
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License. You may obtain
7 | # a copy of the License at
8 | #
9 | # https://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 | # License for the specific language governing permissions and limitations
15 | # under the License.
16 |
17 | """A barebones Tornado application that uses Facebook for login.
18 |
19 | Assumes a database with a schema as specified in schema.sql. We store a
20 | local copy of basic user data so we don't need to make a round-trip to
21 | the Facebook API on every request once a user has logged in.
22 | """
23 |
24 | import facebook
25 | import tornado.database
26 | import tornado.httpserver
27 | import tornado.options
28 | import tornado.web
29 |
30 | from tornado.options import define, options
31 |
32 | define("port", default=8888, help="run on the given port", type=int)
33 | define("facebook_app_id", help="Facebook Application ID")
34 | define("facebook_app_secret", help="Facebook Application Secret")
35 | define("mysql_host", help="MySQL database host")
36 | define("mysql_database", help="MySQL database database")
37 | define("mysql_user", help="MySQL database user")
38 | define("mysql_password", help="MySQL database password")
39 |
40 |
41 | class BaseHandler(tornado.web.RequestHandler):
42 | """Implements authentication via the Facebook JavaScript SDK cookie."""
43 | def get_current_user(self):
44 | cookies = dict((n, self.cookies[n].value) for n in self.cookies.keys())
45 | cookie = facebook.get_user_from_cookie(
46 | cookies, options.facebook_app_id, options.facebook_app_secret)
47 | if not cookie:
48 | return None
49 | user = self.db.get(
50 | "SELECT * FROM users WHERE id = %s", cookie["uid"])
51 | if not user:
52 | # TODO: Make this fetch async rather than blocking
53 | graph = facebook.GraphAPI(cookie["access_token"])
54 | profile = graph.get_object("me")
55 | self.db.execute(
56 | "REPLACE INTO users (id, name, profile_url, access_token) "
57 | "VALUES (%s,%s,%s,%s)", profile["id"], profile["name"],
58 | profile["link"], cookie["access_token"])
59 | user = self.db.get(
60 | "SELECT * FROM users WHERE id = %s", profile["id"])
61 | elif user.access_token != cookie["access_token"]:
62 | self.db.execute(
63 | "UPDATE users SET access_token = %s WHERE id = %s",
64 | cookie["access_token"], user.id)
65 | return user
66 |
67 | @property
68 | def db(self):
69 | if not hasattr(BaseHandler, "_db"):
70 | BaseHandler._db = tornado.database.Connection(
71 | host=options.mysql_host, database=options.mysql_database,
72 | user=options.mysql_user, password=options.mysql_password)
73 | return BaseHandler._db
74 |
75 |
76 | class MainHandler(BaseHandler):
77 | def get(self):
78 | self.render("example.html", options=options)
79 |
80 |
81 | def main():
82 | tornado.options.parse_command_line()
83 | http_server = tornado.httpserver.HTTPServer(tornado.web.Application([
84 | (r"/", MainHandler),
85 | ]))
86 | http_server.listen(options.port)
87 | tornado.ioloop.IOLoop.instance().start()
88 |
89 |
90 | if __name__ == "__main__":
91 | main()
92 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | REM Command file for Sphinx documentation
4 |
5 | if "%SPHINXBUILD%" == "" (
6 | set SPHINXBUILD=sphinx-build
7 | )
8 | set BUILDDIR=_build
9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
10 | set I18NSPHINXOPTS=%SPHINXOPTS% .
11 | if NOT "%PAPER%" == "" (
12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
14 | )
15 |
16 | if "%1" == "" goto help
17 |
18 | if "%1" == "help" (
19 | :help
20 | echo.Please use `make ^` where ^ is one of
21 | echo. html to make standalone HTML files
22 | echo. dirhtml to make HTML files named index.html in directories
23 | echo. singlehtml to make a single large HTML file
24 | echo. pickle to make pickle files
25 | echo. json to make JSON files
26 | echo. htmlhelp to make HTML files and a HTML help project
27 | echo. qthelp to make HTML files and a qthelp project
28 | echo. devhelp to make HTML files and a Devhelp project
29 | echo. epub to make an epub
30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
31 | echo. text to make text files
32 | echo. man to make manual pages
33 | echo. texinfo to make Texinfo files
34 | echo. gettext to make PO message catalogs
35 | echo. changes to make an overview over all changed/added/deprecated items
36 | echo. linkcheck to check all external links for integrity
37 | echo. doctest to run all doctests embedded in the documentation if enabled
38 | goto end
39 | )
40 |
41 | if "%1" == "clean" (
42 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
43 | del /q /s %BUILDDIR%\*
44 | goto end
45 | )
46 |
47 | if "%1" == "html" (
48 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
49 | if errorlevel 1 exit /b 1
50 | echo.
51 | echo.Build finished. The HTML pages are in %BUILDDIR%/html.
52 | goto end
53 | )
54 |
55 | if "%1" == "dirhtml" (
56 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
57 | if errorlevel 1 exit /b 1
58 | echo.
59 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
60 | goto end
61 | )
62 |
63 | if "%1" == "singlehtml" (
64 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
65 | if errorlevel 1 exit /b 1
66 | echo.
67 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
68 | goto end
69 | )
70 |
71 | if "%1" == "pickle" (
72 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
73 | if errorlevel 1 exit /b 1
74 | echo.
75 | echo.Build finished; now you can process the pickle files.
76 | goto end
77 | )
78 |
79 | if "%1" == "json" (
80 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
81 | if errorlevel 1 exit /b 1
82 | echo.
83 | echo.Build finished; now you can process the JSON files.
84 | goto end
85 | )
86 |
87 | if "%1" == "htmlhelp" (
88 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
89 | if errorlevel 1 exit /b 1
90 | echo.
91 | echo.Build finished; now you can run HTML Help Workshop with the ^
92 | .hhp project file in %BUILDDIR%/htmlhelp.
93 | goto end
94 | )
95 |
96 | if "%1" == "qthelp" (
97 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
98 | if errorlevel 1 exit /b 1
99 | echo.
100 | echo.Build finished; now you can run "qcollectiongenerator" with the ^
101 | .qhcp project file in %BUILDDIR%/qthelp, like this:
102 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\FacebookSDKforPython.qhcp
103 | echo.To view the help file:
104 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\FacebookSDKforPython.ghc
105 | goto end
106 | )
107 |
108 | if "%1" == "devhelp" (
109 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
110 | if errorlevel 1 exit /b 1
111 | echo.
112 | echo.Build finished.
113 | goto end
114 | )
115 |
116 | if "%1" == "epub" (
117 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
118 | if errorlevel 1 exit /b 1
119 | echo.
120 | echo.Build finished. The epub file is in %BUILDDIR%/epub.
121 | goto end
122 | )
123 |
124 | if "%1" == "latex" (
125 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
126 | if errorlevel 1 exit /b 1
127 | echo.
128 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
129 | goto end
130 | )
131 |
132 | if "%1" == "text" (
133 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
134 | if errorlevel 1 exit /b 1
135 | echo.
136 | echo.Build finished. The text files are in %BUILDDIR%/text.
137 | goto end
138 | )
139 |
140 | if "%1" == "man" (
141 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
142 | if errorlevel 1 exit /b 1
143 | echo.
144 | echo.Build finished. The manual pages are in %BUILDDIR%/man.
145 | goto end
146 | )
147 |
148 | if "%1" == "texinfo" (
149 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
150 | if errorlevel 1 exit /b 1
151 | echo.
152 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
153 | goto end
154 | )
155 |
156 | if "%1" == "gettext" (
157 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
158 | if errorlevel 1 exit /b 1
159 | echo.
160 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
161 | goto end
162 | )
163 |
164 | if "%1" == "changes" (
165 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
166 | if errorlevel 1 exit /b 1
167 | echo.
168 | echo.The overview file is in %BUILDDIR%/changes.
169 | goto end
170 | )
171 |
172 | if "%1" == "linkcheck" (
173 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
174 | if errorlevel 1 exit /b 1
175 | echo.
176 | echo.Link check complete; look for any errors in the above output ^
177 | or in %BUILDDIR%/linkcheck/output.txt.
178 | goto end
179 | )
180 |
181 | if "%1" == "doctest" (
182 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
183 | if errorlevel 1 exit /b 1
184 | echo.
185 | echo.Testing of doctests in the sources finished, look at the ^
186 | results in %BUILDDIR%/doctest/output.txt.
187 | goto end
188 | )
189 |
190 | :end
191 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | PAPER =
8 | BUILDDIR = _build
9 |
10 | # Internal variables.
11 | PAPEROPT_a4 = -D latex_paper_size=a4
12 | PAPEROPT_letter = -D latex_paper_size=letter
13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
14 | # the i18n builder cannot share the environment and doctrees with the others
15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
16 |
17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
18 |
19 | help:
20 | @echo "Please use \`make ' where is one of"
21 | @echo " html to make standalone HTML files"
22 | @echo " dirhtml to make HTML files named index.html in directories"
23 | @echo " singlehtml to make a single large HTML file"
24 | @echo " pickle to make pickle files"
25 | @echo " json to make JSON files"
26 | @echo " htmlhelp to make HTML files and a HTML help project"
27 | @echo " qthelp to make HTML files and a qthelp project"
28 | @echo " devhelp to make HTML files and a Devhelp project"
29 | @echo " epub to make an epub"
30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
31 | @echo " latexpdf to make LaTeX files and run them through pdflatex"
32 | @echo " text to make text files"
33 | @echo " man to make manual pages"
34 | @echo " texinfo to make Texinfo files"
35 | @echo " info to make Texinfo files and run them through makeinfo"
36 | @echo " gettext to make PO message catalogs"
37 | @echo " changes to make an overview of all changed/added/deprecated items"
38 | @echo " linkcheck to check all external links for integrity"
39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)"
40 |
41 | clean:
42 | -rm -rf $(BUILDDIR)/*
43 |
44 | html:
45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
46 | @echo
47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
48 |
49 | dirhtml:
50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
51 | @echo
52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
53 |
54 | singlehtml:
55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
56 | @echo
57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
58 |
59 | pickle:
60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
61 | @echo
62 | @echo "Build finished; now you can process the pickle files."
63 |
64 | json:
65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
66 | @echo
67 | @echo "Build finished; now you can process the JSON files."
68 |
69 | htmlhelp:
70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
71 | @echo
72 | @echo "Build finished; now you can run HTML Help Workshop with the" \
73 | ".hhp project file in $(BUILDDIR)/htmlhelp."
74 |
75 | qthelp:
76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
77 | @echo
78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \
79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/FacebookSDKforPython.qhcp"
81 | @echo "To view the help file:"
82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/FacebookSDKforPython.qhc"
83 |
84 | devhelp:
85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
86 | @echo
87 | @echo "Build finished."
88 | @echo "To view the help file:"
89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/FacebookSDKforPython"
90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/FacebookSDKforPython"
91 | @echo "# devhelp"
92 |
93 | epub:
94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
95 | @echo
96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
97 |
98 | latex:
99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
100 | @echo
101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \
103 | "(use \`make latexpdf' here to do that automatically)."
104 |
105 | latexpdf:
106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
107 | @echo "Running LaTeX files through pdflatex..."
108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf
109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
110 |
111 | text:
112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
113 | @echo
114 | @echo "Build finished. The text files are in $(BUILDDIR)/text."
115 |
116 | man:
117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
118 | @echo
119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
120 |
121 | texinfo:
122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
123 | @echo
124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
125 | @echo "Run \`make' in that directory to run these through makeinfo" \
126 | "(use \`make info' here to do that automatically)."
127 |
128 | info:
129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
130 | @echo "Running Texinfo files through makeinfo..."
131 | make -C $(BUILDDIR)/texinfo info
132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
133 |
134 | gettext:
135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
136 | @echo
137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
138 |
139 | changes:
140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
141 | @echo
142 | @echo "The overview file is in $(BUILDDIR)/changes."
143 |
144 | linkcheck:
145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
146 | @echo
147 | @echo "Link check complete; look for any errors in the above output " \
148 | "or in $(BUILDDIR)/linkcheck/output.txt."
149 |
150 | doctest:
151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
152 | @echo "Testing of doctests in the sources finished, look at the " \
153 | "results in $(BUILDDIR)/doctest/output.txt."
154 |
--------------------------------------------------------------------------------
/test/test_facebook.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # Copyright 2015 Mobolic
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License. You may obtain
7 | # a copy of the License at
8 | #
9 | # https://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 | # License for the specific language governing permissions and limitations
15 | # under the License.
16 | import facebook
17 | import os
18 | import unittest
19 |
20 | try:
21 | from urllib.parse import parse_qs, urlencode, urlparse
22 | except ImportError:
23 | from urlparse import parse_qs, urlparse
24 | from urllib import urlencode
25 |
26 |
27 | class FacebookTestCase(unittest.TestCase):
28 | """Sets up application ID and secret from environment."""
29 | def setUp(self):
30 | try:
31 | self.app_id = os.environ["FACEBOOK_APP_ID"]
32 | self.secret = os.environ["FACEBOOK_SECRET"]
33 | except KeyError:
34 | raise Exception("FACEBOOK_APP_ID and FACEBOOK_SECRET "
35 | "must be set as environmental variables.")
36 |
37 | def assert_raises_multi_regex(
38 | self, expected_exception, expected_regexp, callable_obj=None,
39 | *args, **kwargs):
40 | """
41 | Custom function to backport assertRaisesRegexp to all supported
42 | versions of Python.
43 |
44 | """
45 | self.assertRaises(expected_exception, callable_obj, *args, **kwargs)
46 | try:
47 | callable_obj(*args)
48 | except facebook.GraphAPIError as error:
49 | self.assertEqual(error.message, expected_regexp)
50 |
51 |
52 | class TestGetAppAccessToken(FacebookTestCase):
53 | """
54 | Test if application access token is returned properly.
55 |
56 | Note that this only tests if the returned token is a string, not
57 | whether it is valid.
58 |
59 | """
60 | def test_get_app_access_token(self):
61 | token = facebook.GraphAPI().get_app_access_token(
62 | self.app_id, self.secret)
63 | # Since "unicode" does not exist in Python 3, we cannot check
64 | # the following line with flake8 (hence the noqa comment).
65 | assert(isinstance(token, str) or isinstance(token, unicode)) # noqa
66 |
67 | def test_get_offline_app_access_token(self):
68 | """Verify that offline generation of app access tokens works."""
69 | token = facebook.GraphAPI().get_app_access_token(
70 | self.app_id, self.secret, offline=True)
71 | self.assertEqual(token, "{0}|{1}".format(self.app_id, self.secret))
72 |
73 | def test_get_deleted_app_access_token(self):
74 | deleted_app_id = '174236045938435'
75 | deleted_secret = '0073dce2d95c4a5c2922d1827ea0cca6'
76 | deleted_error_message = (
77 | "Error validating application. Application has been deleted.")
78 |
79 | self.assert_raises_multi_regex(
80 | facebook.GraphAPIError,
81 | deleted_error_message,
82 | facebook.GraphAPI().get_app_access_token,
83 | deleted_app_id,
84 | deleted_secret)
85 |
86 |
87 | class TestAPIVersion(FacebookTestCase):
88 | """Test if using the correct version of Graph API."""
89 | def test_no_version(self):
90 | graph = facebook.GraphAPI()
91 | self.assertNotEqual(graph.version, None, "Version should not be None.")
92 | self.assertNotEqual(
93 | graph.version, "", "Version should not be an empty string.")
94 |
95 | def test_valid_versions(self):
96 | for version in facebook.VALID_API_VERSIONS:
97 | graph = facebook.GraphAPI(version=version)
98 | self.assertEqual(str(graph.get_version()), version)
99 |
100 | def test_invalid_version(self):
101 | self.assertRaises(facebook.GraphAPIError,
102 | facebook.GraphAPI, version=1.2)
103 |
104 | def test_invalid_format(self):
105 | self.assertRaises(facebook.GraphAPIError,
106 | facebook.GraphAPI, version="2.a")
107 | self.assertRaises(facebook.GraphAPIError,
108 | facebook.GraphAPI, version="a.1")
109 | self.assertRaises(facebook.GraphAPIError,
110 | facebook.GraphAPI, version=2.23)
111 | self.assertRaises(facebook.GraphAPIError,
112 | facebook.GraphAPI, version="2.23")
113 |
114 |
115 | class TestAuthURL(FacebookTestCase):
116 | def test_auth_url(self):
117 | perms = ['email', 'birthday']
118 | redirect_url = 'https://localhost/facebook/callback/'
119 |
120 | expected_url = facebook.FACEBOOK_OAUTH_DIALOG_URL + urlencode(
121 | dict(client_id=self.app_id,
122 | redirect_uri=redirect_url,
123 | scope=','.join(perms)))
124 | actual_url = facebook.auth_url(self.app_id, redirect_url, perms=perms)
125 |
126 | # Since the order of the query string parameters might be
127 | # different in each URL, we cannot just compare them to each
128 | # other.
129 | expected_url_result = urlparse(expected_url)
130 | actual_url_result = urlparse(actual_url)
131 | expected_query = parse_qs(expected_url_result.query)
132 | actual_query = parse_qs(actual_url_result.query)
133 |
134 | self.assertEqual(actual_url_result.scheme, expected_url_result.scheme)
135 | self.assertEqual(actual_url_result.netloc, expected_url_result.netloc)
136 | self.assertEqual(actual_url_result.path, expected_url_result.path)
137 | self.assertEqual(actual_url_result.params, expected_url_result.params)
138 | self.assertEqual(actual_query, expected_query)
139 |
140 |
141 | class TestAccessToken(FacebookTestCase):
142 | def test_extend_access_token(self):
143 | """
144 | Test if extend_access_token requests the correct endpoint.
145 |
146 | Note that this only tests whether extend_access_token returns the
147 | correct error message when called without a proper user-access token.
148 |
149 | """
150 | try:
151 | facebook.GraphAPI().extend_access_token(self.app_id, self.secret)
152 | except facebook.GraphAPIError as e:
153 | self.assertEqual(
154 | e.message, "fb_exchange_token parameter not specified")
155 |
156 | def test_bogus_access_token(self):
157 | graph = facebook.GraphAPI(access_token='wrong_token')
158 | self.assertRaises(facebook.GraphAPIError, graph.get_object, 'me')
159 |
160 | def test_access_with_expired_access_token(self):
161 | expired_token = (
162 | "AAABrFmeaJjgBAIshbq5ZBqZBICsmveZCZBi6O4w9HSTkFI73VMtmkL9jLuWs"
163 | "ZBZC9QMHvJFtSulZAqonZBRIByzGooCZC8DWr0t1M4BL9FARdQwPWPnIqCiFQ")
164 | graph = facebook.GraphAPI(access_token=expired_token)
165 | self.assertRaises(facebook.GraphAPIError, graph.get_object, 'me')
166 |
167 |
168 | class TestParseSignedRequest(FacebookTestCase):
169 | cookie = (
170 | "O0vd27uj4j6RxdIyMH-VhMwQpUkPgg_9665I9yGDecE."
171 | "eyJhbGdvcml0aG0iOiJITUFDLVNIQTI1NiIsImNvZGUiOiJBUURjSXQ2YnhZ"
172 | "M090T3BSRGtpT1k4UDNlOWgwYzZRNFFuMEFFQnVqR1M3ZEV5LXNtbUt5b3pD"
173 | "dHdhZy1kRmVYNmRUbi12dVBfQVNtek5RbjlkakloZHJIa0VBMHlLMm16T0Ji"
174 | "RS1memVoNUh0Vk5UbnpQUDV3Z2VmUkF1bjhvTkQ4S3I3aUd2a3A4Q2EzODJL"
175 | "NWtqcVl1Z19QV1NUREhqMlY3T2NWaE1GQ2wyWkN2MFk5NnlLUDhfSVAtbnNL"
176 | "b09kcFVLSU5LMks1SGgxUjZfMkdmMUs1OG5uSnd1bENuSVVRSlhSSU83VEd3"
177 | "WFJWOVlfa1hzS0pmREpUVzNnTWJ1UGNGc3p0Vkx3MHpyV04yQXE3YWVLVFI2"
178 | "MFNyeVgzMlBWZkhxNjlzYnUwcnJWLUZMZ2NvMUpBVWlYRlNaY2Q5cVF6WSIs"
179 | "Imlzc3VlZF9hdCI6MTQ0MTUxNTY1OCwidXNlcl9pZCI6IjEwMTAxNDk2NTUz"
180 | "NDg2NjExIn0")
181 |
182 | def test_parse_signed_request_when_erroneous(self):
183 | result = facebook.parse_signed_request(
184 | signed_request='corrupted.payload',
185 | app_secret=self.secret)
186 | self.assertFalse(result)
187 |
188 | def test_parse_signed_request_when_correct(self):
189 | result = facebook.parse_signed_request(
190 | signed_request=self.cookie,
191 | app_secret=self.secret)
192 |
193 | self.assertTrue(result)
194 | self.assertTrue('issued_at' in result)
195 | self.assertTrue('code' in result)
196 | self.assertTrue('user_id' in result)
197 | self.assertTrue('algorithm' in result)
198 |
199 |
200 | if __name__ == '__main__':
201 | unittest.main()
202 |
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # Facebook SDK for Python documentation build configuration file, created by
4 | # sphinx-quickstart on Mon Oct 15 01:01:28 2012.
5 | #
6 | # This file is execfile()d with the current directory set to its containing dir.
7 | #
8 | # Note that not all possible configuration values are present in this
9 | # autogenerated file.
10 | #
11 | # All configuration values have a default; values that are commented out
12 | # serve to show the default.
13 |
14 | import sys, os
15 |
16 | exec(open("../facebook/version.py").read())
17 |
18 | # If extensions (or modules to document with autodoc) are in another directory,
19 | # add these directories to sys.path here. If the directory is relative to the
20 | # documentation root, use os.path.abspath to make it absolute, like shown here.
21 | #sys.path.insert(0, os.path.abspath('.'))
22 |
23 | # -- General configuration -----------------------------------------------------
24 |
25 | # If your documentation needs a minimal Sphinx version, state it here.
26 | #needs_sphinx = '1.0'
27 |
28 | # Add any Sphinx extension module names here, as strings. They can be extensions
29 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
30 | extensions = []
31 |
32 | # Add any paths that contain templates here, relative to this directory.
33 | templates_path = ['_templates']
34 |
35 | # The suffix of source filenames.
36 | source_suffix = '.rst'
37 |
38 | # The encoding of source files.
39 | #source_encoding = 'utf-8-sig'
40 |
41 | # The master toctree document.
42 | master_doc = 'index'
43 |
44 | # General information about the project.
45 | project = u'Facebook SDK for Python'
46 | copyright = u'2010 Facebook, 2015 Mobolic'
47 |
48 | # The version info for the project you're documenting, acts as replacement for
49 | # |version| and |release|, also used in various other places throughout the
50 | # built documents.
51 | #
52 | # The short X.Y version.
53 | version = '.'.join(__version__.split('.')[:2])
54 | # The full version, including alpha/beta/rc tags.
55 | release = __version__
56 |
57 | # The language for content autogenerated by Sphinx. Refer to documentation
58 | # for a list of supported languages.
59 | #language = None
60 |
61 | # There are two options for replacing |today|: either, you set today to some
62 | # non-false value, then it is used:
63 | #today = ''
64 | # Else, today_fmt is used as the format for a strftime call.
65 | #today_fmt = '%B %d, %Y'
66 |
67 | # List of patterns, relative to source directory, that match files and
68 | # directories to ignore when looking for source files.
69 | exclude_patterns = ['_build']
70 |
71 | # The reST default role (used for this markup: `text`) to use for all documents.
72 | #default_role = None
73 |
74 | # If true, '()' will be appended to :func: etc. cross-reference text.
75 | #add_function_parentheses = True
76 |
77 | # If true, the current module name will be prepended to all description
78 | # unit titles (such as .. function::).
79 | #add_module_names = True
80 |
81 | # If true, sectionauthor and moduleauthor directives will be shown in the
82 | # output. They are ignored by default.
83 | #show_authors = False
84 |
85 | # The name of the Pygments (syntax highlighting) style to use.
86 | pygments_style = 'sphinx'
87 |
88 | # A list of ignored prefixes for module index sorting.
89 | #modindex_common_prefix = []
90 |
91 |
92 | # -- Options for HTML output ---------------------------------------------------
93 |
94 | # The theme to use for HTML and HTML Help pages. See the documentation for
95 | # a list of builtin themes.
96 | html_theme = 'default'
97 |
98 | # Theme options are theme-specific and customize the look and feel of a theme
99 | # further. For a list of options available for each theme, see the
100 | # documentation.
101 | #html_theme_options = {}
102 |
103 | # Add any paths that contain custom themes here, relative to this directory.
104 | #html_theme_path = []
105 |
106 | # The name for this set of Sphinx documents. If None, it defaults to
107 | # " v documentation".
108 | #html_title = None
109 |
110 | # A shorter title for the navigation bar. Default is the same as html_title.
111 | #html_short_title = None
112 |
113 | # The name of an image file (relative to this directory) to place at the top
114 | # of the sidebar.
115 | #html_logo = None
116 |
117 | # The name of an image file (within the static path) to use as favicon of the
118 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
119 | # pixels large.
120 | #html_favicon = None
121 |
122 | # Add any paths that contain custom static files (such as style sheets) here,
123 | # relative to this directory. They are copied after the builtin static files,
124 | # so a file named "default.css" will overwrite the builtin "default.css".
125 | html_static_path = ['_static']
126 |
127 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
128 | # using the given strftime format.
129 | #html_last_updated_fmt = '%b %d, %Y'
130 |
131 | # If true, SmartyPants will be used to convert quotes and dashes to
132 | # typographically correct entities.
133 | #html_use_smartypants = True
134 |
135 | # Custom sidebar templates, maps document names to template names.
136 | #html_sidebars = {}
137 |
138 | # Additional templates that should be rendered to pages, maps page names to
139 | # template names.
140 | #html_additional_pages = {}
141 |
142 | # If false, no module index is generated.
143 | #html_domain_indices = True
144 |
145 | # If false, no index is generated.
146 | #html_use_index = True
147 |
148 | # If true, the index is split into individual pages for each letter.
149 | #html_split_index = False
150 |
151 | # If true, links to the reST sources are added to the pages.
152 | #html_show_sourcelink = True
153 |
154 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
155 | #html_show_sphinx = True
156 |
157 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
158 | #html_show_copyright = True
159 |
160 | # If true, an OpenSearch description file will be output, and all pages will
161 | # contain a tag referring to it. The value of this option must be the
162 | # base URL from which the finished HTML is served.
163 | #html_use_opensearch = ''
164 |
165 | # This is the file name suffix for HTML files (e.g. ".xhtml").
166 | #html_file_suffix = None
167 |
168 | # Output file base name for HTML help builder.
169 | htmlhelp_basename = 'FacebookSDKforPythondoc'
170 |
171 |
172 | # -- Options for LaTeX output --------------------------------------------------
173 |
174 | latex_elements = {
175 | # The paper size ('letterpaper' or 'a4paper').
176 | #'papersize': 'letterpaper',
177 |
178 | # The font size ('10pt', '11pt' or '12pt').
179 | #'pointsize': '10pt',
180 |
181 | # Additional stuff for the LaTeX preamble.
182 | #'preamble': '',
183 | }
184 |
185 | # Grouping the document tree into LaTeX files. List of tuples
186 | # (source start file, target name, title, author, documentclass [howto/manual]).
187 | latex_documents = [
188 | ('index', 'FacebookSDKforPython.tex', u'Facebook SDK for Python Documentation',
189 | u'Martey Dodoo', 'manual'),
190 | ]
191 |
192 | # The name of an image file (relative to this directory) to place at the top of
193 | # the title page.
194 | #latex_logo = None
195 |
196 | # For "manual" documents, if this is true, then toplevel headings are parts,
197 | # not chapters.
198 | #latex_use_parts = False
199 |
200 | # If true, show page references after internal links.
201 | #latex_show_pagerefs = False
202 |
203 | # If true, show URL addresses after external links.
204 | #latex_show_urls = False
205 |
206 | # Documents to append as an appendix to all manuals.
207 | #latex_appendices = []
208 |
209 | # If false, no module index is generated.
210 | #latex_domain_indices = True
211 |
212 |
213 | # -- Options for manual page output --------------------------------------------
214 |
215 | # One entry per manual page. List of tuples
216 | # (source start file, name, description, authors, manual section).
217 | man_pages = [
218 | ('index', 'facebooksdkforpython', u'Facebook SDK for Python Documentation',
219 | [u'Martey Dodoo'], 1)
220 | ]
221 |
222 | # If true, show URL addresses after external links.
223 | #man_show_urls = False
224 |
225 |
226 | # -- Options for Texinfo output ------------------------------------------------
227 |
228 | # Grouping the document tree into Texinfo files. List of tuples
229 | # (source start file, target name, title, author,
230 | # dir menu entry, description, category)
231 | texinfo_documents = [
232 | ('index', 'FacebookSDKforPython', u'Facebook SDK for Python Documentation',
233 | u'Martey Dodoo', 'FacebookSDKforPython', 'One line description of project.',
234 | 'Miscellaneous'),
235 | ]
236 |
237 | # Documents to append as an appendix to all manuals.
238 | #texinfo_appendices = []
239 |
240 | # If false, no module index is generated.
241 | #texinfo_domain_indices = True
242 |
243 | # How to display URL addresses: 'footnote', 'no', or 'inline'.
244 | #texinfo_show_urls = 'footnote'
245 |
--------------------------------------------------------------------------------
/docs/api.rst:
--------------------------------------------------------------------------------
1 | =============
2 | API Reference
3 | =============
4 |
5 | This page contains specific information on the SDK's classes, methods and
6 | functions.
7 |
8 | class facebook.GraphAPI
9 | =======================
10 |
11 | A client for the Facebook Graph API. The Graph API is made up of the objects or
12 | nodes in Facebook (e.g., people, pages, events, photos) and the connections or
13 | edges between them (e.g., friends, photo tags, and event RSVPs). This client
14 | provides access to those primitive types in a generic way.
15 |
16 | You can read more about `Facebook's Graph API here`_.
17 |
18 | .. _Facebook's Graph API here: https://developers.facebook.com/docs/graph-api
19 |
20 | **Parameters**
21 |
22 | * ``access_token`` – A ``string`` that identifies a user, app, or page and can
23 | be used by the app to make graph API calls.
24 | `Read more about access tokens here`_.
25 | * ``timeout`` - A ``float`` describing (in seconds) how long the client will be
26 | waiting for a response from Facebook's servers. `See more here`_.
27 | * ``version`` - A ``string`` describing the `version of Facebook's Graph API to
28 | use`_. The default version is the oldest current version. It is used if
29 | the version keyword argument is not provided.
30 | * ``proxies`` - A ``dict`` with proxy-settings that Requests should use. `See Requests documentation`_.
31 | * ``session`` - A `Requests Session object`_.
32 |
33 | .. _Read more about access tokens here: https://developers.facebook.com/docs/facebook-login/access-tokens
34 | .. _See more here: http://docs.python-requests.org/en/latest/user/quickstart/#timeouts
35 | .. _version of Facebook's Graph API to use: https://developers.facebook.com/docs/apps/changelog#versions
36 | .. _See Requests documentation: http://www.python-requests.org/en/latest/user/advanced/#proxies
37 | .. _Requests Session object: http://docs.python-requests.org/en/master/user/advanced/#session-objects
38 |
39 | **Example**
40 |
41 | .. code-block:: python
42 |
43 | import facebook
44 |
45 | graph = facebook.GraphAPI(access_token='your_token', version='2.2')
46 |
47 | Methods
48 | -------
49 |
50 | get_object
51 | ^^^^^^^^^^
52 |
53 | Returns the given object from the graph as a ``dict``. A list of
54 | `supported objects can be found here`_.
55 |
56 | .. _supported objects can be found here: https://developers.facebook.com/docs/graph-api/reference/
57 |
58 | **Parameters**
59 |
60 | * ``id`` – A ``string`` that is a unique ID for that particular resource.
61 | * ``**args`` (optional) - keyword args to be passed as query params
62 |
63 | **Example**
64 |
65 | .. code-block:: python
66 |
67 | post = graph.get_object(id='post_id')
68 | print(post['message'])
69 |
70 | .. code-block:: python
71 |
72 | event = graph.get_object(id='event_id', fields='attending_count,declined_count')
73 | print(event['attending_count'])
74 | print(event['declined_count'])
75 |
76 |
77 | get_objects
78 | ^^^^^^^^^^^
79 |
80 | Returns all of the given objects from the graph as a ``dict``. Each given ID
81 | maps to an object.
82 |
83 | **Parameters**
84 |
85 | * ``ids`` – A ``list`` containing IDs for multiple resources.
86 | * ``**args`` (optional) - keyword args to be passed as query params
87 |
88 | **Example**
89 |
90 | .. code-block:: python
91 |
92 | post_ids = ['post_id_1', 'post_id_2']
93 | posts = graph.get_objects(ids=post_ids)
94 |
95 | # Each given id maps to an object.
96 | for post_id in post_ids:
97 | print(posts[post_id]['created_time'])
98 |
99 | .. code-block:: python
100 |
101 | event_ids = ['event_id_1', 'event_id_2']
102 | events = graph.get_objects(ids=event_ids, fields='attending_count,declined_count')
103 |
104 | # Each given id maps to an object the contains the requested fields.
105 | for event_id in event_ids:
106 | print(posts[event_id]['declined_count'])
107 |
108 |
109 | get_connections
110 | ^^^^^^^^^^^^^^^
111 |
112 | Returns all connections for a given object as a ``dict``.
113 |
114 | **Parameters**
115 |
116 | * ``id`` – A ``string`` that is a unique ID for that particular resource.
117 | * ``connection_name`` - A ``string`` that specifies the connection or edge
118 | between objects, e.g., feed, friends, groups, likes, posts. If left empty,
119 | ``get_connections`` will simply return the authenticated user's basic
120 | information.
121 |
122 | **Example**
123 |
124 | .. code-block:: python
125 |
126 | # Get all of the authenticated user's friends
127 | friends = graph.get_connections(id='me', connection_name='friends')
128 |
129 | # Get all the comments from a post
130 | comments = graph.get_connections(id='post_id', connection_name='comments')
131 |
132 |
133 | put_object
134 | ^^^^^^^^^^
135 |
136 | Writes the given object to the graph, connected to the given parent.
137 |
138 | **Parameters**
139 |
140 | * ``parent_object`` – A ``string`` that is a unique ID for that particular
141 | resource. The ``parent_object`` is the parent of a connection or edge. E.g.,
142 | profile is the parent of a feed, and a post is the parent of a comment.
143 | * ``connection_name`` - A ``string`` that specifies the connection or edge
144 | between objects, e.g., feed, friends, groups, likes, posts.
145 |
146 | **Example**
147 |
148 | .. code-block:: python
149 |
150 | # Writes 'Hello, world' to the active user's wall.
151 | graph.put_object(parent_object='me', connection_name='feed',
152 | message='Hello, world')
153 |
154 | # Writes a comment on a post
155 | graph.put_object(parent_object='post_id', connection_name='comments',
156 | message='First!')
157 |
158 |
159 | put_wall_post
160 | ^^^^^^^^^^^^^
161 |
162 | Writes a wall post to the given profile's wall. It defaults to writing to the
163 | authenticated user's wall if no ``profile_id`` is specified.
164 |
165 | **Parameters**
166 |
167 | * ``message`` - A ``string`` that will be posted to the user's wall.
168 | * ``attachment`` - A ``dict`` that adds a structured attachment to the message
169 | being posted to the Wall. If you are sharing a URL, you will want to use the
170 | ``attachment`` parameter so that a thumbnail preview appears in the post. It
171 | should be a ``dict`` of the form:
172 |
173 | .. code-block:: python
174 |
175 | attachment = {
176 | 'name': '',
177 | 'link': '',
178 | 'caption': '',
179 | 'description': '',
180 | 'picture': ''
181 | }
182 |
183 | * ``profile_id`` - A ``string`` that is a unique ID for that particular user.
184 | Defaults to the authenticated user's wall.
185 |
186 | **Example**
187 |
188 | .. code-block:: python
189 |
190 | attachment = {
191 | 'name': 'Link name'
192 | 'link': 'https://www.example.com/',
193 | 'caption': 'Check out this example',
194 | 'description': 'This is a longer description of the attachment',
195 | 'picture': 'https://www.example.com/thumbnail.jpg'
196 | }
197 |
198 | graph.put_wall_post(message='Check this out...', attachment=attachment)
199 |
200 |
201 | put_comment
202 | ^^^^^^^^^^^
203 |
204 | Writes the given message as a comment on an object.
205 |
206 | **Parameters**
207 |
208 | * ``object_id`` - A ``string`` that is a unique id for a particular resource.
209 | * ``message`` - A ``string`` that will be posted as the comment.
210 |
211 | **Example**
212 |
213 | .. code-block:: python
214 |
215 | graph.put_comment(object_id='post_id', message='Great post...')
216 |
217 |
218 | put_like
219 | ^^^^^^^^
220 |
221 | Writes a like to the given object.
222 |
223 | **Parameters**
224 |
225 | * ``object_id`` - A ``string`` that is a unique id for a particular resource.
226 |
227 | **Example**
228 |
229 | .. code-block:: python
230 |
231 | graph.put_like(object_id='comment_id')
232 |
233 |
234 | put_photo
235 | ^^^^^^^^^
236 |
237 | https://developers.facebook.com/docs/graph-api/reference/user/photos#publish
238 |
239 | Upload an image using multipart/form-data. Returns JSON with the IDs of the
240 | photo and its post.
241 |
242 | **Parameters**
243 |
244 | * ``image`` - A file object representing the image to be uploaded.
245 | * ``album_path`` - A path representing where the image should be uploaded.
246 | Defaults to `/me/photos` which creates/uses a custom album for each
247 | Facebook application.
248 |
249 | **Example**
250 |
251 | .. code-block:: python
252 |
253 | # Upload an image with a caption.
254 | graph.put_photo(image=open('img.jpg', 'rb'), message='Look at this cool photo!')
255 | # Upload a photo to an album.
256 | graph.put_photo(image=open("img.jpg", 'rb'), album_path=album_id + "/photos")
257 | # Upload a profile photo for a Page.
258 | graph.put_photo(image=open("img.jpg", 'rb'), album_path=page_id + "/picture")
259 |
260 | delete_object
261 | ^^^^^^^^^^^^^
262 |
263 | Deletes the object with the given ID from the graph.
264 |
265 | **Parameters**
266 |
267 | * ``id`` - A ``string`` that is a unique ID for a particular resource.
268 |
269 | **Example**
270 |
271 | .. code-block:: python
272 |
273 | graph.delete_object(id='post_id')
274 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/facebook/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # Copyright 2010 Facebook
4 | # Copyright 2015 Mobolic
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may
7 | # not use this file except in compliance with the License. You may obtain
8 | # a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15 | # License for the specific language governing permissions and limitations
16 | # under the License.
17 |
18 | """Python client library for the Facebook Platform.
19 |
20 | This client library is designed to support the Graph API and the
21 | official Facebook JavaScript SDK, which is the canonical way to
22 | implement Facebook authentication. Read more about the Graph API at
23 | https://developers.facebook.com/docs/graph-api.
24 |
25 | """
26 |
27 | import hashlib
28 | import hmac
29 | import binascii
30 | import base64
31 | import requests
32 | import json
33 | import re
34 |
35 | try:
36 | from urllib.parse import parse_qs, urlencode
37 | except ImportError:
38 | from urlparse import parse_qs
39 | from urllib import urlencode
40 |
41 | from . import version
42 |
43 |
44 | __version__ = version.__version__
45 |
46 | FACEBOOK_GRAPH_URL = "https://graph.facebook.com/"
47 | FACEBOOK_OAUTH_DIALOG_URL = "https://www.facebook.com/dialog/oauth?"
48 | VALID_API_VERSIONS = ["2.2", "2.3", "2.4", "2.5", "2.6", "2.7", "2.8"]
49 |
50 |
51 | class GraphAPI(object):
52 | """A client for the Facebook Graph API.
53 |
54 | https://developers.facebook.com/docs/graph-api
55 |
56 | The Graph API is made up of the objects in Facebook (e.g., people,
57 | pages, events, photos) and the connections between them (e.g.,
58 | friends, photo tags, and event RSVPs). This client provides access
59 | to those primitive types in a generic way. For example, given an
60 | OAuth access token, this will fetch the profile of the active user
61 | and the list of the user's friends:
62 |
63 | graph = facebook.GraphAPI(access_token)
64 | user = graph.get_object("me")
65 | friends = graph.get_connections(user["id"], "friends")
66 |
67 | You can see a list of all of the objects and connections supported
68 | by the API at https://developers.facebook.com/docs/graph-api/reference/.
69 |
70 | You can obtain an access token via OAuth or by using the Facebook
71 | JavaScript SDK. See
72 | https://developers.facebook.com/docs/facebook-login for details.
73 |
74 | If you are using the JavaScript SDK, you can use the
75 | get_user_from_cookie() method below to get the OAuth access token
76 | for the active user from the cookie saved by the SDK.
77 |
78 | """
79 |
80 | def __init__(self, access_token=None, timeout=None, version=None,
81 | proxies=None, session=None):
82 | # The default version is only used if the version kwarg does not exist.
83 | default_version = VALID_API_VERSIONS[0]
84 |
85 | self.access_token = access_token
86 | self.timeout = timeout
87 | self.proxies = proxies
88 | self.session = session or requests.Session()
89 |
90 | if version:
91 | version_regex = re.compile("^\d\.\d$")
92 | match = version_regex.search(str(version))
93 | if match is not None:
94 | if str(version) not in VALID_API_VERSIONS:
95 | raise GraphAPIError("Valid API versions are " +
96 | str(VALID_API_VERSIONS).strip('[]'))
97 | else:
98 | self.version = "v" + str(version)
99 | else:
100 | raise GraphAPIError("Version number should be in the"
101 | " following format: #.# (e.g. 2.0).")
102 | else:
103 | self.version = "v" + default_version
104 |
105 | def get_object(self, id, **args):
106 | """Fetches the given object from the graph."""
107 | return self.request("{0}/{1}".format(self.version, id), args)
108 |
109 | def get_objects(self, ids, **args):
110 | """Fetches all of the given object from the graph.
111 |
112 | We return a map from ID to object. If any of the IDs are
113 | invalid, we raise an exception.
114 | """
115 | args["ids"] = ",".join(ids)
116 | return self.request(self.version + "/", args)
117 |
118 | def get_connections(self, id, connection_name, **args):
119 | """Fetches the connections for given object."""
120 | return self.request(
121 | "{0}/{1}/{2}".format(self.version, id, connection_name), args)
122 |
123 | def put_object(self, parent_object, connection_name, **data):
124 | """Writes the given object to the graph, connected to the given parent.
125 |
126 | For example,
127 |
128 | graph.put_object("me", "feed", message="Hello, world")
129 |
130 | writes "Hello, world" to the active user's wall. Likewise, this
131 | will comment on the first post of the active user's feed:
132 |
133 | feed = graph.get_connections("me", "feed")
134 | post = feed["data"][0]
135 | graph.put_object(post["id"], "comments", message="First!")
136 |
137 | Certain operations require extended permissions. See
138 | https://developers.facebook.com/docs/facebook-login/permissions
139 | for details about permissions.
140 |
141 | """
142 | assert self.access_token, "Write operations require an access token"
143 | return self.request(
144 | "{0}/{1}/{2}".format(self.version, parent_object, connection_name),
145 | post_args=data,
146 | method="POST")
147 |
148 | def put_wall_post(self, message, attachment={}, profile_id="me"):
149 | """Writes a wall post to the given profile's wall.
150 |
151 | We default to writing to the authenticated user's wall if no
152 | profile_id is specified.
153 |
154 | attachment adds a structured attachment to the status message
155 | being posted to the Wall. It should be a dictionary of the form:
156 |
157 | {"name": "Link name"
158 | "link": "https://www.example.com/",
159 | "caption": "{*actor*} posted a new review",
160 | "description": "This is a longer description of the attachment",
161 | "picture": "https://www.example.com/thumbnail.jpg"}
162 |
163 | """
164 | return self.put_object(profile_id, "feed", message=message,
165 | **attachment)
166 |
167 | def put_comment(self, object_id, message):
168 | """Writes the given comment on the given post."""
169 | return self.put_object(object_id, "comments", message=message)
170 |
171 | def put_like(self, object_id):
172 | """Likes the given post."""
173 | return self.put_object(object_id, "likes")
174 |
175 | def delete_object(self, id):
176 | """Deletes the object with the given ID from the graph."""
177 | self.request("{0}/{1}".format(self.version, id), method="DELETE")
178 |
179 | def delete_request(self, user_id, request_id):
180 | """Deletes the Request with the given ID for the given user."""
181 | self.request("{0}_{1}".format(request_id, user_id), method="DELETE")
182 |
183 | def put_photo(self, image, album_path="me/photos", **kwargs):
184 | """
185 | Upload an image using multipart/form-data.
186 |
187 | image - A file object representing the image to be uploaded.
188 | album_path - A path representing where the image should be uploaded.
189 |
190 | """
191 | return self.request(
192 | "{0}/{1}".format(self.version, album_path),
193 | post_args=kwargs,
194 | files={"source": image},
195 | method="POST")
196 |
197 | def get_version(self):
198 | """Fetches the current version number of the Graph API being used."""
199 | args = {"access_token": self.access_token}
200 | try:
201 | response = self.session.request(
202 | "GET",
203 | FACEBOOK_GRAPH_URL + self.version + "/me",
204 | params=args,
205 | timeout=self.timeout,
206 | proxies=self.proxies)
207 | except requests.HTTPError as e:
208 | response = json.loads(e.read())
209 | raise GraphAPIError(response)
210 |
211 | try:
212 | headers = response.headers
213 | version = headers["facebook-api-version"].replace("v", "")
214 | return float(version)
215 | except Exception:
216 | raise GraphAPIError("API version number not available")
217 |
218 | def request(
219 | self, path, args=None, post_args=None, files=None, method=None):
220 | """Fetches the given path in the Graph API.
221 |
222 | We translate args to a valid query string. If post_args is
223 | given, we send a POST request to the given path with the given
224 | arguments.
225 |
226 | """
227 |
228 | if post_args is not None:
229 | method = "POST"
230 |
231 | # Add `access_token` to post_args or args if it has not already been
232 | # included.
233 | if self.access_token:
234 | # If post_args exists, we assume that args either does not exists
235 | # or it does not need `access_token`.
236 | if post_args and "access_token" not in post_args:
237 | post_args["access_token"] = self.access_token
238 | elif "access_token" not in args:
239 | args["access_token"] = self.access_token
240 |
241 | try:
242 | response = self.session.request(
243 | method or "GET",
244 | FACEBOOK_GRAPH_URL + path,
245 | timeout=self.timeout,
246 | params=args,
247 | data=post_args,
248 | proxies=self.proxies,
249 | files=files)
250 | except requests.HTTPError as e:
251 | response = json.loads(e.read())
252 | raise GraphAPIError(response)
253 |
254 | headers = response.headers
255 | if 'json' in headers['content-type']:
256 | result = response.json()
257 | elif 'image/' in headers['content-type']:
258 | mimetype = headers['content-type']
259 | result = {"data": response.content,
260 | "mime-type": mimetype,
261 | "url": response.url}
262 | elif "access_token" in parse_qs(response.text):
263 | query_str = parse_qs(response.text)
264 | if "access_token" in query_str:
265 | result = {"access_token": query_str["access_token"][0]}
266 | if "expires" in query_str:
267 | result["expires"] = query_str["expires"][0]
268 | else:
269 | raise GraphAPIError(response.json())
270 | else:
271 | raise GraphAPIError('Maintype was not text, image, or querystring')
272 |
273 | if result and isinstance(result, dict) and result.get("error"):
274 | raise GraphAPIError(result)
275 | return result
276 |
277 | def get_app_access_token(self, app_id, app_secret, offline=False):
278 | """
279 | Get the application's access token as a string.
280 | If offline=True, use the concatenated app ID and secret
281 | instead of making an API call.
282 |
284 | """
285 | if offline:
286 | return "{0}|{1}".format(app_id, app_secret)
287 | else:
288 | args = {'grant_type': 'client_credentials',
289 | 'client_id': app_id,
290 | 'client_secret': app_secret}
291 |
292 | return self.request("oauth/access_token",
293 | args=args)["access_token"]
294 |
295 | def get_access_token_from_code(
296 | self, code, redirect_uri, app_id, app_secret):
297 | """Get an access token from the "code" returned from an OAuth dialog.
298 |
299 | Returns a dict containing the user-specific access token and its
300 | expiration date (if applicable).
301 |
302 | """
303 | args = {
304 | "code": code,
305 | "redirect_uri": redirect_uri,
306 | "client_id": app_id,
307 | "client_secret": app_secret}
308 |
309 | return self.request("oauth/access_token", args)
310 |
311 | def extend_access_token(self, app_id, app_secret):
312 | """
313 | Extends the expiration time of a valid OAuth access token. See
314 |
316 |
317 | """
318 | args = {
319 | "client_id": app_id,
320 | "client_secret": app_secret,
321 | "grant_type": "fb_exchange_token",
322 | "fb_exchange_token": self.access_token,
323 | }
324 |
325 | return self.request("oauth/access_token", args=args)
326 |
327 | def debug_access_token(self, token, app_id, app_secret):
328 | """
329 | Gets information about a user access token issued by an app. See
330 |
332 |
333 | We can generate the app access token by concatenating the app
334 | id and secret:
336 |
337 | """
338 | args = {
339 | "input_token": token,
340 | "access_token": "{0}|{1}".format(app_id, app_secret)
341 | }
342 | return self.request("/debug_token", args=args)
343 |
344 |
345 | class GraphAPIError(Exception):
346 | def __init__(self, result):
347 | self.result = result
348 | self.code = None
349 | try:
350 | self.type = result["error_code"]
351 | except:
352 | self.type = ""
353 |
354 | # OAuth 2.0 Draft 10
355 | try:
356 | self.message = result["error_description"]
357 | except:
358 | # OAuth 2.0 Draft 00
359 | try:
360 | self.message = result["error"]["message"]
361 | self.code = result["error"].get("code")
362 | if not self.type:
363 | self.type = result["error"].get("type", "")
364 | except:
365 | # REST server style
366 | try:
367 | self.message = result["error_msg"]
368 | except:
369 | self.message = result
370 |
371 | Exception.__init__(self, self.message)
372 |
373 |
374 | def get_user_from_cookie(cookies, app_id, app_secret):
375 | """Parses the cookie set by the official Facebook JavaScript SDK.
376 |
377 | cookies should be a dictionary-like object mapping cookie names to
378 | cookie values.
379 |
380 | If the user is logged in via Facebook, we return a dictionary with
381 | the keys "uid" and "access_token". The former is the user's
382 | Facebook ID, and the latter can be used to make authenticated
383 | requests to the Graph API. If the user is not logged in, we
384 | return None.
385 |
386 | Read more about Facebook authentication at
387 | https://developers.facebook.com/docs/facebook-login.
388 |
389 | """
390 | cookie = cookies.get("fbsr_" + app_id, "")
391 | if not cookie:
392 | return None
393 | parsed_request = parse_signed_request(cookie, app_secret)
394 | if not parsed_request:
395 | return None
396 | try:
397 | result = GraphAPI().get_access_token_from_code(
398 | parsed_request["code"], "", app_id, app_secret)
399 | except GraphAPIError:
400 | return None
401 | result["uid"] = parsed_request["user_id"]
402 | return result
403 |
404 |
405 | def parse_signed_request(signed_request, app_secret):
406 | """ Return dictionary with signed request data.
407 |
408 | We return a dictionary containing the information in the
409 | signed_request. This includes a user_id if the user has authorised
410 | your application, as well as any information requested.
411 |
412 | If the signed_request is malformed or corrupted, False is returned.
413 |
414 | """
415 | try:
416 | encoded_sig, payload = map(str, signed_request.split('.', 1))
417 |
418 | sig = base64.urlsafe_b64decode(encoded_sig + "=" *
419 | ((4 - len(encoded_sig) % 4) % 4))
420 | data = base64.urlsafe_b64decode(payload + "=" *
421 | ((4 - len(payload) % 4) % 4))
422 | except IndexError:
423 | # Signed request was malformed.
424 | return False
425 | except TypeError:
426 | # Signed request had a corrupted payload.
427 | return False
428 | except binascii.Error:
429 | # Signed request had a corrupted payload.
430 | return False
431 |
432 | data = json.loads(data.decode('ascii'))
433 | if data.get('algorithm', '').upper() != 'HMAC-SHA256':
434 | return False
435 |
436 | # HMAC can only handle ascii (byte) strings
437 | # https://bugs.python.org/issue5285
438 | app_secret = app_secret.encode('ascii')
439 | payload = payload.encode('ascii')
440 |
441 | expected_sig = hmac.new(app_secret,
442 | msg=payload,
443 | digestmod=hashlib.sha256).digest()
444 | if sig != expected_sig:
445 | return False
446 |
447 | return data
448 |
449 |
450 | def auth_url(app_id, canvas_url, perms=None, **kwargs):
451 | url = FACEBOOK_OAUTH_DIALOG_URL
452 | kvps = {'client_id': app_id, 'redirect_uri': canvas_url}
453 | if perms:
454 | kvps['scope'] = ",".join(perms)
455 | kvps.update(kwargs)
456 | return url + urlencode(kvps)
457 |
--------------------------------------------------------------------------------