Skip to content

Commit 8bc7781

Browse files
authoredJul 13, 2023
Prepare repository for open-source (GH-6)
2 parents 1f3248a + 5be639a commit 8bc7781

27 files changed

+443
-169
lines changed
 

Diff for: ‎.github/workflows/publish.yml

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: Publish to PyPI
2+
3+
on:
4+
release:
5+
types: [ published ]
6+
7+
jobs:
8+
deploy:
9+
10+
runs-on: ubuntu-latest
11+
12+
steps:
13+
- uses: actions/checkout@v3
14+
- name: Set up Python
15+
uses: actions/setup-python@v3
16+
with:
17+
python-version: '3.x'
18+
- name: Install dependencies
19+
run: |
20+
python -m pip install --upgrade pip
21+
pip install setuptools wheel twine
22+
- name: Build and publish
23+
env:
24+
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
25+
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
26+
run: |
27+
python setup.py sdist bdist_wheel
28+
twine upload dist/*

Diff for: ‎.github/workflows/tests.yml

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
name: tests
2+
3+
on:
4+
push:
5+
branches: [ master ]
6+
pull_request:
7+
branches: [ master ]
8+
9+
jobs:
10+
test:
11+
runs-on: ${{ matrix.os || 'ubuntu-latest' }}
12+
strategy:
13+
matrix:
14+
include:
15+
- python: "3.6"
16+
env: py36-fastapi68
17+
os: ubuntu-20.04 # 3.6 is not available on ubuntu-20.04
18+
- python: "3.8"
19+
env: py38-fastapi68
20+
- python: "3.10"
21+
env: py310-fastapi68
22+
- python: "3.11"
23+
env: py311-fastapi68
24+
25+
- python: "3.7"
26+
env: py37-fastapi84
27+
- python: "3.9"
28+
env: py39-fastapi84
29+
- python: "3.10"
30+
env: py310-fastapi84
31+
- python: "3.11"
32+
env: py311-fastapi84
33+
34+
- python: "3.7"
35+
env: py37-fastapi99
36+
- python: "3.9"
37+
env: py39-fastapi99
38+
- python: "3.10"
39+
env: py310-fastapi99
40+
- python: "3.11"
41+
env: py311-fastapi99
42+
43+
steps:
44+
- uses: actions/checkout@v2
45+
- name: Set up Python ${{ matrix.python }}
46+
uses: actions/setup-python@v2
47+
with:
48+
python-version: ${{ matrix.python }}
49+
- name: Install dependencies
50+
run: |
51+
pip install --upgrade pip
52+
sh build.sh
53+
pip install tox tox-gh-actions
54+
- name: Run tests using tox
55+
run: tox -e ${{ matrix.env }}

Diff for: ‎.gitignore

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# caches
2+
.idea
3+
.tox
4+
.pytest_cache
5+
*.egg-info
6+
__pycache__
7+
8+
# docs
9+
docs/node_modules
10+
docs/package-lock.json
11+
docs/.vitepress/cache
12+
docs/.vitepress/dist
13+
14+
# build
15+
build
16+
dist

Diff for: ‎README.md

+97-18
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,107 @@
1-
# fastapi-oauth2
1+
# fastapi-oauth2 <img src="https://github.com/pysnippet.png" align="right" height="64" />
22

3-
Easy to setup OAuth2 social authentication mechanism with support for several auth providers.
3+
[![PyPI](https://img.shields.io/pypi/v/fastapi-oauth2.svg)](https://pypi.org/project/fastapi-oauth2/)
4+
[![Python](https://img.shields.io/pypi/pyversions/fastapi-oauth2.svg?logoColor=white)](https://pypi.org/project/fastapi-oauth2/)
5+
[![FastAPI](https://img.shields.io/badge/fastapi-%E2%89%A50.68.1-009486)](https://pypi.org/project/fastapi-oauth2/)
6+
[![Tests](https://github.com/pysnippet/fastapi-oauth2/actions/workflows/tests.yml/badge.svg)](https://github.com/pysnippet/fastapi-oauth2/actions/workflows/tests.yml)
7+
[![License](https://img.shields.io/pypi/l/fastapi-oauth2.svg)](https://github.com/pysnippet/fastapi-oauth2/blob/master/LICENSE)
48

5-
## Demo
9+
FastAPI OAuth2 is a middleware-based social authentication mechanism supporting several auth providers. It depends on
10+
the [social-core](https://github.com/python-social-auth/social-core) authentication backends.
611

7-
This sample application is made to demonstrate the use of the [**fastapi-oauth2**](./fastapi_oauth2) package.
12+
## Features to be implemented
813

9-
## Running the application
14+
- Use multiple OAuth2 providers at the same time
15+
* There need to be provided a way to configure the OAuth2 for multiple providers
16+
- Provide `fastapi.security.*` implementations that use cookies
17+
- Token -> user data, user data -> token easy conversion
18+
- Customizable OAuth2 routes
19+
- Registration support
20+
21+
## Installation
1022

11-
```bash
12-
uvicorn main:app --reload
23+
```shell
24+
python -m pip install fastapi-oauth2
1325
```
1426

15-
## TODO
27+
## Configuration
1628

17-
- Make the [**fastapi-oauth2**](./fastapi_oauth2) depend
18-
on (overuse) the [**social-core**](https://github.com/python-social-auth/social-core)
29+
Configuration requires you to provide the JWT requisites and define the clients of the particular providers. The
30+
middleware configuration is declared with the `OAuth2Config` and `OAuth2Client` classes.
1931

20-
## Features
32+
### OAuth2Config
2133

22-
- Integrate with any existing FastAPI project (no dependencies of the project should stop the work of
23-
the `fastapi-oauth2`)
24-
* Implementation must allow to provide a context for configurations (also, see how it is done in another projects)
25-
- Use multiple OAuth2 providers at the same time
26-
* There need to be provided a way to configure the OAuth2 for multiple providers
27-
- Token -> user data, user data -> token easy conversion
28-
- Customize OAuth2 routes
34+
- `allow_http` - Allow insecure HTTP requests. Defaults to `False`.
35+
- `jwt_secret` - The secret key used to sign the JWT. Defaults to `None`.
36+
- `jwt_expires` - The expiration time of the JWT in seconds. Defaults to `900`.
37+
- `jwt_algorithm` - The algorithm used to sign the JWT. Defaults to `HS256`.
38+
- `clients` - The list of the OAuth2 clients. Defaults to `[]`.
39+
40+
### OAuth2Client
41+
42+
- `backend` - The [social-core](https://github.com/python-social-auth/social-core) authentication backend classname.
43+
- `client_id` - The OAuth2 client ID for the particular provider.
44+
- `client_secret` - The OAuth2 client secret for the particular provider.
45+
- `redirect_uri` - The OAuth2 redirect URI to redirect to after success. Defaults to the base URL.
46+
- `scope` - The OAuth2 scope for the particular provider. Defaults to `[]`.
47+
48+
It is also important to mention that for the configured clients of the auth providers, the authorization URLs are
49+
accessible by the `/oauth2/{provider}/auth` path where the `provider` variable represents the exact value of the auth
50+
provider backend `name` attribute.
51+
52+
```python
53+
from fastapi_oauth2.client import OAuth2Client
54+
from fastapi_oauth2.config import OAuth2Config
55+
from social_core.backends.github import GithubOAuth2
56+
57+
oauth2_config = OAuth2Config(
58+
allow_http=False,
59+
jwt_secret=os.getenv("JWT_SECRET"),
60+
jwt_expires=os.getenv("JWT_EXPIRES"),
61+
jwt_algorithm=os.getenv("JWT_ALGORITHM"),
62+
clients=[
63+
OAuth2Client(
64+
backend=GithubOAuth2,
65+
client_id=os.getenv("OAUTH2_CLIENT_ID"),
66+
client_secret=os.getenv("OAUTH2_CLIENT_SECRET"),
67+
redirect_uri="https://pysnippet.org/",
68+
scope=["user:email"],
69+
),
70+
]
71+
)
72+
```
73+
74+
## Integration
75+
76+
To integrate the package into your FastAPI application, you need to add the `OAuth2Middleware` with particular configs
77+
in the above-represented format and include the router to the main router of the application.
78+
79+
```python
80+
from fastapi import FastAPI
81+
from fastapi_oauth2.middleware import OAuth2Middleware
82+
from fastapi_oauth2.router import router as oauth2_router
83+
84+
app = FastAPI()
85+
app.include_router(oauth2_router)
86+
app.add_middleware(OAuth2Middleware, config=oauth2_config)
87+
```
88+
89+
After adding the middleware, the `user` attribute will be available in the request context. It will contain the user
90+
data provided by the OAuth2 provider.
91+
92+
```jinja2
93+
{% if request.user.is_authenticated %}
94+
<a href="/oauth2/logout">Sign out</a>
95+
{% else %}
96+
<a href="/oauth2/github/auth">Sign in</a>
97+
{% endif %}
98+
```
99+
100+
## Contribute
101+
102+
Any contribution is welcome. If you have any ideas or suggestions, feel free to open an issue or a pull request. And
103+
don't forget to add tests for your changes.
104+
105+
## License
106+
107+
Copyright (C) 2023 Artyom Vancyan. [MIT](https://github.com/pysnippet/fastapi-oauth2/blob/master/LICENSE)

Diff for: ‎demo/dependencies.py

-39
This file was deleted.

Diff for: ‎demo/router.py

-17
This file was deleted.

Diff for: ‎.env renamed to ‎examples/demonstration/.env

-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
OAUTH2_CLIENT_ID=eccd08d6736b7999a32a
22
OAUTH2_CLIENT_SECRET=642999c1c5f2b3df8b877afdc78252ef5b594d31
3-
OAUTH2_CALLBACK_URL=http://127.0.0.1:8000/oauth2/token
43

54
JWT_SECRET=secret
65
JWT_ALGORITHM=HS256

Diff for: ‎examples/demonstration/README.md

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
## Demonstration
2+
3+
This sample application is made to demonstrate the use of
4+
the [**fastapi-oauth2**](https://github.com/pysnippet/fastapi-oauth2) package.
5+
6+
## Installation
7+
8+
You got to have `fastapi-oauth2` installed in your environment. To do so, run the following command in
9+
the `pyproject.toml` file's directory.
10+
11+
### Regular install
12+
13+
```bash
14+
pip install fastapi-oauth2
15+
```
16+
17+
### Install in editable mode
18+
19+
```bash
20+
pip install -e .
21+
```
22+
23+
## Running the application
24+
25+
```bash
26+
uvicorn main:app --reload
27+
```

Diff for: ‎examples/demonstration/config.py

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import os
2+
3+
from dotenv import load_dotenv
4+
from social_core.backends.github import GithubOAuth2
5+
6+
from fastapi_oauth2.client import OAuth2Client
7+
from fastapi_oauth2.config import OAuth2Config
8+
9+
load_dotenv()
10+
11+
oauth2_config = OAuth2Config(
12+
allow_http=True,
13+
jwt_secret=os.getenv("JWT_SECRET"),
14+
jwt_expires=os.getenv("JWT_EXPIRES"),
15+
jwt_algorithm=os.getenv("JWT_ALGORITHM"),
16+
clients=[
17+
OAuth2Client(
18+
backend=GithubOAuth2,
19+
client_id=os.getenv("OAUTH2_CLIENT_ID"),
20+
client_secret=os.getenv("OAUTH2_CLIENT_SECRET"),
21+
# redirect_uri="http://127.0.0.1:8000/",
22+
scope=["user:email"],
23+
),
24+
]
25+
)

Diff for: ‎examples/demonstration/main.py

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from fastapi import APIRouter
2+
from fastapi import FastAPI
3+
4+
from config import oauth2_config
5+
from fastapi_oauth2.middleware import OAuth2Middleware
6+
from fastapi_oauth2.router import router as oauth2_router
7+
from router import router as app_router
8+
9+
router = APIRouter()
10+
11+
app = FastAPI()
12+
app.include_router(app_router)
13+
app.include_router(oauth2_router)
14+
app.add_middleware(OAuth2Middleware, config=oauth2_config)

Diff for: ‎examples/demonstration/router.py

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import json
2+
3+
from fastapi import Depends
4+
from fastapi import Request, APIRouter
5+
from fastapi.responses import HTMLResponse
6+
from fastapi.security import OAuth2
7+
from fastapi.templating import Jinja2Templates
8+
9+
oauth2 = OAuth2()
10+
router = APIRouter()
11+
templates = Jinja2Templates(directory="templates")
12+
13+
14+
@router.get("/", response_class=HTMLResponse)
15+
async def root(request: Request):
16+
return templates.TemplateResponse("index.html", {"request": request, "user": request.user, "json": json})
17+
18+
19+
@router.get("/user")
20+
def user(request: Request, _: str = Depends(oauth2)):
21+
return request.user

Diff for: ‎examples/demonstration/templates/index.html

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport"
6+
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
7+
<meta http-equiv="X-UA-Compatible" content="ie=edge">
8+
<title>OAuth2 Demo</title>
9+
</head>
10+
<body style="margin: 0; display: flex; flex-direction: column; background: #1e1e20; color: #dfdfd6; font-family: sans-serif;">
11+
<header style="display: flex; background: #161618; height: 70px; width: 100%;">
12+
<div style="display: flex; align-items: center; margin: auto 50px auto auto;">
13+
{% if request.user.is_authenticated %}
14+
<a href="/oauth2/logout" style="text-decoration: none; color: #dfdfd6; margin-right: 20px;">Sign out</a>
15+
<img style="height: 50px; width: 50px;" src="{{ request.user.avatar_url }}" alt="Pic">
16+
{% else %}
17+
<a href="/oauth2/github/auth" style="display: flex; align-items: center;">
18+
<svg style="height: 50px; width: 50px;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
19+
<path fill="#dfdfd6" d="M7.499,1C3.91,1,1,3.906,1,7.49c0,2.867,1.862,5.299,4.445,6.158C5.77,13.707,6,13.375,6,13.125 c0-0.154,0.003-0.334,0-0.875c-1.808,0.392-2.375-0.875-2.375-0.875c-0.296-0.75-0.656-0.963-0.656-0.963 c-0.59-0.403,0.044-0.394,0.044-0.394C3.666,10.064,4,10.625,4,10.625c0.5,0.875,1.63,0.791,2,0.625 c0-0.397,0.044-0.688,0.154-0.873C4.111,10.02,2.997,8.84,3,7.208c0.002-0.964,0.335-1.715,0.876-2.269 C3.639,4.641,3.479,3.625,3.961,3c1.206,0,1.927,0.873,1.927,0.873s0.565-0.248,1.61-0.248c1.045,0,1.608,0.234,1.608,0.234 S9.829,3,11.035,3c0.482,0.625,0.322,1.641,0.132,1.918C11.684,5.461,12,6.21,12,7.208c0,1.631-1.11,2.81-3.148,3.168 C8.982,10.572,9,10.842,9,11.25c0,0.867,0,1.662,0,1.875c0,0.25,0.228,0.585,0.558,0.522C12.139,12.787,14,10.356,14,7.49 C14,3.906,11.09,1,7.499,1z"></path>
20+
</svg>
21+
</a>
22+
{% endif %}
23+
</div>
24+
</header>
25+
<section
26+
style="display: flex; flex-direction: column; align-items: center; justify-content: center; height: calc(100vh - 70px);">
27+
{% if request.user.is_authenticated %}
28+
<h1>Hi, {{ request.user.name }}</h1>
29+
<p>This is what your JWT contains currently</p>
30+
<pre>{{ json.dumps(request.user, indent=4) }}</pre>
31+
{% else %}
32+
<h1>You are not authenticated</h1>
33+
<p>You should sign in by clicking the GitHub's icon</p>
34+
{% endif %}
35+
</section>
36+
</body>
37+
</html>

Diff for: ‎main.py

-43
This file was deleted.

Diff for: ‎pyproject.toml

+4-1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[tool.pytest.ini_options]
66
testpaths = ["tests"]
7-
filterwarnings = ["ignore::DeprecationWarning"]
7+
filterwarnings = [
8+
"ignore::DeprecationWarning",
9+
"ignore::trio.TrioDeprecationWarning",
10+
]

Diff for: ‎requirements.txt

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
fastapi>=0.68.1
2+
httpx>=0.22.0
3+
oauthlib>=3.2.2
4+
python-jose>=3.3.0
5+
social-auth-core>=4.4.2
6+
starlette>=0.19.1

Diff for: ‎setup.cfg

+14-4
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,31 @@ name = fastapi-oauth2
33
version = attr: fastapi_oauth2.__version__
44
author = Artyom Vancyan
55
author_email = artyom@pysnippet.org
6-
description = Easy to setup OAuth2 social authentication mechanism with support for several auth providers.
6+
description = Easy to setup OAuth2 authentication with support for several auth providers.
77
long_description = file: README.md
88
long_description_content_type = text/markdown
99
project_urls =
1010
Documentation=https://github.com/pysnippet/fastapi-oauth2/
1111
Source Code=https://github.com/pysnippet/fastapi-oauth2/
1212
keywords =
1313
python
14+
sso
1415
auth
1516
login
16-
social
17+
oauth
1718
oauth2
19+
social
1820
fastapi
21+
allauth
22+
security
23+
middleware
1924
authentication
2025
license = MIT
2126
license_files = LICENSE
2227
platforms = unix, linux, osx, win32
2328
classifiers =
2429
Operating System :: OS Independent
25-
Development Status :: 1 - Planning
30+
Development Status :: 2 - Pre-Alpha
2631
Framework :: FastAPI
2732
Programming Language :: Python
2833
Programming Language :: Python :: 3
@@ -37,7 +42,12 @@ classifiers =
3742
packages =
3843
fastapi_oauth2
3944
install_requires =
40-
fastapi>=0.85.2
45+
fastapi>=0.68.1
46+
httpx>=0.22.0
47+
oauthlib>=3.2.2
48+
python-jose>=3.3.0
49+
social-auth-core>=4.4.2
50+
starlette>=0.19.1
4151
include_package_data = yes
4252
python_requires = >=3.6
4353
package_dir =

Diff for: ‎src/fastapi_oauth2/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.0.0"
1+
__version__ = "1.0.0-alpha"

Diff for: ‎src/fastapi_oauth2/config.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os
22
from typing import List
3+
from typing import Union
34

45
from .client import OAuth2Client
56

@@ -16,7 +17,7 @@ def __init__(
1617
*,
1718
allow_http: bool = False,
1819
jwt_secret: str = "",
19-
jwt_expires: int = 900,
20+
jwt_expires: Union[int, str] = 900,
2021
jwt_algorithm: str = "HS256",
2122
clients: List[OAuth2Client] = None,
2223
):

Diff for: ‎src/fastapi_oauth2/core.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
from urllib.parse import urljoin
1010

1111
import httpx
12-
import requests
1312
from oauthlib.oauth2 import WebApplicationClient
1413
from social_core.backends.oauth import BaseOAuth2
1514
from social_core.strategy import BaseStrategy
@@ -38,7 +37,7 @@ def get_setting(self, name):
3837

3938
@staticmethod
4039
def get_json(url, method='GET', *args, **kwargs):
41-
return requests.request(method, url, *args, **kwargs)
40+
return httpx.request(method, url, *args, **kwargs)
4241

4342

4443
class OAuth2Core:

Diff for: ‎src/fastapi_oauth2/middleware.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def jwt_decode(cls, token: str) -> dict:
6363

6464
@classmethod
6565
def jwt_create(cls, token_data: dict) -> str:
66-
expire = datetime.utcnow() + timedelta(minutes=cls.expires)
66+
expire = datetime.utcnow() + timedelta(seconds=cls.expires)
6767
return cls.jwt_encode({**token_data, "exp": expire})
6868

6969

@@ -85,7 +85,10 @@ def __init__(self, config: OAuth2Config) -> None:
8585
Auth.register_client(client)
8686

8787
async def authenticate(self, request: Request) -> Optional[Tuple["Auth", "User"]]:
88-
authorization = request.cookies.get("Authorization")
88+
authorization = request.headers.get(
89+
"Authorization",
90+
request.cookies.get("Authorization"),
91+
)
8992
scheme, param = get_authorization_scheme_param(authorization)
9093

9194
if not scheme or not param:

Diff for: ‎src/fastapi_oauth2/router.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ async def token(request: Request, provider: str):
1616

1717

1818
@router.get("/logout")
19-
async def logout(request: Request):
19+
def logout(request: Request):
2020
response = RedirectResponse(request.base_url)
2121
response.delete_cookie("Authorization")
2222
return response

Diff for: ‎templates/index.html

-39
This file was deleted.

Diff for: ‎demo/__init__.py renamed to ‎tests/__init__.py

File renamed without changes.

Diff for: ‎tests/conftest.py

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import importlib
2+
import os
3+
4+
import pytest
5+
import social_core.backends as backends
6+
from social_core.backends.oauth import BaseOAuth2
7+
8+
package_path = backends.__path__[0]
9+
10+
11+
@pytest.fixture
12+
def backends():
13+
backend_instances = []
14+
for module in os.listdir(package_path):
15+
try:
16+
module_instance = importlib.import_module("social_core.backends.%s" % module[:-3])
17+
backend_instances.extend([
18+
attr for attr in module_instance.__dict__.values()
19+
if type(attr) is type and all([
20+
issubclass(attr, BaseOAuth2),
21+
attr is not BaseOAuth2,
22+
])
23+
])
24+
except ImportError:
25+
continue
26+
return backend_instances

Diff for: ‎tests/requirements.txt

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
tox==3.24.3
2+
trio>=0.19.0
3+
pytest==6.2.5
4+
httpx==0.22.0
5+
appengine-python-standard # for loading the gae backend

Diff for: ‎tests/test_oauth2_middleware.py

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import pytest
2+
from fastapi import FastAPI
3+
from httpx import AsyncClient
4+
from social_core.backends.github import GithubOAuth2
5+
6+
from fastapi_oauth2.client import OAuth2Client
7+
from fastapi_oauth2.core import OAuth2Core
8+
from fastapi_oauth2.middleware import OAuth2Middleware
9+
from fastapi_oauth2.router import router as oauth2_router
10+
11+
app = FastAPI()
12+
13+
app.include_router(oauth2_router)
14+
app.add_middleware(OAuth2Middleware, config={
15+
"allow_http": True,
16+
"clients": [
17+
OAuth2Client(
18+
backend=GithubOAuth2,
19+
client_id="test_id",
20+
client_secret="test_secret",
21+
),
22+
],
23+
})
24+
25+
26+
@pytest.mark.anyio
27+
async def test_auth_redirect():
28+
async with AsyncClient(app=app, base_url="http://test") as client:
29+
response = await client.get("/oauth2/github/auth")
30+
assert response.status_code == 303 # Redirect
31+
32+
33+
@pytest.mark.anyio
34+
async def test_core_init(backends):
35+
for backend in backends:
36+
try:
37+
OAuth2Core(OAuth2Client(
38+
backend=backend,
39+
client_id="test_client_id",
40+
client_secret="test_client_secret",
41+
))
42+
except (NotImplementedError, Exception):
43+
assert False

Diff for: ‎tox.ini

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[tox]
2+
envlist =
3+
py{36,38,310,311}-fastapi68
4+
py{37,39,310,311}-fastapi{84,99}
5+
6+
[testenv]
7+
deps =
8+
fastapi99: fastapi>=0.99.0
9+
fastapi84: fastapi<=0.84.0
10+
fastapi68: fastapi<=0.68.1
11+
-r{toxinidir}/tests/requirements.txt
12+
allowlist_externals = sh
13+
commands =
14+
sh build.sh
15+
pytest

0 commit comments

Comments
 (0)
Please sign in to comment.