Flask

 

Python 기반 마이크로 웹 프레임 워크

작지만, essential한 기능은 다 있다.


가상환경 설치하기

가상환경 설치

 

pip install virtualenv 

 

가상환경 생성

 

virtualenv <가상환경 이름>

 

가상환경 진입

 

source <가상환경 이름>/bin/activate -> Macos의 경우

.\venv\Scripts\activate.bat -> Windows의 경우

 

가상환경 진입 후 pip freeze를 해보면 아무런 라이브러리도 설치가 되어 있지 않음을 확인할 수 있다. 즉, 독립적이란 것을 알 수 있다.


Flask를 이용해 웹 브라우저를 통해 Hello World 제공받기!

 

가상환경을 만든 폴더에 다음과 같은 app.py를 생성한다.

 

 

이를 실행하게 되면 다음과 같은 결과가 나타나게 되는데

 

 

이 때 나타난 웹 사이트 주소로 들어가게 되면 다음과 같이 "Hello World!"란 문구가 나타나 있는 사이트가 뜬다.

 


인터넷과 웹

 

인터넷(Internet)

 

전 세계 컴퓨터를 하나로 합치는 거대한 통신망

 

/

 

웹(Web)

 

인터넷에 연결된 사용자들이 정보를 공유할 수 있는 공간

 

웹에 있는 개개의 정보는 웹페이지

 

웹페이지의 집합은 웹사이트

 

웹은 클라이언트(정보 요청자)서버(정보 제공자) 사이의 소통이다.


웹 동작방식

 

  1. Client가 Server에 정보를 요청한다. = Request
  2. Server는 이 요청받은 정보에 대한 처리를 진행한다. (웹 페이지에 요청한 정보를 렌더링하여 제공)
  3. Server가 Client에게 요청에 대해 응답한다. = Response

요청을 하고, 응답을 하는 과정에서 사람들마다 정보를 제공하는 형식이 제각각이라면 인터넷을 사용하고 웹을 구현하기 어려울 것이다.

그래서 웹을 사용하는 사람들끼리의 약속을 했는데 그것이 바로 HTTP이다.

즉, 웹은 HTTP RequestHTTP Response를 통해 동작하게 된다.


REST API

 

API란? 프로그램들이 서로 상호작용하는 것을 도와주는 매개체

 

REST(Representational State Transfer)란? 웹 서버가 요청을 응답하는 방법론 중 하나. 데이터가 아닌, 자원(Resource)의 관점으로 접근

 

HTTP URI(웹 상에 정보를 보낼 때 보낼 위치 식별자)를 통해 자원을 명시하고 HTTP Method(GET POST PUT 등)를 통해 해당 자원에 대한 CRUD를 진행


REST API의 Stateless(무상태성)

 

Client의 Context를 서버에서 유지하지 않는다. 각각의 Request를 독립적으로 관리한다.


Coffee shop REST API 구축해보기

 

from flask import Flask, jsonify, request
# jsonify는 딕셔너리 타입을 json타입으로 변환하는 것
# request는 HTTP request를 다룰 수 있는 모듈

app = Flask(__name__)

menus = [
    {"id" : 1, "name" : "Espresso", "price" : 3800},
    {"id" : 2, "name" : "Americano", "price" : 4100},
    {"id" : 3, "name" : "CafeLatte", "price" : 4600}
]

@app.route('/')
def hello_flask():
    return "Hello World!"

# GET /menus 자료를 가지고 온다.
@app.route('/menus')
def get_menus():
    return jsonify({"menus" : menus})

# POST /menus 자료를 자원에 추가한다.
@app.route('/menus', methods=['POST'])
def create_menu(): # request가 JSON이라고 가정
    # 전달받은 자료를 menus 자원에 추가
    request_data = request.get_json() # {"name" : ..., "price" : ...}
    new_menu = {
        "id" : 4,
        "name" : request_data['name'],
        "price" : request_data['price']
    }
    menus.append(new_menu)
    return jsonify(new_menu)

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

 

GET /menus 실행 결과 (READ)

 

 

POSTMAN(API 테스트 툴)을 사용해 POST /menus 실행 결과 (CREATE)

 

Body 탭에 raw 데이터를 선택해 전송할 데이터를 입력해주고, JSON 타입으로 변경해서 Send하면 다음과 같은 결과가 나온다.

 

 

원하는 결과가 제대로 나오는 것을 확인할 수 있다. 데이터를 추가해주었으니 이후에 GET을 해주어 결과를 확인하면

 

 

다음과 같이 추가한 데이터가 잘 들어가 있음을 확인할 수 있다.

 

우리는 위 과정을 통해 CRUDC(Create)R(Read)을 구현했다.

U(Update)D(Delete)도 마저 구현해보자! 

 

from flask import Flask, jsonify, request

app = Flask(__name__)

menus = [
    {"id" : 1, "name" : "Espresso", "price" : 3800},
    {"id" : 2, "name" : "Americano", "price" : 4100},
    {"id" : 3, "name" : "CafeLatte", "price" : 4600}
]

@app.route('/')
def hello_flask():
    return "Hello World!"

# GET /menus 자료를 가지고 온다.
@app.route('/menus')
def get_menus():
    return jsonify({"menus" : menus})

# POST /menus 자료를 자원에 추가한다.
@app.route('/menus', methods=['POST'])
def create_menu(): # request가 JSON이라고 가정
    # 전달받은 자료를 menus 자원에 추가
    request_data = request.get_json() # {"name" : ..., "price" : ...}
    
    new_menu = {
        "id" : len(menus)+1,
        "name" : request_data['name'],
        "price" : request_data['price']
    }

    menus.append(new_menu)
    return jsonify(new_menu)

# PUT /menus/id 해당하는 id에 해당하는 데이터를 갱신한다.
@app.route('/menus/<id>', methods=['PUT'])
def update_data(id):
    request_data=request.get_json()
    request_data["id"]=id
    for i in range(len(menus)):
        if menus[i]["id"]==int(id):
            menus[i]=request_data
    return jsonify({"menus" : menus})

# DELETE /menus/id 해당하는 id에 해당하는 데이터를 삭제한다.
@app.route('/menus/<id>', methods=['DELETE'])
def delete_data(id):
    print(id)
    for i in range(len(menus)):
        if menus[i]["id"]==int(id):
            menus.pop(i)
    return jsonify({"menus" : menus})
    
if __name__ == '__main__':
    app.run()

PUT/menus 실행 결과 (UPDATE)

 

 

DELETE/menus 실행 결과 (DELETE)

 

 


Coffee shop REST API 구축해보기 With Database

 

지금까지 코딩을 통해 사이트가 우리가 원하는 방식으로 잘 작동하는 것처럼 느껴질 수도 있다.

그러나 서버를 껐다키고 오면 정보가 사라지는 불상사가 생길 수도 있다. 이를 방지하기 위해선 데이터만을 보관하는 

데이터베이스를 활용하여 Flask에 연동해 사용하도록 하자.

 

Flask-SQLAlchemy를 사용하였다.

 

참고 사이트

 

flask-sqlalchemy.palletsprojects.com/en/2.x/queries/

lowelllll.github.io/til/2019/04/19/TIL-flask-sqlalchemy-orm/

 

model.py

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import os

app = Flask(__name__)

basedir = os.path.abspath(os.path.dirname(__file__))
dbfile = os.path.join(basedir, 'db.sqlite')

app.config['SQLALCHEMY_DATABASE_URI'] ='sqlite:///'+dbfile

db = SQLAlchemy(app)

class Menu(db.Model):
    __tablename__="Menu"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(32), nullable=False)
    price = db.Column(db.Integer, nullable=False)

 

primary key인 int형 id 속성과

String형 name과 int형 price 속성을 갖고 있는

Menu 테이블을 선언 후,

 

cmd 내에서 model.py를 생성한 디렉토리로 이동하여 파이썬 커널로 적용시켜준 후 다음을 적용시켜주었다.

from Model import db

db.create_all()

menu1 = Menu(id = 1, name = "Espresso", 3800)
menu2 = Menu(id = 2, name = "Americano", 4100)
menu3 = Menu(id = 3, name = "CafeLatte", 4600)

db.session.add(menu1)
db.session.add(menu2)
db.session.add(menu3)

db.session.commit()

 

db.create_all()을 통해 실제 테이블을 생성하고,

 

데이터베이스에 넣을 데이터들을 선언해주고

 

db.session.add(menu1)

...

을 통해 테이블 내 데이터를 삽입하여

 

db.session.commit()을 통해 변경 사항을 적용시킨다.

 

app.py

from flask import Flask, jsonify, request
from model import db
from model import Menu
from model import app

@app.route('/')
def hello_flask():
    return "Hello World!"

@app.route('/menus')
def get_menus():
    menus = Menu.query.all() # Menu 테이블 내 모든 행 가져오기
    m_list = [] # 가져온 menu obj들을 담을 리스트 선언
    for menu in menus:
        m_list.append({"id":menu.id, "name":menu.name,"price":menu.price})

    return jsonify({"menu" : m_list})


@app.route('/menus', methods=['POST'])
def create_menu(): 
    request_data = request.get_json()
    menus = Menu.query.all()
    new_menu = Menu(id = len(menus)+1, name = request_data['name'],price = request_data['price'])
    db.session.add(new_menu) # 행 추가
    db.session.commit() # 변경사항 적용

    return {"id":len(menus)+1, "name":request_data['name'], "price":request_data['price']}

@app.route('/menus/<id>', methods=['PUT'])
def update_data(id):
    request_data=request.get_json()
    menu = db.session.query(Menu).filter(Menu.id==int(id)).first() # URL에 적힌 id와 Menu 테이블 내 행들 중 id가 일치하는 행 갖고 오기 
    menu.name = request_data['name'] # 갖고 온 행의 속성 값 변경
    menu.price = request_data['price']
    db.session.commit()

    return get_menus()

@app.route('/menus/<id>', methods=['DELETE'])
def delete_data(id):
    menu = db.session.query(Menu).filter(Menu.id==int(id)).first()
    db.session.delete(menu) # 갖고 온 행 삭제
    db.session.commit()

    return get_menus()

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

 

GET /menus 실행 결과 (READ)

 

 

POST /menus 실행 결과 (CREATE)

 

 

PUT/menus 실행 결과 (UPDATE)

 

 

DELETE/menus 실행 결과 (DELETE)

 

 

데이터베이스를 적용하지 않았을 때와 유사한 결과가 나온다는 것을 확인할 수 있지만 서버를 껐다 다시 시작해도 데이터가 사라지지 않고 온전히 남아있다는 중요한 사실을 알 수 있다.

Matplotlib

 

파이썬 데이터 시각화 라이브러리

 

라이브러리 vs 프레임워크

 

라이브러리는 개발자들이 만들었을 뿐 이를 바탕으로 원하는 목표를 달성하려면 라이브러리 내부에 있는 코드들을 조합해서 만들어 내야 하지만,

 

프레임워크는 이미 틀이 짜여있고, 그 틀에서 내용물을 채워가며 결과물을 만들어가는 구조이다.

 

matplotlib 사용하기

 

plt.plot([list]) 

 

꺾은선 그래프를 그리는 함수이다. 

다음과 같은 그래프가 나타난다.

 

 

x는 각 리스트의 인덱스,

y는 각 리스트의 요소를 나타냄을 알 수 있다.

 

plt.figure(figsize=(tuple))을 통해 도면 사이즈를 조정할 수 있다.

 

figsize가 (6,6)일 땐 다음과 같은 그래프가 나타난다.

 

 

(3,3)일 땐 다음과 같은 그래프가 나타난다.

 

 

확실히 도면의 크기가 조절된 것을 확인할 수 있다.

 

plt.plot(x, y)

 

x는 정의역, y는 f(x)의 값 혹은 연산 그 자체를 넣으면 2차함수 그래프를 그릴 수 있다.

 

 

 

plt.xlabel('str') / plt.ylabel('str')

 

x축과 y축에 대한 설명을 추가할 수 있다.

 

 

 

plt.axis([x_min, x_max, y_min, y_max])

 

그래프의 범위를 설정할 수 있다.

 

plt.xticks([range]) / plt.yticks([range])

 

각 x축과 y축에 눈금을 그래프의 어디에 위치시킬 것인지 설정할 수 있다.

 

 

plt.title("str")

 

그래프의 위에 제목을 다는 것이다.

 

 

plt.legend()

 

plt.plot()에서 선이 갖는 의미를 라벨링해준 후 plt.legend()를 해주면 선에 대한 범례를 나타내준다. 이 때 주의할 점은, 꼭 선이 생성된 후에 나타내주어야 하므로 plt.plot() 이후에 작성해주어야 한다.

 


그래프의 종류

 

꺾은선 그래프 (Plot)

 

.plot()

 

시계열 데이터에서 많이 사용한다.

 

산점도 (Scatter Plot)

 

.scatter()

 

 

상관관계를 확인할 때 좋다.

 

박스 그림 (Box Plot)

 

.boxplot()

 

 

수치형 데이터에 대한 정보를 나타낼 때 사용한다. Q1, Q2, Q3(사분위수)와 최소값, 최대값에 대한 정보가 나타나있다.

 

맨 아래 선이 min 값

그 위 박스가 시작되는 부분이 Q1

주황색 선이 Q2 (median)

박스가 끝나는 부분이 Q3

맨 위 선이 max 값이다.

 

.boxplot((tuple))을 넣어주면 두 가지 값에 대한 박스 그림을 나타낼 수 있다.

 

 

막대형 그래프 (Bar plot)

 

.bar(x, y)

 

 

범주형 데이터의 값과 그 값의 크기를 직사각형으로 나타낸 그림이다.

 

 

※ 히스토그램

 

.hist()

 

 

막대형 그래프와 비슷하지만 계급으로 나타낸다는 특징이 있다.

즉, 0, 1, 2 그 때 그 때의 값을 나타내는 것이 아니라 0~2까지의 "범주형" 데이터로 구성 후 그림을 그린다.

 

원형 그래프 (Pie Chart)

 

.pie()

 

 

데이터에서 전체에 대한 부분의 비율을 부채꼴로 나타낸 그래프이다.

다른 그래프에 비해 비율 확인에 용이하다.


Seaborn

 

커널밀도그림 (Kernel Density Plot)

 

sns.kdeplot()

 

 

히스토그램과 같은 연속적 분포를 곡선화해서 그린 그림이다.

kdeplot에는 shade라는 옵션이 있는데 이를 True로 해주면 다음과 같이 나온다.

 


카운트그림 (Count Plot)

 

sns.countplot()

 

범주형 column의 빈도수를 시각화한다. => Groupby 후 도수를 하는 것과 동일한 효과가 나온다.

 

 

vote_df란 dataframe 생성 후 groupby 해주어 bar chart로 나타낸 모습이다. 이는 sns.countplot()으로 더 간단하면서도 깔끔하게 나타낼 수 있다. 

 

 

다음과 같이 xlabel과 ylabel도 자동으로 붙여주고, count에 다른 색깔을 넣어주어 더 깔끔하게 구분할 수 있게 만들어 준다.


캣그림 (Cat Plot)

 

sns.catplot()

 

catplot - strip형태
catplot - violin형태

 

범주형 데이터와 수치형 데이터의 관계를 보여주는 함수이다.


스트립그림 (Strip Plot)

 

sns.stripplot()

 

 

scatter plot과 유사하게 데이터의 수치를 표현하는 그래프이다.

 

※ swarmplot

 

sns.swarmplot()

 

 

점이 겹쳐있는 부분은 제대로 확인할 수가 없다는 단점이 있는 스트립그림을 보완한 그래프이다.


히트맵 (Heatmap)

 

sns.heatmap()

 

 

데이터의 행렬을 색상으로 표현해주는 그래프이다.

밝으면 밝을 수록 1, 어두우면 어두울수록 0을 나타낸다.

Pandas

 

Table

 

행과 열을 이용해 데이터를 저장하고 관리하는 자료구조(컨테이너)

행은 주로 개체, 열은 속성을 나타낸다.

 

pandas 설치

 

pip install pandas


Series

 

one dimension labeled array이다.

인덱스를 지정해줄 수 있다.

 

Series는 ndarray와 유사하다.

 

슬라이싱이 가능하다.

인덱스 안에 조건을 주어 조건에 충족한 인자만 반환받을 수 있다.

 

Seriss는 dict와 유사하다.

 

series[키 값]을 작성하면 value 값을 가져올 수 있음.

 

Series는 이름을 붙일 수 있다.


Dataframe

 

two dimension labeled table이다.

 

d = any dictionary

df = pd.DataFrame( d ) 

 

다음과 같이 Dataframe은 dictionary를 인자로 넣어주어 생성을 하면 다음과 같은 표가 나타나게 된다.

 

 

각 column의 dtype을 확인하려면

 

df.dtypes를 이용하면 된다.

 


CSV to DataFrame

 

CSV (Comma Seperated Value)를 DataFrame으로 변환하려면

 

pd.read_csv()를 이용하면 된다.

 


dataframe의 일부분만 관찰하기

 

앞부분만 관찰

 

df.head()

 

뒷부분만 관찰

 

df.tail()


dataframe 데이터 접근하기

 

df['column_name'] 또는 df.column_name으로 접근한다.

 

만약 키 값에 공백이 존재할 때는 앞의 방식으로만 접근이 가능해진다.

 

조건을 이용해 데이터를 접근하려면 어떻게 해야 할까?

 

df[df[key_value] > condition] 이러한 식으로 조건 구문을 dataframe의 키로 넣어준다.

 


행을 기준으로 dataframe 데이터 접근하기

 

.loc[row, col]나 .iloc[rowidx, colidx]을 이용하면 된다.

 


groupby

 

  • Split : 특정한 '기준'을 바탕으로 DataFrame을 분할
  • Apply : 통계함수 - sum(), mean(), median(), ... 을 적용해 각 데이터를 압축
  • Combine : Apply된 결과를 바탕으로 새로운 Series를 생성 ( group_key : applied_value )

 

df_group = df[키값].groupby(by=df[그룹화의 조건이 될 키 값])

Git

 

분산 버전관리 시스템이다.

 

git 설치 링크 : git-scm.com/

 

Git

 

git-scm.com

해당 운영체제에 맞는 Git을 설치하면 된다.

 

Git 시작하기

 

  • git init
    • 로컬 저장소 생성
    • 현재 작업중인 디렉토리를 git 저장소로 지정할 수 있다.

 

vim을 통해 initialize한 저장소에 "Hello World!"문을 출력하는 example.py 파일을 생성한다.

 

  • git status
    • git 저장소의 상태를 확인하는 것

 

example.py 파일은 다음 commit에 반영이 안 된다는 문구가 나오게 된다. 이 파일을 커밋에 반영할 파일로 지정해보자.

 

  • git add
    • 다음에 commit을 무엇을 남길지 지정하는 행위

 

example.py 파일을 unstaged 상태에서 staged 상태로 바꾼다.

즉, example.py을 다음 commit에 넣겠다 선언하는 것이다.

 

  • git commit
    • add된 것들을 다음 commit으로서, snapshot으로 남긴다

 

snapshot에 대한 메세지를 담아 commit한다. 메세지를 담으려면 git commit 뒤에 -m을 붙이고 남길 메세지를 작성하면 된다.

 

  • git log
    • commit을 확인한다.


Git의 Branch

 

코드의 흐름을 분산, 가지치기 하여 각각 독립적인 환경에서 코드를 수정해나갈 수 있도록 해주는 것이 Branch이다. 

 

  • git branch <branch_name>
    • git branch를 생성하는 명령어이다.
    • branch를 생성하기 전에는 Default로 master라는 branch가 기본적으로 생성된다.

 

이전과 다르게 master와 develop이라는 두 개의 branch가 있는 것을 확인할 수 있다.

 

  • git checkout <branch_name>
    • branch_name의 branch로 전환해주는 명령어이다.

 

현재 branch가 develop을 가리키고 있음을 확인할 수 있다.

이 전환된 branch 상태에서 example.py 파일에 변화를 줘보자.

 

 

"Hello World in Develop branch!"라는 문장을 example.py에 추가했다.

그 후 상태를 확인하면

 

 

다음과 같이 변경 사항이 commit에 반영되지 않았다는 상태를 확인할 수 있다.

이 example.py를 commit하도록 하자.

 

 

master branch에서 작업한 commit 내역과 develop branch에서 작업한 commit 내역이 따로 저장되어 있는 것을 확인할 수 있다. 다시 master branch로 돌아가게 된다면 이 example.py는 어떻게 되어 있을까?

 

 

develop branch에서 작업한 변경 사항이 master branch에는 반영되어 있지 않은 것을 확인할 수 있다.

이 때 master branch가 중심 branch이기 때문에

develop의 변경 사항을 master에도 병합을 해야 할 때가 있을텐데 그렇게 하려면 어떻게 해야 할까?

 

  • git merge <branch_name>
    • 현재 작업중인 Branch를 원하는 Branch에 병합할 수 있다.

 

 

develop과 master가 같은 commit을 가지게 되는 것을 확인할 수 있다.

 

 

실제로 exmaple.py 파일을 확인해보면 develop branch에서 변경했던 사항이 master branch에도 잘 반영되어 있는 것을 확인할 수 있다. 이를 통해 Fast forward 방식의 merge가 잘 이루어졌음을 알 수 있다. 이외에도 merge 방식은 다양한 방식이 있다.

 

  • git branch -d <branch_name>
    • branch를 삭제하는 명령어이다.

 

 

 

branch가 잘 삭제되었음을 확인할 수 있다.


Github

 

지금까지 살펴봤던 git은 로컬 저장소고 원격 저장소는 이 git과 상호작용하면서 분산 시스템을 구축하게 된다.

다양한 원격 저장소가 있지만 그 중 Github를 사용해보도록 하겠다.

github의 계정을 생성했으면 다음과 같이 new repository를 생성한다.

 

 

생성했으면 로컬 저장소와 원격 저장소를 연결해주도록 하자.

 

  • git remote add <별칭> <원격 저장소 주소>
    • 로컬 저장소와 원격 저장소를 연결해주는 명령어이다.

 

 

이렇게 연결해주면 원격 저장소에 로컬 저장소에서 작업하고 있던 정보들을 전달해줄 수 있다. 그 전에 github에서는 중심 branch의 이름을 main이라는 이름으로 사용하기 때문에 git의 master branch의 이름을 다음과 같이 main으로 변경해주어야 한다.

 

 

  • git push <remote_repo_name> <branch_name>
    • 로컬 저장소에서 작업하고 있던 정보들을 원격 저장소에 반영하는 명령어이다.

 

 

push가 이뤄지고 난 후 연결한 원격 저장소로 가보면

 

다음과 같이 변경사항이 잘 반영되었음을 확인할 수 있다.

 

 

github 원격 저장소 우상단에 있는 Fork를 하면 다른 사람의 원격 저장소를 자신의 원격 저장소로 복사 - 붙여넣기 할 수 있다.

 

 

이 원격 저장소에 있는 내용을 바탕으로 로컬 저장소에서 작업을 진행해보도록 하자.

 

  • git clone <remote_repo><alias>

 

 

다음과 같이 git clone을 통해 원격 저장소에 있는 material을 로컬 저장소로 가져올 수 있게 된다.

+ Recent posts