Skip to content

Commit 61c0b86

Browse files
authored
Merge pull request #137 from kurusugawa-computer/modify-upload-file
file objectをs3にアップロードするメソッドを追加
2 parents 802267d + a695c76 commit 61c0b86

File tree

4 files changed

+78
-25
lines changed

4 files changed

+78
-25
lines changed

annofabapi/__version__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '0.27.9'
1+
__version__ = '0.27.10'

annofabapi/api.py

+31-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import functools
22
import json
33
import logging
4-
import warnings
54
from typing import Any, Callable, Dict, List, Optional, Tuple, Union # pylint: disable=unused-import
65

76
import backoff
@@ -87,6 +86,9 @@ def __init__(self, login_user_id: str, login_password: str, endpoint_url: str =
8786
#: login, refresh_tokenで取得したtoken情報
8887
token_dict: Optional[Dict[str, Any]] = None
8988

89+
#: Signed Cookie情報
90+
cookies: Optional[Dict[str, Any]] = None
91+
9092
class __MyToken(AuthBase):
9193
"""
9294
requestsモジュールのauthに渡す情報。
@@ -106,6 +108,7 @@ def _create_kwargs(self, params: Optional[Dict[str, Any]] = None, headers: Optio
106108
request_body: Optional[Any] = None) -> Dict[str, Any]:
107109
"""
108110
requestsモジュールのget,...メソッドに渡すkwargsを生成する。
111+
109112
Args:
110113
params: クエリパラメタに設定する情報
111114
headers: リクエストヘッダに設定する情報
@@ -211,26 +214,48 @@ def _request_wrapper(self, http_method: str, url_path: str, query_params: Option
211214
content = self._response_to_content(response)
212215
return content, response
213216

214-
def _get_signed_cookie(self, project_id):
217+
def _get_signed_cookie(self, project_id) -> Tuple[Dict[str, Any], requests.Response]:
215218
"""
216219
アノテーション仕様の履歴情報を取得するために、非公開APIにアクセスする。
217220
変更される可能性あり.
218221
219-
.. deprecated:: X
220-
221222
Args:
222223
project_id: プロジェクトID
223224
224225
Returns:
225-
Tuple[Content, Reponse)
226+
Tuple[Content, Response)
226227
227228
"""
228-
warnings.warn("deprecated", DeprecationWarning)
229229
url_path = f'/private/projects/{project_id}/sign-headers'
230230
http_method = 'GET'
231231
keyword_params: Dict[str, Any] = {}
232232
return self._request_wrapper(http_method, url_path, **keyword_params)
233233

234+
def _request_get_with_cookie(self, project_id: str, url: str) -> requests.Response:
235+
"""
236+
Signed Cookie を使って、AnnoFabのURLにGET requestを投げる。
237+
238+
Args:
239+
project_id: プロジェクトID
240+
url: アクセス対象のURL
241+
242+
Returns:
243+
Response
244+
245+
"""
246+
if self.cookies is None:
247+
self.cookies, _ = self._get_signed_cookie(project_id)
248+
249+
kwargs = {"cookies": self.cookies}
250+
response = requests.get(url, **kwargs)
251+
252+
# CloudFrontから403 Errorが発生したとき
253+
if response.status_code == requests.codes.forbidden and response.headers.get("server") == "CloudFront":
254+
self.cookies, _ = self._get_signed_cookie(project_id)
255+
return self._request_get_with_cookie(project_id, url)
256+
else:
257+
return response
258+
234259
#########################################
235260
# Public Method : Login
236261
#########################################

annofabapi/wrapper.py

+38-18
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import urllib
77
import urllib.parse
88
import warnings
9-
from typing import Any, Callable, Dict, List, Optional, Union
9+
from typing import Any, Callable, Dict, List, Optional
1010

1111
import requests
1212

@@ -241,14 +241,26 @@ def upload_file_to_s3(self, project_id: str, file_path: str, content_type: Optio
241241
content_type: アップロードするファイルのMIME Type. Noneの場合、ファイルパスから推測する。
242242
243243
Returns:
244-
AnnoFabに登録するときのpath
244+
一時データ保存先であるS3パス
245245
"""
246246

247247
# content_type を推測
248248
new_content_type = self._get_content_type(file_path, content_type)
249-
return self._upload_file_to_s3(project_id, file_path, content_type=new_content_type)
249+
with open(file_path, 'rb') as f:
250+
return self.upload_file_object_to_s3(project_id, fp=f, content_type=new_content_type)
251+
252+
def upload_file_object_to_s3(self, project_id: str, fp: typing.IO, content_type: str) -> str:
253+
"""
254+
createTempPath APIを使ってアップロード用のURLとS3パスを取得して、"file object"をアップロードする。
255+
256+
Args:
257+
project_id: プロジェクトID
258+
fp: アップロードするファイルのfile object
259+
content_type: アップロードするfile objectのMIME Type.
250260
251-
def _upload_file_to_s3(self, project_id: str, fp: Union[str, typing.IO], content_type: str) -> str:
261+
Returns:
262+
一時データ保存先であるS3パス
263+
"""
252264
# 一時データ保存先を取得
253265
content = self.api.create_temp_path(project_id, header_params={'content-type': content_type})[0]
254266

@@ -259,12 +271,7 @@ def _upload_file_to_s3(self, project_id: str, fp: Union[str, typing.IO], content
259271
s3_url = content["url"].split("?")[0]
260272

261273
# アップロード
262-
if isinstance(fp, str):
263-
with open(fp, 'rb') as f:
264-
res_put = self.api.session.put(s3_url, params=query_dict, data=f,
265-
headers={'content-type': content_type})
266-
else:
267-
res_put = self.api.session.put(s3_url, params=query_dict, data=fp, headers={'content-type': content_type})
274+
res_put = self.api.session.put(s3_url, params=query_dict, data=fp, headers={'content-type': content_type})
268275

269276
annofabapi.utils.log_error_response(logger, res_put)
270277
annofabapi.utils.raise_for_status(res_put)
@@ -818,7 +825,7 @@ def get_latest_instruction(self, project_id: str) -> Optional[Instruction]:
818825
def upload_instruction_image(self, project_id: str, image_id: str, file_path: str,
819826
content_type: Optional[str] = None) -> str:
820827
"""
821-
作業ガイドの画像をアップロードする。image_idはUUIDv4
828+
作業ガイドの画像をアップロードする。
822829
823830
Args:
824831
project_id: プロジェクトID
@@ -827,15 +834,30 @@ def upload_instruction_image(self, project_id: str, image_id: str, file_path: st
827834
content_type: アップロードするファイルのMIME Type. Noneの場合、ファイルパスから推測する。
828835
829836
Returns:
830-
AnnoFabに登録するときのpath
837+
一時データ保存先であるS3パス
831838
"""
832-
833-
# content_type を推測
834839
new_content_type = self._get_content_type(file_path, content_type)
840+
with open(file_path, 'rb') as f:
841+
return self.upload_file_object_as_instruction_image(project_id, image_id, fp=f,
842+
content_type=new_content_type)
843+
844+
def upload_file_object_as_instruction_image(self, project_id: str, image_id: str, fp: typing.IO,
845+
content_type: str) -> str:
846+
"""
847+
file objectを作業ガイドの画像としてアップロードする。
848+
849+
Args:
850+
project_id: プロジェクトID
851+
image_id: 作業ガイド画像ID
852+
fp: アップロードするファイルのfile object
853+
content_type: アップロードするファイルのMIME Type.
835854
855+
Returns:
856+
一時データ保存先であるS3パス
857+
"""
836858
# 作業ガイド登録用/更新用のURLを取得
837859
content = self.api.get_instruction_image_url_for_put(project_id, image_id,
838-
header_params={'content-type': new_content_type})[0]
860+
header_params={'content-type': content_type})[0]
839861

840862
url_parse_result = urllib.parse.urlparse(content["url"])
841863
query_dict = urllib.parse.parse_qs(url_parse_result.query)
@@ -844,9 +866,7 @@ def upload_instruction_image(self, project_id: str, image_id: str, file_path: st
844866
s3_url = content["url"].split("?")[0]
845867

846868
# アップロード
847-
with open(file_path, 'rb') as f:
848-
res_put = self.api.session.put(s3_url, params=query_dict, data=f,
849-
headers={'content-type': new_content_type})
869+
res_put = self.api.session.put(s3_url, params=query_dict, data=fp, headers={'content-type': content_type})
850870
annofabapi.utils.log_error_response(logger, res_put)
851871
annofabapi.utils.raise_for_status(res_put)
852872
return content["path"]

tests/test_api.py

+8
Original file line numberDiff line numberDiff line change
@@ -449,3 +449,11 @@ def test_get_task_or_none(self):
449449
assert wrapper.get_task_or_none(project_id, "not-exists") is None
450450

451451
assert wrapper.get_task_or_none("not-exists", task_id) is None
452+
453+
454+
class TestProtectedMethod:
455+
def test__request_get_with_cookie(self):
456+
images, _ = api.get_instruction_images(project_id)
457+
url = images[0]["url"]
458+
r = api._request_get_with_cookie(project_id, url)
459+
assert r.headers["Content-Type"].startswith("image/")

0 commit comments

Comments
 (0)