반응형

출처: https://blogs.oracle.com/opal/post/how-to-use-python-flask-with-oracle-database

 

Python, Flask와 Oracle 데이터베이스 함께 사용하기 (Linux, Mac)


오라클 데이터베이스를 사용하는 플라스크 앱을 설치하는 만드는 과정을 다룬다.

플라스크(Flask)는 Python에서 사용할 수 있는 가벼운 웹 프레임워크이다.
플라스크는 오라클 제공 cx_Oracle 모듈을 이용하여 오라클 디비에 접근할 수 있다.

데모 앱은 오라클 user 테이블에 사람 이름을 저장하는 간단한 서비스를 제공한다. 
그리고 저장된 사람의 id 번호를 이용하여 사용자 이름을 쿼리(질의)할 수도 있다. .

플라스크 앱은 다중 병렬 어플리케이션 유저를 다룰 수 있으므로, 
애플리케이션에서 데이터베이스 연결 풀(connection pool)을 만드는 것은 중요하다. 
데이터베이스 연결 풀은 성능에 중요한 영향을 미치는데, 특히
애플리케이션이 자주 데이터베이스에 연결하고 종료하는 경우에 그렇다.
참고로 풀(pool)은 빠를 처리 뿐만 아니라 오라클 높은 가용성을 잘 지원한다. 
빈번하지 않은 연결이 일어나는 경우에는 작은 풀이 더 유용하다. 

[[ 설치와 실행 ]]

1) Python 3를 설치한다. (3.6 이상의 버전을 권장한다.)
   python.org에 다운로드 받는다.
   또는 아나콘다 가상 환경 생성 시 파이썬 버전을 지정하여 설치할 수도 있다.

2) 플라스크 모듈을 설치한다. 
  pip install --upgrade Flask 

3) 오라클 cx_Oracle 8 모듈과 Oracle Client libraries를 설치한다. 
  pip install --upgrade cx_Oracle. 

[참조] https://cx-oracle.readthedocs.io/en/latest/user_guide/
                        installation.html#quick-start-cx-oracle-installation

  Oracle Instant Client 다운로드 페이지는 다음과 같다. 
  https://www.oracle.com/database/technologies/instant-client/downloads.html

[참고] 플라스크와 오라클 디비가 같은 서버에서 동작할 때 Instant Client 없어도 된다.

4) 오라클 데이터베이스가 없다면 설치해야 한다. 
Oracle Express Edition 또는 free Oracle Cloud Database를 설치할 수 있다. 
https://www.oracle.com/au/database/technologies/appdev/xe.html

맥 유저는 더 쉽게 설치할 수 있는데 아래 링크를 참조한다.
https://blogs.oracle.com/opal/post/
          the-easiest-way-to-install-oracle-database-on-apple-macos

5) 아래 쪽에 보이는 코드를 demo.py로 저장한다. (아래 링크에서 다운로드할 수도 있다. ) 
https://blogs.oracle.com/content/published/api/v1.1/assets/
             CONTF12D7698699A4551BB10B0F353802197/native?

             cb=_cache_f7a0&channelToken=1448e5c922f54865a717c9545b46e7b9 

cx_Oracle 8 초기화에 대해서는 아래 링크를 참조한다. 
https://cx-oracle.readthedocs.io/en/latest/user_guide/initialization.html

데모에서 연결 풀은 애플리케이션 시작 시 start_pool()에서 만들어진다. 
그 후 각 앱플리케이션 라우트(서비스 경로)에서 풀에서 연결 정보를 얻어서 사용한다.
현대 main 플라스크 기능은 세 가지 라우트를 제공한다. .
  - "/" : return welcome message
  - "/post/<username>" : 주어진 이름을 디비에 등록하고(post) 결과 메시지를 돌려준다.
  - "/users/<n>" : n에 해당하는 id를 디비에서 찾아서 해당하는 이름을 돌려준다.  

6) 오라클 username, password를 환경변수에 설정한다.
데이터베이스 연결 문자열은 PYTHON_CONNECTSTRING에 설정한다. 
오라클 계정이 cj / welcome, 경로가 "example.com/XEPDB1"이면 다음처럼 명령한다.

  $ export PYTHON_USERNAME=cj
  $ export PYTHON_PASSWORD=welcome
  $ export PYTHON_CONNECTSTRING=example.com/XEPDB1

7) demo.py를 실행한다.

  $ python demo.py

  [실행 결과]
  Connecting to example.com/orclpdb1
   * Serving Flask app "demo" (lazy loading)
   * Environment: production
     WARNING: This is a development server. Do not use it in a production deployment.
     Use a production WSGI server instead.
   * Debug mode: off
   * Running on http://127.0.0.1:8080/ (Press CTRL+C to quit)

플라스크 간편 실행 링크: https://flask.palletsprojects.com/en/1.1.x/quickstart/

참고로 환경변수에 FLASK_APP=demo.py 설정하고 플라스크를 실행하는 것도 가능하다. 
$ export FLASK_APP=demo.py
$ flask run
 * Running on http://127.0.0.1:5000/

8) 웹 브라우저에서 http://127.0.0.1:8080/ 주소에 접속하면 아래 문구를 볼 수 있다. 

    Welcome to the demo app

9) http://127.0.0.1:8080/user/1 주소에 접속하면 1번 id의 사용자 이름을 볼 수 있다.

    chris

10) http://127.0.0.1:8080/user/2 주소처럼 없는 id를 요청하면 다음처럼 응답한다.

    Unknown user id 

11) 새로 등록하려면 http://127.0.0.1:8080/post/alison 주소처럼 요청한다.

    Insserted alison with id 2
  
12) 이제 다시 http://127.0.0.1:8080/user/2 주소로 요청하면 다음처럼 응답한다.

    alison 


데모 앱 코드 demo.py는 다음과 같다.

[[ Demo Code ]]
"""
demo.py
Christopher Jones, 10 Sep 2020
Demo of using flask with Oracle Database
Before running, set these environment variables:
    PYTHON_USERNAME    - your DB username
    PYTHON_PASSWORD    - your DB password
    PYTHON_CONNECTSTRING - DB connection string, e.g. "example.com/XEPDB1"
    PORT                        - Web server port.  default 8080(FastAPI)
"""
import os
import sys
import cx_Oracle
from flask import Flask

################################################################
# On macOS tell cx_Oracle 8 where the Instant Client libraries are.  You can do
# the same on Windows, or add the directories to PATH.  On Linux, use ldconfig
# or LD_LIBRARY_PATH.  cx_Oracle installation instructions are at:
https://cx-oracle.readthedocs.io/en/latest/user_guide/installation.html

if sys.platform.startswith("darwin"):
    cx_Oracle.init_oracle_client(lib_dir=os.environ.get("HOME")+"/instantclient_19_3")
elif sys.platform.startswith("win32"):
    cx_Oracle.init_oracle_client(lib_dir=r"c:\oracle\instantclient_19_8")

################################################################
# Start a connection pool.
#
# Connection pools allow multiple, concurrent web requests to be efficiently
# handled.  The alternative would be to open a new connection for each use
# which would be very slow, inefficient, and not scalable.  Connection pools
# support Oracle high availability features.
#
# Doc link: https://cx-oracle.readthedocs.io/en/latest/user_guide/
                        connection_handling.html#connection-pooling

# init_session(): a 'session callback' to efficiently set any initial state
# that each connection should have.
#
# If you have multiple SQL statements, then put them all in a PL/SQL anonymous
# block with BEGIN/END so you only call execute() once.  This is shown later in
# create_schema().
#
# This particular demo doesn't use dates, so sessionCallback could be omitted,
# but it does show settings many apps would use.
#
# Note there is no explicit 'close cursor' or 'close connection'.  At the
# end-of-scope when init_session() finishes, the cursor and connection will be
# closed automatically.  In real apps with a bigger code base, you will want to
# close each connection as early as possible so another web request can use it.
#
# Doc link: https://cx-oracle.readthedocs.io/en/latest/user_guide/
  connection_handling.html#session-callbacks-for-setting-pooled-connection-state

# TIME_ZONE = 'UTC'
def init_session(connection, requestedTag_ignored): 
    cursor = connection.cursor()
    cursor.execute("""
          ALTER SESSION SET
          TIME_ZONE = 'Asia/Seoul'
          NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI'""")

# start_pool(): starts the connection pool
##################################
def start_pool():
    # Generally a fixed-size pool is recommended, i.e. pool_min=pool_max.
    # Here the pool contains 4 connections, which is fine for 4 concurrent users.
    # The "get mode" is chosen so that if all connections are already in use, any
    # subsequent acquire() will wait for one to become available.
    pool_min = 4
    pool_max = 4
    pool_inc = 0
    pool_gmd = cx_Oracle.SPOOL_ATTRVAL_WAIT
    print("Connecting to", os.environ.get("PYTHON_CONNECTSTRING"))

    pool = cx_Oracle.SessionPool(user=os.environ.get("PYTHON_USERNAME"),
                                 password=os.environ.get("PYTHON_PASSWORD"),
                                 dsn=os.environ.get("PYTHON_CONNECTSTRING"),
                                 min=pool_min,  max=pool_max,
                                 increment=pool_inc,
                                 threaded=True,
                                 getmode=pool_gmd,
                                 sessionCallback=init_session)
    return pool

###############################################################
# create_schema(): drop and create the demo table, and add a row
###############################################################
def create_schema():
    connection = pool.acquire()
    cursor = connection.cursor()
    cursor.execute("""
        begin
          begin
            execute immediate 'drop table demo';
            exception when others then
              if sqlcode <> -942 then
                raise;
              end if;
          end;

          execute immediate 'create table demo (
               id        number generated by default as identity,
               username varchar2(40))';

          execute immediate 'insert into demo (username) values (''chris'')';

          commit;
        end;""")

###############################################################
# Specify some routes
#
# The default route will display a welcome message:
#   http://127.0.0.1:8080/
# To insert a new user 'fred' you can call:
#    http://127.0.0.1:8080/post/fred
# To find a username you can pass an id, for example 1:
#   http://127.0.0.1:8080/user/1

app = Flask(__name__)

# Display a welcome message on the 'home' page
@app.route('/')
def index():
    return "Welcome to the demo app"

# Add a new username
#
# New user's id is generated by the DB and returned in the OUT bind variable 'idbv'.  
# As before, we leave closing the cursor and connection to the end-of-scope cleanup.
@app.route('/post/<string:username>')
def post(username):
    connection = pool.acquire()
    cursor = connection.cursor()
    connection.autocommit = True
    idbv = cursor.var(int)
    cursor.execute("""
        insert into demo (username)
        values (:unbv)
        returning id into :idbv""", [username, idbv])
    return 'Inserted {} with id {}'.format(username, idbv.getvalue()[0])

# Show the username for a given id
@app.route('/user/<int:id>')
def show_username(id):
    connection = pool.acquire()
    cursor = connection.cursor()
    cursor.execute("select username from demo where id = :idbv", [id])
    r = cursor.fetchone()
    return (r[0] if r else "Unknown user id")

#################################################################
# Initialization is done once at startup time
#################################################################
if __name__ == '__main__':

    # Start a pool of connections
    pool = start_pool()

    # Create a demo table
    create_schema()

    # Start a webserver
    app.run(port=int(os.environ.get('PORT', '8080')))

반응형

+ Recent posts