빅데이터 국비 교육

[아이티윌 빅데이터 52기] LAB 10 | Python Basic | Flask 웹 프로그래밍

datahaseo 2025. 11. 3. 10:20

*모든 저작권은 IT WILL 이광호 선생님

 

LAB -10  Python Basic |  Flask 웹 프로그래밍

<학습 목표>

1. 웹 서버의 이해

2. Restful API 의 이해

3. 데이터 조회 API

4. 데이터 입력/수정/삭제 API

 

 

 

1. 웹 서버의 이해

웹 서버에는 크게 두 주체가 존재함

요청을 보내는 클라이언트 (ex 소비자, 터미널, 웹브라우저) ,

요청을 처리하고 대응하는 서버 (ex 판매자 ,DB, 웹 서버)

 

플라스크는 불필요한 기능을 최소화한 "마이크로" "웹 프레임워크"

코드에 대한 기본 구조와 틀이 이미 정해져있음

 

 

 

Flask 프로그램 기본 구조

#Flask 프로그램의 기본 구조
#프로그램 최 상단에서 채키지 참조 처리와 Flask 메인 객체를 생성
# 프로그램 마지막에 생성한 Flask 메인 객체 가동


# [1] 패키지 참조
from flask import Flask, render_template

# [2] Flask 의 메인 객체를 생성 (__name__ 은 소스 파일의 이름)
app=Flask(__name__)


# [3] 웹 페이지 함수 구현하기 (url 요청에 실행될 함수를 연결하는 처리를 라우팅이라고 함)

@app.route("/hello")
def hello():

  #웹 브라우저에 전달할 본문
  html =""" Hello Flask!
            This is Flask Wevbpage
"""
  #웹 브라우저에게 문자열 전달
  return html


#[4] Flask 웹 서버 가능
if __name__ =="__main__":
  app.run(port=9091, debug= True)

 

flask 클래스로 app 객체 생성

app 에 필요한 항목들 넣어서 그걸 마지막에 실행 시킴

이 파일을 직접 실행

 

 

라우팅 - 이 웹페이지에 부여하고자 하는 url (주소에 함수들을 분배한다)

웹브라우저에 접속하면 그 밑에 있는 함수들이 실행

 

(소스코드에서는 줄바꿈을 했어도 브라우저에서는 줄바꿈을 처리하진 못함)

 

 

 

 

 

웹 브라우저가 식별할 수 있는 규칙에 맞춰 문자열 리턴하기

#Flask 프로그램의 기본 구조
#프로그램 최 상단에서 채키지 참조 처리와 Flask 메인 객체를 생성
# 프로그램 마지막에 생성한 Flask 메인 객체 가동


# [1] 패키지 참조
from flask import Flask, render_template

# [2] Flask 의 메인 객체를 생성 (__name__ 은 소스 파일의 이름)
app=Flask(__name__)



# [3-2] 웹 브라우저가 식별할 수 있는 규칙에 맞춘 문자열 리턴하기
@app.route("/world")
def world():
    # 웹 브라우저가 인식할수 있는 특정한 형식을 갖춘 문자열 리턴
    html =""" <h1> 안녕 플라스크!! </h1>
              <p style = 'color: blue'> 첫 번쨰 플라스크 웹 페이지 </p>
    """

    return html

#[4] Flask 웹 서버 가능
if __name__ =="__main__":
  app.run(port=9091, debug= True)

 

이번엔 h 태그와 p style 이 적용되었음

 

 

 

 

별도로 구현된 프론트엔드 웹 페이지를 웹 브라우저에 전달하기

모든 프론트엔드 코드를 문자열로 리턴하는 것은 비효율 적이기 떄문에, 프론트엔드 개발자로부터 소스 코드를 전달받아 FLASK 작업 폴터의 특정 파일에 넣어두고, FLASK 가 이 파일을 읽어서 웹 브라우저에게 전달하게 하는것이 일반적

 

# [1] 패키지 참조
from flask import Flask, render_template

# [2] Flask 의 메인 객체를 생성 (__name__ 은 소스 파일의 이름)
app=Flask(__name__)



# [3-3] 별도로 구현된 프론트엔드 웹 페이지를 웹 브라우저에게 전달하기
@app.route("/myfood")
def myfood():
   return render_template("myfood.html")


#[4] Flask 웹 서버 가능
if __name__ =="__main__":
  app.run(port=9091, debug= True)

 

 

 

 

 

 JSON 구조의 응답 결과 반환하기 (딕셔너리 데이터를 웹 브라우저에 전달)

 

#Flask 프로그램의 기본 구조
#프로그램 최 상단에서 채키지 참조 처리와 Flask 메인 객체를 생성
# 프로그램 마지막에 생성한 Flask 메인 객체 가동


# [1] 패키지 참조
from flask import Flask, render_template

# [2] Flask 의 메인 객체를 생성 (__name__ 은 소스 파일의 이름)
app=Flask(__name__)


#[3-4] JSON 구조의 응답 결과 반환하기 (딕셔너리 데이터를 웹 브라우저에 전달)

@app.route("/mydata")
def mydata():
   mydict={"name":"LEE","age":24,"height":175,"weight":82}
   return mydict


#[4] Flask 웹 서버 가능
if __name__ =="__main__":
  app.run(port=9091, debug= True)

 

 

 

 

 

 

2.Restfull API의 이해

웹 상에 공개되어 있는 데이터에 접근하기 위한 표준 규격이 RESTFUL API

 

API 는 프로그램 간 데이터를 주고받기 위한 규칙과 약속이고

REST 는 웹에서 데이터를 효율적으로 주고받기 위한 설계 원칙

 

 

HTTP Method 에 작동 방식

PK 인 10101 이 마치 폴더 구조처럼 존재

 

 

 

HTTP GET 방식 (조회)

QUERYSTRING 방식의 전송으S GET 방식의 요청에서만 가능

 

URL 을 통해서 웹 서버에 특정 함수를 호출할 수 있지

이떄 데이터를 요청할 세부 사항을 파라미터, 즉 쿼리 스트링으로서 작성해서 호출할 수 있음

 

 

 

 

 

 

request.args.get() 이 부분이 있어야 백엔드에 함수에 파라미터 쓸 수 있는 요건이 충족되는 것과 같음

 

 

 

 

모든 데이터는 string 으로 들어오니까 형변환 해야함

 

엔드 포인트에서 확인해보면 아래와 왔음

 

 

 

 

get 방식을 제외한 나머지 방식은 웹 브라우저로 접근이 불가함

즉 post 이런 거는 웹 브라우저로 테스트 불가, 전문 테스트 프로그램 사용 필요

 

 

 

# restful api 의 이해

# [1] 패키지 참조
from flask import Flask, request

#[2] Flask 메인 객체 생성 (__name__ 은 이 소스 파일의 이름)

app =Flask(__name__)

# [3] GET 방식 요청에 따른 응답 결과 생성하기
#GET 방식의 데이터 수신

@app.route("/parameter",methods=["GET"])
def get():
  #url 에 포함된 변수 추출
  my_num1 =request.args.get('num1')
  my_num2 =request.args.get('num2')

  #클라이언트로부터 전달받은 모든 파라미터는 무조건 문자열인 것 주의해야함
  sum1 = my_num1 + my_num2
  sum2 = int(my_num1) + int(my_num2)


  mydict ={
    "expr" :"%s + %s" %(my_num1,my_num2),
    "sum1":sum1,
    "sum2":sum2
  }
  return mydict

# [4] Flask 웹 서버 가동
if __name__ == "__main__":
  app.run(port=9091,debug = True)

 

 

 

 

http://127.0.0.1:9091/parameter?num1=1000&num2=500

url 에 있는 파라미터를 인식해서 웹 서버를 호출하는 방식도 가능

 

 

 

 

> 다만 이렇게 쿼리 스트링으로 모든 변수를 URL에 노출시키기엔 URL 길이 제한이 있다보니 단점을 보완하기 위해서는 form 객체의 메서드를 사용할 수있음

 

 

 

HTTP POST, PUT, DELETE 방식의 파라미터 전달하기

쿼리 스트링을 활용하는건 GET 만 가능하니 다른 세 방식은 URL 에 노출하지 않느S HTTP HEADER 방식 사용 필요

methods 부분이랑 request.form.##

 

 

 

으로 수정

 

테스트 할 떄는 확장 프로그램 사용 필요

 

 

POST 방식

# restful api 의 이해

# [1] 패키지 참조
from flask import Flask, request

#[2] Flask 메인 객체 생성 (__name__ 은 이 소스 파일의 이름)

app =Flask(__name__)


# [3-2] POST 방식의 파라미터 처리하기
@app.route("/parameter",methods=["POST"])
def post():

  x=request.form.get("x")
  y=request.form.get("y")

  z=int(x)*int(y)

  return {
"expr":"%s*%s"%(x,y),
"z":z

  }


# [4] Flask 웹 서버 가동
if __name__ == "__main__":
  app.run(port=9091,debug = True)

 

 

 

PUT 방식

# restful api 의 이해

# [1] 패키지 참조
from flask import Flask, request

#[2] Flask 메인 객체 생성 (__name__ 은 이 소스 파일의 이름)

app =Flask(__name__)


#[3-3] PUT 방식으로 데이터 수신하기
@app.route("/parameter",methods=["PUT"])
def put():

  a=request.form.get("a")
  b=request.form.get("b")

  c=int(a)-int(b)

  return {
"expr":"%s-%s"%(a,b),
"c":c

  }

# [4] Flask 웹 서버 가동
if __name__ == "__main__":
  app.run(port=9091,debug = True)

 

DELETE 방식

# restful api 의 이해

# [1] 패키지 참조
from flask import Flask, request

#[2] Flask 메인 객체 생성 (__name__ 은 이 소스 파일의 이름)

app =Flask(__name__)

#[3-4] DELETE 방식의 데이터 수신
@app.route("/parameter",methods=["DELETE"])
def delete():

  m=request.form.get("m")
  n=request.form.get("n")

  o=int(m)/int(n)

  return {
"expr":"%s/%s"%(m,n),
"o":o

  }


# [4] Flask 웹 서버 가동
if __name__ == "__main__":
  app.run(port=9091,debug = True)

 

 

 

PATH 파라미터 전달하기

PATH 형식으로 파라미터를 전달하는건 모든 METHOD(Get,post,put,delete) 에서 사용 가능

URL 에 변수를 폴더 경로인 것 처럼 포함하는 방식

 

# restful api 의 이해

# [1] 패키지 참조
from flask import Flask, request

#[2] Flask 메인 객체 생성 (__name__ 은 이 소스 파일의 이름)

app =Flask(__name__)

#[3-5] PATH 파라미터 처리하기 (GET,POST,PUT,DELETE 모두 공통)
@app.route("/parameter/<myname>/<myage>", methods=['GET'])
def path_params(myname, myage):
    msg = "안녕하세요 {name}님. 당신은 {age}세 입니다."

    return {
        "msg": msg.format(name=myname, age=myage)
    }


# [special] 예외 발생 시 호출되는 함수 정의 (전역 try except 효과)
@app.errorhandler(Exception)
def error_handling(error):
  return({"message":str(error)},500)


# [4] Flask 웹 서버 가동
if __name__ == "__main__":
  app.run(port=9091,debug = True)

 

 

 

데이터 조회 API

 

위에서 실행한 함수들마다 데이터베이스 접속해줘야하고, 접속 후엔 해제까지 해줘야함

이 부분을 반복하기 쉽게 모듈로 만들어 주기

 

 

<접속/접속 끊기 모듈화>

 

# [1/3] 모든 함수에서 공통적으로 사용하는 기능 모듈화
# 데이터베이스 접속 정보
from sqlalchemy import create_engine

config = {
    'username': 'root',       # 접속할 사용자 이름
    'password': '1234',       # 접속할 사용자의 비밀번호
    'hostname': 'localhost',  # 접속할 시스템 주소 (내 컴퓨터인 경우 localhost)
    'port': '9090',           # 설치시 설정한 포트번호 (기본값: 3306)
    'database': 'myschool',   # 사용할 데이터베이스 이름
    'charset': 'utf8mb4'      # 한글 깨짐 방지 (데이터베이스의 설정과 동일하게 지정)
}

conn = None  # 데이터베이스 접속 객체에 대한 전역 변수 선언

# [2/3] 모든 함수에서 공통적으로 사용하는 기능 모듈화
# 데이터베이스 접속 함수
def connect():
    global conn  # 함수 외부의 변수를 함수 안으로 끌고 들어옴

    con_str_tpl = "mariadb+pymysql://{username}:{password}@{hostname}:{port}/{database}?charset={charset}"
    
    con_str = con_str_tpl.format(**config)
    engine = create_engine(con_str)
    conn = engine.connect()

    return conn

# 데이터베이스 접속 해제 함수
def disconnect():
    global conn  # 함수 외부의 변수를 함수 안으로 끌고 들어옴

    if conn != None:
        conn.close()

# [3/3] 모듈 테스트 -->> 에러 없이 실행되면 정상 (출력결과 없음)
# 모듈 테스트
if __name__ == "__main__":
    connect()
    disconnect()

 

 

 

<연결된 웹 서버에서 sql 을 통해 단일행 및 다중행 가져오기>

# [1] 패키지 참조
from flask import Flask, request
from sqlalchemy import text
import datetime as dt
from mylibrary import MyDB

# [2] Flask 메인 객체 생성
app = Flask(__name__) # __name__ 은 이 소스파일의 이름
app.json.sort_keys = False # 출력 결과의 JSON 정렬 방지


# [3-1] 다중행 조회 구현
@app.route("/departments", methods=['GET'])
def get_list():
    # 실행할 SQL문 정의
    sql = text("SELECT id, dname, loc, phone, email FROM departments")
    
    conn = MyDB.connect()      # DB 접속
    result = conn.execute(sql) # SQL문을 실행하여 결과 객체 받기
    MyDB.disconnect()          # DB 접속 해제
    
    # 단일행 조회에 대한 결과 집합 추출하기
    resultset = result.mappings().all()
    
    # 반복문으로 탐색하면서 개별 레코드틀 딕셔너리로 변환해야 함
    for i in range(0, len(resultset)):
        resultset[i] = dict(resultset[i])
        
    # 결과 반환하기
    return {
        "result": resultset,
        "timestamp": dt.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    }




# [3-2] 단일행 조회 구현
# 단일행의 경우 결과 값이 0개 아니면 1개이기 때문에 마지막 return 에서 resultset[0] 처리

@app.route("/departments/<id>", methods=['GET'])
def get_item(id):
    # SQL문 정의
    sql = text("""SELECT id, dname, loc, phone, email, established, homepage 
                  FROM departments WHERE id=:id""")
    
    conn = MyDB.connect()                # DB 접속
    result = conn.execute(sql, {"id": id}) # SQL문을 실행하여 결과 객체 받기
    MyDB.disconnect()                    # DB 접속 해제
    
    # 단일행 조회에 대한 결과 집합 추출하기
    resultset = result.mappings().all()
    
    return {
        "result": dict(resultset[0]),  # 조회 결과에 대한 딕셔너리 변환
        "timestamp": dt.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    }



# [4] 전역 예외 처리
@app.errorhandler(Exception)
def error_handling(error):
    MyDB.disconnect() # 데이터베이스 접속 해제
    return {
        'message': "".join(error.args),
        'timestamp': dt.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    }, 500

# [5] Flask 웹 서버 가동
if __name__ == "__main__":
    app.run(host='127.0.0.1', port=9091, debug=True)

 

 

<결과1. 단일행 (상세 페이지)>

 

 

<결과2. 다중행>

 

 

 

 

4. 데이터 저장 /수정/삭제 API

 

 

 

4-1 데이터 저장

RESTFUL 은 URL 1개에 METHODS 를 바꿔가는 것

RESTFUL 은 일종의 테이블이고, 이 테이블에서 데이터를 입력 수정 삭제 조회할 수 있는 것

 

 

# [1] 패키지 참조
from flask import Flask, request
from sqlalchemy import text
import datetime as dt
from mylibrary import MyDB

# [2] Flask 메인 객체 생성
app = Flask(__name__) # __name__ 은 이 소스파일의 이름
app.json.sort_keys = False # 출력 결과의 JSON 정렬 방지

#[3] 생략


# [4] 전역 예외 처리
@app.errorhandler(Exception)
def error_handling(error):
    MyDB.disconnect() # 데이터베이스 접속 해제
    return {
        'message': "".join(error.args),
        'timestamp': dt.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    }, 500


# =================================================================
# [5-1] 데이터 입력 API
#데이터 저장하기
@app.route("/departments",methods=["POST"])
def post ():
    #DB 접속하기
    conn = MyDB.connect()
  
    #1) 데이터 저장하기
    dname = request.form.get("dname")
    loc = request.form.get("loc")
    phone = request.form.get("phone")
    email = request.form.get("email")
    established = request.form.get("established")
    homepage = request.form.get("homepage")

    # SQL문 정의
    sql = text("""INSERT INTO departments (dname, loc, phone, email, established, homepage) 
                  VALUES (:dname, :loc, :phone, :email, :established, :homepage)""")

    # POST 파라미터를 SQL에 맵핑하기 위해 딕셔너리로 묶음
    params = {
        "dname": dname, "loc": loc, "phone": phone, "email": email,
        "established": established, "homepage": homepage
    }

    conn.execute(sql, params)   # SQL문 실행하기 -> 자동 트랜잭션
    conn.commit()               # 변경사항을 데이터베이스에 영구 저장

    #2) 데이터 저장 결과 조회하기
    #생성된 PK 값 추출하기
    pk_result = conn.execute(text("SELECT LAST_INSERT_ID()"))
    pk = pk_result.scalar()

    sql = text("""SELECT id, dname, loc, phone, email, established, homepage 
                  FROM departments WHERE id=:id""")

    result = conn.execute(sql, {"id": pk}) #SQL 문을 실행하여 결과 객체 받기
    resultset = result.mappings().all() #단일행 조회에 대한 결과 집합 추출하기


    #DB 접속 해제
    MyDB.disconnect()

    #응답 결과 반환하기
    return {
        "result": dict(resultset[0]),
        "timestamp": dt.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
}

# =================================================================


# [6] Flask 웹 서버 가동
if __name__ == "__main__":
    app.run(host='127.0.0.1', port=9091, debug=True)

 

지금 쓰는건 SQLalchemy > 자동으로 트랜젝션 진행되고,오류 발생해도 전역적으로 적용된다

 

 

보통 데이터를 insert 하면 바로 select 이 작용하니까 

방금 마지막에 insert 된 것을 select 한다

(ex) 리뷰 쓰면 리뷰 바로볼 수있듯이

 

 

 

==================

4-2 데이터 수정

# [1] 패키지 참조
from flask import Flask, request
from sqlalchemy import text
import datetime as dt
from mylibrary import MyDB

# [2] Flask 메인 객체 생성
app = Flask(__name__) # __name__ 은 이 소스파일의 이름
app.json.sort_keys = False # 출력 결과의 JSON 정렬 방지


# [4] 전역 예외 처리
@app.errorhandler(Exception)
def error_handling(error):
    MyDB.disconnect() # 데이터베이스 접속 해제
    return {
        'message': "".join(error.args),
        'timestamp': dt.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    }, 500


# =================================================================
# [5-2] 데이터 수정 API
@app.route("/departments/<id>",methods=["PUT"])
def put(id):
    #DB 접속하기
    conn=MyDB.connect()
    
    # 1) 데이터 수정하기


    # PUT 파라미터 수신하기
    dname = request.form.get("dname")
    loc = request.form.get("loc")
    phone = request.form.get("phone")
    email = request.form.get("email")
    established = request.form.get("established")
    homepage = request.form.get("homepage")


    #SQL 문 정의
    sql = text("""UPDATE departments SET 
                  dname=:dname, loc=:loc, phone=:phone, email=:email, established=:established, homepage=:homepage 
                  WHERE id=:id""")
  
    # PUT 파라미터를 sql에 맵핑하기 위해 딕셔너리로 묶음 -> where 절에 사용할 id 값은 path 파라미터
    params = {
        "id": id, "dname": dname, "loc": loc, "phone": phone,
        "email": email, "established": established, "homepage": homepage
    }

    conn.execute(sql, params) # sql 문 실행하기 > 자동 트랜젝션 
    conn.commit()


   # 2) 데이터 수정 결과 조회하기

   #sql 문 정의
    sql = text("""SELECT id, dname, loc, phone, email, established, homepage 
                  FROM departments WHERE id=:id""")


    # sql 문을 시랳ㅇ하여 결과 객체 받기
    result = conn.execute(sql, {"id": id})

    # 단일행 조회에 대한 결과 집합 추출하기
    resultset = result.mappings().all()


    #DB 접속 해제
    MyDB.disconnect()


    #응답 결과 반환하기
    return {
        "result": dict(resultset[0]),
        "timestamp": dt.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    }


# =================================================================

# [6] Flask 웹 서버 가동
if __name__ == "__main__":
    app.run(host='127.0.0.1', port=9091, debug=True)

 

데이터 수정과 삭제는 where 절이 꼭 걸려있어야함

특히 pk 에 대해서만 씀 

> restfulapi 에서는 path 로 받음

 

 

pk 가 아닌 애들은 request.form.get() 으로 따로 받아와야함

 

 

====================

4-3 데이터 삭제

# [1] 패키지 참조
from flask import Flask, request
from sqlalchemy import text
import datetime as dt
from mylibrary import MyDB

# [2] Flask 메인 객체 생성
app = Flask(__name__) # __name__ 은 이 소스파일의 이름
app.json.sort_keys = False # 출력 결과의 JSON 정렬 방지


# [4] 전역 예외 처리
@app.errorhandler(Exception)
def error_handling(error):
    MyDB.disconnect() # 데이터베이스 접속 해제
    return {
        'message': "".join(error.args),
        'timestamp': dt.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    }, 500



# =================================================================
# [5-3] 데이터 삭제 API > where 절에 사용할 pk 값을 path 파라미터로 정의
@app.route("/departments/<id>",methods=["DELETE"])
def delete(id):
    conn=MyDB.connect()

    #참조키를 고려하여 데이터 삭제구문 준비

    sql1 = text("""DELETE FROM enrollments 
                  WHERE subject_id IN (SELECT id FROM subjects WHERE department_id=:id) 
                  OR student_id IN (SELECT id FROM students WHERE department_id=:id)""")
    sql2 = text("DELETE FROM subjects WHERE department_id=:id")
    sql3 = text("DELETE FROM students WHERE department_id=:id")
    sql4 = text("DELETE FROM professors WHERE department_id=:id")
    sql5 = text("DELETE FROM departments WHERE id=:id")

    params = {"id": id}

    conn.execute(sql1, params)
    conn.execute(sql2, params)
    conn.execute(sql3, params)
    conn.execute(sql4, params)
    conn.execute(sql5, params)

    conn.commit()         #변경 사항을 데이터 베이스에 영구 저장
    MyDB.disconnect()

    return {"timestamp": dt.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}    

# =================================================================
# [6] Flask 웹 서버 가동
if __name__ == "__main__":
    app.run(host='127.0.0.1', port=9091, debug=True)

 

 

자식키가 지워지지 않으면 부모키를 못지운다

따라서 참조 관계를 거슬러 올라가면서 하나씩 다 지워야함

 

 

삭제의 경우 출력할 값이 없으므로 처리 시각만 표시된다