Skip to content

Commit feed0ab

Browse files
authored
docs: add Sphinx documentation site and prepare 0.1.3 release (#1)
Add the Sphinx docs site and Read the Docs config, fix docs accuracy, and bump the version to 0.1.3. No runtime behaviour changes.
2 parents 7d98658 + 3e287e1 commit feed0ab

14 files changed

Lines changed: 535 additions & 9 deletions

File tree

‎.readthedocs.yaml‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
version: 2
2+
3+
build:
4+
os: ubuntu-22.04
5+
tools:
6+
python: "3.12"
7+
8+
sphinx:
9+
configuration: docs/conf.py
10+
11+
python:
12+
install:
13+
- method: pip
14+
path: .
15+
- requirements: docs/requirements.txt

‎CHANGELOG.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
## 0.1.3
4+
5+
- Add a Sphinx documentation site with installation, quick start,
6+
configuration, examples and API reference pages.
7+
- Configure Read the Docs to build the documentation from the repository.
8+
- Add the documentation URL to package metadata.
9+
310
## 0.1.2
411

512
- Clear the request-cached GitHubKit client on logout, so logging out

‎README.md‎

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ A Flask extension for GitHub OAuth login, built on
44
[GitHubKit](https://github.com/yanyongyu/githubkit). It handles expiring-token
55
refresh and stores tokens in `flask.session`.
66

7+
Documentation: [flask-githubkit.readthedocs.io](https://flask-githubkit.readthedocs.io/en/latest/)
8+
79
## Installation
810

911
Install the latest release from PyPI:
@@ -15,8 +17,8 @@ pip install flask-githubkit
1517
To pin a release or install directly from GitHub:
1618

1719
```console
18-
pip install "flask-githubkit==0.1.2"
19-
pip install "flask-githubkit @ git+https://github.com/LJFCoding/flask-githubkit.git@v0.1.2"
20+
pip install "flask-githubkit==0.1.3"
21+
pip install "flask-githubkit @ git+https://github.com/LJFCoding/flask-githubkit.git@v0.1.3"
2022
```
2123

2224
It requires Python 3.10+ and installs Flask, GitHubKit and httpx.
@@ -116,15 +118,15 @@ def create_app():
116118

117119
## Examples
118120

119-
Each file in [examples/](examples) is a complete app. They are checked by the
121+
Each file in [examples/](https://github.com/LJFCoding/flask-githubkit/tree/main/examples) is a complete app. They are checked by the
120122
test suite, which confirms the login route redirects to GitHub.
121123

122124
| File | Shows |
123125
| --- | --- |
124-
| [single_file.py](examples/single_file.py) | A module-level app with `GitHub(app)` |
125-
| [factory.py](examples/factory.py) | An app factory with `init_app` and the optional config keys |
126-
| [blueprint.py](examples/blueprint.py) | Login routes in a blueprint; the `auth.login` endpoint is found from `@github.login` |
127-
| [routes.py](examples/routes.py) | A `login_required` decorator and a route that calls the GitHub API |
126+
| [single_file.py](https://github.com/LJFCoding/flask-githubkit/blob/main/examples/single_file.py) | A module-level app with `GitHub(app)` |
127+
| [factory.py](https://github.com/LJFCoding/flask-githubkit/blob/main/examples/factory.py) | An app factory with `init_app` and the optional config keys |
128+
| [blueprint.py](https://github.com/LJFCoding/flask-githubkit/blob/main/examples/blueprint.py) | Login routes in a blueprint; the `auth.login` endpoint is found from `@github.login` |
129+
| [routes.py](https://github.com/LJFCoding/flask-githubkit/blob/main/examples/routes.py) | A `login_required` decorator and a route that calls the GitHub API |
128130

129131
Set `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET` and `SECRET_KEY` in your
130132
environment, then run one with, for example,
@@ -177,7 +179,7 @@ github = flask_githubkit.GitHub(
177179
OAuth flow. You can also call `github.login()` inside a view and return
178180
the result, which is useful if you want to run code first. The decorated
179181
function's body never runs.
180-
- `@callback` wraps the callback view.
182+
- `@github.callback` wraps the callback view.
181183
- `client()` returns the request-cached GitHubKit `GitHub` client, or `None`
182184
when there is no valid stored token.
183185
- `logout()` removes the stored GitHub token from the session.

‎docs/_static/custom.css‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.rst-content code.literal,
2+
.rst-content tt.literal,
3+
.rst-content .sig-name,
4+
.rst-content .sig-prename,
5+
.rst-content .sig-param {
6+
color: #333333;
7+
}

‎docs/api.md‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# API reference
2+
3+
The package's public imports are `GitHub`, `Config` and `Token`:
4+
5+
```python
6+
import flask_githubkit
7+
8+
github = flask_githubkit.GitHub()
9+
```
10+
11+
Most applications only need `GitHub`. The extension handles callback
12+
state validation, code exchange and token persistence. Application code
13+
uses `github.client()` to make GitHub API calls.
14+
15+
## `GitHub`
16+
17+
The generated reference below documents the public extension methods
18+
and their signatures.
19+
20+
```{eval-rst}
21+
.. autoclass:: flask_githubkit.GitHub
22+
:members: init_app, login, callback, client, logout
23+
```
24+
25+
### Callback behavior
26+
27+
`@github.callback` checks and consumes the one-time OAuth `state` value
28+
before calling the decorated view. Invalid or replayed state returns
29+
HTTP 400 and the view is not called. If the user denies authorization,
30+
the view receives `None`. When the exchange succeeds, the view receives
31+
the access token as its first argument.
32+
33+
If GitHub redirects back without an authorization code, the extension
34+
returns HTTP 400. Network failures while exchanging the code return HTTP 502. If the
35+
GitHubKit client later raises `AuthExpiredError`, the extension clears
36+
the token and redirects to the configured or discovered login endpoint.
37+
38+
## `Config`
39+
40+
`Config.from_current_app()` reads the OAuth client ID, client secret,
41+
redirect URI, scope and Flask `SECRET_KEY` from the active Flask app.
42+
It returns `None` when required configuration is missing or invalid.
43+
Most applications do not need to construct or call `Config` directly.
44+
45+
## `Token`
46+
47+
`Token` serializes and restores the GitHubKit OAuth token strategy in
48+
`flask.session`. Application code normally uses `GitHub.callback`,
49+
`GitHub.client()` and `GitHub.logout()` instead of calling `Token`
50+
directly.
51+
52+
For GitHubKit's complete API, see its
53+
[REST API documentation](https://yanyongyu.github.io/githubkit/).

‎docs/conf.py‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
project = "flask-githubkit"
2+
author = "LJFCoding"
3+
release = "0.1.3"
4+
5+
extensions = [
6+
"myst_parser",
7+
"sphinx.ext.autodoc",
8+
]
9+
10+
root_doc = "index"
11+
myst_heading_anchors = 3
12+
source_suffix = {
13+
".rst": "restructuredtext",
14+
".md": "markdown",
15+
}
16+
17+
html_theme = "sphinx_rtd_theme"
18+
html_static_path = ["_static"]
19+
html_css_files = ["custom.css"]

‎docs/configuration.md‎

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# Configuration
2+
3+
The extension reads OAuth credentials from the active Flask application's
4+
config. Configure the app before serving requests.
5+
6+
## Flask configuration
7+
8+
| Key | Required | Default | Purpose |
9+
| --- | --- | --- | --- |
10+
| `SECRET_KEY` | Yes | — | Flask session signing key. Keep it secret and stable. |
11+
| `GITHUB_CLIENT_ID` | Yes | — | Client ID from your GitHub OAuth App. |
12+
| `GITHUB_CLIENT_SECRET` | Yes | — | Client secret from your GitHub OAuth App. |
13+
| `GITHUB_REDIRECT_URI` | Yes | — | Absolute callback URL registered with the OAuth App. |
14+
| `GITHUB_SCOPE` | No | `read:user` | Space-separated OAuth scopes requested at login. |
15+
| `GITHUB_LOGIN_VIEW` | No | Decorated login view, otherwise `login` | Flask endpoint to use when a refresh token has expired. |
16+
17+
The client ID, client secret, redirect URI, scope and `SECRET_KEY` must be
18+
non-empty strings. If any is missing or empty, the login route returns a
19+
JSON error with HTTP 500, the callback route returns the same error once its
20+
`state` check passes, and `github.client()` returns `None`.
21+
22+
## Initialize the extension
23+
24+
For a single app, pass the app to the extension:
25+
26+
```python
27+
github = flask_githubkit.GitHub(app)
28+
```
29+
30+
For an application factory, create the extension without an app and call
31+
`init_app` when the app is created:
32+
33+
```python
34+
github = flask_githubkit.GitHub()
35+
36+
37+
def create_app():
38+
app = flask.Flask(__name__)
39+
app.config.from_mapping(...)
40+
github.init_app(app)
41+
return app
42+
```
43+
44+
See [application factory and Blueprint examples](examples.md).
45+
46+
## Scope and login endpoint overrides
47+
48+
The constructor accepts optional `scope` and `login_view` keyword
49+
arguments:
50+
51+
```python
52+
github = flask_githubkit.GitHub(
53+
scope="read:user repo",
54+
login_view="auth.login",
55+
)
56+
```
57+
58+
For either option, the constructor value takes precedence over its Flask
59+
config key. If no scope is supplied, the default is `read:user`. If no
60+
login endpoint is supplied, the extension looks for a route decorated
61+
with `@github.login` and uses its Flask endpoint. This works with
62+
Blueprints (for example, `auth.login`). If no decorated route exists, it
63+
uses the endpoint named `login`.
64+
65+
`login_view` is an endpoint name used by `flask.url_for`, not a URL path.
66+
Most apps do not need to set it when they decorate their login route with
67+
`@github.login`.
68+
69+
Scopes determine what user access the OAuth App requests. Request only
70+
the access your app needs. GitHub's OAuth scope behavior is documented
71+
in the [GitHub OAuth scopes guide](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/scopes-for-oauth-apps).
72+
73+
## Session storage
74+
75+
Tokens are stored in `flask.session`. The extension marks the session
76+
permanent when it saves tokens, but does not select or configure a
77+
session backend or its expiration policy.
78+
79+
Flask's default session is a signed client-side cookie; it is **not
80+
encrypted**, so users can read its contents. For production, use a
81+
server-side session backend, such as Flask-Session with Redis, and
82+
configure the backend and session lifetime for your application.
83+
Sliding expiry and TTL behavior depend on that backend.
84+
85+
## Token refresh
86+
87+
Enable **Expire user authorization tokens** in the GitHub OAuth App if
88+
you want expiring access tokens and refresh tokens. GitHubKit refreshes
89+
an expired access token when an API request uses the client.
90+
flask-githubkit writes the updated token values back to the session after
91+
the request.
92+
93+
GitHub refresh tokens are single-use. Concurrent requests using the same
94+
expired token can race; this extension does not provide a distributed
95+
lock. A deployment that needs coordination across workers must implement
96+
that policy in its own storage or infrastructure.
97+
98+
## Limitations
99+
100+
- Only synchronous Flask applications and GitHub OAuth Apps are supported.
101+
GitHub Apps and GitHub Enterprise endpoints are not configurable.
102+
- Real GitHub code exchange and token refresh, Redis, and Flask-Session TTL
103+
behavior are not verified by the automated tests. Test them against those
104+
services before relying on them in production.
105+
- Errors raised by your own API calls, such as network failures, are not
106+
caught by the extension.

‎docs/examples.md‎

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Examples
2+
3+
The complete, runnable apps live in the repository's
4+
[`examples/` directory](https://github.com/LJFCoding/flask-githubkit/tree/main/examples).
5+
The test suite smoke-tests these examples so documentation snippets do
6+
not have to be maintained separately.
7+
8+
## Single-file application
9+
10+
Use this pattern for a small app with a module-level Flask application.
11+
`GitHub(app)` initializes the extension immediately:
12+
13+
```{literalinclude} ../examples/single_file.py
14+
:language: python
15+
```
16+
17+
Run it from the repository root after setting `SECRET_KEY`,
18+
`GITHUB_CLIENT_ID` and `GITHUB_CLIENT_SECRET`:
19+
20+
```console
21+
flask --app examples/single_file run
22+
```
23+
24+
## Application factory
25+
26+
An application factory creates the Flask app inside a function. This
27+
pattern is useful for tests, multiple app configurations, and larger
28+
projects. Create the extension once, then call `init_app(app)` inside
29+
the factory:
30+
31+
```{literalinclude} ../examples/factory.py
32+
:language: python
33+
```
34+
35+
## Blueprint
36+
37+
Blueprints give routes an endpoint prefix. In the following example,
38+
the login endpoint is `auth.login`. The extension discovers this from
39+
`@github.login`, so no `GITHUB_LOGIN_VIEW` setting is needed:
40+
41+
```{literalinclude} ../examples/blueprint.py
42+
:language: python
43+
```
44+
45+
The callback URL registered with GitHub must include the Blueprint
46+
prefix, for example `http://localhost:5000/auth/callback`.
47+
48+
## Call the API from a route
49+
50+
`github.client()` returns a GitHubKit client for the signed-in user, or
51+
`None` when no token is available. Use the client's typed REST methods:
52+
53+
```{literalinclude} ../examples/routes.py
54+
:language: python
55+
```
56+
57+
Prefer API calls over reading the access token directly. If server-side
58+
integration code needs the token, it is available as
59+
`client.auth.token`. It is a bearer credential: do not return it to the
60+
browser, include it in a URL, or write it to logs. An API request may
61+
refresh the token, so read the value after using the client if you need
62+
the refreshed credential.

‎docs/index.md‎

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# flask-githubkit
2+
3+
**GitHub OAuth for Flask, with GitHubKit's typed API client.**
4+
5+
`flask-githubkit` connects Flask routes to GitHub's OAuth flow and API. It
6+
validates the OAuth callback's `state`, stores user tokens in the Flask
7+
session, and saves refreshed token values after API calls.
8+
9+
```python
10+
@app.route("/login")
11+
@github.login
12+
def login():
13+
"""Redirect the user to GitHub."""
14+
15+
16+
@app.route("/callback")
17+
@github.callback
18+
def callback(access_token):
19+
if access_token is None:
20+
return "GitHub authorization was denied.", 401
21+
return flask.redirect(flask.url_for("profile"))
22+
```
23+
24+
## What you get
25+
26+
- A GitHub OAuth login flow designed for Flask.
27+
- Callback state validation to protect against login CSRF.
28+
- GitHubKit's typed REST API client, cached for the current request.
29+
- Automatic refresh of expiring user tokens when the client is used, with
30+
refreshed tokens saved back to the Flask session.
31+
- Support for Flask application factories and Blueprints.
32+
- A small synchronous API: `@github.login`, `@github.callback`,
33+
`github.client()` and `github.logout()`.
34+
35+
Token refresh requires a GitHub OAuth App configured to issue expiring user
36+
authorization tokens. GitHub refresh tokens are single-use, so simultaneous
37+
requests can race while refreshing. Flask's default session cookie is signed
38+
but not encrypted; see [session security](configuration.md).
39+
40+
## Start here
41+
42+
```{toctree}
43+
:maxdepth: 2
44+
45+
installation
46+
quickstart
47+
configuration
48+
examples
49+
api
50+
```
51+
52+
For GitHubKit's REST methods and other client features, see the
53+
[GitHubKit documentation](https://yanyongyu.github.io/githubkit/). This
54+
extension currently supports synchronous Flask applications.

0 commit comments

Comments
 (0)