본문 바로가기
IT/엘리스 AI 트랙

[엘리스 AI 트랙 2기] 4주차-Flask 기초 (07/15, 16)

by 무녈 2021. 7. 16.

자료의 출처는 '엘리스 AI 트랙 2기 (https://aitrack.elice.io/)' 'Flask 기초 I'이며, 학습 후 정리한 내용입니다.

⚡️올바르지 않은 내용이 있을 경우 댓글로 남겨주시면 감사하겠습니다.⚡️


1 .Flask 시작하기

수강목표

  1. 웹 서비스의 흐름을 안다.
    포털 사이트에 나오는 화면과 데이터들이 이동하는 과정과 순서들을 이해할 수 있습니다.
  2. 웹 프레임워크의 종류를 알고 Flask를 사용할 줄 안다.
    웹서비스를만들기위한웹프레임워크의종류를배우고,그중 Flask를 실행하는 방법을 배웁니다.
  3. 서버 통신의 방법을 알고 사용할 줄 안다.
    서버가 어떤 방법으로 통신하는지 알고, 직접 사용해 봅니다.

01. 웹 서버의 동작 과정 이해하기

웹 서버 동작 과정

우리가 흔히 정보들을 접하고 사용하는 많은 서비스는 '웹 서버'를 통해 우리에게 제공되고 있다.

 

클라이언트:사용자/내컴퓨터/크롬등...

서버: 클라이언트로부터 요청을 받아서 처리해 주고, '응답'으로 데이터를 돌려주는 곳

클라이언트가 서버로 요청을 하고, 요청을 받은 서버는 요청에 해당하는 데이터를 응답으로 돌려준다.

서버의 응답으로 클라이언트가 보게 되는 데이터는 HTML, JSON, XML 등 다양한 형태

 

# JSON: dictionary와 list의 조합
# XML: html과 유사

요청과 응답

원하는 정보를 아무렇게나 요청하고 받는 것이 아니라

미리 약속한 규칙을 통해서 요청하면
정해진 형태의 데이터응답

# 클라이언트가 서버에 요청 서버가 응답

API

네이버와 구글 각각 페이지의 접속 주소는 미리 약속된 주소

미리 약속된 방법으로 요청한 후 해당하는 페이지를 보게 되는 것

 

API: 정해진 방식으로 데이터의 통로 역할을 하는 것

02. Flask Framework

Framework

하나의 결과물을 만들기 위해서 제공하는 '틀'
미리 작성되어 있는 함수 (라이브러리) 이상의 기능을 제공

Flask Framework

Flask는 Python을 사용해서
웹 서버를 만들 수 있게 도와주는 Web Framework

 

# 최소한의 기능만을 제공하기 때문에 많은 기능이 포함되어있지 않다.
또한, 적은 양의 코드로 서버를 만들 수 있기 때문에 접근성이 좋다.

장점

나만의 서버를 쉽게 작성할 수 있다.

간단한 코드를 빠르게 실행할 수 있다.

원하는 기능을 유연하게 확장하기 편리하다 -> Micro Framework의 특징

파이썬을 사용하는 Framework

다량의 기능을 미리 제공
- Django (사용 할 수 있는 기능이 많다

기본적인 기능만 제공
- Flask, Pyramid, Bottle (개발이 자유롭다)

03. Flask 웹 서버 만들기

Flask 웹 서버 만들기

웹서버를만드는방법은복잡할것같지만 Flask Framework를 사용해서 간단한 코드만으로 서버를 만들 수 있다.

 

# 시작: Python의 패키지 -> import -> app.py 생성

Flask Simple Web Server 만들기

app.py

# flask 모듈 import
from flask import Flask
app = Flask(__name__) # __name__:이 파일이 다른 파일에 의해 실행되는지 직접 실행됐는지 여부를 알려줌 /app에 Flask 객체를 변수로 선언
# API 주소 생성
@app.route("/")  #url 주소를 설정하여 API를 만들어주는 것
def elice(): # 플라스크의 기능과 파이썬의 함수가 합쳐져 하나의 API를 만듬
    return "hello elice"
# 서버 실행
if __name__== "__main__":
    app.run()

 

  • “pip3 install flask” 명령어를 통해 flask 설치
  • Flask 패키지에서 Flaskimport
  • @app.route() 는 서버에 접속 할 수 있는 url을 만들어 준다.
  • @app.route("/") 아래의 def elice()라는 함수는 app.route()url에서 실행할 함수
  • If __name__ == "__main__" , 파일 이름이 main일 때만 app.run()이 실행되도록 한다.

# @: decorator - 반복적인 기능을 만들기 위해 다양한 코드를 써야하는 경우가 있는데, 그런 수고를 덜어줌.

04. URL을 연결하고 데이터를 화면에 나타내기

URL 연결

app.route()는 url을 만들어 준다.

app.route()와 이어져 있는 함수를 사용해 HTMLJSON 형식의 데이터를 전달

JSON 형식 데이터 나타내기

app.py

from flask import Flask,jsonify
app = Flask(__name__)
@app.route("/")
def elice_json():
    my_data = {"name":"elice"}
    return jsonify(my_data)
if __name__== "__main__": 
	app.run()

 

  • Flask 패키지에서 Flaskjsonifyimpor
  • jsonify 라는 함수는 jsonify() 안에 있는 내용을 화면에 전달
    # json 형태의 파일을 줄 것이다~
  • @app.route("/") 아래의 def elice_json() 함수는 app.route()url에서 실행할 함수이고, {"name": "elice"} 라는 데이터를 화면에 전달

HTML 형식의 데이터 나타내기 - 파일 준비하기

  • html을 화면에 전달하기 위해 html 파일이 필요
  • html 파일은 위 사진처럼 templates라는 폴더 아래에 넣어 주어줌
  • templates 폴더에 html파일을 넣어 놓으면 Flask가 자동으로 찾아서 연결해준

HTML 형식의 데이터 나타내기 - templates 준비하기

templates/index.html

<html> 
  <head>
	<title> HTML 만세 </title> 
  </head>
  <body>
	<h4> html파일 열기 </h4>
  </body>
</html>

 

  • Flask를 통해 가져올 index.htmltemplates폴더 아래에 생성
  • 왼쪽의 코드와 같이 간단한 html을 작성

HTML 형식의 데이터 나타내기 – app.py 준비하기

app.py

from flask import Flask,render_template 
app = Flask(__name__)

@app.route("/")
def elice_html():
    return render_template("index.html")
if __name__== "__main__":
    app.run()

 

  • Flask 패키지에서 Flaskrender_templateimport
  • render_template 함수는 templates 폴더 안의 html파일을 불러와 주는 역할
  • templates 폴더 내의 html 파일의 이름과 render_template()안의 이름이 같아야 화면에 전달
  • index.htmlrender_template에 넣어서 elice_html 함수의 return에 넣어준다.

여러 가지 url 연결하기

app.py

@app.route("URL주소")

 

  • "URL 주소" 라고 되어있는 부분은 우리가 만들어 줄 수 있는 url의 주소
  • 1개의 @app.route는 1개의 함수와 연결될 수 있다.
    # 실제로는 하나의 @app.route는 여러개의 함수를 가질 수 있지만, 이번 과정은 위와 같은 내용으로 진행
  •  @app.route를 여러 개 사용해서 다양한 url을 만들 수 있다.

# 다른 url 주소를 생성했을 떄, 동일한 함수명을 사용한다면 errorr가 발생
# 이미 함수명이 존재하며, 다른 route에 매핑되어 있기 때문에 플라스크 서버가 실행시키지 못함.
-> 중복된 함수 생성 불가

 

from flask import * 
app = Flask(__name__)

@app.route("/")
def elice():
    return jsonify("home")

@app.route("/admin")
def elice_admin():
    return jsonify("admin page")

@app.route("/student")
def elice_student():
    return jsonify("student Page")

@app.route("/student/<name>") #위 /studet와 달리 <name>을 통해 매개변수를 전달받는다.
def elice_user(name):
    user = {"name":name}
    return jsonify(user)

if __name__== "__main__":
    app.run()

05. REST API

REST API

HTTP URL을 통해 데이터의 자원을 표현하고 HTTP Method를 통해서 데이터를 다루는 방법을 의미 

Database, 이미지, 텍스트 등의 다양한 데이터에 적용

  • HTTP URI(Uniform Resource Identifier)를 통해 자원을 명시하고, HTTP Method(POST, GET, PUT, DELETE)를 통해 해당 자원에 대한 CRUD Operation을 적용
  • 다양한 클라이언트가 생겨남에 따라서 REST API가 필요 (다양한 곳에서 통신해야 하기 때문)
    # 클라이언트 종류 다양화: 핸드폰, 컴퓨터, 테블릿 등
  • REST API는 메시지가 의도하는 바를 URL에서 나타내므로, 쉽게 기능을 파악
  • HTTP 표준 프로토콜에 따르는 플랫폼에서 사용 가능
  • 서버와 클라이언트의 명확한 구분

 

  • REST API의 표준이 존재하지 않는 단점
CRUD HTTP Method
Creat 생성 GET 조회
Read 조회 POST 생성/ 수정/ 삭제 
Update 수정  
Delete 삭제  

06. HTTP Method 사용하기

HTTP Method의 개념 - GET / POST

GETPOST는 HTTP Method 중 일부

GET은 데이터를 URL 뒤에 ?와 함께 사용

# GET 함수를 사용할 경우 내가 원하는 데이터가 주소창에 모두 되기 때문에 보안성이 떨어지며길이 제한이 있다.
# 하지만 사용하기 편리하기에 민감한 데이터가 아닌 것에 하에 사용 -> 로그인 X
# GET은 url에 데이터를 추가하기에 html head에 데이터를 추가하는 개념


POST는 특정 양식(form)에 데이터를 넣어 전송하는 방법

# HTML은 head와 body로 구성, form data는 body에 들어간다. 
# data를 body에 저장하여 서버에 전송하기 떄문에 보안 우수, 길이 제한 없음

HTTP Method의 예시 - GET / POST

GET 방식의 예시

http://사이트의_주소?데이터=123

GET의 예시 -> url 뒤에 물음표를 붙여서 데이터를 서버에 전송

 

네이버 영화 페이지

-> https://movie.naver.com/movie/bi/mi/basic.nhn

네이버 영화의 해리포터와 마법사의 돌 페이지

-> https://movie.naver.com/movie/bi/mi/basic.nhn?code=30688

 

POST방식의 예시

http://사이트의_주소

POST의 예시 -> 일정한 양식에 담아 데이터를 숨겨서 서버에 전송

 

네이버 로그인 페이지 url

-> https://nid.naver.com/nidlogin.login

네이버 로그인 데이터 전송 url
-> https://nid.naver.com/nidlogin.login

app.route()에 method 옵션 사용

app.py

@app.route('url1', methods=["GET"])
@app.route('url2', methods=["POST"])
@app.route('url3', methods=["GET", "POST"])

 

  • app.route()methods라는 옵션을 추가해서 해당하는 HTTP Method만 사용 할 수 있도록 적용 가능
  • url1, url2 는 각각 GET, POST 메서드만 사용 가능
    # methods=[“GET], [“POST”]를 나누면 좋은 점은 url 1, url 2가 같아도 상관이 없음 - methods가 다르기 때문
  • url3GET, POST 모두 사용이 가능

GET 요청만 사용하기

app.py

from flask import * 
app = Flask(__name__) # Flask 기능을 사용하겠다

@app.route("/" , methods=["GET"] ) # URL 뒤에 ?name=elice를 넣어 GET 요청을 합니다. 
def elice():
     name = request.args.get['name']
    result = "hello. " + name
    return result

if __name__== "__main__": 
	app.run()

# 대부분의 서버는 localhost: 5000/  기본주소 ex)localhost: 5000/ ?name=elice

# request.args는 GET 요청으로 들어온 데이터들을 키 값을 가지고 하나씩 볼 수 있게 해줌

POST 요청만 사용하기 - templates/index.html 만들기

<html>
<body>
  <form action='/login' method='post'> 
	<p>
	아이디 : <input type='text' name='id'>
	</p>
	<p>
	비밀번호 : <input type='password' name='pwd'> 
    </p>
    <button type='submit' />
  </form>
</body> 
</html>

# [POST]를 사용할 경우 form 태그를 사용하여 data를 한다.

여라 가지 url 연결하기

app.py

from flask import *
app = Flask(__name__)

@app.route("/" , methods=["GET"] ) 
def elice():
    return render_template("index.html")

@app.route("/login" , methods=["POST"] )
def elice_post():
	id= request.form['id'] 
    pwd= request.form['pwd']
    if id == 'elice' and pwd = '1234':
       return 'Hi! Elice!"
   else:
       return "ERROR"
if __name__== "__main__":
    app.run()

2. 서비스 구현하기

수강 목표

  1. Flask의 기본 기능을 이해한다.
    Blueprint와 Jinja Template를 사용해 Flask 서버를 다룰 수 있습니다.
  2. Data를 다룰 수 있다.
    CRUD의 개념을 알고, Flask 서버에서 CRUD의 기능을 작성할 수 있습니다.
  3. 세션, 쿠키, 로깅에 대해서 이해한다.
    세션과 쿠키를 이해할 수 있고, 로그인에 적용할 수 있습니다. 또한, 서버의 로깅에 대해 이해하고 사용할 수 있습니다.

01. Blueprint와 Jinja Template

Blueprint

# API들을 분류 및 관리

Flask의 기능이 점점 늘어날수록, 자연스럽게 코드의 양이 증가,

이때, Blueprint를 사용해서 길어진 코드를 모듈화해주어

수정 개발과 유지보수에 용이하게 코드를 관리할 수 있다.

 

# Flask에서는 render_template()을 사용하지 않고도 HTML 코드를 통해 화면에 원하는 그래픽을 구성하여 보여줄 수 있다.

# Blueprint는 웹 애플리케이션에 등록할 수 있는 청사진, 경로, 기타 앱 관련 기능을 나타낸다.
# Blueprint는 웹 애플리케이션의 객체를 미리 요구하지 않고 기능을 정의할 수 있다. 
# 또한, 파일을 여러개로 효과적으로 개발할 수 있게끔 할 수 있어 유지보수적인 측면에서 이점을 가진다.
# Blueprint 없이 Flask 웹 애플리케이션을 만들 수 있으나, 복잡한 웹 애플리케이션을 구현하고자 할 때 블루프린트를 사용하는 것을 권장

Blueprint를 사용하지 않을 때

from flask import Flask,jsonify
app = Flask(__name__)

@app.route("/", methods=['GET'])
def home_route():
   return jsonfiy('home')

@app.route("/first",methods=['GET'])
def first_route():
   return jsonify('first page')

@app.route("/second",methods=['GET'])
def second_route():
   return jsonfiy('second page')

@app.route("/third",methods=['GET'])
def third_route():
   return jsonfiy('third page')

#...생략

if __name__ == '__main__':
   app.run(debug=True)

# @app.route -> API 수

Blueprint를 사용했을 때

app.py

# 오로지 서버 실행

from flask import Flask 
from first_api import bp

app = Flask(__name__) 
app.register_blueprint(bp)

if __name__ == '__main__':
   app.run(debug=True)

first_api.py

from flask import Blueprint,jsonify
bp = Blueprint('bp',__name__)

@bp.route('/first',methods=['GET'])
def first_route():
   return jsonify('first page')

@bp.route('/second',methods=['GET'])
def second_route():
   return jsonify('second page')
   
#... 생략
   
# blueprint의 내용은 flask 서버의 내용과 굉장히 유사하게 작성이 되어, 
# flask에 연동이 될 수 있도록 코드가 이와 같이 작성됨
# bp는 flask app.py에 있는 @app과 유사하게 동작함

# blueprint를 사용하면,  app.py에서 실제로 사용했었던 routing들을 blueprint를 사용한 객체에 바로 사용할 수 있다.
# 함수 작성, return jsonify, url 작성, methods 제한해주는 거나 다 동일하나,

# 그 API을 따로 모아 파이썬 파일을 만들고 그 파이썬 파일을 사용하는 것이 key point

Jinja 2

Jinja2는 Python에서 가장 많이 사용되는 템플릿

서버에서 받아온 데이터를 효과적으로 보여주고 비교적 간략한 표현으로 데이터 가공 가능

# HTML 내에서 python문법처럼 데이터를 불러오고, 반복문,if 문등을 사용하여 데이터를 나타낼 수 있다.

 

# if나 for를 사용할 때는 {%for 또는 if%} 태그를 사용, 제어문이 끝날 때는 {%endfor 또는 endif%}로 마무리
# 변수를 사용할 때는 {{변수명}} 형태로 사용

Jinja2 Template에서 데이터 넘겨주기 – 단일변수

app.py

 @app.route("/") 
 def elice():
   return render_template(
             'index.html' ,
             data = 'elice'
		)

templates/index.html

<html>
  <head>
	<title> jinja example </title> 
  </head>
  <body>
	{{ data }}     <!-- jinja -->
  </body> 
</html>

 

Jinja2 Template에서 데이터 넘겨주기 – list

app.py

@app.route("/")
def elice():
	my_list = [1,2,3,4,5] 
    return render_template(
			'index.html' , 
            data = my_list 
            )

templates/index.html

<html> 
  <head>
    <title> jinja example </title>
  </head>
  <body>
    {{ data }}
    {% for d in data %}
	  {{ d }} 
    {% endfor %}
  </body>
</html>

 

Jinja2 Template에서 데이터 넘겨주기 – dictionary

app.py

@app.route("/")
def elice():
	my_data = {'name': 'elice'} 
    return render_template(
			'index.html' , 
            data = my_data 
            )

templates/index.html

<html>
  <head>
	<title> jinja example </title> 
  </head>
  <body>
    {{ data.get('name') }}
  </body> 
</html>

02. 게시판을 위한 CRUD 설계 및 제작

API 동작 원리

클라이언트 HTTP request -> API ----------------> 서버
<--------------- <-HTTP response

CRUD

Create 데이터 생성 API 설계
Read 데이터 조회
Update 데이터 수정
Delete 데이터 삭제

 

게시판 내용 생성 # append

게시판 조회 # for data in data_list: print(data) /// for data in data_list: if data.get(‘id’)= 2. print(data)

게시판 수정 및 추가 # 특정 data “id” - 해당 데이터의 유니크한 값

게시판 내용 삭제 # 특정 data “id” - 해당 데이터의 유니크한 값

CRUD HTTP Method DB 명령어
Create POST INSERT
Read GET SELECT
Update PUT, PATCH, POST UPDATE
Delete DELETE DELETE

03. Authentication

Authentication

Authentication이란 사용자가 누구인지 확인하는 절차를 의미 

→ 회원가입하고 로그인하는 과정

 

# 1. 회원가입 -> 내 정보 서버전송 Create ~> 암호화
# 2. 로그인 -> ID/pw 서버전송 Read ~> 로그인- session - jwt - token
# 3. 로그아웃 / session X

04. 로그인 기능 구현

쿠키

클라이언트에 저장되는 키/값 이 들어 있는 데이터

사용자가 따로 요청하지 않아도, Request 시에 자동으로 서버에 전송

 

# 쿠키에는 유효기간이 존재. 유효기간이 만료되지 않았다면 브라우저를 껐다 켜도 로근인이 돼 있다.

# 클라이언트가 서버에 요청(request)할 경우서버는 html header에 쿠키 정보를 담아 전송.  -> 쿠키가 웹 브라우저에 저장된다

# 쿠키는 내 컴퓨터의 브라우저에 저장이 된다.

세션

# Session -> 서버에서 관리 / 고유한 ID 부여

쿠키를 기반으로 하지만 서버 측에서 관리하는 데이터

클라이언트에 고유 ID를 부여하고 클라이언트에 알맞은 서비스를 제공

서버에서 관리하기 때문에 보안이 쿠키보다 우수

 

# client   —req—> server
               <—res—
http 통신의 한 사이클
# 쿠키와 세션이 없다면 기존에 요청이 있었는지 모르는 상태로, 매번 새로 요청 및 응답이 발생

로그인 예제

user_id = request.form['user_id']
user_pw = request.form['user_pw']
user ={'user_id': 'elice', 'user_pw': '1234'} 
	if user is not None: 
    if user_id == user['user_id'] and user_pw == user['user_pw']:
        session['login'] = user.id
        return jsonify({"result":"success"})
    else:
        return jsonify({"result":"fail"}) # 로그인 실패
  • request로 받아온 로그인 정보 (user_id, user_pw)를 변수에 저장
  • 데이터베이스에 user_id와 같은 데이터가 있는지 조사
  • 입력된 user_pw와 저장된 패스워드가 같은지 체크

로그아웃 예제

 session['login'] = None

 

- 로그아웃을 할 때는 정보를 받을 필요가 없다.
-
현재 저장된 session의 데이터를 비워 주면 기능 완료

05. 로깅

로깅

프로그램이 작동할 때 발생하는 이벤트를 추적하는 행위

프로그램의 문제들을 파악하고 유지보수 하는 데 사용되며, 로깅을 통해 발생한 에러를 추적할 수 있다.

로깅 - level

DEBUG < INFO < WARNING < ERROR < CRITICAL

기본 로거 레벨 세팅은 WARNING이기 때문에 설정 없이 INFO, DEBUG를 출력할 수 없다.

 

DEBUG: 상세한 정보
INFO: 일반적인 정보
WARNING: 예상치 못하거나 가까운 미래에 발생할 문제

ERROR: 에러 로그. 심각한 문제
CRITICAL: 프로그램 자체가 실행되지 않을 수 있는 문제

 

example

# 기본 세팅이 warning이기 때문에 출력되지 않는다.

import logging
if __name__ == '__main__':
      logger.info("hello elice!")

# 로깅을 만들어주는 메소드

mport logging
if __name__ == '__main__':
      logger = logging.getLogger()
      logger.setlevel(logging.DEBUG)
      logger.info("hello elice!")

Flask에서 로깅

에러가 발생했을 때 Flasklogger를 사용하여 에러를 확인 가능

 

# @app.errorhandler(404): 특정 에러에 대하여 errorhandler를 사용하면 해당 에러가 발생했을 때 매칭된다.

 

Flask에서 기본적으로 logging 제공

 from flask import Flask
app = Flask(__name__)
if __name__ == '__main__': 
	app.logger.info("test")
	app.logger.debug("debug test") 
    app.logger.error("error test")
    app.run()

3. RDB로 리소스 관리 및 저장하기

수강 목표

  1. 데이터베이스의 종류를 안다.
    데이터베이스의 종류를 알고, 각각의 특징을 설명할 수 있습니다.
  2. Flask에서 DB가 하는 역할을 이해한다.
    Flask에서 DB가 하는 역할을 이해하고, Database를 사용해서 서비스를 작성할 수 있습니다.
  3. JWT를이해할수있다.
    JWT의 개념을 이해하고, 사용하는 방법과 이유를 설명할 수 있습니다.

01. 관계형 데이터베이스

데이터 베이스

데이터베이스(Database)는 데이터를 저장하는 공간으로서,
서비스를 개발하는 곳에서는 빠질 수 없는 중요한 요소

 

데이터베이스의 종류는 크게 관계형 데이터베이스(RDB, Relational Data Base)NoSQL(Not Only SQL)로 나누어져 있다.

관계형 데이터베이스(RDB)

키(key)와 값(value)의 간단한 관계를 테이블화 시킨 데이터베이스

# 굉장히 정형화 되어 있기 때문에 자유도가 떨어진다.

RDB의 특징

DML(Data Management Language)을 사용해서 데이터 간 결합, 제약조건 등의 설정을 통해 데이터를 추출할 수 있다.

테이블 간의 데이터 관계를 설정할 수 있다.

RDB의 형태

  • RDB는 정형화된 데이터를 저장하고 있다. (다른 형태의 데이터가 들어올 수 없다)
  • 각 컬럼(세로줄) 마다 데이터의 형태가 동일
  • SQL 질의어 (SQL 쿼리)를 사용

02. RDB와 Flask의 상호작용

RDB와 Flask

파이썬은 오픈 소스와 상용 데이터베이스에 대한 대부분의 데이터베이스 엔진을 위한 패키지를 가지고 있다.

Flask에서 입력받은 내용을 DB에 저장할 수 있다.

→효율적인 데이터 관리 가능

 

Client   Server
사람 -------> Web
browser
-HTT--> Flask Python
object ->
SQL
Alchemy
SQL
statement->
MySQL
<------- <-JWT- <------- <-------

# 1. 로그인
ID, pw 입력하여  서버로 http 통신을 시작
플라스크는 해당하는 API에 기능을 보고 
ID와 pw를 python object로 보유
SQLAlchemy or database package  데이터베이스에 저장된 값과 python object id, pw 정보가 같은지 확인
같다면 -> 로그인
로그인이 됐다면 저장을 한 정보: 쿠키/ 세션/ JWT
웹 브라우저는 위 정보를 응답을 받아 사용자처리가 됨
2. 게시판 읽기
사용자가 웹 브라우저에 게시판이나 블로그 주소를 요청
해당 API를 플라스크 서버가 게시글을 읽어와야겠다 -> db에서 data를 플라스크로 가져옴 ->  웹 브라우저에 읽을 목록들을 전달

 

'''

- database package
- SQLAlchemy
등 다양한 옵션존재

어떤 기능을 할것인지, 어떤 서비스를 제공할 것인지에 따라 플라스크 서버가 할 일이 달라짐

'''

03. 간단한 게시판 만들기

구현 사항

데이터베이스에 저장되는 데이터를 활용해서 사용자를 검색해보자.
데이터베이스에 저장되는 데이터를 활용해서 사용자를 추가해보자. 

, 중복 사용자를 방지한다.
게시판 내용을 생성, 조회, 수정, 삭제하자.

게시판 등록

게시판의 기능을 DB에 적용하기 위해서는 리소스를 다뤄야 한다.

  • 아이템 리소스를 데이터베이스에 쓰기(Create)
  • 데이터베이스에서 아이템 리소스 검색(Read)
  • 데이터베이스에서 아이템 리소스 수정(Update)
  • 데이터베이스에서 아이템 리소스 삭제(Delete)
# [create]
cur.execute(‘insert into board(name, context) values(?, ?)’, (name,context))

con = sqlite3.connect(정보) # 데이터베이스에 연결
cur = con.cursor() # sql문을 다를 수 있게 도와주는 커서 라는 객체
cur = database connect 부분

# [Read]
cur.exectue(‘select * from board’ (where name = name, context = context) )

04. Flask JWT

JWT(JSON Web Token)

웹 표준(RFC 7519)으로서 두 개체에서 JSON 객체를 사용하여 통신

  • JSON 포맷을 이용하여 사용자에 대한 속성을 저장하는 Web Token
  • 토큰 자체를 정보로 사용하는 Self-Contained 방식으로 정보를 안정하게 전달

'''

JWT는 서버와 클라이언트 간의 인증을 도와주는 도구 중의 하나
서버와 클라이언트의 각각의 역할에 집중할 수 있게 해주는 매개체가 되기도 한다.
유저는 인증을 받아야 하고, 서버는 그 인증을 보고 확인을 해주는 역할을 수행 하는 데에 집중하게 해준다.
세션 관리를 하지 않고 JWT로 인증을 수행 하므로 더는 인증을 위한 세션을 관리하면서 서버의 리소스를 낭비하지 않아도 된다.

'''

JWT의 생김새

header . payload . signature

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6ImVsa

WNlIiwiaWF0IjoxNTE2MjM5MDIyfQ.KeQ_ZRON0VcycCQ6YtOIoNfKPvUjn8gNlR6HdE_xsis

 

Header에는 토큰의 타입과 알고리즘을 저장
Payload에는 토큰에 담을 정보를 넣는다.
Signature에는 헤더와 정보의 인코딩 값들과 관련된 비밀키가 들어있다.

JWT 구조

JWTHeader, Payload, Signature 3가지로 이루어지며, JSON 형태인 각 부분은 Base64로 인코딩되어 표현됨.

각각의 부분을 이어주기 위해 "." 구분자를 사용하여 구분합니다.

 

Base64는 암호화된 문자열이 아니고, 동일한 문자열에 대해 항상 같은 인코딩 문자열을 반환

Header

토큰의 헤더는 typalg 총 두 가지 정보로 구성

  • typ: 토큰의 타입 지정
  • alg: 알고리즘 방식 지정 (서명 및 토큰 검증에 사용)

 { "typ": "JWT", "alg": "HS256" }

Payload

토큰의 Payload에는 토큰에서 사용할 정보의 조각들인 클레임(Claim)이 담겨 있다.

클레임은 총 3가지로 나누어지며, JSON 형태로 다수의 정소를 넣을 수 있다.

 

토큰에 담을 ‘정보’ 
- registered
- public
- private

 

등록된 클레임 (registered claim)

토큰 정보를 표현하기 위해 이미 정해진 종류의 데이터

모두 선택적으로 작성이 가능하며 사용하는 것을 권장

 

'''

  • 토큰 발급자 (issuer)
  • 토큰 제목 (subject)
  • 대상자 (audience)
  • exp (만료시간)
  • iat (issued at)
  • jti (고유식별자) - 토큰의 중복사용을 방지 (일회용 토큰)

'''

공개 클레임 (Public claim)

충돌이 방지된 이름을 가지고 있어야 한다.

충돌을 방지하기 위해 클레임 이름을 URI 형식으로 짓는다.

 

{ https://elice.io: true }

 

비공개 클레임 (Private Claim)

등록과 공개를 제외한 클레임이자 사용자 정의 클레임으로 서버와 클라이언트 협의로 사용되는 클레임 

서버와 클라이언트 사이에 임의로 지정한 정보를 저장

이 클레임은 공개 클레임과 달리 이름이 중복되어 충돌될 수 있으니 유의

{ "student_name": "elice" }

Signature

JWT의 마지막 부분인 서명토큰을 인코딩하거나 유효성 검증을 할 때 사용하는 고유한 암호화 코드

 

서명은 헤더의 인코딩 값과 정보의 인코딩 값을 합친후 주어진 비밀키로 해시를 하여 생성

JWT 예시

import jwt

data_to_encode = {"name": "elice"}
encryption_secret = "secrete"
algorithm = "HS256"
encoded = jwt.encode(data_to_encode, encryption_secret, algorithm=algorithm)
decoded = jwt.encode(encoded, encryption_secert, algorithm=[algorithm])
  • PyJWT 모듈 필요
  • data_to_encode는 jwt로 암호화할 데이터
  • encryption_secret은, jwt 모듈이 사용되는 곳을 특정하기 위해서 비밀키를 넣어줌
  • algorithm은 암호화 알코리즘
  • encoded는 jwt화된 문자열, decoded는 암호를 풀어 낸 원본 문자열이 출력됨.

4. SQL Alchemy

수강목표

  1. ORM을 이해할 수 있다.
    SQLAlchemy를 사용한 ORM을 이해하고 사용할 수 있습니다.
  2. ORM을 사용한 모델을 작성할 수 있다.
    이해한 ORM을 바탕으로 우리가 원하는 데이터베이스 모델을 만들 수 있습니다.
  3. ORM의 쿼리를 이해할 수 있다.
    ORM의 쿼리를 이해하고 상황과 조건에 맞게 사용할 수 있습니다.

01. SQL Alchemy와 ORM

ORM

데이터베이스에 객체를 통해 접근하는 방법

ORM(Obejct Relational Mapping, 객체 관계 매핑)이라 함.

ORMSQL 질의어가 없어도 데이터베이스를 다룰 수 있도록 도와준다.

# python Class -> table

ORM의 장점

DB에 대한 큰 고민 없이, 데이터베이스를 코드로 다룰 수 있다.

테이블 구조가 변경될 때, ORM 모델만 수정하면 된다.
코드로 작성하기 때문에 쿼리를 직관적으로 이해할 수 있다.

단점

라이브러리의 내부 구현에 따라 잘못 동작하여 성능 저하가 발생하는 경우가 있다.

SQL Alchemy

# ORM을 쓸 수 있도록 도와주는 python 라이브러리

 

파이썬 ORM 라이브러리
→ 파이썬 코드에서
Database와 연결하기 위해 사용할 수 있는 라이브러리

 

'''

SQLAlchemy를 사용하는 이유

프로그램 유지 보수가 편리해진다.
프로그래밍 언어로 객체간의 관계를 표현할 수 있다.
SQLAlchemy를 이용하면 SQL 쿼리 오류가 발생할 확률이 적다.
SQLAlchemy를 사용하면 SQL 쿼리를 사용하지 않고 프로그래밍 언어로 객체간의 관계를 표현할 수 있다.

'''

02. DB와 Model

models.py

 member1 = Member(name='여왕’, age='18') 
 db.session.add(member1)

 '''
기존 코드
meber1 = Member()
member1.name = ____
member1 .age = ____
db.session.add(member1)
db.session.commit()

 

데이터를 다루는 파이썬 파일은 대부분 model.py에서 작업 할 것
model.py라는 곳에서 데이터 베이스 클래스를 만들고 거기 안에서 이 테이블은 이 클래스라는 것을 선언하고 작업 진행

 

하나의 객체를 db.session.add 해서 넣어주는 과정을 SQLAlchemy가 도와주고 있다.
'''

  • 위의 코드에서 Member는 파이썬 클래스이며, DBMember 테이블과 매핑하여 사용
  • DB의 테이블과 매핑되는 클래스를 모델이라고 한다.

ORM Model

models.py

class Members(db.Model):
	__tablename__ = 'my_user' # 선언 여부는 선택, 만약 원하는 테이블이름이 있다면,  만들어주면 된다.
							    #이미 존재하는 테이블을 꼭 명시하고 싶다면 테이블 이름을 적어주자.
	id = db.Column(db.Integer, primary_key=True, nullable=False) 
    name = db.Column(db.String(20), nullable=False)
	age = db.Column(db.Integer, nullable=False)

'''

Member라는  객체가 db.Model을 상속하는 것
db.Model은 SQLAlchemy 패키지 안에 존재 
class Member라는 ORM 모델을 선언하기 전에 db = SQLAlchemy() 객체를 선언
SQLAlchemy라는 객체가 db안에 들어감
db는 SQLAlchemy에 대한 모든 기능을 사용
그 안에 있는 Model이라는 기능을 클래스에 넣어주어
Members라는 클래스 안에서는 db.Model과 관련된 내용을 사용할 수 있으면서 ORM을 나타내는 형태를 띔

 

id, name, age가 각각 어떤 형태의 data 저장 할 것인지  직접적인 데이터를 넣어주지 않음 (선언에서)
하지만 어떤 데이터가 들어갈 것인지 명시해줌

'''

  •  __tablename__ 은 데이터베이스 테이블을 명시
  • id, name, age는 데이터베이스 테이블의 컬럼을 명시
  • 해당하는 데이터베이스를 다룰 때, Members 클래스로 접근
 members = Members()
members = Members() --> members.id --> db.session.query(members)
.filter(members.name == name)
members.name
members.age

'''

members라는 변수에 Members()라는 클래스를 대입
-> members는 클래스 및 ORM
-> Table 접근 할 수 있다.

 

db.session.query(Members) = Members.query.  으로 동일하게 사용
왼쪽: 명시적 - 내가 뭘 할건지, 어떤걸 할건지 얘기해줌: db에 붙을 건데, db session에 붙을 건데, 그리고 query를 사용할건데,
어떤 테이블에서 사용할거야~ 라는 정보를 제공
오른쪽: 나 이미 db에 붙어있는거 알아, 그러니까 Member안에서 query사용할거야~ 라는 얘기를 해줌

 

filter: 나는 특정 데이터만 불러오고 싶어~ Members라는 테이블에 있는 name이 지금 내가 가지고 있는 name이랑 동일할 때

'''

03. Query

CRUD 예제 - Create: 데이터 저장

models.py

class Members(db.Model):
	__tablename__ = 'my_user'
	id = db.Column(db.Integer, primary_key=True, nullable=False) 
    name = db.Column(db.String(20), nullable=False)
	age = db.Column(db.Integer, nullable=False)
members = Members() ---> members.id ---> db.session.query.add(members)
members.name ad.session.commit()
members.age  

CRUD 예제 - Read: 데이터 읽기

model.py

class Members(db.Model):
	__tablename__ = 'my_user'
	id = db.Column(db.Integer, primary_key=True, nullable=False) 
    name = db.Column(db.String(20), nullable=False)
	age = db.Column(db.Integer, nullable=False)
 members = db.session.query(Members)
.filter(Members.name == 'elice').all() # == Members.query.filter

'''

~.all() -> List 형태로 출력(반복문 출력 가능)
1. db.session.query(Members).all() : filter x -> members의 전체 데이터를 가져올 것이다. (전체 데이터의 개수)
2. db.session.query(Members).first() -> 1개, 리스트 x, 클래스 모델 변수로 돌아옴 (Model이다 -> class)

'''

CRUD 예제 - Update: 데이터 수정

class Members(db.Model):
	__tablename__ = 'my_user'
	id = db.Column(db.Integer, primary_key=True, nullable=False) 
    name = db.Column(db.String(20), nullable=False)
	age = db.Column(db.Integer, nullable=False)
members=db.session.query(Members)
.filter(Member.name == 'elice').all()
------> members[0].name = 'oliver'
 
db.session.commit()

# [0]을 안 쓰고싶으면 all()대신 first() 사용

CRUD 예제 - Delete: 데이터 삭제

class Members(db.Model):
	__tablename__ = 'my_user'
	id = db.Column(db.Integer, primary_key=True, nullable=False) 
    name = db.Column(db.String(20), nullable=False)
	age = db.Column(db.Integer, nullable=False)
me=db.session.query(Members)
.filter(Member.name == 'elice').firstl()
------> me.session.delete(me)
 
db.session.commit()

'''

CRUD 공통점
- db.session.commit()을 써주어야 데이터가 반영이 된다.

Create -> add() / 나머지는 필요 x
db.session.query(A) -> A.query

'''

Query 사용법 - equal

@app.route('/')
def list():
   member_list = Member.query.filter(Member.name == 'Elice')
   return " ".join(i.name for i in member_list)

Query 사용법 - not equal

@app.route('/')
def list():
   member_list = Member.query.filter(Member.name != 'Elice')
   return " ".join(i.name for i in member_list)

Query 사용법 - like

@app.route('/')
def list():
   member_list = Member.query.filter(Member.name.like('Elice')) #Member.name.like('%Eli%')
   return " ".join(i.name for i in member_list)

Query 사용법 - in

@app.route('/')
def list():
   member_list = Member.query.filter(Member.name.in_(['Elice', 'Dodo']))
   return " ".join(i.name for i in member_list)

'''

in = 내가 갖고 있는 리스트에 있는 모든 친구를 찾고싶다
내 친구 이름이 리스트에 있을 때 이친구들 데이터 다 줘~

'''

Query 사용법 - not in

@app.route('/')
def list():
   member_list = Member.query.filter(~Member.name.in_(['Elice', 'Dodo']))
   return " ".join(i.name for i in member_list)

Query 사용법 - is null

@app.route('/')
def list():
   member_list = Member.query.filter(Member.name == None) # None == is null
   return " ".join(i.name for i in member_list)

Query 사용법 - is not null

@app.route('/')
def list():
   member_list = Member.query.filter(Member.name != None)
   return " ".join(i.name for i in member_list)

Query 사용법 - and

 

from sqlalchemy import and_
@app.route('/')
def list():
   member_list = Member.query.filter(and_(Member.name == 'Elice',Member.age == '15'))
   return " ".join(i.name for i in member_list)

# db.session.query(Member).filter(A, B, C) = and_ 동일

and &을 사용

member_list = Member.query.filter((Member.name==key1) & (Member.age==key2)).all()

Query 사용법 - or

from sqlalchemy import or_
@app.route('/')
def list():
   member_list = Member.query.filter(or_(Member.name == 'Elice', Member.age == '15'))
   return " ".join(i.name for i in member_list)

 or |을 사용

member_list = Member.query.filter((Member.name==key1) | (Member.age==key2)).all()

Query 사용법 - order by

@app.route('/')
def list():
   member_list = Member.query.order_by(Member.age.desc())
   return " ".join(i.name for i in member_list)

 

Query 문 실행 결과 오름차순으로 정렬(desc() 사용시 내림차순)

Query 사용법 - limit

@app.route('/')
def list(limit_num = 5):
   if limit_num is None:
      limit_num = 5
   member_list = Member.query.order_by(Member.age.desc()).limit(limit_num)
   return " ".join(i.name for i in member_list)

 

Query 문 실행 결과에서 limit 크기만큼 반환

Query 사용법 - offset

@app.route('/')
def list(off_set = 5):
   if off_set is None:
      off_set = 5
   member_list = Member.query.order_by(Member.age.desc()).off_set(off_set)
   return " ".join(i.name for i in member_list)

 

Query 문 실행 결과에서 offset 크기만큼 앞에서부터 생략하고 반환

member_list = Member.query.order_by(Member.age.desc()).limit(limit_num).offset(offset_num)

Query 사용법 - count

@app.route('/')
def list():
   member_list = Member.query.order_by(Member.age.desc()).count()
   return str(member_list)

 

Query 문 실행 결과로 반환된 tuple 수를 반환

반응형

댓글