python 도 fastAPI 도 잘 모르지만(기본적인 것만 알고 있음)
ai코딩툴을 이용해서 간단한 CRUD 코딩을 해보았습니다.
대략 어떻게 흘러가고, 어떻게 데이터를 관리하는지 알아보기 위해서였습니다.
기본적으로 환경설정용으로 .env, .python-version, requirements.txt 파일을 생성했습니다.
DB_HOST=localhost
DB_NAME=postgres
DB_USER=postgres
DB_PASSWORD=xxxxxx
DB_PORT=5432
requirements.txt 는 pip 패키지목록을 정리해 두고,
pip install -r requirements.txt
로 실행하면 모든 패키지를 한번에 설치해 줍니다.
fastapi==0.104.1
uvicorn==0.24.0
sqlalchemy==2.0.23
psycopg2-binary==2.9.9
python-dotenv==1.0.0
pydantic==2.5.2
jinja2==3.1.3
main.py 파일은 설정과 초기화를 정의합니다.
"""
FastAPI 애플리케이션의 메인 모듈
이 모듈은 FastAPI 애플리케이션의 설정과 초기화를 담당합니다.
"""
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from database.database import engine, Base
from dotenv import load_dotenv
from controller.user_controller import router
# 환경 변수 로드
load_dotenv()
# FastAPI 애플리케이션 생성
app = FastAPI(
title="FastAPI Backend",
description="사용자 관리를 위한 RESTful API",
version="1.0.0"
)
# Jinja2 템플릿 설정
templates = Jinja2Templates(directory="templates")
# 라우터 등록
app.include_router(router)
# 데이터베이스 테이블 생성
Base.metadata.create_all(bind=engine)
@app.get("/", response_class=HTMLResponse)
async def root(request: Request):
"""
루트 경로 핸들러
웹 애플리케이션의 메인 페이지를 반환합니다.
Args:
request (Request): FastAPI의 요청 객체
Returns:
HTMLResponse: index.html 템플릿을 렌더링한 응답
"""
return templates.TemplateResponse("index.html", {"request": request})
환경변수 .env 를 로드하고
FastAPI 애플리케이션을 생성하고
Jinja2 템플릿 설정을 하고
라우터 등록하고
데이터베이스 테이블 생성(없으면) 하고
최초 화면으로 index.html 템플릿을 호출해서 화면을 시작합니다.
여기서 @app.get 에서 app 은 FastAPI 클래스의 '인스턴스' 입니다.
실행은 아래 명령어를 입력해서 실행합니다.
uvicorn main:app --reload
localhost:8000
으로 브라우저에서 실행하면
저는 이런 화면이 나타납니다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FastAPI Backend</title>
<style>
.....
</style>
<script src="https://accounts.google.com/gsi/client" async defer></script>
</head>
<body>
<div class="container">
<h1>Welcome to FastAPI Backend</h1>
<div id="loginStatus">
<!-- Login status will be shown here -->
</div>
<div id="googleSignInButton">
<div id="g_id_onload"
data-client_id="881299416391-xxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com"
data-callback="handleCredentialResponse"
data-auto_prompt="false">
</div>
<div class="g_id_signin"
data-type="standard"
data-size="large"
data-theme="outline"
data-text="sign_in_with"
data-shape="rectangular"
data-logo_alignment="left">
</div>
</div>
</div>
<script>
let isLoggedIn = false;
let currentUserId = null;
function showLoginStatus(message, isError = false) {
const loginStatus = document.getElementById('loginStatus');
loginStatus.innerHTML = `
<div class="user-info" ${isError ? 'style="color: red;"' : ''}>
${message}
</div>
`;
}
function updateUIForLoggedInUser(userData, payload) {
isLoggedIn = true;
currentUserId = userData.id;
const googleSignInButton = document.getElementById('googleSignInButton');
googleSignInButton.style.display = 'none';
showLoginStatus(`
<p>로그인 중: ${payload.name}</p>
<p>이메일: ${payload.email}</p>
<p>DB에 저장된 ID: ${userData.id}</p>
<button class="logout-btn" onclick="handleLogout()">로그아웃</button>
<button class="btn" onclick="viewLoginHistory()">로그인 기록 보기</button>
`);
}
function updateUIForLoggedOutUser() {
isLoggedIn = false;
currentUserId = null;
const googleSignInButton = document.getElementById('googleSignInButton');
googleSignInButton.style.display = 'block';
showLoginStatus('<p>로그인이 필요합니다</p>');
}
async function handleCredentialResponse(response) {
if (response.credential) {
// JWT 디코딩
const base64Url = response.credential.split('.')[1];
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
const jsonPayload = decodeURIComponent(atob(base64).split('').map(function(c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
const payload = JSON.parse(jsonPayload);
try {
// 백엔드 API 호출하여 사용자 정보 저장
const response = await fetch('/users/google-login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: payload.name,
email: payload.email
})
});
if (!response.ok) {
throw new Error('Failed to save user data');
}
const userData = await response.json();
updateUIForLoggedInUser(userData, payload);
} catch (error) {
console.error('Error:', error);
showLoginStatus('로그인 정보 저장 중 오류가 발생했습니다.', true);
}
}
}
async function handleLogout() {
if (!currentUserId) {
updateUIForLoggedOutUser();
return;
}
try {
// 백엔드에 로그아웃 기록
const response = await fetch(`/users/google-logout/${currentUserId}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
});
if (!response.ok) {
throw new Error('Failed to save logout data');
}
google.accounts.id.disableAutoSelect();
google.accounts.id.revoke(localStorage.getItem('userEmail'), done => {
localStorage.removeItem('userEmail');
updateUIForLoggedOutUser();
});
} catch (error) {
console.error('Error:', error);
showLoginStatus('로그아웃 처리 중 오류가 발생했습니다.', true);
}
}
async function viewLoginHistory() {
if (!currentUserId) {
showLoginStatus('로그인이 필요합니다', true);
return;
}
try {
const response = await fetch(`/users/login-history/${currentUserId}`);
if (!response.ok) {
throw new Error('Failed to fetch login history');
}
const history = await response.json();
let historyHtml = '<div class="login-history">';
historyHtml += '<h3>로그인/로그아웃 기록</h3>';
historyHtml += '<ul>';
history.forEach(record => {
const actionType = record.action_type === 'login' ? '로그인' : '로그아웃';
const time = record.action_time;
const formattedTime = `${time.slice(0,4)}-${time.slice(4,6)}-${time.slice(6,8)} ${time.slice(8,10)}:${time.slice(10,12)}:${time.slice(12,14)}`;
historyHtml += `<li>${actionType} - ${formattedTime}</li>`;
});
historyHtml += '</ul></div>';
showLoginStatus(historyHtml);
} catch (error) {
console.error('Error:', error);
showLoginStatus('로그인 기록을 가져오는 중 오류가 발생했습니다.', true);
}
}
// 페이지 로드 시 로그인 상태 체크
window.onload = function() {
if (!google.accounts.id.initialize) {
showLoginStatus('로그인이 필요합니다');
}
}
</script>
</body>
</html>
최초 실행할때 database 에 연결하고 테이블이 존재하는지 확인하고 없으면 테이블도 생성해줍니다.
database/database.py
"""
데이터베이스 연결 및 세션 관리
이 모듈은 PostgreSQL 데이터베이스 연결 설정과 세션 관리를 담당합니다.
"""
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
import os
# 데이터베이스 연결 URL 설정
# 환경 변수에서 URL을 가져오거나, 기본값 사용
SQLALCHEMY_DATABASE_URL = os.getenv(
"DATABASE_URL",
"postgresql://postgres:xxxxxxxx@localhost/postgres"
)
# 데이터베이스 엔진 생성
engine = create_engine(SQLALCHEMY_DATABASE_URL)
# 세션 팩토리 생성
# autocommit=False: 명시적 커밋 필요
# autoflush=False: 명시적 플러시 필요
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
# 모든 모델의 기본 클래스
Base = declarative_base()
def get_db():
"""
데이터베이스 세션 생성 및 관리를 위한 제너레이터 함수
Yields:
Session: 데이터베이스 세션 객체
Note:
FastAPI의 의존성 주입 시스템에서 사용됩니다.
세션은 요청 처리가 완료되면 자동으로 닫힙니다.
"""
db = SessionLocal()
try:
yield db
finally:
db.close()
user_test 라는 테이블 생성
model/user.py
"""
사용자 데이터베이스 모델 정의
이 모듈은 사용자 테이블의 구조와 기본 설정을 정의합니다.
"""
from sqlalchemy import Column, Integer, String, DateTime
from sqlalchemy.orm import relationship
from database.database import Base
from datetime import datetime
def get_current_time():
"""
현재 시간을 'yyyymmddhhmmss' 형식의 문자열로 반환
Returns:
str: 'yyyymmddhhmmss' 형식의 현재 시간 (예: '20250326123456')
"""
return datetime.now().strftime('%Y%m%d%H%M%S')
class UserTest(Base):
"""
사용자 정보를 저장하는 데이터베이스 모델
Attributes:
id (int): 사용자 고유 식별자 (기본키)
username (str): 사용자 이름 (고유값)
email (str): 이메일 주소 (고유값)
created_at (str): 계정 생성 시간 ('yyyymmddhhmmss' 형식)
updated_at (str): 정보 수정 시간 ('yyyymmddhhmmss' 형식)
"""
__tablename__ = "user_test" # 테이블 이름
# 컬럼 정의
id = Column(Integer, primary_key=True, index=True) # 기본키
username = Column(String, unique=True, index=True) # 사용자 이름 (유니크 인덱스)
email = Column(String, unique=True, index=True) # 이메일 (유니크 인덱스)
created_at = Column(String, default=get_current_time) # 생성 시간
updated_at = Column(String, default=get_current_time, onupdate=get_current_time) # 수정 시간
# 관계 정의
login_history = relationship("LoginInfo", back_populates="user")
이렇게 하면서 postgreSql 에서 자동으로 테이블을 생성합니다.
-- public.user_test definition
-- Drop table
-- DROP TABLE public.user_test;
CREATE TABLE public.user_test (
id serial4 NOT NULL,
username varchar NULL,
email varchar NULL,
created_at varchar NULL,
updated_at varchar NULL,
CONSTRAINT user_test_pkey PRIMARY KEY (id)
);
CREATE UNIQUE INDEX ix_user_test_email ON public.user_test USING btree (email);
CREATE INDEX ix_user_test_id ON public.user_test USING btree (id);
CREATE UNIQUE INDEX ix_user_test_username ON public.user_test USING btree (username);
나머지는 다음에...
'Python' 카테고리의 다른 글
KOSIS 통계정보를 이용한 데이터 조회 방법 (0) | 2025.03.29 |
---|---|
python+fastAPI+postgresql 로 구성된 환경에서 간단한 CRUD 구현2 (0) | 2025.03.28 |
FastAPI + Python 으로 구성한 Back-End (0) | 2025.03.26 |
table 에 crud 할 수 있는 python code (0) | 2025.03.10 |
db 연결하는 python 코드 (0) | 2025.03.10 |