hackforla / HomeUniteUs

We're working with community non-profits who have a Host Home or empty bedrooms initiative to develop a workflow management tool to make the process scalable (across all providers), reduce institutional bias, and effectively capture data.
https://homeunite.us/
GNU General Public License v2.0
38 stars 21 forks source link

Research Dependency Injection in FastAPI #777

Closed lasryariel closed 2 weeks ago

lasryariel commented 3 weeks ago

Overview

As part of our ongoing effort to improve code readability and accessibility for junior developers, we are exploring the use of Dependency Injection (DI) in FastAPI. The goal is to leverage FastAPI’s DI capabilities to create a more modular, testable, and maintainable codebase.

Action Items

  1. Understand FastAPI's Dependency Injection System

    • [x] Research how Dependency Injection works in FastAPI.
    • [x] Identify best practices for implementing DI in FastAPI, with a focus on readability and simplicity.
  2. Evaluate Current Codebase for DI Opportunities

    • [x] Review the current FastAPI codebase to identify areas where Dependency Injection could simplify or improve the architecture.
    • [x] Document the parts of the code that could benefit from DI, such as services, database connections, or third-party integrations.
  3. Decision Record

Resources/Instructions

A well-researched assessment of Dependency Injection system in FastAPI and documentation of areas to implement.

tylerthome commented 2 weeks ago

So far from a brief look at the docs, it looks like the Depends() mechanism available in FastAPI will be handy for abstracting some common processes (along with middlewares), but a bit different than DI within OOP frameworks as the top-level injected Callable's only passed arguments will be parameters equivalent to the Path operations within FastAPI.

Thinking about database, logging, etc dependencies we might consider using global singletons, and allow the ContextManager to handle any open and close operations on e.g. connections.

Overall so far it looks like existing code will port without too much headache, but @paulespinosa may catch something I missed on this read through.

paulespinosa commented 2 weeks ago

@tylerthome got the basics down. I also second the assertion that porting the existing code base can be done.

The Dependency Injection system in FastAPI is easy to use and capable of doing lots for us. The tutorial is extensive https://fastapi.tiangolo.com/tutorial/dependencies/ with details that provide guidance on injecting database dependencies, security related dependencies, and more. In addition, the Advanced provides another way to make an instance of a class callable and use it as a dependency.

The tutorial tells us FastAPI will create a Context Manager for any dependency with yield and also provides guidance on how to create custom Context Managers. https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-with-yield/#what-are-context-managers.

The tutorial on writing bigger applications guides us to put dependencies into its own module. https://fastapi.tiangolo.com/tutorial/bigger-applications/#dependencies. This can aid us in maintaining easy to understand code structure.

For logging, it appears the standard is to use Python's logging facility for all logging. The logger can be customized at startup and removes the need for associating DI and logging. https://docs.python.org/3/library/logging.html

import logging
import mylib
logger = logging.getLogger(__name__)

The FastAPI project template also uses this technique for logging. For example: https://github.com/fastapi/full-stack-fastapi-template/blob/master/backend/app/backend_pre_start.py

Regarding, best practices. I think it's too early to know what they are until we have designed the API's architecture(s).

Below is a just a brainstorm for how DI could be used to inject the current user as a guest and case manager.

@router.post("/intake_profile")
def submit_intake_profile(intake_profile: IntakeProfile, guest: Annotated[Guest, Depends(get_current_user_as_guest)], case_manager: Annotated[CaseManager, Depends(get_case_manager)]):
    case = case_manager.caseFor(guest)
    case.submit_intake_profile(intake_profile)
lasryariel commented 2 weeks ago

Conversation between Ariel and Paul 9/4

As a follow up we need to design for both infrastructure and business objects

Infrastructure

Business objects

Current state of dependency injection in HUU code

lasryariel commented 2 weeks ago

Decision Record Created: https://github.com/hackforla/HomeUniteUs/wiki/Migrating-from-Flask-to-FastAPI