Skip to content

Commit

Permalink
Add support for Python 3.12
Browse files Browse the repository at this point in the history
These changes:
  * enable testing for Python 3.12 on CI
  * update classifiers at pyproject.toml
  * fix deprecation warning for tutorials
  • Loading branch information
Jamim committed Nov 17, 2023
1 parent 4806203 commit 6a803e9
Show file tree
Hide file tree
Showing 16 changed files with 67 additions and 35 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Expand Up @@ -42,7 +42,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11"]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
pydantic-version: ["pydantic-v1", "pydantic-v2"]
fail-fast: false
steps:
Expand Down
7 changes: 4 additions & 3 deletions docs_src/security/tutorial004.py
@@ -1,8 +1,9 @@
from datetime import datetime, timedelta
from datetime import timedelta
from typing import Union

from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from fastapi.utils import utcnow
from jose import JWTError, jwt
from passlib.context import CryptContext
from pydantic import BaseModel
Expand Down Expand Up @@ -78,9 +79,9 @@ def authenticate_user(fake_db, username: str, password: str):
def create_access_token(data: dict, expires_delta: Union[timedelta, None] = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
expire = utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
expire = utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
Expand Down
7 changes: 4 additions & 3 deletions docs_src/security/tutorial004_an.py
@@ -1,8 +1,9 @@
from datetime import datetime, timedelta
from datetime import timedelta
from typing import Union

from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from fastapi.utils import utcnow
from jose import JWTError, jwt
from passlib.context import CryptContext
from pydantic import BaseModel
Expand Down Expand Up @@ -79,9 +80,9 @@ def authenticate_user(fake_db, username: str, password: str):
def create_access_token(data: dict, expires_delta: Union[timedelta, None] = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
expire = utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
expire = utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
Expand Down
7 changes: 4 additions & 3 deletions docs_src/security/tutorial004_an_py310.py
@@ -1,8 +1,9 @@
from datetime import datetime, timedelta
from datetime import timedelta
from typing import Annotated

from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from fastapi.utils import utcnow
from jose import JWTError, jwt
from passlib.context import CryptContext
from pydantic import BaseModel
Expand Down Expand Up @@ -78,9 +79,9 @@ def authenticate_user(fake_db, username: str, password: str):
def create_access_token(data: dict, expires_delta: timedelta | None = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
expire = utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
expire = utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
Expand Down
7 changes: 4 additions & 3 deletions docs_src/security/tutorial004_an_py39.py
@@ -1,8 +1,9 @@
from datetime import datetime, timedelta
from datetime import timedelta
from typing import Annotated, Union

from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from fastapi.utils import utcnow
from jose import JWTError, jwt
from passlib.context import CryptContext
from pydantic import BaseModel
Expand Down Expand Up @@ -78,9 +79,9 @@ def authenticate_user(fake_db, username: str, password: str):
def create_access_token(data: dict, expires_delta: Union[timedelta, None] = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
expire = utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
expire = utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
Expand Down
7 changes: 4 additions & 3 deletions docs_src/security/tutorial004_py310.py
@@ -1,7 +1,8 @@
from datetime import datetime, timedelta
from datetime import timedelta

from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from fastapi.utils import utcnow
from jose import JWTError, jwt
from passlib.context import CryptContext
from pydantic import BaseModel
Expand Down Expand Up @@ -77,9 +78,9 @@ def authenticate_user(fake_db, username: str, password: str):
def create_access_token(data: dict, expires_delta: timedelta | None = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
expire = utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
expire = utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
Expand Down
7 changes: 4 additions & 3 deletions docs_src/security/tutorial005.py
@@ -1,4 +1,4 @@
from datetime import datetime, timedelta
from datetime import timedelta
from typing import List, Union

from fastapi import Depends, FastAPI, HTTPException, Security, status
Expand All @@ -7,6 +7,7 @@
OAuth2PasswordRequestForm,
SecurityScopes,
)
from fastapi.utils import utcnow
from jose import JWTError, jwt
from passlib.context import CryptContext
from pydantic import BaseModel, ValidationError
Expand Down Expand Up @@ -93,9 +94,9 @@ def authenticate_user(fake_db, username: str, password: str):
def create_access_token(data: dict, expires_delta: Union[timedelta, None] = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
expire = utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
expire = utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
Expand Down
7 changes: 4 additions & 3 deletions docs_src/security/tutorial005_an.py
@@ -1,4 +1,4 @@
from datetime import datetime, timedelta
from datetime import timedelta
from typing import List, Union

from fastapi import Depends, FastAPI, HTTPException, Security, status
Expand All @@ -7,6 +7,7 @@
OAuth2PasswordRequestForm,
SecurityScopes,
)
from fastapi.utils import utcnow
from jose import JWTError, jwt
from passlib.context import CryptContext
from pydantic import BaseModel, ValidationError
Expand Down Expand Up @@ -94,9 +95,9 @@ def authenticate_user(fake_db, username: str, password: str):
def create_access_token(data: dict, expires_delta: Union[timedelta, None] = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
expire = utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
expire = utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
Expand Down
7 changes: 4 additions & 3 deletions docs_src/security/tutorial005_an_py310.py
@@ -1,4 +1,4 @@
from datetime import datetime, timedelta
from datetime import timedelta
from typing import Annotated

from fastapi import Depends, FastAPI, HTTPException, Security, status
Expand All @@ -7,6 +7,7 @@
OAuth2PasswordRequestForm,
SecurityScopes,
)
from fastapi.utils import utcnow
from jose import JWTError, jwt
from passlib.context import CryptContext
from pydantic import BaseModel, ValidationError
Expand Down Expand Up @@ -93,9 +94,9 @@ def authenticate_user(fake_db, username: str, password: str):
def create_access_token(data: dict, expires_delta: timedelta | None = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
expire = utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
expire = utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
Expand Down
7 changes: 4 additions & 3 deletions docs_src/security/tutorial005_an_py39.py
@@ -1,4 +1,4 @@
from datetime import datetime, timedelta
from datetime import timedelta
from typing import Annotated, List, Union

from fastapi import Depends, FastAPI, HTTPException, Security, status
Expand All @@ -7,6 +7,7 @@
OAuth2PasswordRequestForm,
SecurityScopes,
)
from fastapi.utils import utcnow
from jose import JWTError, jwt
from passlib.context import CryptContext
from pydantic import BaseModel, ValidationError
Expand Down Expand Up @@ -93,9 +94,9 @@ def authenticate_user(fake_db, username: str, password: str):
def create_access_token(data: dict, expires_delta: Union[timedelta, None] = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
expire = utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
expire = utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
Expand Down
7 changes: 4 additions & 3 deletions docs_src/security/tutorial005_py310.py
@@ -1,11 +1,12 @@
from datetime import datetime, timedelta
from datetime import timedelta

from fastapi import Depends, FastAPI, HTTPException, Security, status
from fastapi.security import (
OAuth2PasswordBearer,
OAuth2PasswordRequestForm,
SecurityScopes,
)
from fastapi.utils import utcnow
from jose import JWTError, jwt
from passlib.context import CryptContext
from pydantic import BaseModel, ValidationError
Expand Down Expand Up @@ -92,9 +93,9 @@ def authenticate_user(fake_db, username: str, password: str):
def create_access_token(data: dict, expires_delta: timedelta | None = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
expire = utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
expire = utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
Expand Down
7 changes: 4 additions & 3 deletions docs_src/security/tutorial005_py39.py
@@ -1,4 +1,4 @@
from datetime import datetime, timedelta
from datetime import timedelta
from typing import Union

from fastapi import Depends, FastAPI, HTTPException, Security, status
Expand All @@ -7,6 +7,7 @@
OAuth2PasswordRequestForm,
SecurityScopes,
)
from fastapi.utils import utcnow
from jose import JWTError, jwt
from passlib.context import CryptContext
from pydantic import BaseModel, ValidationError
Expand Down Expand Up @@ -93,9 +94,9 @@ def authenticate_user(fake_db, username: str, password: str):
def create_access_token(data: dict, expires_delta: Union[timedelta, None] = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
expire = utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
expire = utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
Expand Down
13 changes: 13 additions & 0 deletions fastapi/_compat.py
@@ -1,6 +1,7 @@
from collections import deque
from copy import copy
from dataclasses import dataclass, is_dataclass
from datetime import datetime
from enum import Enum
from typing import (
Any,
Expand Down Expand Up @@ -627,3 +628,15 @@ def is_uploadfile_sequence_annotation(annotation: Any) -> bool:
is_uploadfile_or_nonable_uploadfile_annotation(sub_annotation)
for sub_annotation in get_args(annotation)
)


# in Python 3.12, datetime.datetime.utcnow() was deprecated
# in favor of datetime.datetime.now(datetime.UTC)
try:
from datetime import UTC
except ImportError:
utcnow = datetime.utcnow
else:

def utcnow() -> datetime:
return datetime.now(UTC)
1 change: 1 addition & 0 deletions fastapi/utils.py
Expand Up @@ -24,6 +24,7 @@
UndefinedType,
Validator,
lenient_issubclass,
utcnow, # noqa: F401
)
from fastapi.datastructures import DefaultPlaceholder, DefaultType
from pydantic import BaseModel, create_model
Expand Down
7 changes: 7 additions & 0 deletions pyproject.toml
Expand Up @@ -36,6 +36,8 @@ classifiers = [
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: Implementation :: CPython",
"Topic :: Internet :: WWW/HTTP :: HTTP Servers",
"Topic :: Internet :: WWW/HTTP",
]
Expand Down Expand Up @@ -107,6 +109,11 @@ filterwarnings = [
"ignore::trio.TrioDeprecationWarning",
# TODO remove pytest-cov
'ignore::pytest.PytestDeprecationWarning:pytest_cov',
# TODO: remove after upgrading SQLAlchemy to a version that includes the following changes
# https://github.com/sqlalchemy/sqlalchemy/commit/59521abcc0676e936b31a523bd968fc157fef0c2
'ignore:datetime\.datetime\.utcfromtimestamp\(\) is deprecated and scheduled for removal in a future version\..*:DeprecationWarning:sqlalchemy',
# TODO: remove after upgrading python-jose to a version that explicitly supports Python 3.12
'ignore:datetime\.datetime\.utcnow\(\) is deprecated and scheduled for removal in a future version\..*:DeprecationWarning:jose',
]

[tool.coverage.run]
Expand Down
2 changes: 1 addition & 1 deletion tests/test_annotated.py
Expand Up @@ -57,7 +57,7 @@ async def unrelated(foo: Annotated[str, object()]):
{
"ctx": {"min_length": 1},
"loc": ["query", "foo"],
"msg": "String should have at least 1 characters",
"msg": "String should have at least 1 character",
"type": "string_too_short",
"input": "",
"url": match_pydantic_error_url("string_too_short"),
Expand Down

0 comments on commit 6a803e9

Please sign in to comment.