간단한 Flask, mysql, 파이썬 기반 Open API 웹서버 개발 방법

 

이 글은 Flask, mysql, 파이썬 기반 Open API 웹서버 개발 방법을 간략히 정리한다. 이 방법은 Open API를 쉽게 만드는 기술 중 하나로, 파이썬 기반 오픈 API 개발 시 가장 많이 사용되는 구조이다. 

  • 간단한 Open API 서버 개발
  • 서버에서 특정 프로그램 실행 결과 API 로 호출해 리턴
  • MySQL 데이터와 Flask 연동을 통한 객체 관계 맵핑 API 자동 생성
  • 이미지 업로드 서버 개발
머리말
Flask는 마이크로 웹 프레임웍(microframework)으로 파이썬으로 개발되어 있다. 플라스크는 다양한 애드인을 지원하여, ORM(object relational mapping), 폼 검사(form validation), 파일 업로드 처리, 다양한 개방형 인증, 도구 등을 사용할 수 있다.  플라스크는 국제 파이썬 그룹의  Armin Ronacher에 의해 개발되어 2010년 첫버전이 발표되었고, 2020년에는 GitHub에서 가장 인기있는 파이썬 웹 개발 프레임웍에 선정되었다. 플라스크는 Pocoo 프로젝트 기반이며, WSGI(Web Server Gateway Interface) 지원을 위한 Werkzeug, Django와 유사한 템플릿 엔진을 제공하는 Jinja 를 사용한다. 
 
본 개방형 API 서버는 다음과 같은 구조를 가지게 되다.
 
 
참고로, 다음 구글에서 Flask서버를 이용해 Open API 서버를 만든 아키텍처이다.
구글 Open Restful API 서버 구조

환경 준비

다음과 같이 관련 환경을 설정한다.

sudo apt-get install mysql-server
sudo ufw allow mysql
sudo systemctl start mysql
sudo systemctl enable mysql

이제 마이SQL을 실행한다.

sudo /usr/bin/mysql -u root -p

 
mysql 프롬프트에서 아래 명령을 입력한다. 
SHOW VARIABLES LIKE "%version%";
 
기본 정보를 확인해 본다.
SELECT User, Host, authentication_string FROM mysql.user;
 
필요하다면, 루트 계정으로 암호를 수정한다.
ALTER USER 'root'@'localhost' IDENTIFIED BY 'password';
 
데이터베이스를 생성한다.
create database study;
show databases;

암호정책은 약하게 우선 설정한다. 그리고 계정을 만든다.

SET GLOBAL validate_password_policy=LOW;

CREATE USER 'ktw'@'localhost' IDENTIFIED BY 'password';
FLUSH PRIVILEGES;
 
생성한 데이터베이스 모든 권한을 본인 계정에 전달한다. 
GRANT ALL PRIVILEGES ON study.* to ktw@localhost;
FLUSH PRIVILEGES;
 
만약, mysql 서버를 다른 PC에서 제한없이 접속하고자 한다면 다음 명령을 입력한다.
CREATE USER 'root'@'%' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON * . * TO 'root'@'%' WITH GRANT OPTION;
FLUSH PRIVILEGES;
 
참고로, 주로 많이 사용되는 mysql 명령어는 다음과 같다.
  • 데이터 테이블 정보 조회: describe [table name];
  • 원하는 필드 조회: select [field1] [field2] from [table];
  • 조건 검색: select * from [table] where ([field] = 'value') OR ([field] < 'value');
  • 글자 검색: select * from [table] where [field] like 'Kang%' order by [field] ASC;
  • 범위 검색: select * from [table] limit 2, 4;
  • 갱신: update [table] set [field1] = 'value', [field2]='value' where [field] = 'value';
  • 삭제: delete from [table] where [field] = 'value';
  • 조인: select [table1].[field1], [table2].[field1] from [table1], [table2] where [table1].[field1] = [table2].[field1];
  • 정보 확인: select version(), user(), database();
  • 기타: 테이블 생성 시 필드에 NOT NULL, AUTO_INCREMENT, PRIMARY KEY 등을 지정할 수 있다. 예시는 다음과 같다.

 

create table study(no int, subject varchar(100), memo varchar(50), writedate date, count int, primary key(no));

 

테이블 스키마 및 데이터 생성
다음 명령어를 이용해 테스트용 데이터를 생성한다. 이 결과로 점수 테이블에 3개 레코드가 추가된다. 
use study;
create table score (subject char(20) NOT NULL, score int(10) NOT NULL);
insert into score(subject, score) values('math', 100);
insert into score(subject, score) values('eng', 90);
insert into score(subject, score) values('kor', 95);
 

간단한 MySQL 기반 Open API 서버 개발

다음과 같이 실행해 플라스크와 MySQL 테이블을 바로 API로 만드는 flask-mysql을 설치한다. 

pip install Flask flask-mysql
pip freeze > requirements.txt

다음과 같이 my_app.py 파일을 코딩한다.

from flask import Flask, jsonify
from flaskext.mysql import MySQL

app = Flask(__name__)
mysql = MySQL()

# MySQL configurations
app.config['MYSQL_DATABASE_USER'] = 'my_user'
app.config['MYSQL_DATABASE_PASSWORD'] = 'my_password'
app.config['MYSQL_DATABASE_DB'] = 'my_database'
app.config['MYSQL_DATABASE_HOST'] = 'localhost'

mysql.init_app(app)

@app.route('/')
def get():
    cur = mysql.connect().cursor()
    cur.execute('''select * from study.score''')
    r = [dict((cur.description[i][0], value)
                for i, value in enumerate(row)) for row in cur.fetchall()]
    return jsonify({'myCollection' : r})

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

다음과 같이 실행한다.

python my_app.py

그리고, http://127.0.0.1:5000 링크를 브라우저로 열어 본다.

그럼, 다음과 같이 생성된 데이터베이스 테이블에 대한 myCollection Open API가 자동 생성되 동작되는 것을 확인할 수 있다. json파일을 리턴하기 때문에 컨텐츠 유형이 appliation/json 이다.

간단한 Open API 서버 개발

이번에는 간단한 Open API 서버를 개발하겠다.  다음을 open_api.py에 코딩한다. 
from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello():
    return 'Hello, World!'

if __name__ == '__main__':
    app.run()
 
다음과 같이 실행한다.
export FLASK_APP=open_api.py
flask run
 * Running on http://localhost:5000/
 
혹은 다음과 같이 실행한다.
python open_api.py
 
결과는 다음과 같다. 여기서 @app.route('/'에 해당하는 hello() 함수가 호출되어 실행된 것을 알 수 있다.
 
이제 다음과 같이 실행파일(내부 프로세스)를 실행해 본다.
from flask import Flask
import os
import subprocess
import time

app = Flask(__name__)

@app.route('/')
def hello():
    return 'Hello, World!'

@app.route('/list')
def list():
    out = ""
    try:
        pid = subprocess.Popen(["ls", "-l"]).pid
        out = subprocess.check_output(["ls", "-l"])
    except EnvironmentError:
        os.kill(pid)
        print('unable to run subprocess')
        return
    return out

if __name__ == '__main__':
    app.run()
 
결과는 다음과 같다. 내부 프로그램을 실행해 그 결과를 리턴하는list API가 동작되는 것을 확인할 수 있다.

이미지 업로드 서버 개발

같은 방식으로 이미지 업로드 서버를 개발해 본다. image.py 코딩한다.

from flask import Flask, jsonify, request, render_template, redirect
import os
import subprocess
import time

app = Flask(__name__)

app.config["IMAGE_UPLOADS"] = "/home/ktw/project/flask_simple/data"

@app.route('/')
def hello():
    return 'Hello, World!' 
 
@app.route("/upload-image", methods=["GET", "POST"])
def upload_image():
    if request.method == "POST":
        if request.files:
            image = request.files["image"]
            print(image)
            image.save(os.path.join(app.config["IMAGE_UPLOADS"], image.filename))                                                     
            return redirect(request.url)   
    return render_template("upload_image.html")

if __name__ == '__main__':
    app.run()
 
현재 폴더 아래에 templates 폴더를 만들고, upload_image.html 파일을 코딩한다.
{% block title %}Upload{% endblock %}
{% block main %}
<div class="container">
  <div class="row">
    <div class="col"><h1>Upload an image</h1>
      <hr><form action="/upload-image" method="POST" enctype="multipart/form-data"><div class="form-group">
          <label>Select image</label>
          <div class="custom-file">
            <input type="file" class="custom-file-input" name="image" id="image">
            <label class="custom-file-label" for="image">Select image...</label>
          </div>
        </div><button type="submit" class="btn btn-primary">Upload</button></form></div>
  </div>
</div>
{% endblock %}

다음은 이미지 업로드 서버 실행 결과이다.

 

다중 이미지 업로드 서버 개발

경축! 아무것도 안하여 에스천사게임즈가 새로운 모습으로 재오픈 하였습니다.
어린이용이며, 설치가 필요없는 브라우저 게임입니다.
https://s1004games.com

이제 여러 장의 이미지들을 업로드하고 보는 서버를 개발해 보자. 다음 multi_images.py를 코딩한다(참고. Dustin D'Avignon 관련 글)

from flask import Flask, redirect, render_template, request, session, url_for
from flask_dropzone import Dropzone
from flask_uploads import UploadSet, configure_uploads, IMAGES, https://daddynkidsmakers.blogspot.com/2020/09/dns.htmlpatch_request_class
import os

app = Flask(__name__)
dropzone = Dropzone(app)  # 드롭존 객체 획득

# Dropzone 설정
app.config['DROPZONE_UPLOAD_MULTIPLE'] = True     # 다중 업로드 app.config['DROPZONE_ALLOWED_FILE_CUSTOM'] = True
app.config['DROPZONE_ALLOWED_FILE_TYPE'] = 'image/*'
app.config['DROPZONE_REDIRECT_VIEW'] = 'results'
app.config['UPLOADED_PHOTOS_DEST'] = os.getcwd() + '/uploads'  # 파일 위치
app.config['SECRET_KEY'] = 'secretkey'

photos = UploadSet('photos', IMAGES)    # 업로드 셋 설정
configure_uploads(app, photos)
patch_request_class(app)  # 파일 크기 설정, default is 16MBhttps://daddynkidsmakers.blogspot.com/2020/09/dns.html

@app.route('/', methods=['GET', 'POST'])
def index():
    # 이미지 결과에 대한 세션 설정
    if "file_urls" not in session:
        session['file_urls'] = []
    # 이미지 URL 획득
    file_urls = session['file_urls']    # handle image upload from Dropzone
    if request.method == 'POST':   # 요청이 POST 인 경우
        file_obj = request.files
        for f in file_obj:                            # 업로드 요청 온 파일 리스트를 얻어 서버에 저장
            file = request.files.get(f)
            
            # 폴더에 저장.
            filename = photos.save(
                file,
                name=file.filename    
            )            # 이미지 URL 추가 보관
            file_urls.append(photos.url(filename))
            
        session['file_urls'] = file_urls   # 이미지 URL을 세션에 보관
        return "uploading..."    # return dropzone template on GET request    
    return render_template('index.html')   # 웹 업로드 템플릿 렌더링

@app.route('/results')
def results():간단한 포트포워드로 외부 접속 가능한 공개 웹서버 인터넷 서비스 및 DNS 설정 방법
    # 세션에 URL이 없으면 index로 redirect 처리.
    if "file_urls" not in session or session['file_urls'] == []:
        return redirect(url_for('index'))
        
    # file URL 정보를 세션에서 얻어, results 템플릿 렌더링
    file_urls = session['file_urls']
    session.pop('file_urls', None)
    
    return render_template('results.html', file_urls=file_urls)

if __name__ == '__main__':
    app.run(host = '0.0.0.0', port = 5000)

templates 폴더 아래에 index.html파일을 코딩한다. dropzone 객체를 이용해, 드래그&드롭 이미지 업로드 컴포넌트를 만들고 있다.

<!DOCTYPE html>
<html>
    <head>
        <title>Flask App</title>
        {{ dropzone.load() }}
        {{ dropzone.style('border: 2px dashed #0087F7; margin: 10%; min-height: 400px;') }}
    </head>
    <body>
        <h1>Hello from Flask!</h1>
        {{ dropzone.create(action_view='index') }}
    </body>
</html>

그리고,  results.html파일을 코딩한다. 세션의 file_url을 src로 하여, 이미지를 렌더링하게 되어있다.

<h1>Hello Results Page!</h1>
<a href="{{ url_for('index') }}">Back</a><p>
<ul>
{% for file_url in file_urls %}
    <li><img style="height: 150px" src="{{ file_url }}"></li>
{% endfor %}
</ul>

서버를 실행한 결과는 다음과 같다. 

 

잘 실행된다. 만약, 외부로 웹서버를 공개하고 싶다면, 다음 링크를 참고한다.

마무리

플라스크를 이용해 데이터 업로드, 프로그램 실행, 데이터 저장 및 API를 통한 데이터 획득 서버 개발 방법을 간단히 알아보았다. 앞의 예제를 따라하면 프론트, 미들, 엔드를 포함한 웹서버가 이렇게 쉽게 개발된다는 것에 깊은 인상을 느끼게 된다. 플라스크는 이런 전 과정을 매우 간단한 간단한 포트포워드로 외부 접속 가능한 공개 웹서버 인터넷 서비스 및 DNS 설정 방법문법과 기능으로 해결해 준다. 다양한 애드인을 이용해 이미지 등 다양한 파일을 처리할 수 있다. 구글에서도 Open API 서버로 플라스크를 이용할 만큼 성능과 안정성이 좋다. 

플라스크 개발에 공헌한 개발자는 오스트리아 오픈소스 프로그래머인 아민 로너커이다. 그는 1989년 생으로 소프트웨어 개발이란 유명한 블로그를 운영하고 있다. 2005년 우분투 그룹에서 오픈소스 개발자로 경력을 시작하였고,  WSGI 에 관심을 가졌다. 그는 2004년 부터 one-file microframework을 주장하였고, 플라스크 개발을 시작했다.

 

 

 

플라스크에 대한 상세 문서는 여기(한글 문서)를 참고하길 바란다.

참고: 가상환경 설정

플라스크 개발 시 다음과 같이 가상환경을 설정하는 것이 좋다.

python3 -m venv env
source env/bin/activate
pip install -e .

참고: [Errno 98] Address already in use

sudo lsof -i :5000
sudo kill -9 [address]

레퍼런스

[출처] http://daddynkidsmakers.blogspot.com/2020/09/django-flask-open-api.html

 

 

본 웹사이트는 광고를 포함하고 있습니다.
광고 클릭에서 발생하는 수익금은 모두 웹사이트 서버의 유지 및 관리, 그리고 기술 콘텐츠 향상을 위해 쓰여집니다.
번호 제목 글쓴이 날짜 조회 수
47 [python] 파이썬 f-string (파이썬 스트링 앞에 f') file 졸리운_곰 2021.07.17 446
46 [Python] 파이썬으로 복리 계산하기 file 졸리운_곰 2021.07.17 562
45 [python, 파이썬] 연습 문제: 복리 이자 계산 졸리운_곰 2021.07.17 925
44 [python] 파이썬 기초 문법 정리 졸리운_곰 2021.05.17 861
43 [python] [GPU]GPU 사용 Python 코드 실행 졸리운_곰 2021.04.21 567
42 [python] 파이썬 스케줄 수행 - schedule, apscheduler file 졸리운_곰 2020.07.26 454
41 파이썬 스크립트 스케줄링하기 file 졸리운_곰 2020.07.26 1012
40 Python Scheduler 만들기(APScheduler) 졸리운_곰 2020.07.26 1089
39 [python] 주피터 노트북에서 패키지 설치 졸리운_곰 2018.12.06 1225
38 Data Structures and Algorithm In python 파이썬으로 자료구조와 알고리즘 : 출처 인터넷 file 졸리운_곰 2018.08.27 514
37 Mastering Basic Algorithms in the Python Language 이북 출처 인터넷 file 졸리운_곰 2018.08.27 629
36 python dev 사용자 입력과 출력 졸리운_곰 2018.03.01 472
35 APScheduler 사용기 file 졸리운_곰 2018.02.09 1070
34 [파이썬, 조각코드] 스케쥴러 만들기. 졸리운_곰 2018.02.09 795
33 Python virtualenv 정리 (Linux/Windows) 졸리운_곰 2018.01.23 486
32 [Python] 코드난독화 file 졸리운_곰 2018.01.19 1002
31 Python - Way to recursively find and replace string in text files 졸리운_곰 2017.08.30 478
30 Python Coding for Minecraft 마인크래프트를 위한 파이썬 프로그래밍 file 졸리운_곰 2017.05.05 1794
29 Atom을 Python 개발 툴로 사용하기 file 졸리운_곰 2017.03.06 938
28 IPython Notebook을 통한 데이터 분석 #3 file 졸리운_곰 2017.03.05 754
대표 김성준 주소 : 경기 용인 분당수지 U타워 등록번호 : 142-07-27414
통신판매업 신고 : 제2012-용인수지-0185호 출판업 신고 : 수지구청 제 123호 개인정보보호최고책임자 : 김성준 sjkim70@stechstar.com
대표전화 : 010-4589-2193 [fax] 02-6280-1294 COPYRIGHT(C) stechstar.com ALL RIGHTS RESERVED