220616_TIL / Flask

신두다·2022년 6월 16일
0

TIL

목록 보기
44/82

Key words

Flask, Jinja, Bootstrap, Template Engine, Web Application

1. Flask란?

  • Flask란 Micro Web Framework이며, 파이썬을 활용하여 웹 어플리케이션을 작성할 수 있도록 도와준다.
  • 장고(Django)와 거의 유사하게 만들어졌지만, 장고에 비해선 훨씬 가볍고 핵심적인 기능만 가지고 있다. 따라서 크기가 작고 매우 유연하며 제작이 쉽기 때문에, 이러한 환경이 필요한 경우 많이 쓰인다고 한다. (장고는 나중에 필요시 추가 공부해보라고 함)
  • 뒤에 다룰 Jinja를 비롯해 Werkzeug, Flask-SQLAlchemy 등의 패키지와 라이브러리 등이 있어 개발이 수월하도록 도와준다.
  • 쉽게 생각하면 파이썬으로 만든 웹 어플리케이션으로 돌아가도록 만들 때 쓰는 거라고 생각하면 될 듯!

Flask를 시작하는 기본 순서는 다음과 같다.

  1. Flask 설치
  • $ pip install flask
  1. 폴더 생성하기
  • mkdir 명령어를 사용하여 Flask 어플리케이션을 저장할 폴더를 생성하는데, 폴더 이름은 주로 어플리케이션 이름으로 한다고 한다.
  1. 생성한 폴더 안에 __init__.py 파일 만들기.
  • 이 파일은 해당 디렉토리가 패키지, 혹은 라이브러리인 것임을 나타내주는 역할을 한다.
  • 과거 파이썬 버전에서는 필수로 생성해야했지만, 요즘 버전에서는 필수는 아니고 원하는 이름으로 생성해도 된다고 한다. 그치만 여러 사람과 협업할 때의 용이함을 위해 관습을 따르는 걸 추천한다고 한다.
  • 쉽게 말해 이 파일이 어플리케이션을 구동하는 중심 구조(뼈대)라고 생각하면 될 것 같다!! 메인 내용은 여기 다 있어!.
  1. Flask 어플리케이션을 생성하기 위해 __init__.py 파일에 들어갈 기본 내용은 다음과 같다.
# __init__.py

from flask import Flask

app = Flask(__name__)
  • 여기에 이제 라우트 등 필요한 내용을 추가하면 된다!
  • 라우트 추가하기
# __init__.py

from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return 'Hello World!'
  • 자, 라우트는 뭐냐? 웹 주소를 보면 test.com/user/... 이런 식으로 붙는게 있는데 이렇게 앞 꼬리를 따라서 쭉 이어지는 거라고 생각하면 된다! (/로 이어진 걸 Endpoint라고도 한다. 여러 량이 이어진 기차를 생각하면 쉬울 것 같다!)
  • 그럼 위 코드 예시에서는 맨 처음이니까 그냥 처음 들어갈 때 보이는 메인페이지라고 생각하면 된다! (그 이후 예시는 실습 코드 참고하기~~)
  • 참고로 라우트 기능 중에는 HTTP Ruquest Method를 허용한다. 이런 식으로!
@app.route('/', methods=['POST', 'GET'])
def index():
    ...
  • GET, HEAD, OPTIONS는 디폴트로 제공하고, 그 외 POST, PUT, PATCH, DELETE 등을 쓰려면 추가를 해줘야 한다.
    • 참고로 디폴트 제공 methed에 대해,
      @app.route('/', methods=['GET']) 이렇게 해두면 GET만 쓸 수 있다는 뜻이다! 다른 head, options를 쓸 수 있는 게 아냐~
  • 이 라우터에는 변수를 지정해서 넣을 수도 있다! 대표적인 예시는 아래와 같다.
@app.route('/index/<num>')
def index_number(num):
    return 'Welcome to Index %i' % int(num)
  • 이러면 이제 127.0.0.1:5000/index/num를 치면 그 변수에 맞는 값이 웹 페이지에 리턴되어 보인다는 얘기지~ (플라스크에서 변수는 <>를 사용함!)
    • 접속한 사람의 정보에 따라 페이지가 달라질 때가 있잖아~ 그때 쓸 수 있다고 생각하면 된다.
  • 이렇게 하다보면 라우터가 굉장히 많아지겠지! 이때 블루프린트 기능을 통해 많아지는 라우터를 한 곳에 묶어둘 수 있는데, 이건 내가 오늘 보긴 했지만 직접 써보진 않아서 다음에 공부노트로 해보기로 하자ㅠㅠ
  • Application Factory라는게 있는데 말 그대로 어플리케이션을 만드는 공장을 세우는 거다. 추후 프로젝트가 커지고 파일이 많아지면 import할 것들이 많아지는데, circular import를 피하기 위해 이 사용을 추천하고 있다고 한다.
    • 참고 : circular import (순환 참조) - A가 B를 참조하고, B가 A를 참조하고.

CLI 기본 명령어

  • $ FLASK_APP=app_name flask run
    • 생성한 Flask 어플리케이션을 구동할 때 쓰는 명령어다. 이걸 생성하면 `Running on http://127.0.0.1:5000/ (press CTRL+C to quit) 메시지를 마지막에 볼 수 있는데, 저 주소를 웹에서 접속하면 내용이 보인다!
    • 참고로 CLI 명령어를 실행할 때에는 프로젝트 폴더 상위 디렉토리에서 실행해야 한다!!!
    • 참고로 파일 내용을 수정하고 그걸 다시 반영하려면 어플을 종료했다가 다시 실행해줘야 반영된다.
      • (어플을 구동시켜놓고 바로바로 수정사항을 확인할 수 있는 디버거 모드도 있긴 한데, 그건 실습한 코드 내에 주석으로 달아뒀으니 참고)
  • 그 외는 공식문서 참고하기~ 오늘은 위에 것만 써본 것 같다.

2. Jinja

  • 내가 오늘 이해한 Jinja의 효용은 HTML 렌더링과 관련 되어있다. 무슨 말이냐 하면, 만약 상황에 따라 페이지를 다르게 보여줘야 한다면? 예를 들어, 오늘의 날짜를 페이지에 표시하려고 하는데, 이걸 매일 매일 수작업으로 수정해야 할까?
  • 이때 유용한 도구가 바로 Jinja와 같은 웹 템플릿 엔진이다. 즉, 템플릿 엔진은 맞춤형 웹 페이지를 자동으로 생산할 수 있도록 도와준다
    • 검색창에서 특정 검색얼르 찾았을 때, 해당 검색어를 기준으로 결과 페이지를 보이는 것도 한 예시가 될 수 있다.
  • 여하간 오늘 내가 써본 Jinja의 기능은 다음이었다.
  1. render_template 함수를 통해 index.html에 변수 전달하기!
  • index.html에 다음과 같은 코드가 있다고 해보자.
(... 생략 ...)

<body>
  <h2>Apple is {{ fruit_color }}</h2>
  <h2>{{ number }} 개의 과일이 있습니다.</h2>
</body>

(... 생략 ...)
  • 저기 변수로 놓은 부분은 내가 __init__.py에서 변수로 넘겨준 부분을 html 내에서 받아 웹 페이지에 보여주는 거라고 생각하면 된다. 이렇게 넘길 수 있다.
(... 생략 ...)
@app.route('/')
def index():
    apple = 'red'
    apple_count = 10
    return render_template('index.html', fruit_color=apple, number=apple_count)
  • 지금은 물론 apple, apple_count를 픽스해서 넘겨줬지만, 상황에 따라 가변적으로 바꿀 수도 있겠지! 이런 느낌으로 생각하면 된다.
  • 이 외에도 Jinja의 기능 중 객체 태그, if
    for 등 자주 사용되는 기능들이 있는데 오늘 다 써보지는 못해서 필요시 공식문서의 여기를 참고하자.
  • Jinja 상속에 대해서도 추가 공부 필요ㅠㅠ 여기서의 의미는 반복적으로 사용되는 부분들을 하나로 묶어서 사용할 수 있게 하는 기능으로 상속은 Jinja 기능의 핵심 중 하나라고 한다.
  • 간단히만 말하면 아래 두 가지를 사용한다.
    • {% extends %}
    • {% block %} ... {% endblock %}
  • 진자 블록도 추가 공부 필요..

3. Bootstrap

  • HTML을 빠르고 예쁘게 꾸밀 수 있는 도구이다.
  • 공식 페이지에 가면 다양한 html 템플릿을 보고 복사해 사용할 수 있는데, 프론트엔드 개발자가 아닌 내가 필요한 부분만 간편히 가져와 웹을 꾸밀 수 있는 도구로 그 효용을 생각하면 될 것 같다.
  • 오늘 더 해본 건 없으므로 설명은 여기까지! 아마 프로젝트하면서 써보게 되지 않을까 싶다.

4. 실습한 것

오늘은 아래 실습을 해보았다. 하면서 궁금한 건, 메모할 것은 주석으로 달아두었다.


  1. index 함수에서는 '/' 엔드 포인트로 접속했을 때 'templates' 폴더 안에 있는 'index.html' 파일을 렌더링 해줍니다.
  • 요구사항:
    - HTTP Method: GET
    - Endpoint: /
  • 상황별 요구사항:
    - GET 요청이 들어오면 templates/index.html 파일을 렌더해야 합니다.
  1. users 함수는 'users.csv' 파일을 읽은 뒤에 파일에 있는 유저들을 리스트에 담아 다음과 같은 형태로 응답해야 합니다.
  • CSV 파일을 읽어온 뒤에 가장 첫 줄인 'username' 은 리스트에 담지 않습니다!
  • 요구사항:
    - HTTP Method: GET
    - Endpoint: /users
  1. display_user 함수에서는 'users.csv' 파일의 유저 중에서 전달받은 { 유저 아이디 } 번째 유저를 문자열로 돌려줘야 합니다.
    (만약에 사용자가 { 유저 아이디 } 를 명시하지 않은 경우에는 유저 아이디는 '1' 이라고 가정합니다.)
  • 요구사항:
    - HTTP Method: GET
    - Endpoint: /users/{ 유저 아이디 } (유저 아이디는 1 이상의 숫자입니다)
  • 상황별 요구사항:
    - 문제 주석 참고

[1번]

  • 작성했던 기본 메모 및 import 했던 내용도 포함함.
"""
[Memo]
    - flask run - '$ FLASK_APP=simple_flask_app/app.py flask run' => 뒤에 경로 안 붙이면 안 됨. __init__.py 이름이 아니라 그런가?
    - 디버깅 모드 실행
        $ export FLASK_APP=simple_flask_app/app.py
        $ export FLASK_ENV=development
        $ flask run
"""

"""
Simple Flask App 입니다.

간단한 플라스크 애플리케이션을 만들어 보면서
어떻게 동작을 하는지 살펴보세요!
"""
from crypt import methods
from email.policy import default
import os
from flask import Flask, render_template  # render_template 추가함
import csv, json # 추가함

# 추가로 필요한 패키지들이 있는 경우 가져와 사용합니다.

# 'app' 은 플라스크 애플리케이션 객체입니다.
app = Flask(__name__)

# CSV 파일이 어디에 있는지 알려주는 설정입니다.
# CSV 파일은 수정하지 않습니다!
app.config['USERS_CSV_FILE'] = os.path.join(os.path.dirname(__file__), 'users.csv')

# 플라스크 애플리케이션의 라우팅 설정입니다.
# 서버 주소 + '/' 로 들어가면 아래 함수가 실행이 됩니다.
@app.route('/', methods = ['GET'])
def index():
    """
    index 함수에서는 'simple_flask_app/templates' 폴더에 있는 'index.html'
    파일을 렌더링 해줘야 합니다!
    """
    return render_template('index.html')
  • 이건 별도의 html 파일에 있는 내용을 가져오는 작업으로 생각하면 된다.
  • 난 http method를 get으로 제한했는데, 동기들 코드보니 아예 안 넣은 사람도 있는 것 같다. 문제 해석의 차이인 것 같다.
  • 처음 과정 잘 기억해라!!

[2번]

@app.route('/users', methods = ['GET'])
def users():
    """
    users 함수에서는 사용자가 '서버 주소 + /users' 로 접속하게 되면
    'users.csv' 파일을 읽은 뒤에 파일에 있는 유저들을 리스트에 담아 다음과 같이 dictionary 형태로 넘겨줘야합니다.
    
        {
            "users": ["spongebob", "patrick", "squidward"]
        }

    NOTE: CSV 파일을 읽어온 뒤에 가장 첫 줄인 'username' 은 리스트에 담지 않습니다!  """

    # 빈 리스트, 딕셔너리 생성 
    users_1 = []
    users_2 = {}

    # csv 파일 불러오기
    with open(app.config['USERS_CSV_FILE'], 'r') as csvfile:
        names = csv.reader(csvfile)
        for name in names:
            users_1.append(name[0]) # 빈 리스트에 이름만 넣어두기  (cf. 리스트로 뽑혀서 슬라이싱 해서 값만 넣어주는 것)
    
    # username 제외 딕셔너리에 할당.
    users_2['users'] = users_1[1:] 

    return users_2

    """
    궁금증
        # 난 출력하면 '{"users":["spongebob","patrick","squidward"]}' 한 줄로 나오는데 상관 없나? 일단 pytest 통과하긴 하는데..
        # json으로 바꿔주고 넘겨도 출력되는 형태가 똑같긴 함. 
          users_2 = json.dumps(users_2) 
    """
  • 이 문제의 핵심은 역시 별도로 존재하는 csv 파일의 내용을 웹 어플리케이션으로 어떻게 불러올 것이냐 하는 것이다.
  • CSV 파일을 어떻게 읽어올 수 있을까?라는 처음 접근법만 생각해낸다면 그리 어려운 문제는 아니다.
  • 근데 난 웹에 출력되는 내용이 흔히 보던 제이슨 처음 줄바꿈되어 보이는 것과 다르게 일자였는데.. 이건 나중에 동기들 코드 그대로 가져다 실행해보아도 똑같았다. 뭔가 웹 환경이 다른 걸까..? 이건 나중에 케이스로 만나보게 될 것 같다.

[3번]

@app.route('/users/', defaults = {'user_order': 1})
@app.route('/users/<user_order>', methods = ['GET'])
def display_user(user_order):
    """
    display_user 함수에서는 사용자가 '서버 주소 + /users/{ 유저 아이디 }' 로
    접속하게 되면 'users.csv' 파일의 { 유저 아이디 } 번째 유저를 문자열로 
    돌려줘야 합니다.

    만약에 사용자가 { 유저 아이디 } 를 명시하지 않은 경우에는 유저 아이디는 '1'
    이라고 가정합니다.

    예시)

    ('users.csv' 파일에 'spongebob', 'patrick', 'squidward' 순으로 적혀있다고
    가정합니다.)

    -   예를 들어 사용자가 '/users/' 에 접속하면 { 유저 아이디 } 가 주어지지 않았기
        때문에 '1' 번째, 즉 첫 번째 유저인 'spongebob' 문자열을 돌려줍니다.

    -   예를 들어 사용자가 '/users/1' 에 접속하면 '1' 번째, 즉 첫 번째 유저인
        'spongebob' 문자열을 돌려줍니다.

    -   예를 들어 사용자가 '/users/2' 에 접속하면 '2' 번째, 즉 두 번째 유저인
        'patrick' 문자열을 돌려줍니다.
    """

    #------ users() 함수 내용 시작 ------#
    # 빈 리스트, 딕셔너리 생성 
    users_1 = []
    users_2 = {}

    # csv 파일 불러오기
    with open(app.config['USERS_CSV_FILE'], 'r') as csvfile:
        names = csv.reader(csvfile)
        for name in names:
            users_1.append(name[0]) # 빈 리스트에 이름만 넣어두기  (cf. 리스트로 뽑혀서 슬라이싱 해서 값만 넣어주는 것. 이렇게 안 하고 extend() 해도 됨)
    
    # username 제외 딕셔너리에 할당.
    users_2['users'] = users_1[1:] 

    #------ users() 함수 내용 끝 ------#

    # 이름 슬라이싱
    res = users_2['users'][int(user_order) - 1]

    return res, 200
    
    """
    [기록] - 이 문제를 풀면서 의아했던 것
        1. 문제의 '{ 유저 아이디 }'를 무조건 지켜야만 하는 줄 알았다.
            - 그래서 아니 flask 변수 표현은 '<>'로 하는데 이건 뭐지? 싶어 상당히 헤맸다. 
        2. 라우트 설정시 '/users/<int:user_order>' 로 이 단계에서 int 지정해주면 왠지 모르지만 Pytest에서 틀렸다고 나온다.
            - 아래서 지정하나 위에서 해주나 결과는 똑같은데!! 내가 모르는 뭔가가 있는 건가? 
            - 진짜 혹시 몰라서 위에서 안하고 아래에 직접 해줬더니 통과했다. 침착하자...
    """

# 문제 주어질 때 기본으로 있던 코드
if __name__ == "__main__":
    app.run(debug=True)
  • 주석 달아둔 것처럼 주어진 문제를 해석하는 내 관점때문인지 실제 기술적인 내용보다 문제 해석 때문에 헤맸던 문제였다.
  • 여하간 나중에 이 문제를 통해 변수 표현 어떻게 하는지 잘 보면 도움이 될거다.
  • 2번 문제의 users() 함수를 그대로 가져올 수도 있었지만 그냥 해당 함수의 코드를 그대로 3번에도 옮겨왔다. 함수 여러 개 있을 때 다른 함수의 값을 참조하면 상황에 따라 안될 수도 있을 것 같아서이다. (직접 쓸 함수 뿐 아니라, 그 함수가 참조한 함수도 import 안 하면 오류가 날 것 아닌가?)

5. 그 외

  • 정적(static) 웹 / 동적(dynamic) 웹 (출처)
    • 정적(static) 웹 - 편의점 / 동적(dynamic) - 음식점
      ⇒ 직접 해먹야야 하냐 해주냐?
    • 정적 웹의 기준은 접속할 때마다 받게 되는 HTML/CSS/Javascript 코드들, 그리고 동봉된 이미지, 동영상 등의 파일들이 같은가?이다.
    • 좀 더 정확히는 서버에서 매번 이것들을 가공해서 제공하는 것이 아니라, 프로그래머가 작성해서 갖다 준 제품들이 진열되어 있는 걸 그대로 가져가게 하냐는 것이다.
      • 접속시마다 내용이 변할 필요가 없는 사이트들. 예를 들어 회사 소개 페이지 같은 것을 정적 웹으로 만들곤 한다.
    • 근데 만약 유저가 글을 올리는 게시판은 어떨까? 즉, 정보가 끊임없이 올라오는 사이트는? 그런 사이트는 서버가 그때그때 요리하여 음식을 내주도록 만든다. 이것을 동적 웹이라고 한다.
      • 여러 기술이 있지만 간단히는 PHP가 있음.
      • 예를 들어, 데이터베이스 서버와 연결해서 접속할 때마다 최신 정보를 보여주기.
    • 근데 그럼 동적 웹이 무조건 좋냐? 아니다.
      • 블로그 같은 경우 정적 웹 생성툴로 많이 운영한다고 한다. 왜냐면 서버에 매번 연결되어 정보를 주면 비용도 들고 하니까.
      • 차라리 글 하나하나 html로 저장해서 보관하고 꺼내다주는 방식이 나을 수도 있음. github.io 같은 것이 그런 정적 블로그들이라고 한다.
  • IP에 대한 추가 설명 (출처)
    • ICANN 이라는 국제기관에서 IP를 관리하고, 국가별 IP 분배 등을 담당한다.
      • 대륙별로 지역 인터넷 등록기관으로 분화됨.
      • 즉, ICANN → APNIC(아시아) → KRNIC(한국) → skt/kt/Lgu+ 순서로 IP가 배분된다고 생각하면 된다.
      • KISA 들어가보면 대륙별/국가별 분배현황, 미할당 잔여 IP 수 등을 확인할 수 있음.
      • 남은 IP가 얼마 안 남아 나온게 IPv6 !!
    • IP가지고 대략 어느 지역에 있는지까지 쉽게 알아낼 수 있는데, 처음 이 IP 정책이 만들어지던 시절에는 사생활 보호와 거리가 멀었다고 한다. 왜냐하면 그때는 연구소, 학교, 회사에서 큰 컴퓨터 한 대 놓고 다 함께 썼기 때문이다.
    • 이렇게 컴퓨터가 개개인한테 퍼질지 몰랐던 거다. 암튼 그래서 요즘은 VPN 등 우회하며 사용하기도 한다.

Feeling


  • 오늘 가장 많이 든 생각은 오, 이게 정녕 하루만에 칠 수 있는 공부량이란 말입니까~였다. 실제로 오늘 배운 내용 중 실습까지 해보지 못한 부분이 지금까지와 비교해도 많지 않았나 싶다.
  • 내일부터 2일은 과제가 없다. 그때 오늘 못한 도전과제와 함께 실습 못 한부분 보충해야겠다.
  • 고생 많았어..ㅠㅠ 이번 스프린트 빡쎄다.
profile
B2B SaaS 회사에서 Data Analyst로 일하고 있습니다.

0개의 댓글