피터의 Deta 사용기 (3) Deta Drive

Pt J·2021년 6월 28일
0

Story Of My Life

목록 보기
26/31
post-thumbnail

Deta Drive (soon)
Upload, host and serve images and files.
― 이미지와 파일 업로드, 호스트, 및 제공

계정 당 10GB 공간을 사용할 수 있는 암호화된 스토리지라고 할 수 있겠다.
Base의 경우 내부적으로는 AWS에 암호화되어 저장된다고 하는데 Drive는 자기네들이 암호화 키를 관리한다고 한다.
soon 상태인데 왜 되는지 모르겠다.
어느 정도 구현되긴 하였으나 beta 할 정도까진 안되어서 그런가?
존재는 하지만 beta는 된 이후에 사용하는 게 좋을지도?

Deta Drive

Drive는 Project ID project_id 와 Drive 이름 drive_name 에 대해 다음과 같은 URL로 접근할 수 있다.

https://drive.deta.sh/v1/{project_id}/{drive_name}

존재하지 않는 Drive에 PUT 또는 POST 요청 시 새 Drive를 생성할 수 있으며, Drive 개수 자체엔 제한이 없다.

각 파일은 이름을 통해 식별되며, 파일 이름에 백슬래시 \ 를 통해 디렉토리 계층 구조를 논리적으로 나타낼 수 있다.
파일 이름을 \ 로 끝내는 등 빈 디렉토리는 생성할 수 없다.

HTTP 이용하기

HTTP를 통한 Drive 접근은 X-Api-Key Key에 Project Key를 Value로 한 헤더를 필요로 한다.

작은 파일 업로드

10MB 이하의 payload를 받는 엔드포인트로, 단일 요청으로 전송 가능한 충분히 작은 파일을 전송할 때 사용된다.
파일 내용을 payload로, 파일 이름을 쿼리로 전송하며, 기존에 있는 이름일 경우 파일을 덮어 쓴다.

POST /files?name={name}

typekeydescriptionremark
HeaderContent-Type파일 유형; 미지정 시 파일이름으로 유추 (default는 application/octet-stream)optional
Queryname파일 이름required

201 Created

Content-Type: application/json
{
    "name": "file name",
    "project_id": "deta project id",
    "drive_name": "deta drive_name"
}

큰 파일 업로드

큰 파일을 업로드할 땐 몇 가지 엔드포인트를 순차적으로 이용해야 한다.

초기화

먼저, 파일 업로드를 초기화한다.

POST /uploads?name={name}

typekeydescriptionremark
Queryname파일 이름required

202 Accepted

Content-Type: application/json
{
    "name": "file name",
    "upload_id": "a unique upload id"
    "project_id": "deta project id",
    "drive_name": "deta drive name"
}

업로드

최소 5MB 최대 10MB 크기의 덩어리로서 파일을 부분 업로드 한다.
파일의 모든 내용이 업로드될 때까지 이를 반복하며, 마지막 덩어리에 한해 5MB보다 작을 수 있다.

POST /uploads/{upload_id}/parts?name={name}&part={part}

typekeydescriptionremark
Pathupload_id업로드 초기화 시 얻은 식별자required
Queryname파일 이름required
Querypart몇 번째 덩어리인가 (1부터 시작)required

200 Ok

Content-Type: application/json
{
    "name": "file name",
    "upload_id": "a unique upload id"
    "part": 1, // upload part number
    "project_id": "deta project id",
    "drive_name": "deta drive name"
}

업로드 종료

파일을 모두 올렸다면 업로드를 종료한다.

PATCH /uploads/{upload_id}?name={name}

typekeydescriptionremark
Pathupload_id업로드 초기화 시 얻은 식별자required
Queryname파일 이름required

200 Ok

Content-Type: application/json
{
    "name": "file name",
    "upload_id": "a unique upload id"
    "project_id": "deta project id",
    "drive_name": "deta drive name"
}

업로드 취소

만약 파일을 올리던 도중 취소하고 싶다면 이 엔드포인트를 이용한다.

DELETE /uploads/{upload_id}?name={name}

typekeydescriptionremark
Pathupload_id업로드 초기화 시 얻은 식별자required
Queryname파일 이름required

200 Ok

Content-Type: application/json
{
    "name": "file name",
    "upload_id": "a unique upload id"
    "project_id": "deta project id",
    "drive_name": "deta drive name"
}

다운로드

업로드한 파일을 다시 내려 받기 위해 사용하는 엔드포인트다.

GET /files/download?name={name}

typekeydescriptionremark
Queryname파일 이름required

200 Ok

Accept-Ranges: bytes
Content-Type: {content_type}
Content-Length: {content_length}
{Body}

파일 목록

Drive에 존재하는 파일들의 이름을 받아오는 엔드포인트다.

GET /files?limit={limit}&prefix={prefix}&last={last}

typekeydescriptionremark
Querylimit가져올 파일 이름의 개수 (default는 1000)optional
Queryprefix파일 이름의 접두사optional
Querylast이전 응답 페이지의 마지막 파일 이름optional

200 Ok

Content-Type: application/json
{
    "paging": {
        "size": 1000, // the number of file names in the response
        "last": "last file name in response"
    },
    "names": ["file1", "file2", ...]
}

파일 삭제

Drive에 존재하는 파일을 삭제하고자 할 때 사용하는 엔드포인트다.

정황 상 이런 엔드포인트일 것 같은데 어째서인지 문서 상에 엔드포인트가 명시되어 있지 않다?
파라미터랑 응답까지도 나와 있으면서 엔드포인트만 없다???
DELETE /files?name={name}

typekeydescriptionremark
Queryname파일 이름required

200 Ok

Content-Type: application/json
{
    "deleted": ["file_1", "file_2", ...] // deleted file names
    "failed": {
        "file_3": "reason why file could not be deleted",
        "file_4": "reason why file could not be deleted",
        //...
    } 
}

SDK 이용하기

JavaScript나 Python에서는 SDK를 통해 Deta Drive를 이용할 수 있다.

JavaScript 사용 시 다음 명령어 중 하나로 설치할 수 있으며

$ npm install deta
$ yarn add deta

Python 사용 시 다음 명령어로 설치할 수 있다.

$ pip install deta

Python을 기준으로 이야기하도록 하겠다.

Project Key PROJECT_KEY 와 Drive 이름 DRIVE_NAME 에 대하여 다음과 같이 Drive를 인스턴스화할 수 있다.

from deta import Deta

deta = Deta("PROJECT_KEY")
drive = deta.Drive("DRIVE_NAME")

Deta Micro에서 사용할 땐 deta = Deta("PROJECT_KEY") 를 생략하고 drive = Drive("DRIVE_NAME") 로 사용할 수 있다.

업로드

― Drive에 파일을 저장하되, 이미 존재한다면 이를 덮어 씌운다.

put 메서드는 다음과 같다.

put(name, data, *, path, content_type)

parameterdata typedescriptionremark
namestring파일 이름required
datastring | bytes | io.TextIOBase | io.BufferedIOBase | io.RawIOBase파일 데이터optional
pathstring파일 경로optional
content_typestring파일 유형; 미지정 시 파일이름으로 유추 (default는 application/octet-stream)optional

put 메서드는 성공 시 파일 이름을, 오류 발생 시 그 오류를 반환한다.

다운로드

― 파일 이름을 통해 Drive에서 파일을 내려 받는다.

get 메서드는 다음과 같다.

get(name)

parameterdata typedescriptionremark
namestring파일 이름required

get 메서드는 DriveStreamingBody 객체를 반환한다.

DriveStreamingBody

nametypedescription
read(size=None)method데이터 읽기 (size 지정 시 그만큼만 읽기)
iter_chunks(chunk_size:int=1024)methodchunk_size만큼씩 생성하는 반복자 반환
iter_lines(chunk_size:int=1024)methodchunk_size 단위로 라인을 생성하는 반복자 반환
close()method스트림을 닫는 메서드
closedproperty스트림이 닫혔는지 나타내는 bool

삭제

― 파일 이름을 통해 Drive에서 파일을 삭제한다.

delete 메서드는 다음과 같다.

delete(name)

parameterdata typedescriptionremark
namestring파일 이름required

deleted 메서드는 성공적으로 삭제하거나 해당 파일이 없을 시 파일 이름을, 오류 발생 시 그 오류를 반환한다.

여러 개 삭제

여러 개의 파일을 삭제할 땐 delete_many 메서드를 사용한다.
delete_many 메서드는 다음과 같다.

delete_many(names)

parameterdata typedescriptionremark
nameslist of string파일 이름들의 리스트required

delete_many 메서드는 삭제한 파일과 실패한 파일의 정보가 담긴 dict 를 반환한다.

파일 목록

― Drive에 저장되어 있는 파일들의 이름을 불러온다.

list 메서드는 다음과 같다.

list(limit, prefix, last)

parameterdata typedescriptionremark
limitint가져올 파일 이름의 개수 (default는 1000)optional
prefixstring파일 이름의 접두사optional
laststring이전 응답 페이지의 마지막 파일 이름optional

list 메서드는 파일 이름과 페이지 정보가 담긴 dict 를 반환한다.

튜토리얼

공식 Python 튜토리얼을 보며 코드를 작성해보았다.
간단한 이미지 서버를 작성하는 것이다.

먼저 프로젝트를 위한 디렉토리를 만들고 이동한다.

$ mkdir image-server && cd image-server

그 안에 requirements.txt 라는 파일을 생성하고 다음과 같이 의존성을 작성한다.
Project Key는 코드 안에 포함해서 좋을 게 없기에 dotenv 를 통해 .env 파일에서 읽어오기로 하였다.
따라서 공식 예제에 없는 python-dotenv 가 포함된다.

requirements.txt

fastapi
uvicorn
deta
python-multipart
python-dotenv

fastapiuvicorn 을 서버를 구축하는 데 사용되며 python-multipart 는 업로드된 파일에 접근하는 데 사용된다.

다음 명령어를 통해 requirements.txt 의 의존성을 설치한다.

$ pip install -r requirements.txt

짧은 예제이므로 단일 파일 내에서 작업해도 되지만 보다 더 확장성을 고려해 작성하였다.
프로젝트 구조는 다음과 같다.

.
├── routers/
│   ├── __init__.py
│   └── images.py
├── .env
└── main.py

초기 설정

먼저 앱을 구성하기 위한 초기 설정을 하고 Drive를 인스턴스화 한다.

main.py

from fastapi import FastAPI

app = FastAPI(
    title='DETA Drive Test',
    description='Deta Drive test with FastAPI')

routers/images.py

from deta import Deta
import os
from dotenv import load_dotenv

load_dotenv(verbose=True)
PROJECT_KEY = os.getenv('PROJECT_KEY')
DRIVE_NAME = os.getenv('DRIVE_NAME')

deta = Deta(PROJECT_KEY)
drive = deta.Drive(DRIVE_NAME)

업로드

파일을 업로드할 수 있는 HTML 코드를 작성한다.

routers/images.py

from fastapi import APIRouter
from fastapi.responses import HTMLResponse, StreamingResponse

# 생략

router = APIRouter(tags=['images'])

@router.get('/', response_class=HTMLResponse)
def render():
    return '''
        <form action='/upload'enctype="multipart/form-data" method="post">
            <input name="file" type="file">
            <input type="submit">
        </form>
    '''

그리고 form 태그가 호출할 POST 메서드의 엔드포인트를 작성한다.

routers/images.py

# 생략
@router.post('/upload')
def upload_image(file: UploadFile = File(...)):
    name = file.filename
    f = file.file
    result = drive.put(name, f)
    return result

다운로드

routers/images.py

# 생략
@router.get('/download/{name}', response_class=StreamingResponse)
def download_image(name: str):
    result = drive.get(name)
    return StreamingResponse(result.iter_chunks(1024), media_type='image/png')

실행

main.py 를 다음과 같이 수정하여 router를 적용한다.

main.py

from fastapi import FastAPI
from .routers import images

tags_metadata = [
    {
        'name': 'images',
        'description': 'Upload/Download the images',
    }
]

app = FastAPI(
    title='DETA Drive Test',
    description='Deta Drive test with FastAPI',
    openapi_tags=tags_metadata)

app.include_router(images.router)

그리고 다음 명령어로 서버를 실행한다.

$ uvicorn main:app

업로드하고자 하는 파일을 선택하고

submit 버튼을 누르면 다음과 같이 /upload 엔드포인트로 넘어가 파일 이름이 출력되는 것을 확인할 수 있다.

그리고 /download 엔드포인트에 방금 업로드한 파일 이름을 입력하여 내려 받을 수 있다.

Deta 웹사이트의 Drive에는 아무것도 안뜨고 그저 초기 화면만 있는 건, 이게 아직 soon 상태이기 때문인가 싶다.

결론

Base나 Micro보다는 덜 사용할 것 같긴 하지만 Drive도 꽤나 괜찮은 것 같다.
데이터베이스에 저장하기엔 좀 큰 데이터들을 보관하는 용도로 괜찮지 않을까.
언젠가 써먹을 일이 있었으면 좋겠다ㅋㅋ


작성한 코드는 여기에서 확인할 수 있다.

profile
Peter J Online Space - since July 2020

0개의 댓글