I'm trying to use OAuth2 in my fastapi back-end project.
and I
This belows is my code snippet
from fastapi import APIRouter, HTTPException, status, Depends
from fastapi.security.oauth2 import OAuth2PasswordRequestForm
from ..modules import oauth_module
from ..models.user_model import User
from ..schemas import token_schema
router = APIRouter(
prefix="/auth",
tags=["auth"],
)
...
@router.get("/my_info")
async def read_user_info(current_user: User = Depends(oauth_module.get_current_active_user)):
return current_user
and this code return python TypeError like belows
Process SpawnProcess-12:
Traceback (most recent call last):
File "/Users/supergrammer/anaconda3/envs/FastAPI/lib/python3.9/multiprocessing/process.py", line 315, in _bootstrap
self.run()
File "/Users/supergrammer/anaconda3/envs/FastAPI/lib/python3.9/multiprocessing/process.py", line 108, in run
self._target(*self._args, **self._kwargs)
File "/Users/supergrammer/anaconda3/envs/FastAPI/lib/python3.9/site-packages/uvicorn/subprocess.py", line 76, in subprocess_started
target(sockets=sockets)
File "/Users/supergrammer/anaconda3/envs/FastAPI/lib/python3.9/site-packages/uvicorn/server.py", line 50, in run
loop.run_until_complete(self.serve(sockets=sockets))
File "/Users/supergrammer/anaconda3/envs/FastAPI/lib/python3.9/asyncio/base_events.py", line 642, in run_until_complete
return future.result()
File "/Users/supergrammer/anaconda3/envs/FastAPI/lib/python3.9/site-packages/uvicorn/server.py", line 57, in serve
config.load()
File "/Users/supergrammer/anaconda3/envs/FastAPI/lib/python3.9/site-packages/uvicorn/config.py", line 318, in load
self.loaded_app = import_from_string(self.app)
File "/Users/supergrammer/anaconda3/envs/FastAPI/lib/python3.9/site-packages/uvicorn/importer.py", line 22, in import_from_string
module = importlib.import_module(module_str)
File "/Users/supergrammer/anaconda3/envs/FastAPI/lib/python3.9/importlib/__init__.py", line 127, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "<frozen importlib._bootstrap>", line 1030, in _gcd_import
File "<frozen importlib._bootstrap>", line 1007, in _find_and_load
File "<frozen importlib._bootstrap>", line 986, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 680, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 855, in exec_module
File "<frozen importlib._bootstrap>", line 228, in _call_with_frames_removed
File "/Users/supergrammer/GitRepository/AI_Stock_Helper/./user_app/main.py", line 6, in <module>
from .routers import user_router, user_auth_router
File "/Users/supergrammer/GitRepository/AI_Stock_Helper/./user_app/routers/user_auth_router.py", line 29, in <module>
async def read_user_info(current_user: User = Depends(oauth_module.get_current_active_user)):
File "/Users/supergrammer/anaconda3/envs/FastAPI/lib/python3.9/site-packages/fastapi/routing.py", line 564, in decorator
self.add_api_route(
File "/Users/supergrammer/anaconda3/envs/FastAPI/lib/python3.9/site-packages/fastapi/routing.py", line 509, in add_api_route
route = route_class(
File "/Users/supergrammer/anaconda3/envs/FastAPI/lib/python3.9/site-packages/fastapi/routing.py", line 393, in __init__
self.dependant = get_dependant(path=self.path_format, call=self.endpoint)
File "/Users/supergrammer/anaconda3/envs/FastAPI/lib/python3.9/site-packages/fastapi/dependencies/utils.py", line 295, in get_dependant
sub_dependant = get_param_sub_dependant(
File "/Users/supergrammer/anaconda3/envs/FastAPI/lib/python3.9/site-packages/fastapi/dependencies/utils.py", line 117, in get_param_sub_dependant
return get_sub_dependant(
File "/Users/supergrammer/anaconda3/envs/FastAPI/lib/python3.9/site-packages/fastapi/dependencies/utils.py", line 153, in get_sub_dependant
sub_dependant = get_dependant(
File "/Users/supergrammer/anaconda3/envs/FastAPI/lib/python3.9/site-packages/fastapi/dependencies/utils.py", line 295, in get_dependant
sub_dependant = get_param_sub_dependant(
File "/Users/supergrammer/anaconda3/envs/FastAPI/lib/python3.9/site-packages/fastapi/dependencies/utils.py", line 117, in get_param_sub_dependant
return get_sub_dependant(
File "/Users/supergrammer/anaconda3/envs/FastAPI/lib/python3.9/site-packages/fastapi/dependencies/utils.py", line 153, in get_sub_dependant
sub_dependant = get_dependant(
File "/Users/supergrammer/anaconda3/envs/FastAPI/lib/python3.9/site-packages/fastapi/dependencies/utils.py", line 302, in get_dependant
param_field = get_param_field(
File "/Users/supergrammer/anaconda3/envs/FastAPI/lib/python3.9/site-packages/fastapi/dependencies/utils.py", line 394, in get_param_field
field = create_response_field(
File "/Users/supergrammer/anaconda3/envs/FastAPI/lib/python3.9/site-packages/fastapi/utils.py", line 65, in create_response_field
return response_field(field_info=field_info)
File "pydantic/fields.py", line 342, in pydantic.fields.ModelField.__init__
File "pydantic/fields.py", line 445, in pydantic.fields.ModelField.prepare
File "pydantic/fields.py", line 473, in pydantic.fields.ModelField._set_default_and_type
File "pydantic/fields.py", line 345, in pydantic.fields.ModelField.get_default
File "pydantic/utils.py", line 630, in pydantic.utils.smart_deepcopy
File "/Users/supergrammer/anaconda3/envs/FastAPI/lib/python3.9/copy.py", line 172, in deepcopy
y = _reconstruct(x, memo, *rv)
File "/Users/supergrammer/anaconda3/envs/FastAPI/lib/python3.9/copy.py", line 270, in _reconstruct
state = deepcopy(state, memo)
File "/Users/supergrammer/anaconda3/envs/FastAPI/lib/python3.9/copy.py", line 146, in deepcopy
y = copier(x, memo)
File "/Users/supergrammer/anaconda3/envs/FastAPI/lib/python3.9/copy.py", line 230, in _deepcopy_dict
y[deepcopy(key, memo)] = deepcopy(value, memo)
File "/Users/supergrammer/anaconda3/envs/FastAPI/lib/python3.9/copy.py", line 172, in deepcopy
y = _reconstruct(x, memo, *rv)
File "/Users/supergrammer/anaconda3/envs/FastAPI/lib/python3.9/copy.py", line 270, in _reconstruct
state = deepcopy(state, memo)
File "/Users/supergrammer/anaconda3/envs/FastAPI/lib/python3.9/copy.py", line 146, in deepcopy
y = copier(x, memo)
File "/Users/supergrammer/anaconda3/envs/FastAPI/lib/python3.9/copy.py", line 230, in _deepcopy_dict
y[deepcopy(key, memo)] = deepcopy(value, memo)
File "/Users/supergrammer/anaconda3/envs/FastAPI/lib/python3.9/copy.py", line 172, in deepcopy
y = _reconstruct(x, memo, *rv)
File "/Users/supergrammer/anaconda3/envs/FastAPI/lib/python3.9/copy.py", line 270, in _reconstruct
state = deepcopy(state, memo)
File "/Users/supergrammer/anaconda3/envs/FastAPI/lib/python3.9/copy.py", line 146, in deepcopy
y = copier(x, memo)
File "/Users/supergrammer/anaconda3/envs/FastAPI/lib/python3.9/copy.py", line 230, in _deepcopy_dict
y[deepcopy(key, memo)] = deepcopy(value, memo)
File "/Users/supergrammer/anaconda3/envs/FastAPI/lib/python3.9/copy.py", line 172, in deepcopy
y = _reconstruct(x, memo, *rv)
File "/Users/supergrammer/anaconda3/envs/FastAPI/lib/python3.9/copy.py", line 270, in _reconstruct
state = deepcopy(state, memo)
File "/Users/supergrammer/anaconda3/envs/FastAPI/lib/python3.9/copy.py", line 146, in deepcopy
y = copier(x, memo)
File "/Users/supergrammer/anaconda3/envs/FastAPI/lib/python3.9/copy.py", line 230, in _deepcopy_dict
y[deepcopy(key, memo)] = deepcopy(value, memo)
File "/Users/supergrammer/anaconda3/envs/FastAPI/lib/python3.9/copy.py", line 161, in deepcopy
rv = reductor(4)
TypeError: cannot pickle 'module' object
I don't know why this code return TypeError and I think it may related with async/await.
And I add my oauth_module code snippet below...
from datetime import datetime, timedelta
from fastapi import HTTPException, status, Depends, Security
from fastapi.security.oauth2 import OAuth2PasswordBearer, OAuth2PasswordRequestForm, SecurityScopes
from passlib.context import CryptContext
from jose import JWTError, jwt
from pydantic import ValidationError
from sqlalchemy.orm import Session
from ..models.user_model import User
from ..schemas import token_schema
from ..config.config import get_configurations
from ..config.database import get_db
c = get_configurations()
SECRET_KEY = c.secret_key
HASH_ALGORITHM = c.hash_algorithm
ACCESS_TOKEN_EXPIRE_MINUTES = c.access_token_expire_minutes
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(
tokenUrl="token",
scopes={"userinfo": "gg"}
)
def verify_password(plain, hashed):
return pwd_context.verify(plain, hashed)
def get_hashed_password(plain):
return pwd_context.hash(plain)
def is_authenticated_user(form_data: OAuth2PasswordRequestForm, db: Session = get_db()):
user = db.query(User).filter(User.email == form_data.username).first()
if not user or \
not verify_password(form_data.password, user.password.password_history[-1]):
return False
return user
def create_access_token(data: dict, expire_minutes: int = ACCESS_TOKEN_EXPIRE_MINUTES):
to_encode = data.copy()
to_encode.update(
{"exp": datetime.utcnow() + timedelta(minutes=expire_minutes)})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=HASH_ALGORITHM)
return encoded_jwt
async def get_current_user(security_scopes: SecurityScopes, token: str = Depends(oauth2_scheme), db: Session = get_db()):
authenticate_value = "Bearer" \
+ f' scope="{security_scopes.scope_str}"' if security_scopes.scopes else ''
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": authenticate_value}
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[HASH_ALGORITHM])
email: str = payload.get("email")
if not email:
raise credentials_exception
token_scopes = payload.get("scopes", [])
token_data = token_schema.TokenData(email=email, scopes=token_scopes)
except (JWTError, ValidationError):
raise credentials_exception
user = db.query(User).filter(User.email == email).first()
if not user:
raise credentials_exception
for scope in security_scopes.scopes:
if scope not in token_data.scopes:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Not enough permissions",
headers={"WWW-Authenticate": authenticate_value}
)
return user
async def get_current_active_user(current_user: User = Security(get_current_user, scopes=[])):
if not current_user:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Inactive user")
return current_user
Please Help me... 도와주세요...
On top of the endpoint definition you posted, the important part is indeed in the User model.
# base_model.py
from sqlalchemy import Column, DateTime
from sqlalchemy.sql import func
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class BaseMixin():
created_date = Column(DateTime(timezone=True), default=func.now(), nullable=False)
updated_date = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now(), nullable=False)
# user.py
#... other sqlalchemy imports
from .base_model import Base, BaseMixin
class User(Base, BaseMixin):
__tablename__ = "user"
id = Column(UUID(as_uuid=True), primary_key=True, index=True, nullable=False, default=uuid.uuid4)
email = Column(String(51), unique=True, index=True, nullable=False)
password = relationship( \
"Password",
backref=backref("user", uselist=False),
primaryjoin='foreign(User.id) == remote(Password.id)')
# etc sqlalchemy fields
The fundamental problem is that your User model for which you define a Depends in your endpoint as current_user: User = Depends(oauth_module.get_current_active_user)) is a sqlalchemy model. Sure, the oauth_module.get_current_active_user is a callable, and I'm assuming it works on its own. But since you define the type of the Depends as User, fastapi will look into that model to generate the api documentation and validation. And then fail as it does not understand sqlalchemy models. Fastapi works with pydantic models.
You can see in your stacktrace fastapi calls onto many get_dependant (for your nested Depends structure), followed by get_param_field and create_response_field, which then brings it to pydantic to try to resolve the model.
Your solution would be to not type your Depends, and/or create a proxy class. You can declare a Depends() in the __init__ of any class, which would be where you define this call to get the user from db, and use that class to act as proxy you can type more safely.
class ProxyUser():
def __init__(self, user = Depends(oauth_module.get_current_active_user)):
self.user = user
@router.get("/my_info")
async def read_user_info(current_user = Depends(ProxyUser)):
return current_user.user
In any case, it would be a good idea to keep the public API representation of your user separate from the DB representation, so two separate models would also help on that front.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With