question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[QUESTION] Parametrizing a class dependency on a sub-dependency

See original GitHub issue

First check

  • I used the GitHub search to find a similar issue and didn’t find it.
  • I searched the FastAPI documentation, with the integrated search.
  • I already searched in Google “How to X in FastAPI” and didn’t find any information.

Description

When dealing with sub-dependencies, the documentation introduces e.g.

oauth2_scheme = OAuth2PasswordBearer(...)
async def get_current_user(token: str = Depends(oauth2_scheme)):
    ...

Chained dependencies / sub-dependencies can be achieved with free functions.

The documentation also introduces parametrized dependencies in the form of callable instances, e.g.

class FixedContentQueryChecker:
    def __init__(self, fixed_content: str):
        self.fixed_content = fixed_content

    def __call__(self, q: str = ""):
        if q:
            return self.fixed_content in q
        return False

checker = FixedContentQueryChecker("bar")

@app.get("/query-checker/")
async def read_query_check(fixed_content_included: bool = Depends(checker)):
    return {"fixed_content_in_query": fixed_content_included}

Now is there a way I can write a CurrentUserFetcher class, parametrized on an OAuth2 instance?

This would take the following form:

oauth2_scheme = OAuth2PasswordBearer(...)

get_current_user = CurrentUserFetcher(oauth2_scheme)

@app.get("/foo/")
async def get_foo(current_user: User = Depends(get_current_user)):
  ...

Thank you

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:3
  • Comments:16 (5 by maintainers)

github_iconTop GitHub Comments

5reactions
alvarogf97commented, Feb 26, 2020

Maybe something like this?

import datetime
import jwt
import pydantic
from fastapi import HTTPException
from fastapi import Depends
from fastapi.security import OAuth2PasswordBearer
from fastapi.security import OAuth2PasswordRequestForm
from starlette import status


TOKEN_AUDIENCE = 'urn:users'
TOKEN_URL = '/auth/login/'
TOKEN_LIFETIME_SECONDS = 1000
TOKEN_ALGORITHM = 'HS256'
TOKEN_SECRET = 'yoursupersecret'


authorization = OAuth2PasswordBearer(TOKEN_URL, auto_error=False)


class User():
  """User data class."""

  id: int
  is_verified: bool
  create_date: datetime.datetime
  password: str
  name: str
  email: pydantic.EmailStr
  birth_date: datetime.date


async def get_current_user(
    token: str = Depends(authorization)
) -> User:
  """Get current user from the request metadata.

  Args:
    token (str): authorization token.

  Raises:
    HTTPException: whether there is no authenticated user.

  Returns:
    models.user.schemas.User: the user who performs the request.
  """
  if not token:
    _unauthorized()
  try:
    data = await decode_token(token, TOKEN_AUDIENCE)
    user_id = data.get('user_id')
    if not user_id:
      _unauthorized()
    # Get your user here from database
    return await User.get(user_id)
  except jwt.PyJWTError:
    _unauthorized()


async def generate_token(
    user: User,
    audience: str = TOKEN_AUDIENCE,
    lifetime: int = TOKEN_LIFETIME_SECONDS
) -> str:
  """Generates authentication token for the given user.

  Args:
    user (models.user.schemas.User): the user the token will be generated for.
    audience (str, optional): token audience. Defaults to st.TOKEN_AUDIENCE.
    lifetime (int, optional): token seconds lifetime. Defaults to
      st.TOKEN_LIFETIME_SECONDS

  Returns:
    str: the user authentication token.
  """
  expire = (
      datetime.datetime.utcnow() +
      datetime.timedelta(seconds=int(lifetime)))
  data = {
      'user_id': user.id,
      'aud': audience,
      'exp': expire
  }
  return jwt.encode(
      data, TOKEN_SECRET, algorithm=TOKEN_ALGORITHM).decode('utf-8')


async def decode_token(token: str, audience: str = TOKEN_AUDIENCE):
  """Decode token.

  Args:
    token (str): the token which will be decoded.
    audience (str, optional): token audience. Defaults to st.TOKEN_AUDIENCE.

  Raises:
    jwt.PyJWTError: whether the given token is expired or malformed.

  Returns:
    dict: token decoded data.
  """
  return jwt.decode(
      token, TOKEN_SECRET,
      audience=audience, algorithms=[TOKEN_ALGORITHM])


def verify_and_update_password(
    plain_password: str, hashed_password: str
) -> Tuple[bool, str]:
  """Verify the given password by matching it with the hashed one.

  Args:
    plain_password (str): plain password.
    hashed_password (str): hashed password.

  Returns:
    Tuple[bool, str]: a tuple which contains if the password has been verified
    and the new password hash.
  """
  return pwd_context.verify_and_update(plain_password, hashed_password)


async def authenticate(
    credentials: OAuth2PasswordRequestForm
) -> User:
  """Authenticates a user with the given credentials.

  Args:
    credentials (OAuth2PasswordRequestForm): user credentials.

  Returns:
    schemas.User: the user who match with the given credentials.
  """
  user = await User.get_by_email(credentials.username)

  verified, updated_password_hash = verify_and_update_password(
      credentials.password, user.password)

  if not verified:
    raise Exception('User not verified')

  # Update password hash to a more robust one if needed
  if updated_password_hash is not None:
    user.hashed_password = updated_password_hash
    await User.update(user)

  return user


def _unauthorized(headers=None):
  """Raise HTTPException with 401 status code.

  Args:
    headers: (dict, optional): additional headers to the response. Defaults to
      None.

  Raises:
    HTTPException: elevate handler unauthorized exception.
  """
  detail = 'Not authenticated'
  raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=detail)

Take care with User get get_by_email update, the are operations examples and are not implemented in the example code.

4reactions
tiangolocommented, Jun 17, 2020

Hmm, you can probably do something like:

from typing import Callable
from fastapi import FastAPI, Depends
from fastapi.security import OAuth2PasswordBearer

oauth2_scheme = OAuth2PasswordBearer(...)

app = FastAPI()


def get_dependency(auth: Callable, some_config: str):
    def dependency(value: str = Depends(auth)):
        print(some_config)
        return value

    return dependency


@app.get("/foo/")
async def get_foo(
    current_user: dict = Depends(get_dependency(oauth2_scheme, some_config="bar"))
):
    return "Hello World"
Read more comments on GitHub >

github_iconTop Results From Across the Web

Advanced Dependencies - FastAPI
All the dependencies we have seen are a fixed function or class. ... the parameters of the instance that we can use to...
Read more >
How to add dependency of one parameter type on another in ...
In other words, I would like to parameterize the element type in the returned list without explicitly making it I or J or...
Read more >
Dependency injection of parameters that depends on each other
Suppose your Body and Motor classes need the Car instance as a parameter. Instead of having the Car depend on Body and Motor...
Read more >
The Simplest Guide to FastAPI Dependency Injection using ...
Learn how to use FastAPI Dependency Injection using Depends keywords to handle dependencies in the form of dict, classes, global dependency.
Read more >
Dependency injection using parameters | F# for fun and profit
Six approaches to dependency injection, Part 2. ... In this post, we'll look at “dependency parameterization” as a way of ... No problem!...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found