본문 바로가기
데이터 시각화 및 애플리케이션 개발/AI 컴퓨터 비전프로젝트

[Python] 동기와 비동기 처리 및 FastAPI 사용법

by 바다의 공간 2024. 6. 7.

1. 동기(Synchronous)

- 작업을 순차적으로 실행하는 것 

- 하나의 작업이 끝나기 전에는 다음 작음작업을 시작하지 않음

 


예제)

 

def func1():
    print('1')
    print('2')
    print('3')



def main():
    func1()

# main 함수는 func1 함수가 종료될 때까지 기다림
# 하지만 func1 함수가 종료되기 전에 main함수에서 처리해야할 일이 있다면 어떻게 해야할까?
# 그래서 나온것이 비동기입니다.

 

# 동기식

print(f"start: {time.strftime('%X')}")
main()
print(f"end: {time.strftime('%X')}")

 

스타트시간 끝나는 시간을 찍어줍니다.

#결과

start: 01:26:57

😊😊😊😊😊 😊😊😊😊😊

end: 01:26:59

 

2. 비동기(Asynchronous)

- 작업이 병렬적으로 실행되는 것

- 시간이 오래 걸리는 작업을 기다리는 동안 다른 작업을 수행할 수 있음

import time
def smile():
    time.sleep(1)
    print('😊😊😊😊😊')

def main():
    smile()
    smile()

- 1초동안 대기시키는 매소드 : time

 

# 비동기

import asyncio

async def smile():
    await asyncio.sleep(1)
    print('😊😊😊😊😊')

async def main():
    await asyncio.gather(
        smile(),
        smile()
  )

비동기로 처리할때에는 async를 사용해줍니다.

  await는 함수안에서 동기처럼 진행해달라는 말입니다.

print(f"start: {time.strftime('%X')}")
asyncio.run(main())
print(f"end: {time.strftime('%X')}")

start: 01:26:59

😊😊😊😊😊 😊😊😊😊😊

end: 01:27:00


✔️코랩에서만 병렬적으로 사용하는것을 막아놓았기에 따로 병렬적으로 실행할 수 있도록 하는 작업이 필요합니다.

✔️ asyncio.run() cannot be called from a running event loop

  • asyncio.run()함수는 새로운 이벤트 루프를 생성하고 주어진 서브루틴(코루틴)을 실행하기 위해 사용
  • 코랩이나 주피터 노트북 환경에서는 이미 이벤트 루프가 실행중이기떄문에 asyncio.run()을 호출하면 에러가 발생합니다.
  • 따라서 아래와 같이 모듈을 import하고 메서드를 호출해야합니다.
import nest_asyncio
nest_asyncio.apply()

 


파이참 이라는 프로그램을 이용할거고 파일을 하나 생성하면

.idea ▶ 툴에서 사용하는 환경파일

.venv ▶ 파이썬의 모듈들이 담기는 파일

라는 폴더가 자동으로 생성됩니다.

노트북이나 컴퓨터 사양에 따라서 시간이 조금 걸리는 작업같습니다.

저는 바로생성은 되지않고 1~2분정도 소요된것같습니다.


3. FastAPI(웹서버)

FastAPI는 Python 기반의 웹 프레임워크(웹을 개발하기 위한 모듈)로,
주로 API(개발자들의 문서같은 개념)를 빠르게 개발하기 위해 설계되었습니다.
FastAPI는 강력한 타입 힌팅(Type Hints)을 활용하여
개발자에게 코드 작성의 안정성과 가독성을 제공합니다.

https://fastapi.tiangolo.com/ko/

※  타입 힌팅(Type Hints)

 

전 시간에 공부했던 타입 어노테이션과 비슷한 느낌입니다.

타입 힌팅(Type Hints)은 프로그래밍 언어에서 변수,
함수 매개변수, 함수 반환값 등에 대한 데이터 타입 정보를
코드에 명시적으로 제공하는 기술입니다.
Python 3.5 이상에서 도입된 기능으로,
코드의 가독성을 높이고 프로그램의 안정성을 강화하는 데
도움이 됩니다.

4. Fast API vs Flask vs Django


FastApi
장점
FastAPI는 최신 Python 기반 프레임워크로
 빠른 성능과 사용하기 쉬운 API로 유명합니다.
 비동기 프로그래밍을 지원하므로 실시간 애플리케이션 구축에 적합합니다.
 또한 자동 API 문서화 및 유효성 검사를 제공하여
 개발자의 시간과 노력을 절약합니다.

단점
FastAPI는 비교적 새로운 프레임워크이며
기존 프레임워크에 비해 커뮤니티 지원 및 리소스가 많지 않을 수 있습니다.
또한 비동기 프로그래밍을 처음 접하는 개발자를 위한 학습 곡선도 있습니다.

활용도
FastAPI는 특히 데이터 집약적인 애플리케이션을 위한
실시간 및 고성능 API 구축에 적합합니다.

데이터가 많이 필요한 ai쪽에 많이 사용합니다.



Django
장점
Django는 웹 애플리케이션 개발에 널리 사용되는
성숙한 Python 기반 프레임워크입니다.
인증, 관리자 패널 및 ORM과 같은 많은 기본 기능을 제공합니다.
또한 지원 및 리소스를 제공하는 크고 활동적인 커뮤니티가 있습니다.

단점
Django는 복잡할 수 있으며 설정하려면 상당한 구성이 필요합니다.(=무거운 프로그램)
소규모 프로젝트나 경량 API
축에는 적합하지 않을 수도 있습니다.

활용
Django는 웹 애플리케이션, 특히 콘텐츠 기반 웹사이트,
전자상거래 플랫폼 및 소셜 미디어 플랫폼을 구축하는 데 널리 사용됩니다.



Flask
장점
Flask는 배우고 사용하기 쉬운 경량 Python 기반 프레임워크입니다.
유연성을 제공하고 개발자가 모듈식 및 확장 가능한 방식으로
웹 애플리케이션을 구축할 수 있도록 합니다.
또한 사용자 정의가 가능하고 소규모 프로젝트를 구축하는 데 적합합니다.

단점
Flask는 다른 프레임워크에 비해 기본 제공 기능이 적기 때문에
개발자가 구현하는 데 더 많은 노력과 시간이 필요할 수 있습니다.
또한 대규모 웹 애플리케이션을 구축하는 데 적합하지 않을 수도 있습니다.

활용
Flask는 개인 웹 사이트, 간단한 API 및 내부 대시보드와 같은
소규모 웹 애플리케이션 및 프로토타입을 구축하는 데 적합합니다.



요약
FastAPI는 실시간 및 고성능 API를 구축하는 데 적합한 현대적이고
빠른 프레임워크이고, Django는 복잡한 웹 애플리케이션을 구축하는 데
적합한 성숙한 프레임워크이며,
Flask는 소규모 웹 애플리케이션 및 프로토타입을 구축하는 데 적합한
가볍고 유연한 프레임워크입니다.
그들 사이의 선택은 개발 팀의 기술과 경험뿐만 아니라
프로젝트의 특정 요구 사항과 요구 사항에 따라 다릅니다.

 

즉 어떤것이 정답이다 좋다 이런거라기보다는 본인의 해야하는 프로젝트에 맞춰서 선택하면 더 나은개발환경을 구축할 수 있을것같다 라는 생각을 했습니다.


5. 설치

FastAPI를 설치하기 전에 Python 가상환경을 만들어줍니다. 가상환경을 사용하면 프로젝트 간에 의존성 충돌을 방지하고 프로젝트 별로 필요한 패키지를 독립적으로 관리할 수 있습니다.

▶ 내가 파이썬 여러버전을 가지고 있다면 가상환경을 통하여 여러개의 파이선을 베이스를하고 버전에 맞는 모듈을 사용할 수 있지만 현재 나는 파이참을 사용하면 .venv라는 가상환경을 만들어놓기떄문에 프로그램을 설치하지 않습니다

수업에 잠깐 다루었던 내용이니 방법은 기록했습니다.

# Windows
python -m venv venv

# macOS/Linux
python3 -m venv venv

가상환경이 생성되면 해당 가상환경을 활성화합니다.

# Windows
venv\Scripts\activate

# macOS/Linux
source venv/bin/activate

 


FastAPI를 사용하려면 먼저 FastAPI 패키지를 설치해야 합니다.

설치는 파이참기준 터미널(Alt+f12)를 통해서 터미널을 살짝 변경해줍니다

저는 Git Bash로사용하기로했습니다.

옵션을 git bash로 변경후 터미널에 아래코드를 입력해줍니다.

pip install fastapi

 

그 후 FastAPI 애플리케이션을 실행하기 위해uvicorn이라는 ASGI 서버가 필요합니다.

pip install "uvicorn[standard]"

uvicorn[standard]

1. 이 패키지는 uvicorn에 기본적으로 포함된 기능 외에도 추가적인 표준 미들웨어를 포함합니다.

2. 표준 미들웨어는 보안, 로깅 및 기타 서버 관련 기능을 추가하는 데 도움이 됩니다.

3. 예를 들어, Gzip 압축, CORS(Cross-Origin Resource Sharing) 지원 등이 기본적으로 포함되어 있습니다.

 

uvicorn

1. 기본적으로 필요한 최소한의 기능만을 제공하는 패키지입니다.

2. uvicorn[standard]에 비해 미들웨어가 적고, 기본적인 서버 기능만을 제공합니다.


이후 내가 사용할 프로그램(fast api)의 번호를 알아야하고

fastapi의 기본 번호는 8000 입니다

다른프로그램은 또다른 번호를 쳐야합니다.

이 부분은 내가 데이터를 입력하고 실행할떄 보여지는 프론트 부분의 주소가 되겠습니다.

http://127.0.0.1:8000 (내 컴퓨터의 동작하는 ip)(8000은 기본포트번호)

이후 get방식을 통해 어떤것을 가져올건지 적어주게 됩니다.

@app.get("/users/{id}")
def find_user(id:int):
    item = user[id]
    return item

@ : 데코레이터 

@의 기능 : 클로저를 만들고 클로저로 감싸놓은 기능을 구현하게 함

얘는 파이썬의 동작이나 기능을 확장할 수 있도록 합니다.

보통 @app.get을 하게되면 그 밑에 함수를 쓰고 그 함수를 확장시켜줍니다.

즉 이 코드의 뜻은

@app.get을 하게 되면 겟 방식으로 (브라우저에서 /user/{id}를 쳤을떄, 

def find~의 함수를 실행해주세요 라는 뜻입니다.

 

내가 사용할 포트에 원하는 데이터를 가져오게 하려면 아래와같은 주소가 될 것 입니다.

http://127.0.0.1:8000/users/0

뒤의 {id}는 숫자를 어달라는 의미입니다.

왜냐면 변수타입 어노테이션을 이용하여 

def find_user(id:int): 중

id는 int의 타입이라는것을 알려주었기 때문이죠.

 

이렇게 브라우저에서 호출하면 즉 함수가 호출하게 됩니다.

이 기능을 사용하려면 데이터가 있어야하니까 만들어보겠습니다.

users = {
    0: {"userid" : "apple", "name" : "김사과"},
    1: {"userid" : "banana", "name" : "반하나"},
    2: {"userid" : "orange", "name" : "오렌지"},
    }

 

6. 실행

작성한 FastAPI 애플리케이션을 실행합니다. 다음 명령을 사용하여 uvicorn을 통해 실행할 수 있습니다.

uvicorn main:app --reload

 

FastAPI는 자동으로 생성된 Swagger UI를 통해 API 문서를 제공합니다.

기본적으로 http://127.0.0.1:8000/docs 또는 http://127.0.0.1:8000/redoc에서 확인할 수 있습니다. Swagger UI를 통해 API의 엔드포인트, 매개변수, 응답 형식 등을 살펴볼 수 있습니다.

 

터미널에 실행을 하게되면 아래처럼 됩니다.

유비콘을 통해서 러닝되고있다는 말이 나오면서 창을 클릭할 수 있게 됩니다.

 

여기서 사이트를 누르면 

이렇게 사이트에 값이 입력받아있게됩니다.

이렇게 되면 우리가 만든 딕셔너리 값들은 어디갔나 싶겠지만

여기서 get방식을 통해  id의 번호를  입력해주면 그에 맞는 데이터가  나타날것입니다.

즉 브라우저 맨 끝에 1을 실행한(http://127.0.0.1:800/users/1)을 치면 오른쪽 처럼 변경됩니다.

0,1,2를 자율적으로 넣어서 변하는 값을 확인할 수 있습니다.

 

응용을 하자면

http://127.0.0.1:8000/users/0/userid ▶ 0번에 userid를 리턴시켜줘

http://127.0.0.1:8000/users/1/userid ▶ 1번에 userid를 리턴시켜줘

라고 하려면 아래와같이 작성하면 됩니다. 

@app.get("/users/{id}/{key}")
def find_user_by_key(id: int, key: str):
    user = users[id][key]
    return user


이런것들이 API라고 합니다. 주소를 호출하면 리턴해주는 것은 백앤드 개발자가 하는 역할입니다 라는것을

알았습니다.

즉 프론트,백앤드 개발자들이 어떤일을 하게되는지 어떻게 코드로 업무를 주고받는지

얕게 알아볼 수 있는 수업이었습니다.

 


Name에 의해 id를 찾아오는 방법

# http://127.0.0.1:8000/users/id-by-name?name=김사과
@app.get("/users/id-by-name")
async def find_user_by_name(name: str):
    for userid, user in users.items():
        if user['name'] == name:
            return user
    return {"error" : "데이터를 찾지 못함"}

다른사람들도 사용할때는 병렬적으로 사용이 어렵기에 async를 써주도록 합니다.


다른 방식을 사용하는 매소드를 이용하게 되면 브라우저로 확인할 수 있는 방법은 get입니다.

다른 방법으로 하는 방법은 docs도 있습니다 docs로 보내게되면 브라우저가 아닌아래화면처럼 나오게 됩니다.

docs

여기에 내가 만든 api들이 나오게 됩니다.

 

여기에 id값을 바꾸게 되면 0번을 나오게되는 값들을 보내줍니다.


post사용해보려고합니다.

post를 사용하려면 BaseModel을 임포트 해주어야합니다.

 

from pydantic import BaseModel

 

역할 : 간단하게 클래스를 만들고 제이슨으로 만들고 리턴시켜줄 수 있는 모델입니다.

class User(BaseModel):
    userid: str
    name: str

# http://127.0.0.8000/users/0
class User(BaseModel):
    userid: str
    name: str

@app.post("/users/{id}")
async def create_user(id: int, user: User):
    if id in users:
        return {"error": "이미 존재하는 키"}
    users[id] = user.__dict__
    return {"success":"ok"}

post방식은 FastAPI에서 따로 확인을 해주어야 합니다.

입력을 해주면 이미 존재하는키, ok등 입력값에 맞게 나오게 됩니다.


이렇게 10번에 {"userid":"안가도"}를 하게 된다면 

 

중복되는 값이 없으니 ok가 나오게 됩니다.


데이터 수정

class UserForUpdate(BaseModel):
    userid: Optional[str]
    name: Optional[str]

여기서 Optional은 str으로 넣을걸지만 None이나올수도 있다는 말입니다.

@app.put("/users/{id}")
async def update_user(id: int, user: UserForUpdate):
    if id not in users:
        return {"error": "id가 존재하지 않음"}

    if user.userid:
        users[id]['userid'] = user.userid

    if user.name:
        users[id]['name'] = user.name

    return {"success": "ok"}

 

확인하는 방법

put이 생겼다는걸 확인할 수 있습니다.

임의로 50을 넣고 excute를 누르면 id가 존재하지 않음이 뜹니다.

 

수정할 데이터를 넣고 excute하면  정상적으로 ok라고 되는걸 확인할 수 있습니다.

 

이후 10을 넣으면 방금 바꾼 값이 확인이 됩니다.


데이터 삭제

@app.delete("/users/{id}")
def delete_item(id: int):
    if id in users:
        del users[id]
        return {"success": "ok"}
    return {"error": "사용자를 찾을 수 없습니다."}

여기까지 하면 FastAPI에 delete버튼이 생깁니다.

이후 10번을 넣고 excute하면 당연히 데이터는 삭제가 되고 { "success": "ok" }가 확인이됩니다.

이후 다시 get에 10을 입력하면 사용자를 찾을 수 없습니다.가 표출됩니다.