Python/Python ETC

Python API : Python으로 API 만들기 (FastAPI, Flask)

CosmosProject 2022. 12. 4. 21:41
728x90
반응형

 

 

 

Python으로 API(Application Programming Interface)를 만드는 방법을 알아봅시다.

 

여러 방법이 있지만 본 글에서는 크게 FastAPI 또는 Flask를 이용하는 방법 두 가지를 알아볼겁니다.

 

 

 

일단 본격적으로 API를 구현해보기 전에 API가 어떻게 작동하는지를 살짝 알아봅시다.

(이 내용이 필요없으신 분은 모두 넘긴 후 코드 관련 부분만 보면 됩니다.)

 

(자세히 설명하면 설명할 내용이 굉장히 많고 지금부터 설명한 내용 중 살짝 실제 상황과 맞지 않는 경우가 있을 수 있습니다만, 여기서는 아주 아주 간단하게 그냥 대략적인 감만 잡을 수 있도록 상당히 간추려서 설명할 것이니 대략적인 느낌만 파악하고 넘어가면 됩니다.)

 

먼저 상황을 가정하여 API를 사용하는 과정을 생각해봅시다.

A라는 암호화폐 거래 서비스가 있다고 가정합시다.

근데 암호화폐를 거래하다보니 이것을 좀 자동화하고싶다는 생각이 들었습니다.

암호화폐의 매수/매도/이체 등 다양한 내용을 Python코드로 작성하여 자동화하려고 합니다.

 

이런 경우 가장 먼저 A 암호화폐 거래소에서 제공하는 API를 찾아보겠죠. 그 결과 A 거래소에서 제공하는 Python API가 있는 것을 발견했습니다.

그래서 A 암호화폐 거래소에서 제공하는 Python library를 설치하였습니다.

A 거래소에서 제공하는 Python library에는 다양한 기능이 있습니다.

어떤 코드를 작성하여 실행하면 현재 비트코인의 가격을 return해주거나, 각 코인별 매수량/매도량 정보를 return해주거나, 저의 자산 현황을 보여주는 등 다양한 기능을 사용할 수 있습니다.

 

되게 당연한 내용인데 위 내용에는 한가지 의문점이 있습니다.

A 거래소에서 제공하는 Python API를 설치했다고 해서 이 library에 코인들의 정보까지 같이 받아지는 것은 아닙니다.

즉, 제 컴퓨터에는 코인에 대한 정보가 단 하나도 없는것이죠.

그렇다면 비트코인의 가격, 코인별 매수량/매도량, 저의 자산 현황 등의 데이터는 어디에 존재하고 어떻게 제 코드의 결과로서 얻어질 수 있는 것일까요?

 

당연히 코인에 대한 모든 정보는 A 거래소의 Database에 있습니다.

제가 설치한 A 거래소의 Python API는 단순히 기능만을 가지고 있습니다. 예를들어 비트코인의 현재 가격 정보를 가져오는 기능, 내 코인 자산 현황 정보를 가져오는 기능 등등이죠.

즉, Python API는 A 거래소의 서버에 원하는 데이터를 요청하는 기능을 가지고 있고, 이것을 코드 내에서 실행시키면 A 거래소에서 그 요청을 받아 제가 요청한 정보를 저에게 전달해주는 과정을 거치게 됩니다.

 

그러면 저의 Python 코드와 A 거래소의 서버는 어떻게 서로 정보를 주고받는 것일까요?

제 컴퓨터와 A 거래소의 서버간에 데이터를 주고받아야하기 때문에 당연히 인터넷이 필요하구요.

그 과정에서 HTTP URL을 이용합니다.

 

제 컴퓨터에서는 Python API의 어떠한 기능을 통해 https://~~~~~/~~~~~ 같은 특정 URL을 실행시키고 이 URL이 실행되면 A 거래소의 서버는 저의 요청을 처리하여 결과를 전달해주는 것입니다.

 

https://~~~~~/~~~~~?coin=bitcoin

예를들어 제가 bitcoin의 현재 가격을 얻어올 때 Python API를 실행시키면 위같은 URL이 실행될겁니다. (물론 실제론 다를 수 있으나 단순 예시로서 이해를 위해서만 받아들이면 됩니다.)

위 URL은 get 방식으로 coin = bitcoin 이라는 조건을 만족하는 값을 요청한다는 의미입니다.

그러면 결국 bitcoin에 대한 정보가 나오겠죠.

 

이런식으로 API가 작동하고 API를 통해 원하는 정보를 받을 수 있게 되는 것입니다.

 

 

추가적으로 http에 대한 기본적인 용어와 개념을 알아봅시다.

 

HTTP = Hyper Text Transfer Protocol의 약자로 인터넷에서 데이터를 주고 받을 수 있는 프로토콜(규칙)을 의미합니다. 프로토콜은 규칙이라고 이해하면 되고, 이렇게 정해진 일정한 규칙(규격)에 의해 프로그램들이 개발되어 인터넷을 통해 이 프로그램들은 데이터를 주고 받을 수 있는 것입니다.

HTTP를 이용해서 데이터를 주고 받을 수 있는 방식은 여러 가지가 있습니다. 여기서는 간단하게만 알아봅시다.

 

- GET 방식 = 서버에 데이터를 요청할 때 사용하는 방식입니다. 데이터를 요청할 때 그 조건이 URL에 query로 붙어있습니다.

e.g. https://www.test_page.com?date=20221123&name=apple 

위 URL의 끝부분을 보면 ?date=20221123&name=apple 라고 적혀있습니다.

이렇게 get 방식으로 생성된 URL의 가장 끝 부분에 데이터 추출 조건을 나타낸 부분을 Query String이라고 합니다.

date가 20221123이고 name이 apple인 값을 가져오라는 의미입니다.

 

- POST 방식 = 서버에 데이터를 생성하거나 업데이트할 때 사용하는 방식입니다. POST 방식에서는 요청 내용을 html 코드의 body 태그에 담아서 전달합니다.

따라서 GET 방식처럼 URL 주소의 끝에 ?로 시작하는 query가 없습니다.

 

- PUT 방식 = 서버에 데이터를 추가하거나 update할 때 사용하는 방식입니다.

 

- DELETE 방식 = 이름에서 보이는 것처럼 서버에서 어떤 데이터를 삭제할 때 사용하는 방식입니다.

 

 

 

이제는 URL의 구조를 알아봅시다.

https://www.test_page.com/notice/?date=20221123&name=apple 

위 URL을 예시로 봅시다.

 

- https:// 또는 http://

이것은 protocol을 의미합니다. https, http 둘 중 하나를 사용합니다.

 

 

- www.test_page.com

위 부분은 domain(주소)를 의미합니다. host라고도 합니다.

주소는 프로토콜 이후부터 .com 또는 .co.kr 등의 주소가 끝나는 문구 까지를 의미합니다.

 

 

- notice

이것은 endpoint라고 합니다.

예를들어 하나의 웹페이지에도 다양한 화면이나 다양한 기능이 있을겁니다.

즉, www.test_page.com이라는 동일한 domain을 가지고 있지만 domain 뒤에 붙는 endpoint 이름에 따라서 각각의 endpoint마다 실행하는 기능이나 보여지는 화면이 달라지게 됩니다. 

따라서 우리가 API를 작성할 때에도 동일한 domain에 다양한 기능을 가진 여러 개의 API가 있을 수 있으며, 이 API들은 서로 다른 endpoint를 가지게 될 것입니다.

그래서 나중에 endpoint의 이름을 정할 때에도 각각의 endpoint에 어떠한 기능이 연결되어있는지를 잘 알 수 있는 이름으로 endpoint 이름을 정하는 것이 좋습니다.

 

 

- ?date=20221123&name=apple 

Query String입니다. URL을 GET 방식으로 요청할 때 위처럼 URL의 끝에 Query String이 붙게 됩니다.

Query String은 URL에 존재하는 경우도 있고, 존재하지 않는 경우도 있을 수 있습니다. 필요에 따라 존재 여부가 달라질 수 있죠.

 

 

이를 종합하여 URL의 구조를 나타내보면 다음과 같습니다.

https://domain/endpoint/?QueryString

 

 

 

 

 

자 이제 대략적인 흐름을 살펴봤으니 실제 API를 만들고 사용까지 해봅시다.

 

 

 

 

1. FastAPI 사용

먼저 FastAPI를 이용하여 API를 만들어봅시다.

 

일단 FastAPI를 사용하는 방식에서는 fastpi, uvicorn 2개의 python library가 필요합니다.

 

pip install fastapi
pip install uvicorn

위처럼 2개 library를 설치합시다.

 

 

 

저는 1 이상 10 이하의 숫자 중 랜덤한 정수를 출력해주는 API를 만들어보려고 합니다.

 

그 결과가 바로 아래와 같습니다.

 

from fastapi import FastAPI

api = FastAPI()

@api.get('/random_number')
def random_no():
    import random
    val_random_number = random.randint(1, 10)

    return val_random_number

위 코드를 부분별로 해석해봅시다.

 



 

from fastapi import FastAPI

fastapi library를 import합니다.

 

 

 

 

api = FastAPI()

FastAPI를 api 변수에 할당해서 FastAPI 객체를 만듭니다.

 

 

 

 

@api.get('/random_number')

생성한 api 객체의 get에 decorator를 적용합니다.

(decorator 관련 내용 = https://cosmosproject.tistory.com/385)

 

api는 위에서 생성한 FastAPI 객체입니다.

get은 http 프로토콜에서 get 방식을 사용하겠다는 의미입니다.

('/random_number')는 URL 가장 오른쪽에 들어갈 endpoint를 의미합니다. 따라서 지금 생성한 api를 호출하기 위한 URL 주소에는 가장 끝에 random_number라는 글자를 붙여야합니다.

 

 

 

 

 

 

def random_no():
    import random
    val_random_number = random.randint(1, 10)

    return val_random_number

제가 처음에 1~10 사이의 랜덤한 정수를 생성해주는 API를 만들거라고 했죠?

이 함수가 바로 그 부분입니다.

random library를 이용해서 1~10 사이의 랜덤한 정수를 생성하여 return해주고 있습니다.

 

 

 

 

 

@api.get('/random_number')
def random_no():
    import random
    val_random_number = random.randint(1, 10)

    return val_random_number

decorator와 제가 생성한 random_no 함수를 같이 보면 위와 같습니다.

api 객체의 get method는 random_number라는 endpoint를 가진 get 방식의 URL을 생성해주는 기능을 가지고 있을 것이며,

제가 생성한 random_no 함수를 api객체의 get method로 decorating할 것입니다.

따라서 어떠한 과정을 거쳐 endpoint로 random_number라는 글자를 가지는 URL에 제가 생성한 random_no 함수의 기능이 할당되는 것이죠.

 

 

 

 

 

여기까지 해서 API의 기능을 담은 파일은 완성되었습니다.

 

이제 API의 기능을 담은 파일을 실행시켜야합니다.

근데 그냥 실행시키면 안되고 아까 초반에 uvicorn 이라는 library를 실행시켰던 것을 기억하시나요?

이 uvicorn이라는 libary를 이용해서 실행시켜야합니다.

 

from fastapi import FastAPI

api = FastAPI()

@api.get('/random_number')
def random_no():
    import random
    val_random_number = random.randint(1, 10)

    return val_random_number

먼저 저의 API 코드가 적힌 파일의 이름은 test.py입니다.

 

그리고 test.py에서 FastAPI 객체가 담긴 변수의 이름은 api이죠.

 

이 두 가지를 먼저 파악한 후 terminal을 열고 아래 명령어를 입력합시다.

 

uvicorn test:api --reload

uvicorn은 uvicorn library의 기능을 사용하겠다는 것입니다.

 

test는 제가 만든 API 코드가 적힌 파일의 이름입니다. 파일의 이름이 test.py였으므로 확장자는 빼고 이름만 입력합니다.

 

그리고 콜론(:)을 적고, 그 다음 api를 적었습니다.

api는 test.py에서 FastAPI의 객체 이름입니다.

 

이러한 정보를 --reload합니다.

 

위 명령어를 입력하면 아래와 같은 문구가 terminal에 보일겁니다.

이렇게되면 현재 API가 잘 실행되고 있는 것입니다.

위 내용을 보면 http://127.0.0.1:8000 이라는 주소가 보입니다.

일단 API를 저의 로컬 컴퓨터의 terminal에서 실행했기 때문에 host가 위처럼 localhost로 잡혀있습니다.

어찌됐건 일단 위 API를 실행할 땐 http://127.0.0.1:8000/endpoint 형태의 URL을 사용해야 합니다.

 

이제 API를 한번 호출해봅시다.

 

import requests

req = requests.get('http://127.0.0.1:8000/random_number')

print(req.text)


-- Result
2

새로운 python file을 만들고 위 코드를 입력한 후 python 파일을 실행시켰습니다.

 

그랬더니 2라는 결과가 나왔죠.

이것은 1~10 사이의 랜덤한 숫자가 출력된 것입니다.

 

먼저 API는 URL을 통해 요청한다고 했으므로 request library를 사용합니다.

request library를 사용하여 http://127.0.0.1:8000/random_number URL에 요청을 보냅니다.

이 URL은 아까 생성한 API의 localhost(http://127.0.0.1:8000)에 random_number라는 endpoint가 합쳐진 URL입니다.

따라서 localhost에 있는 random_number라는 API를 실행시킨다는 것이죠.

 

일단 이 경우 랜덤한 숫자를 반환해주는 것이므로 반환값이 단순히 어떠한 숫자/문자 하나입니다.

여기서 requests.get()으로서 얻어지는 결과는 request 객체입니다.

이 객체의 text 속성에는 URL로부터 얻어진 값의 결과가 있습니다.

random_number API는 단순히 1~10 사이의 랜덤한 숫자를 return해주는 것이기 때문에 이렇게 간단한 문자/숫자 값을 return해주는 경우 text 속성을 참조하면 편합니다.

따라서 req.text 처럼 text 속성을 참조하면 2라는 결과를 얻을 수 있고, 이것은 1~10 사이의 숫자 중 랜덤한 숫자가 얻어진 것입니다.

위 파일을 계속 실행해보면 1~10 사이의 숫자가 계속해서 바뀌며 출력되는 것을 볼 수 있습니다.

 

 

 

 

 

 

 

 

이제 API를 좀 더 고도화시켜봅시다.

parameter를 전달해보려고 합니다.

 

from fastapi import FastAPI

api = FastAPI()

@api.get('/hello')
def hello(name):
    result_str = 'Hello. ' + name

    return result_str

API의 내용이 적혀있는 test.py 파일을 위처럼 수정하였습니다.

 

일단 @api.get('/hello')를 보면 endpoint가 hello로 된 것을 볼 수 있습니다.

 

그리고 hello 함수에 name이라는 parameter가 생겼습니다.

hello 함수는 name을 받아 Hello. name 이라는 텍스트를 return 해줍니다.

 

 

 

import requests

req = requests.get('http://127.0.0.1:8000/hello?name=James')

print(req.text)


-- Result
"Hello. James"

그리고 새로운 python 파일에 위처럼 코드를 적어 API를 호출해보았습니다.

 

이전과 달라진 점은 바로 requests.get()에 적히는 주소입니다.

 

http://127.0.0.1:8000/hello?name=James

URL을 보면 위와 같습니다.

localhost 부분까지는 동일하나 endpoint가 hello로 바뀌었습니다.

그리고 뒤에 ?name=James라는 Query String이 생겼네요.

 

@api.get('/hello')

API 파일을 보면 일단 get 방식의 API이기 때문에 get 방식의 http 요청 방식을 사용해야하므로 Query String이 있어야 합니다.

 

그리고 Query String이 name=James인 이유는 hello 함수가 받는 parameter의 이름이 name이기 때문입니다.

즉, 위 주소의 의미는 'endpoint가 hello인 API와 연결된 함수에 name이라는 parameter로서 James라는 값을 전달하여 실행하라.' 라는 의미입니다.

 

그렇게되면 hello 함수는 Hello. James라는 텍스트를 return하겠죠.

결과도 동일합니다.

 

 

 

 

 

 

import requests

req = requests.get('http://127.0.0.1:8000/hello')

print(req.text)


-- Result
{"detail":[{"loc":["query","name"],"msg":"field required","type":"value_error.missing"}]}

자 그러면 위처럼 Query String을 제거하고 실행시켜봅시다.

코드의 결과를 보면 value_error.missing 이라는 문구가 보입니다.

 

from fastapi import FastAPI

api = FastAPI()

@api.get('/hello')
def hello(name):
    result_str = 'Hello. ' + name

    return result_str

여기서 다시 API 파일을 보면 endpoint가 hello인 API와 연결된 hello 함수는 name이라는 parameter를 받아야합니다.

근데 http://127.0.0.1:8000/hello 이런식으로 Query String이 없는 URL로 요청을 하면 name이라는 parameter가 전달되지 않은 채로 hello API가 실행됩니다.

그러면 hello 함수도 name parameter를 전달받지 못한 채로 실행되겠죠.

함수의 parameter에 아무 값이 전달되지 않은 채로 함수가 실행되면 그것은 error를 발생시킵니다.

 

따라서 위 API 호출 시 발생한 error인 value_error.missing은 Query String을 통해 parameter를 제대로 전달하라는 의미입니다.

 

 

 

 

상황에 따라 다르겠지만 만약 parameter를 전달하지 않아도 에러가 발생하지 않도록 하려면 hello 함수의 name paramter에 기본값을 추가하는 것입니다.

 

from fastapi import FastAPI

api = FastAPI()

@api.get('/hello')
def hello(name=None):
    if name is None:
        result_str = 'Hello. No name'
    else:
        result_str = 'Hello. ' + name

    return result_str

API 내용이 적혀있는 test.py 파일을 위처럼 수정했습니다.

 

hello 함수의 parameter를 보면 name=None으로 적어놨습니다.

name parameter의 기본값을 None으로 하겠다는 것입니다.

 

그래서 hello 함수 내부에서는 name이 None일 경우 Hello. No name이라는 문구를 출력하고

name에 어떠한 값이 전달된 경우 Hello. name이라는 문구를 출력하도록 하는 조건문을 추가하였습니다.

 

 

 

 

 

import requests

req = requests.get('http://127.0.0.1:8000/hello')

print(req.text)


-- Result
"Hello. No name"

API 파일의 parameter에 기본값을 추가하고 위처럼 다시 Query String이 없는 API 호출을 한 경우 아까와는 달리 에러가 발생하지 않습니다.

그 대신 제가 설정해둔 Hello. No name이라는 글자가 출력되는 것을 볼 수 있죠.

 

 

 

 

 

이제 API가 csv 파일의 데이터를 전송하도록 해봅시다.

sample.csv

먼저 위처럼 데이터가 입력된 sample.csv를 만들어서 저장해뒀습니다.

 

 

 

 

 

from fastapi import FastAPI

api = FastAPI()

@api.get('/get_csv')
def get_csv():
    import pandas as pd
    csv_file = 'sample.csv'

    df_sample = pd.read_csv(csv_file)
    dict_sample = df_sample.to_dict()

    return dict_sample

API 파일의 코드를 위처럼 수정했습니다.

 

pandas를 이용해서 sample.csv를 DataFrame으로 읽어온 후 DataFrame을 dictionary로 바꿔서 return하는 코드입니다.

 

여기서 굳이 DataFrame인 df_sample을 to_dict() method를 이용해서 dictionary로 바꾼 이유는 get 방식의 데이터 전송에서는 DataFrame 타입의 데이터는 허용되지 않기 때문입니다.

주로 JSON 타입이나 JSON과 비슷한 형태인 dictionary 타입이 전송할 수 있는 형태입니다.

 

 

 

 

 

import requests

req = requests.get('http://127.0.0.1:8000/get_csv')

print(req.text)


-- Reuslt
{"col1":{"0":1,"1":2,"2":3,"3":4,"4":5},"col2":{"0":10,"1":20,"2":30,"3":40,"4":50}}

그리고 get_csv API를 호출했습니다.

sample.csv 파일에 있던 내용이 dictionary의 형태로 전환되어 출력된 것을 볼 수 있습니다.

 

 

 

 

여기까지해서 어느정도 API를 다루는 방법은 알아봤습니다.

 

추가로 FastAPI는 API 관리 페이지를 제공합니다.

위처럼 uvicorn으로 API를 실행한 상태에서 브라우저를 열고 아래 URL을 입력해봅시다.

http://127.0.0.1:8000/docs

 

그러면 위같은 화면이 나옵니다.

get_csv라는 것이 보이는데 이것은 제가 작성한 API의 endpoint 이름입니다.

 

http://127.0.0.1:8000/docs

위 페이지는 http://127.0.0.1:8000 URL에 존재하는 모든 API의 종류를 보여주는 페이지입니다.

 

 

 

 

또 다른 방법도 있습니다.

 

이번에는 브라우저에 아래 URL을 입력해봅시다.

http://127.0.0.1:8000/redoc

 

아까와는 다르지만 어쨌든 http://127.0.0.1:8000 URL에 존재하는 모든 API의 종류를 보여주고 관리할 수 있는 페이지가 나옵니다.

 

 

 

 

여기까지해서 FastAPI를 이용하여 API를 만드는 방법을 살펴보았습니다.

 

 

 

이번엔 Flask를 이용해서 API를 만들어봅시다.

 

 

 

2. Flask 사용

Flask를 사용하려면 Flask library를 먼저 설치해야겠죠.

 

pip install Flask

위 command로 Flask를 설치합니다.

 

 

 

저는 일단 간단하게 Hello! 라는 글자를 return해주는 API를 작성하려고 합니다.

 

그 결과는 아래와 같습니다. (참고로 아래 코드가 적힌 파일의 이름은 test.py 입니다.)

 

from flask import Flask

api = Flask(import_name=__name__)

@api.route('/hello', methods=['GET'])
def hello():
    return 'Hello!'


if __name__ == '__main__':
    api.run(debug=True, port=8000)

위 코드를 부분별로 해석해봅시다.

 

 

 

 

from flask import Flask

api = Flask(import_name=__name__)

Flask library를 import한 후 Flask 객체(=인스턴스, instance)를 생성합니다.

 

Flask instance를 생성할 때 parameter를 보면 import_name=__name__이라고 적혀있습니다.

일단 import_name이라는 parameter의 의미를 알아봐야합니다.

import_name parameter는 현재 API(또는 application이라고도 함)의 내용이 적힌 python file(= module, = library)를 의미합니다.

Flask가 어느 파일의 내용을 보고 application을 실행시킬지를 알려주는 부분이죠.

직접 원하는 module name을 적어줘도 되지만 거의 대부분 __name__ 을 이용하면 쉽게 해결됩니다.

(참고 = https://flask.palletsprojects.com/en/2.2.x/quickstart/)

 

 

 

 

@api.route('/hello', methods=['GET'])
def hello():
    return 'Hello!'

FastAPI에서와 마찬가지로 API의 기능을 구성할 땐 decorator를 이용합니다.

 

api -> 제가 생성한 Flask 객체의 이름입니다.

route('/hello', methods=['GET']) -> API를 호출할 때 필요한 endpoint를 명시하고 HTTP 호출 방식을 알려주는 부분입니다.

methods는 GET, POST 등 여러 가지가 있을 수 있기 때문에 list의 형태로 전달합니다.

(일단 이 예시에서는 GET만 전달했습니다.

 

그리고 hello 함수를 만들었습니다.

hello 함수는 api.route()로 decoratin되어서 endpoint=hello인 API가 호출되었을 때 실행될 함수입니다.

Hello라는 글자를 return하죠.

 

 

 

 

 

if __name__ == '__main__':
    api.run(debug=True, port=8000)

맨 마지막 부분은 제가 만든 API를 실행하는 부분입니다.

FastAPI를 이용할 때에는 uvicorn을 이용해 terminal에서 제가 만든 API 파일을 실행했었죠.

하지만 Flask에는 그럴 필요가 없습니다.

위처럼 api.run() method를 이용하여 API가 적힌 파일을 실행해두면 바로 API를 이용할 수 있는 상태가 됩니다.

 

api.run()에 주어진 parameter는 위 예시에서 2가지입니다.

 

debug=True -> 디버그 모드를 활성화한다는 의미입니다. 디버그 모드를 활성화하면 좋은 점은 코드의 변경이 생겼을 때 서버가 코드의 변경을 자동으로 감지하고 최신 코드를 기반으로 API를 리프레쉬해줍니다. 또한 코드에 문제가 있을 경우 문제를 용이하게 발견할 수 있도록 디버거 모드도 지원합니다.

 

port=8000 -> 포트번호를 지정합니다.

 

 

이렇게 application 코드가 적힌 파일을 실행하면 아래와 같은 문구가 뜨면서 실행상태가 될 것입니다.

 

 

 

import requests

req = requests.get('http://127.0.0.1:8000/hello')

print(req.text)


-- Result
Hello!

별도의 python 파일을 만든 후 위처럼 endpoint=hello인 API를 호출하면 제가 만들었던 Hello!라는 글자가 출력된 것을 알 수 있습니다.

API 호출 방식은 크게 다르지 않습니다.

 

 

 

 

 

 

 

이제 Flask에서 parameter를 전달해봅시다.

 

from flask import Flask
from flask import request

api = Flask(import_name=__name__)

@api.route('/hello', methods=['GET'])
def hello():
    name = request.args.get('name', None)

    if name is None:
        result_str = 'Hello. No name'
    else:
        result_str = 'Hello. ' + name

    return result_str


if __name__ == '__main__':
    api.run(debug=True, port=8000)

코드를 위처럼 수정했습니다.

name이라는 parameter를 받아 Hello. name이라는 형태로 텍스트를 출력해주도록 수정한 코드입니다.

 

달라진 부분만 봅시다.

 

 

from flask import request

먼저 User가 URL에서 parameter를 GET 방식으로 전달한다면 주로 아래와 같이 전달할 것입니다.

 

http://127.0.0.1:8000/hello?name=James

보면 name이라는 parameter에 James라는 값을 전달한다는 내용이라고 볼 수 있죠.

따라서 parameter의 정보는 GET방식의 Query String에 있습니다.

즉, 코드에서는 Query String에 있는 정보를 얻어와서 parameter처럼 사용해야하는 것이죠.

 

이를 위해서 URL의 reuqest 내용을 알아야 할 필요가 있습니다.

 

이를 위해 flask의 request 를 import합니다.

 

 

 

@api.route('/hello', methods=['GET'])
def hello():
    name = request.args.get('name', None)

    if name is None:
        result_str = 'Hello. No name'
    else:
        result_str = 'Hello. ' + name

    return result_str

그리고 hello 함수를 위처럼 수정했습니다.

 

- name = request.args.get('name', None)

가장 중요한건 이 부분입니다.

request는 아까 import했던 flask의 request입니다.

request에는 URL의 요청 정보가 담겨있습니다. URL이 GET 방식으로 요청되었는지, POST 방식으로 요청되었는지, GET 방식으로 요청되었을 때 Query String은 무엇인지, POST 방식으로 요청되었을 때 전달받은 정보는 무엇인지 등등 모든 정보가 담겨있습니다.

 

저는 이번 예시에서 GET 방식으로 정보를 요청할 것입니다.

GET 방식으로 정보를 요청할 때 GET 방식의 Query String에 있는 정보는 request의 args 변수에 dictionary같은 형태로 저장되어 있습니다.

URL을 보면 ?name=James라고 전달되었으니 {'name': 'James'} 라는 dictionary가 request.args 변수에 저장된 거라고 볼 수 있습니다.

그래서 request.args라는 dictionary에 get method를 이용해서 name이라는 key의 value값을 얻어오는 것입니다.

물론 request.args['name'] 이렇게 dictionary를 바로 참조하는 형태로 코드를 적어도 되지만 굳이 get() method를 쓴 것은 혹시나 User가 URL에 name을 빼먹은 경우 request.args에는 아무것도 없을 것이고 그런 경우 request.args['name'] 이 코드는 key가 name인 데이터를 찾지 못해 error를 발생시킬 것이기 때문입니다.

그래서 이렇게 name이라는 key가 없는 경우 None값을 reutrn하도록 하는 것이죠.

 

그래서 이렇게 얻어진 URL의 요청값을 name이라는 변수에 저장합니다.

이렇게 얻어진 name 변수를 가지고 if 구문을 이용하여 name 변수에 값이 무엇인지를 판단하여 상황에 따라 원하는 코드를 적으면 됩니다.

(POST 방식으로 요청된 값을 어떻게 얻을 수 있는지는 맨 아래 부분에서 설명합니다.)

 

 

 

 

 

import requests

req = requests.get('http://127.0.0.1:8000/hello?name=James')

print(req.text)


-- Result
Hello. James

자 위처럼 API를 호출해봤습니다.

Query String을 보면 name=James로 적혀있습니다.

따라서 결과가 Hello. James라고 적혀있는 것을 볼 수 있습니다.

 

 

 

 

 

import requests

req = requests.get('http://127.0.0.1:8000/hello')

print(req.text)


-- Result
Hello. No name

만약 위처럼 URL에 Query String을 생략하여 name parameter를 전달하지 않으면 이미 정해둔 if 문에 따라 Hello. No name이라는 문자가 출력됩니다.

 

 

 

 

 

 

 

 

 

 

from flask import Flask

api = Flask(import_name=__name__)

@api.route('/get_csv', methods=['GET'])
def get_csv():
    import pandas as pd

    df_sample = pd.read_csv('sample.csv')
    dict_sample = df_sample.to_dict()

    return dict_sample


if __name__ == '__main__':
    api.run(debug=True, port=8000)

FastAPI에서 했던 것 처럼 csv 파일을 return해주는 API를 작성해보았습니다.

흐름은 똑같습니다.

pandas를 이용해서 sample.csv를 읽어 DataFrame으로 만들고,

DataFrame을 dictionary로 변경해서 return하는 것이죠.

 

 

 

 

import requests

req = requests.get('http://127.0.0.1:8000/get_csv')

print(req.text)


-- Result
{
  "col1": {
    "0": 1,
    "1": 2,
    "2": 3,
    "3": 4,
    "4": 5
  },
  "col2": {
    "0": 10,
    "1": 20,
    "2": 30,
    "3": 40,
    "4": 50
  }
}

위처럼 API를 호출하면 sample.csv 파일의 내용이 dictionary의 형태로 출력되는 것을 볼 수 있습니다.

 

 

 

 

 

 

 

 

- 참고 1

 

from flask import Flask
from flask import request

api = Flask(import_name=__name__)

@api.route('/hello', methods=['GET', 'POST'])
def hello():
    if request.method == 'GET':
        name = request.args.get('name', None)
    elif request.method == 'POST':
        name = request.form.get('name', None)
    else:
        name = None

    if name is None:
        result_str = 'Hello. No name'
    else:
        result_str = 'Hello. ' + name

    return result_str


if __name__ == '__main__':
    api.run(debug=True, port=8000)

Flask를 이용할 때 GET, POST 방식 각각에 대한 데이터 참조 방식입니다.

 

- methods=['GET', 'POST']

일단 api.route() 에서 methods parameter에 GET, POST를 모두 list에 담아 전달했습니다.

그 이유는 GET으로도 POST로도 요청될 가능성이 있기 때문입니다.

 

가장 중요한건 아래 부분입니다.

    if request.method == 'GET':
        name = request.args.get('name', None)
    elif request.method == 'POST':
        name = request.form.get('name', None)
    else:
        name = None

request.method에는 현재 URL이 어떤 방식으로 요청되었는지에 대한 정보를 담고있습니다.

이를 이용해서 if 문에 현재 방식이 GET인지 POST인지 조건을 걸 수 있습니다.

 

GET 방식으로 요청된 경우 reuqest.args 변수에 GET방식으로 요청된 Query String에 대한 key - value 쌍이 dictionary의 형태로 저장된다고 했습니다.

 

POST 방식으로 요청된 경우 request.form 변수에 POST 방식으로 요청된 값들의 정보가 저장되어있습니다.

마찬가지로 dictionary의 형태로 저장되어있어서 get method를 통해 접근할 수 있습니다.

 

 

 

 

 

- 참고 2

 

지금까지 API를 만들고 실행하며 봤던 예시에는 HTML을 통해 주고받는 값의 type이 주로 어떤 text이거나 dictionary였습니다.

근데 사실 대부분의 경우 HTML을 통해 주고 받는 데이터의 형태는 JSON입니다.

 

flask에서는 어떤 dictionary를 JSON type으로 전환해주는 기능도 제공합니다.

 

아래 코드를 봅시다.

 

from flask import Flask
from flask import request
from flask import jsonify

api = Flask(import_name=__name__)

@api.route('/hello', methods=['GET'])
def hello():
    name = request.args.get('name', None)

    if name is None:
        result_str = 'Hello. No name'
    else:
        result_str = 'Hello. ' + name

    return jsonify({'result': result_str})


if __name__ == '__main__':
    api.run(debug=True, port=8000)

본문에서 봤던 예시와 동일하나 hello 함수의 return 부분에 jsonify가 추가되었습니다.

 

위 코드에서 주요한 부분을 살펴봅시다.

 

from flask import jsonify

먼저 flask의 jsonify 기능을 이용하려면 jsonify를 import해야합니다.

jsonify는 dictionary를 JSON 타입으로 바꿔주는 기능을 가집니다.

 

 

@api.route('/hello', methods=['GET'])
def hello():
    name = request.args.get('name', None)

    if name is None:
        result_str = 'Hello. No name'
    else:
        result_str = 'Hello. ' + name

    return jsonify({'result': result_str})

hello 함수에서는 GET method로 요청된 값을 받아서 그 값에 따라 result_str을 구성합니다.

 

그리고 이 값을 return하는데 return하는 곳에 jsonify가 적용되어있습니다.

{'result': result_str}처럼 result라는 key값에 결과값인 result_str가 value로서 존재하는 dictionary를 만들고

이것을 jsonify method로 JSON 타입으로 변경하는 것입니다.

 

 

 

import requests

req = requests.get('http://127.0.0.1:8000/hello?name=James')

print(req.text)


-- Result
{
  "result": "Hello. James"
}

jsonify를 적용한 API를 호출해보면 위처럼 JSON 타입의 결과가 출력되는 것을 알 수 있습니다.

 

 

 

 

 

 

또 다른 예시입니다.

from flask import Flask
from flask import jsonify

api = Flask(import_name=__name__)

@api.route('/get_csv', methods=['GET'])
def get_csv():
    import pandas as pd

    df_sample = pd.read_csv('sample.csv')
    dict_sample = df_sample.to_dict()

    return jsonify({'date': 20221205, 'csv': dict_sample})


if __name__ == '__main__':
    api.run(debug=True, port=8000)

이전에 봤던 예시 중 sample.csv 파일의 데이터를 얻어오는 예시가 있었습니다.

 

- return jsonify({'date': 20221205, 'csv': dict_sample})

전반적인 과정은 같으나 return되는 결과값을 dictionary로 묶고 jsonify method를 적용하여 JSON 타입으로 변경하여 return하도록 수정하였습니다.

JSON type으로 결과를 return하게되면 위 예시의 date처럼 뭔가 추가적인 정보를 한꺼번에 묶어서 전달할 수 있다는 장점이 있습니다.

 

 

 

 

import requests

req = requests.get('http://127.0.0.1:8000/get_csv')

print(req.text)


-- Result
{
  "csv": {
    "col1": {
      "0": 1,
      "1": 2,
      "2": 3,
      "3": 4,
      "4": 5
    },
    "col2": {
      "0": 10,
      "1": 20,
      "2": 30,
      "3": 40,
      "4": 50
    }
  },
  "date": 20221205
}

API를 호출한 결과는 위와 같습니다.

 

key가 csv인 곳에는 sample.csv의 데이터가 dictionary의 형태로 담겨져있고,

key가 date인 곳에는 제가 제시한 20221205라는 날짜값이 적혀있습니다.

 

지금은 csv 파일의 결과와 date같은 정보만 전달했지만 이를 잘 이용하면 원하는 정보를 상당히 많이 그리고 다양하게 전달할 수 있다는 장점이 있죠.

 

 

- 참고 3

지금까지 http://127.0.0.1:8000/~~~ 같은 형태의 URL을 많이 봤습니다.

여기서 127.0.0.1라는 host는 localhost를 의미합니다.

그래서 위 URL은 아래처럼 변경해서 써도 완전히 동일한 기능을 수행합니다.

http://localhost:8000/~~~

 

따라서 지금까지 본 모든 예시에서 http://127.0.0.1:8000/~~의 URL을 http://localhost:8000/~~으로 변경해서 코드를 실행해도 문제없이 실행됩니다.

 

원하는 형태의 URL을 사용하면 됩니다.

 

 

 

 

 

 

728x90
반응형