Deta Drive (soon)
Upload, host and serve images and files.
― 이미지와 파일 업로드, 호스트, 및 제공
계정 당 10GB 공간을 사용할 수 있는 암호화된 스토리지라고 할 수 있겠다.
Base의 경우 내부적으로는 AWS에 암호화되어 저장된다고 하는데 Drive는 자기네들이 암호화 키를 관리한다고 한다.
soon 상태인데 왜 되는지 모르겠다.
어느 정도 구현되긴 하였으나 beta 할 정도까진 안되어서 그런가?
존재는 하지만 beta는 된 이후에 사용하는 게 좋을지도?
Drive는 Project ID project_id
와 Drive 이름 drive_name
에 대해 다음과 같은 URL로 접근할 수 있다.
https://drive.deta.sh/v1/{project_id}/{drive_name}
존재하지 않는 Drive에 PUT 또는 POST 요청 시 새 Drive를 생성할 수 있으며, Drive 개수 자체엔 제한이 없다.
각 파일은 이름을 통해 식별되며, 파일 이름에 백슬래시 \
를 통해 디렉토리 계층 구조를 논리적으로 나타낼 수 있다.
파일 이름을 \
로 끝내는 등 빈 디렉토리는 생성할 수 없다.
HTTP를 통한 Drive 접근은 X-Api-Key
Key에 Project Key를 Value로 한 헤더를 필요로 한다.
10MB 이하의 payload를 받는 엔드포인트로, 단일 요청으로 전송 가능한 충분히 작은 파일을 전송할 때 사용된다.
파일 내용을 payload로, 파일 이름을 쿼리로 전송하며, 기존에 있는 이름일 경우 파일을 덮어 쓴다.
POST /files?name={name}
type key description remark Header Content-Type
파일 유형; 미지정 시 파일이름으로 유추 (default는 application/octet-stream
)optional Query name
파일 이름 required
201 Created
Content-Type: application/json { "name": "file name", "project_id": "deta project id", "drive_name": "deta drive_name" }
큰 파일을 업로드할 땐 몇 가지 엔드포인트를 순차적으로 이용해야 한다.
먼저, 파일 업로드를 초기화한다.
POST /uploads?name={name}
type key description remark Query name
파일 이름 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}
type key description remark Path upload_id
업로드 초기화 시 얻은 식별자 required Query name
파일 이름 required Query part
몇 번째 덩어리인가 (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}
type key description remark Path upload_id
업로드 초기화 시 얻은 식별자 required Query name
파일 이름 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}
type key description remark Path upload_id
업로드 초기화 시 얻은 식별자 required Query name
파일 이름 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}
type key description remark Query name
파일 이름 required
200 Ok
Accept-Ranges: bytes Content-Type: {content_type} Content-Length: {content_length} {Body}
Drive에 존재하는 파일들의 이름을 받아오는 엔드포인트다.
GET /files?limit={limit}&prefix={prefix}&last={last}
type key description remark Query limit
가져올 파일 이름의 개수 (default는 1000) optional Query prefix
파일 이름의 접두사 optional Query last
이전 응답 페이지의 마지막 파일 이름 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}
type key description remark Query name
파일 이름 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", //... } }
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)
parameter data type description remark name
string
파일 이름 required data
string
|bytes
|io.TextIOBase
|io.BufferedIOBase
|io.RawIOBase
파일 데이터 optional path
string
파일 경로 optional content_type
string
파일 유형; 미지정 시 파일이름으로 유추 (default는 application/octet-stream
)optional
put
메서드는 성공 시 파일 이름을, 오류 발생 시 그 오류를 반환한다.
― 파일 이름을 통해 Drive에서 파일을 내려 받는다.
get
메서드는 다음과 같다.
get(name)
parameter data type description remark name
string
파일 이름 required
get
메서드는 DriveStreamingBody
객체를 반환한다.
DriveStreamingBody
name type description read(size=None)
method 데이터 읽기 ( size
지정 시 그만큼만 읽기)iter_chunks(chunk_size:int=1024)
method chunk_size
만큼씩 생성하는 반복자 반환iter_lines(chunk_size:int=1024)
method chunk_size
단위로 라인을 생성하는 반복자 반환close()
method 스트림을 닫는 메서드 closed
property 스트림이 닫혔는지 나타내는 bool
값
― 파일 이름을 통해 Drive에서 파일을 삭제한다.
delete
메서드는 다음과 같다.
delete(name)
parameter data type description remark name
string
파일 이름 required
deleted
메서드는 성공적으로 삭제하거나 해당 파일이 없을 시 파일 이름을, 오류 발생 시 그 오류를 반환한다.
여러 개의 파일을 삭제할 땐 delete_many
메서드를 사용한다.
delete_many
메서드는 다음과 같다.
delete_many(names)
parameter data type description remark names
list
ofstring
파일 이름들의 리스트 required
delete_many
메서드는 삭제한 파일과 실패한 파일의 정보가 담긴 dict
를 반환한다.
― Drive에 저장되어 있는 파일들의 이름을 불러온다.
list
메서드는 다음과 같다.
list(limit, prefix, last)
parameter data type description remark limit
int
가져올 파일 이름의 개수 (default는 1000) optional prefix
string
파일 이름의 접두사 optional last
string
이전 응답 페이지의 마지막 파일 이름 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
fastapi
와 uvicorn
을 서버를 구축하는 데 사용되며 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도 꽤나 괜찮은 것 같다.
데이터베이스에 저장하기엔 좀 큰 데이터들을 보관하는 용도로 괜찮지 않을까.
언젠가 써먹을 일이 있었으면 좋겠다ㅋㅋ
작성한 코드는 여기에서 확인할 수 있다.