diff --git a/.travis.yml b/.travis.yml index 2681b3d9..40f2191f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ version: ~> 1.0 language: python python: - - "3.6" - "3.7" - "3.8" - "3.9" diff --git a/Makefile b/Makefile index 4d4cac46..96ade04e 100644 --- a/Makefile +++ b/Makefile @@ -10,9 +10,9 @@ format: poetry run black annofabapi tests lint: - poetry run mypy annofabapi tests + poetry run mypy annofabapi tests/create_test_project.py poetry run flake8 annofabapi tests/create_test_project.py - poetry run pylint --jobs=0 annofabapi tests/create_test_project.py + poetry run pylint --jobs=0 annofabapi test: # 並列で実行するとエラーになるので、シーケンシャルで実行する diff --git a/annofabapi/__version__.py b/annofabapi/__version__.py index d942e9e7..aa212880 100644 --- a/annofabapi/__version__.py +++ b/annofabapi/__version__.py @@ -1 +1 @@ -__version__ = "0.51.0" +__version__ = "0.52.0" diff --git a/annofabapi/api.py b/annofabapi/api.py index c647753b..a0c353a6 100644 --- a/annofabapi/api.py +++ b/annofabapi/api.py @@ -1,14 +1,18 @@ +import copy import json import logging import warnings +from functools import wraps +from json import JSONDecodeError from typing import Any, Dict, Optional, Tuple +import backoff import requests from requests.auth import AuthBase from requests.cookies import RequestsCookieJar +from annofabapi.exceptions import NotLoggedInError from annofabapi.generated_api import AbstractAnnofabApi -from annofabapi.utils import _log_error_response, _raise_for_status, my_backoff logger = logging.getLogger(__name__) @@ -16,6 +20,166 @@ """AnnoFab WebAPIのデフォルトのエンドポイントURL""" +def _raise_for_status(response: requests.Response) -> None: + """ + HTTP Status CodeがErrorの場合、``requests.exceptions.HTTPError`` を発生させる。 + そのとき ``response.text`` もHTTPErrorに加えて、HTTPError発生時にエラーの原因が分かるようにする。 + + + Args: + response: Response + + Raises: + requests.exceptions.HTTPError: + + """ + try: + response.raise_for_status() + except requests.exceptions.HTTPError as e: + http_error_msg = f"{e.args[0]} , {response.text}" + e.args = (http_error_msg,) + raise e + + +def _log_error_response(arg_logger: logging.Logger, response: requests.Response) -> None: + """ + HTTP Statusが400以上ならば、loggerにresponse/request情報を出力する + + + Args: + arg_logger: logger + response: Response + + """ + + def mask_key(d, key: str): + if key in d: + d[key] = "***" + + if 400 <= response.status_code < 600: + headers = copy.deepcopy(response.request.headers) + # logにAuthorizationを出力しないようにマスクする + mask_key(headers, "Authorization") + + # request_bodyのpassword関係をマスクして、logに出力する + request_body = response.request.body + request_body_for_logger: Optional[Any] + if request_body is not None and request_body != "": + try: + dict_request_body = json.loads(request_body) + except JSONDecodeError: + request_body_for_logger = request_body + else: + request_body_for_logger = _create_request_body_for_logger(dict_request_body) + else: + request_body_for_logger = request_body + + arg_logger.error( + "HTTP error occurred :: %s", + { + "response": { + "status_code": response.status_code, + "text": response.text, + }, + "request": { + "http_method": response.request.method, + "url": response.request.url, + "body": request_body_for_logger, + "headers": headers, + }, + }, + ) + + +def _create_request_body_for_logger(data: Any) -> Any: + """ + ログに出力するためのreqest_bodyを生成する。 + * パスワードやトークンなどの機密情報をマスクする + * bytes型の場合は `(bytes)`と記載する。 + + + Args: + data: request_body + + Returns: + ログ出力用のrequest_body + """ + + def mask_key(d, key: str): + if key in d: + d[key] = "***" + + if not isinstance(data, dict): + return data + elif isinstance(data, bytes): + # bytes型のときは値を出力しても意味がないので、bytesであることが分かるようにする + return "(bytes)" + + MASKED_KEYS = {"password", "old_password", "new_password", "id_token", "refresh_token", "access_token"} + diff = MASKED_KEYS - set(data.keys()) + if len(diff) == len(MASKED_KEYS): + # マスク対象のキーがない + return data + + copied_data = copy.deepcopy(data) + for key in MASKED_KEYS: + mask_key(copied_data, key) + + return copied_data + + +def _should_retry_with_status(status_code: int) -> bool: + """HTTP Status Codeからリトライすべきかどうかを返す。""" + if status_code == 429: + return True + elif 500 <= status_code < 600: + return True + else: + return False + + +def my_backoff(function): + """ + HTTP Status Codeが429 or 5XXのときはリトライする. 最大5分間リトライする。 + """ + + @wraps(function) + def wrapped(*args, **kwargs): + def fatal_code(e): + """ + リトライするかどうか + status codeが5xxのとき、またはToo many Requests(429)のときはリトライする。429以外の4XXはリトライしない + https://requests.kennethreitz.org/en/master/user/quickstart/#errors-and-exceptions + + Args: + e: exception + + Returns: + True: give up(リトライしない), False: リトライする + + """ + if isinstance(e, requests.exceptions.HTTPError): + if e.response is None: + return True + return not _should_retry_with_status(e.response.status_code) + + else: + # リトライする + return False + + return backoff.on_exception( + backoff.expo, + (requests.exceptions.RequestException, ConnectionError), + jitter=backoff.full_jitter, + max_time=300, + giveup=fatal_code, + # loggerの名前をbackoffからannofabapiに変更する + logger=logger, + )(function)(*args, **kwargs) + + return wrapped + + class AnnofabApi(AbstractAnnofabApi): """ Web APIに対応したメソッドが存在するクラス。 @@ -49,7 +213,7 @@ def __init__(self, login_user_id: str, login_password: str, endpoint_url: str = self.__account_id: Optional[str] = None - class __MyToken(AuthBase): + class _MyToken(AuthBase): """ requestsモジュールのauthに渡す情報。 http://docs.python-requests.org/en/master/user/advanced/#custom-authentication @@ -65,6 +229,26 @@ def __call__(self, req): ######################################### # Private Method ######################################### + @staticmethod + def _encode_query_params(query_params: Dict[str, Any]) -> Dict[str, Any]: + """query_paramsのvalueがlist or dictのときは、JSON形式の文字列に変換する。 + `getAnnotationList` webapiで指定できる `query`などのように、2階層のquery_paramsに対応させる。 + + Args: + query_params (Dict[str,Any]): [description] + + Returns: + Dict[str, str]: [description] + """ + new_params = {} + if query_params is not None: + for key, value in query_params.items(): + if isinstance(value, (list, dict)): + new_params[key] = json.dumps(value) + else: + new_params[key] = value + return new_params + def _create_kwargs( self, params: Optional[Dict[str, Any]] = None, @@ -74,16 +258,14 @@ def _create_kwargs( """ requestsモジュールのget,...メソッドに渡すkwargsを生成する。 - Args: - params: クエリパラメタに設定する情報 - headers: リクエストヘッダに設定する情報 - Returns: kwargs情報 """ # query_param + # query_paramsのvalueがlist or dictのときは、JSON形式の文字列に変換する。 + # `getAnnotationList` webapiで指定できる `query`などのように、2階層のquery_paramsに対応させる。 new_params = {} if params is not None: for key, value in params.items(): @@ -97,7 +279,7 @@ def _create_kwargs( "headers": headers, } if self.token_dict is not None: - kwargs.update({"auth": self.__MyToken(self.token_dict["id_token"])}) + kwargs.update({"auth": self._MyToken(self.token_dict["id_token"])}) if request_body is not None: if isinstance(request_body, (dict, list)): @@ -140,14 +322,66 @@ def _response_to_content(response: requests.Response) -> Any: return content + @my_backoff + def _execute_http_request( + self, + http_method: str, + url: str, + *, + params: Optional[Dict[str, Any]] = None, + data: Optional[Any] = None, + json: Optional[Any] = None, # pylint: disable=redefined-outer-name + headers: Optional[Dict[str, Any]] = None, + raise_for_status: bool = True, + **kwargs, + ) -> requests.Response: + """Session情報を使って、HTTP Requestを投げる。 + 引数は ``requests.Session.request`` にそのまま渡す。 + + Args: + raise_for_status: Trueの場合HTTP Status Codeが4XX,5XXのときはHTTPErrorをスローします + + Returns: + requests.Response: [description] + + Raises: + requests.exceptions.HTTPError: http status codeが4XXX,5XXXのとき + + """ + + logger.debug( + "Sending a request :: %s", + { + "http_method": http_method, + "url": url, + "query_params": params, + "request_body_json": _create_request_body_for_logger(json), + "request_body_data": _create_request_body_for_logger(data), + "header_params": headers, + }, + ) + + response = self.session.request( + method=http_method, url=url, params=params, data=data, headers=headers, json=json, **kwargs + ) + + # リトライすべき場合はExceptionを返す + if raise_for_status or _should_retry_with_status(response.status_code): + _log_error_response(logger, response) + _raise_for_status(response) + + return response + @my_backoff def _request_wrapper( self, http_method: str, url_path: str, + *, query_params: Optional[Dict[str, Any]] = None, header_params: Optional[Dict[str, Any]] = None, request_body: Optional[Any] = None, + raise_for_status: bool = True, ) -> Tuple[Any, requests.Response]: """ HTTP Requestを投げて、Responseを返す。 @@ -158,11 +392,15 @@ def _request_wrapper( query_params: header_params: request_body: + raise_for_status: Trueの場合HTTP Status Codeが4XX,5XXのときはHTTPErrorをスローします。Falseの場合はtuple[None, Response]を返します。 Returns: Tuple[content, Response]. contentはcontent_typeにより型が変わる。 application/jsonならDict型, text/*ならばstr型, それ以外ならばbite型。 + Raises: + HTTPError: 引数 ``raise_for_status`` がTrueで、HTTP status codeが4xxx,5xxのときにスローします。 + """ if url_path.startswith("/labor-control") or url_path.startswith("/internal/"): url = f"{self.endpoint_url}/api{url_path}" @@ -172,19 +410,38 @@ def _request_wrapper( kwargs = self._create_kwargs(query_params, header_params, request_body) # HTTP Requestを投げる - response = getattr(self.session, http_method.lower())(url, **kwargs) + logger.debug( + "Sending a request :: %s", + { + "http_method": http_method.lower(), + "url": url, + "query_params": query_params, + "header_params": header_params, + "request_body": _create_request_body_for_logger(request_body) if request_body is not None else None, + }, + ) + response = self.session.request(method=http_method.lower(), url=url, **kwargs) # Unauthorized Errorならば、ログイン後に再度実行する if response.status_code == requests.codes.unauthorized: self.login() - return self._request_wrapper(http_method, url_path, query_params, header_params, request_body) - - _log_error_response(logger, response) + return self._request_wrapper( + http_method, + url_path, + query_params=query_params, + header_params=header_params, + request_body=request_body, + raise_for_status=raise_for_status, + ) response.encoding = "utf-8" - _raise_for_status(response) - content = self._response_to_content(response) + + # リトライすべき場合はExceptionを返す + if raise_for_status or _should_retry_with_status(response.status_code): + _log_error_response(logger, response) + _raise_for_status(response) + return content, response def _get_signed_cookie( @@ -219,7 +476,7 @@ def _request_get_with_cookie(self, project_id: str, url: str) -> requests.Respon """ # Sessionオブジェクトに保存されているCookieを利用して、URLにアクセスする - response = self.session.get(url) + response = self._execute_http_request("get", url, raise_for_status=False) # CloudFrontから403 Errorが発生したときは、別プロジェクトのcookieを渡している可能性があるので、 # Signed Cookieを発行して、再度リクエストを投げる @@ -233,10 +490,8 @@ def _request_get_with_cookie(self, project_id: str, url: str) -> requests.Respon _, r = self._get_signed_cookie(project_id, query_params=query_params) for cookie in r.cookies: self.session.cookies.set_cookie(cookie) - response = self.session.get(url) + response = self._execute_http_request("get", url) - _log_error_response(logger, response) - _raise_for_status(response) return response ######################################### @@ -256,18 +511,15 @@ def login(self) -> Tuple[Dict[str, Any], requests.Response]: login_info = {"user_id": self.login_user_id, "password": self.login_password} url = f"{self.url_prefix}/login" - response = self.session.post(url, json=login_info) - - _log_error_response(logger, response) - _raise_for_status(response) + response = self._execute_http_request("post", url, json=login_info) json_obj = response.json() self.token_dict = json_obj["token"] logger.debug("Logged in successfully. user_id = %s", self.login_user_id) return json_obj, response - def logout(self) -> Optional[Tuple[Dict[str, Any], requests.Response]]: + def logout(self) -> Tuple[Dict[str, Any], requests.Response]: """ ログアウト ログインしていないときはNoneを返す。 @@ -275,20 +527,21 @@ def logout(self) -> Optional[Tuple[Dict[str, Any], requests.Response]]: Returns: - Tuple[Token, requests.Response]. ログインしていないときはNone. + Tuple[Token, requests.Response] + Raises: + NotLoggedInError: ログインしてない状態で関数を呼び出したときのエラー """ if self.token_dict is None: - logger.info("You are not logged in.") - return None + raise NotLoggedInError request_body = self.token_dict content, response = self._request_wrapper("POST", "/logout", request_body=request_body) self.token_dict = None return content, response - def refresh_token(self) -> Optional[Tuple[Dict[str, Any], requests.Response]]: + def refresh_token(self) -> Tuple[Dict[str, Any], requests.Response]: """ トークン リフレッシュ ログインしていないときはNoneを返す。 @@ -296,12 +549,15 @@ def refresh_token(self) -> Optional[Tuple[Dict[str, Any], requests.Response]]: Returns: - Tuple[Token, requests.Response]. ログインしていないときはNone. + Tuple[Token, requests.Response] + + Raises: + NotLoggedInError: ログインしてない状態で関数を呼び出したときのエラー + """ if self.token_dict is None: - logger.info("You are not logged in.") - return None + raise NotLoggedInError request_body = {"refresh_token": self.token_dict["refresh_token"]} content, response = self._request_wrapper("POST", "/refresh-token", request_body=request_body) diff --git a/annofabapi/api2.py b/annofabapi/api2.py index 0975b1b5..b79353b3 100644 --- a/annofabapi/api2.py +++ b/annofabapi/api2.py @@ -5,9 +5,8 @@ from requests.cookies import RequestsCookieJar import annofabapi.utils -from annofabapi.api import AnnofabApi +from annofabapi.api import AnnofabApi, _log_error_response, _raise_for_status from annofabapi.generated_api2 import AbstractAnnofabApi2 -from annofabapi.utils import _log_error_response, _raise_for_status logger = logging.getLogger(__name__) @@ -75,7 +74,7 @@ def _request_wrapper( kwargs.update({"cookies": self.cookies}) # HTTP Requestを投げる - response = getattr(self.api.session, http_method.lower())(url, **kwargs) + response = self.api.session.request(method=http_method.lower(), url=url, **kwargs) # CloudFrontから403 Errorが発生したとき if response.status_code == requests.codes.forbidden and response.headers.get("server") == "CloudFront": diff --git a/annofabapi/exceptions.py b/annofabapi/exceptions.py index c5ca8e10..426f0b90 100644 --- a/annofabapi/exceptions.py +++ b/annofabapi/exceptions.py @@ -32,6 +32,22 @@ def __init__(self, outer_file_path: str, zipfile_path: Optional[str] = None): super().__init__(message) +class NotLoggedInError(AnnofabApiException): + """ + ログインしていない状態で、ログインしていることが前提のwebapiを実行したときのエラー + + Args: + outer_file_path: 存在しなかった外部ファイルのパス + zipfile_path: 指定した場合、「zipファイル内に外部ファイルが存在しなかった」という旨のメッセージを設定する。 + + """ + + def __init__(self, message: Optional[str] = None): + if message is None: + message = "You are not logged in." + super().__init__(message) + + class CheckSumError(AnnofabApiException): """ アップロードしたデータ(ファイルやバイナリデータ)の整合性が一致していないときのエラー。 diff --git a/annofabapi/generated_api.py b/annofabapi/generated_api.py index f34d1455..e77dad86 100644 --- a/annofabapi/generated_api.py +++ b/annofabapi/generated_api.py @@ -30,6 +30,7 @@ def _request_wrapper( query_params: Optional[Dict[str, Any]] = None, header_params: Optional[Dict[str, Any]] = None, request_body: Optional[Any] = None, + raise_for_status: bool = True, ) -> Tuple[Any, requests.Response]: pass @@ -62,6 +63,7 @@ def change_password(self, request_body: Optional[Any] = None, **kwargs) -> Tuple keyword_params: Dict[str, Any] = { "request_body": request_body, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def confirm_reset_email(self, request_body: Optional[Any] = None, **kwargs) -> Tuple[Any, requests.Response]: @@ -88,6 +90,7 @@ def confirm_reset_email(self, request_body: Optional[Any] = None, **kwargs) -> T keyword_params: Dict[str, Any] = { "request_body": request_body, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def confirm_reset_password(self, request_body: Optional[Any] = None, **kwargs) -> Tuple[Any, requests.Response]: @@ -114,6 +117,7 @@ def confirm_reset_password(self, request_body: Optional[Any] = None, **kwargs) - keyword_params: Dict[str, Any] = { "request_body": request_body, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def confirm_signup(self, request_body: Optional[Any] = None, **kwargs) -> Tuple[Any, requests.Response]: @@ -140,6 +144,7 @@ def confirm_signup(self, request_body: Optional[Any] = None, **kwargs) -> Tuple[ keyword_params: Dict[str, Any] = { "request_body": request_body, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def confirm_verify_email(self, request_body: Optional[Any] = None, **kwargs) -> Tuple[Any, requests.Response]: @@ -166,6 +171,7 @@ def confirm_verify_email(self, request_body: Optional[Any] = None, **kwargs) -> keyword_params: Dict[str, Any] = { "request_body": request_body, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def initiate_password_reset(self, request_body: Optional[Any] = None, **kwargs) -> Tuple[Any, requests.Response]: @@ -192,6 +198,7 @@ def initiate_password_reset(self, request_body: Optional[Any] = None, **kwargs) keyword_params: Dict[str, Any] = { "request_body": request_body, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def initiate_reset_email(self, request_body: Optional[Any] = None, **kwargs) -> Tuple[Any, requests.Response]: @@ -218,6 +225,7 @@ def initiate_reset_email(self, request_body: Optional[Any] = None, **kwargs) -> keyword_params: Dict[str, Any] = { "request_body": request_body, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def initiate_signup(self, request_body: Optional[Any] = None, **kwargs) -> Tuple[Any, requests.Response]: @@ -243,6 +251,7 @@ def initiate_signup(self, request_body: Optional[Any] = None, **kwargs) -> Tuple keyword_params: Dict[str, Any] = { "request_body": request_body, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def initiate_verify_email(self, request_body: Optional[Any] = None, **kwargs) -> Tuple[Any, requests.Response]: @@ -269,6 +278,7 @@ def initiate_verify_email(self, request_body: Optional[Any] = None, **kwargs) -> keyword_params: Dict[str, Any] = { "request_body": request_body, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) ######################################### @@ -303,6 +313,7 @@ def batch_update_annotations( keyword_params: Dict[str, Any] = { "request_body": request_body, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_annotation( @@ -330,6 +341,7 @@ def get_annotation( url_path = f"/projects/{project_id}/tasks/{task_id}/inputs/{input_data_id}/annotation/simple" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_annotation_archive(self, project_id: str, **kwargs) -> Tuple[Any, requests.Response]: @@ -353,6 +365,7 @@ def get_annotation_archive(self, project_id: str, **kwargs) -> Tuple[Any, reques url_path = f"/projects/{project_id}/archive/simple" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_annotation_list( @@ -387,6 +400,7 @@ def get_annotation_list( keyword_params: Dict[str, Any] = { "query_params": query_params, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_archive_full_with_pro_id(self, project_id: str, **kwargs) -> Tuple[Any, requests.Response]: @@ -410,6 +424,7 @@ def get_archive_full_with_pro_id(self, project_id: str, **kwargs) -> Tuple[Any, url_path = f"/projects/{project_id}/archive/full" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_editor_annotation( @@ -441,6 +456,7 @@ def get_editor_annotation( keyword_params: Dict[str, Any] = { "query_params": query_params, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def post_annotation_archive_update(self, project_id: str, **kwargs) -> Tuple[Any, requests.Response]: @@ -464,6 +480,7 @@ def post_annotation_archive_update(self, project_id: str, **kwargs) -> Tuple[Any url_path = f"/projects/{project_id}/archive/update" http_method = "POST" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def put_annotation( @@ -495,6 +512,7 @@ def put_annotation( keyword_params: Dict[str, Any] = { "request_body": request_body, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) ######################################### @@ -530,6 +548,7 @@ def get_annotation_specs( keyword_params: Dict[str, Any] = { "query_params": query_params, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_annotation_specs_histories(self, project_id: str, **kwargs) -> Tuple[Any, requests.Response]: @@ -553,6 +572,7 @@ def get_annotation_specs_histories(self, project_id: str, **kwargs) -> Tuple[Any url_path = f"/projects/{project_id}/annotation-specs-histories" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def put_annotation_specs( @@ -582,6 +602,7 @@ def put_annotation_specs( keyword_params: Dict[str, Any] = { "request_body": request_body, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) ######################################### @@ -618,6 +639,7 @@ def batch_update_comments( keyword_params: Dict[str, Any] = { "request_body": request_body, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_comments( @@ -645,6 +667,7 @@ def get_comments( url_path = f"/projects/{project_id}/tasks/{task_id}/inputs/{input_data_id}/comments" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) ######################################### @@ -679,6 +702,7 @@ def batch_update_inputs( keyword_params: Dict[str, Any] = { "request_body": request_body, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def create_temp_path( @@ -708,6 +732,7 @@ def create_temp_path( keyword_params: Dict[str, Any] = { "header_params": header_params, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def delete_input_data(self, project_id: str, input_data_id: str, **kwargs) -> Tuple[Any, requests.Response]: @@ -732,6 +757,7 @@ def delete_input_data(self, project_id: str, input_data_id: str, **kwargs) -> Tu url_path = f"/projects/{project_id}/inputs/{input_data_id}" http_method = "DELETE" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_input_data(self, project_id: str, input_data_id: str, **kwargs) -> Tuple[Any, requests.Response]: @@ -756,6 +782,7 @@ def get_input_data(self, project_id: str, input_data_id: str, **kwargs) -> Tuple url_path = f"/projects/{project_id}/inputs/{input_data_id}" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_input_data_list( @@ -792,6 +819,7 @@ def get_input_data_list( keyword_params: Dict[str, Any] = { "query_params": query_params, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def put_input_data( @@ -822,6 +850,7 @@ def put_input_data( keyword_params: Dict[str, Any] = { "request_body": request_body, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) ######################################### @@ -858,6 +887,7 @@ def batch_update_inspections( keyword_params: Dict[str, Any] = { "request_body": request_body, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_inspections( @@ -885,6 +915,7 @@ def get_inspections( url_path = f"/projects/{project_id}/tasks/{task_id}/inputs/{input_data_id}/inspections" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) ######################################### @@ -914,6 +945,7 @@ def delete_instruction_image(self, project_id: str, image_id: str, **kwargs) -> url_path = f"/projects/{project_id}/instruction-images/{image_id}" http_method = "DELETE" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_instruction( @@ -943,6 +975,7 @@ def get_instruction( keyword_params: Dict[str, Any] = { "query_params": query_params, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_instruction_history( @@ -972,6 +1005,7 @@ def get_instruction_history( keyword_params: Dict[str, Any] = { "query_params": query_params, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_instruction_image_url_for_put( @@ -1002,6 +1036,7 @@ def get_instruction_image_url_for_put( keyword_params: Dict[str, Any] = { "header_params": header_params, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_instruction_images(self, project_id: str, **kwargs) -> Tuple[Any, requests.Response]: @@ -1025,6 +1060,7 @@ def get_instruction_images(self, project_id: str, **kwargs) -> Tuple[Any, reques url_path = f"/projects/{project_id}/instruction-images" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def put_instruction( @@ -1054,6 +1090,7 @@ def put_instruction( keyword_params: Dict[str, Any] = { "request_body": request_body, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) ######################################### @@ -1086,6 +1123,7 @@ def delete_project_job( url_path = f"/projects/{project_id}/jobs/{job_type}/{job_id}" http_method = "DELETE" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_organization_job( @@ -1118,6 +1156,7 @@ def get_organization_job( keyword_params: Dict[str, Any] = { "query_params": query_params, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_project_job( @@ -1150,6 +1189,7 @@ def get_project_job( keyword_params: Dict[str, Any] = { "query_params": query_params, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) ######################################### @@ -1181,6 +1221,7 @@ def confirm_my_account_delete(self, request_body: Optional[Any] = None, **kwargs keyword_params: Dict[str, Any] = { "request_body": request_body, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_my_account(self, **kwargs) -> Tuple[Any, requests.Response]: @@ -1203,6 +1244,7 @@ def get_my_account(self, **kwargs) -> Tuple[Any, requests.Response]: url_path = f"/my/account" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_my_member_in_project(self, project_id: str, **kwargs) -> Tuple[Any, requests.Response]: @@ -1226,6 +1268,7 @@ def get_my_member_in_project(self, project_id: str, **kwargs) -> Tuple[Any, requ url_path = f"/my/projects/{project_id}/member" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_my_organizations( @@ -1255,6 +1298,7 @@ def get_my_organizations( keyword_params: Dict[str, Any] = { "query_params": query_params, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_my_project_members(self, **kwargs) -> Tuple[Any, requests.Response]: @@ -1277,6 +1321,7 @@ def get_my_project_members(self, **kwargs) -> Tuple[Any, requests.Response]: url_path = f"/my/project-members" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_my_projects(self, query_params: Optional[Dict[str, Any]] = None, **kwargs) -> Tuple[Any, requests.Response]: @@ -1309,6 +1354,7 @@ def get_my_projects(self, query_params: Optional[Dict[str, Any]] = None, **kwarg keyword_params: Dict[str, Any] = { "query_params": query_params, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def initiate_my_account_delete(self, **kwargs) -> Tuple[Any, requests.Response]: @@ -1331,6 +1377,7 @@ def initiate_my_account_delete(self, **kwargs) -> Tuple[Any, requests.Response]: url_path = f"/my/account/delete-request" http_method = "POST" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def put_my_account(self, request_body: Optional[Any] = None, **kwargs) -> Tuple[Any, requests.Response]: @@ -1357,6 +1404,7 @@ def put_my_account(self, request_body: Optional[Any] = None, **kwargs) -> Tuple[ keyword_params: Dict[str, Any] = { "request_body": request_body, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def update_organization(self, request_body: Optional[Any] = None, **kwargs) -> Tuple[Any, requests.Response]: @@ -1383,6 +1431,7 @@ def update_organization(self, request_body: Optional[Any] = None, **kwargs) -> T keyword_params: Dict[str, Any] = { "request_body": request_body, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) ######################################### @@ -1414,6 +1463,7 @@ def create_new_organization(self, request_body: Optional[Any] = None, **kwargs) keyword_params: Dict[str, Any] = { "request_body": request_body, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def delete_organization(self, organization_name: str, **kwargs) -> Tuple[Any, requests.Response]: @@ -1437,6 +1487,7 @@ def delete_organization(self, organization_name: str, **kwargs) -> Tuple[Any, re url_path = f"/organizations/{organization_name}" http_method = "DELETE" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_organization(self, organization_name: str, **kwargs) -> Tuple[Any, requests.Response]: @@ -1460,6 +1511,7 @@ def get_organization(self, organization_name: str, **kwargs) -> Tuple[Any, reque url_path = f"/organizations/{organization_name}" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_organization_activity(self, organization_name: str, **kwargs) -> Tuple[Any, requests.Response]: @@ -1483,6 +1535,7 @@ def get_organization_activity(self, organization_name: str, **kwargs) -> Tuple[A url_path = f"/organizations/{organization_name}/activity" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_projects_of_organization( @@ -1520,6 +1573,7 @@ def get_projects_of_organization( keyword_params: Dict[str, Any] = { "query_params": query_params, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) ######################################### @@ -1552,6 +1606,7 @@ def delete_organization_input_data( url_path = f"/organizations/{organization_name}/input_data_set/{input_data_set_id}/inputs/{input_data_id}" http_method = "DELETE" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_input_data_set( @@ -1578,6 +1633,7 @@ def get_input_data_set( url_path = f"/organizations/{organization_name}/input_data_set/{input_data_set_id}" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_input_data_set_list(self, organization_name: str, **kwargs) -> Tuple[Any, requests.Response]: @@ -1601,6 +1657,7 @@ def get_input_data_set_list(self, organization_name: str, **kwargs) -> Tuple[Any url_path = f"/organizations/{organization_name}/input_data_set" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_organization_input_data( @@ -1628,6 +1685,7 @@ def get_organization_input_data( url_path = f"/organizations/{organization_name}/input_data_set/{input_data_set_id}/inputs/{input_data_id}" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_organization_input_data_list( @@ -1665,6 +1723,7 @@ def get_organization_input_data_list( keyword_params: Dict[str, Any] = { "query_params": query_params, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def put_input_data_set( @@ -1695,6 +1754,7 @@ def put_input_data_set( keyword_params: Dict[str, Any] = { "request_body": request_body, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) ######################################### @@ -1730,6 +1790,7 @@ def accept_organization_invitation( keyword_params: Dict[str, Any] = { "request_body": request_body, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def delete_organization_member( @@ -1756,6 +1817,7 @@ def delete_organization_member( url_path = f"/organizations/{organization_name}/members/{user_id}" http_method = "DELETE" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_organization_member(self, organization_name: str, user_id: str, **kwargs) -> Tuple[Any, requests.Response]: @@ -1780,6 +1842,7 @@ def get_organization_member(self, organization_name: str, user_id: str, **kwargs url_path = f"/organizations/{organization_name}/members/{user_id}" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_organization_members(self, organization_name: str, **kwargs) -> Tuple[Any, requests.Response]: @@ -1803,6 +1866,7 @@ def get_organization_members(self, organization_name: str, **kwargs) -> Tuple[An url_path = f"/organizations/{organization_name}/members" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def invite_organization_member( @@ -1833,6 +1897,7 @@ def invite_organization_member( keyword_params: Dict[str, Any] = { "request_body": request_body, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def update_organization_member_role( @@ -1863,6 +1928,7 @@ def update_organization_member_role( keyword_params: Dict[str, Any] = { "request_body": request_body, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) ######################################### @@ -1894,6 +1960,7 @@ def delete_organization_plugin( url_path = f"/organizations/{organization_name}/plugins/{plugin_id}" http_method = "DELETE" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_organization_plugin( @@ -1920,6 +1987,7 @@ def get_organization_plugin( url_path = f"/organizations/{organization_name}/plugins/{plugin_id}" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_organization_plugins(self, organization_name: str, **kwargs) -> Tuple[Any, requests.Response]: @@ -1943,6 +2011,7 @@ def get_organization_plugins(self, organization_name: str, **kwargs) -> Tuple[An url_path = f"/organizations/{organization_name}/plugins" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def put_organization_plugin( @@ -1973,6 +2042,7 @@ def put_organization_plugin( keyword_params: Dict[str, Any] = { "request_body": request_body, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) ######################################### @@ -2001,6 +2071,7 @@ def delete_project(self, project_id: str, **kwargs) -> Tuple[Any, requests.Respo url_path = f"/projects/{project_id}" http_method = "DELETE" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_organization_of_project(self, project_id: str, **kwargs) -> Tuple[Any, requests.Response]: @@ -2024,6 +2095,7 @@ def get_organization_of_project(self, project_id: str, **kwargs) -> Tuple[Any, r url_path = f"/projects/{project_id}/organization" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_project(self, project_id: str, **kwargs) -> Tuple[Any, requests.Response]: @@ -2047,6 +2119,7 @@ def get_project(self, project_id: str, **kwargs) -> Tuple[Any, requests.Response url_path = f"/projects/{project_id}" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_project_inputs_url(self, project_id: str, **kwargs) -> Tuple[Any, requests.Response]: @@ -2070,6 +2143,7 @@ def get_project_inputs_url(self, project_id: str, **kwargs) -> Tuple[Any, reques url_path = f"/projects/{project_id}/rawdata/inputs" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_project_inspections_url(self, project_id: str, **kwargs) -> Tuple[Any, requests.Response]: @@ -2093,6 +2167,7 @@ def get_project_inspections_url(self, project_id: str, **kwargs) -> Tuple[Any, r url_path = f"/projects/{project_id}/rawdata/inspections" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_project_task_histories_url(self, project_id: str, **kwargs) -> Tuple[Any, requests.Response]: @@ -2116,6 +2191,7 @@ def get_project_task_histories_url(self, project_id: str, **kwargs) -> Tuple[Any url_path = f"/projects/{project_id}/rawdata/task_histories" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_project_task_history_events_url(self, project_id: str, **kwargs) -> Tuple[Any, requests.Response]: @@ -2145,6 +2221,7 @@ def get_project_task_history_events_url(self, project_id: str, **kwargs) -> Tupl url_path = f"/projects/{project_id}/rawdata/task_history_events" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_project_tasks_url(self, project_id: str, **kwargs) -> Tuple[Any, requests.Response]: @@ -2168,6 +2245,7 @@ def get_project_tasks_url(self, project_id: str, **kwargs) -> Tuple[Any, request url_path = f"/projects/{project_id}/rawdata/tasks" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def initiate_project_copy( @@ -2197,6 +2275,7 @@ def initiate_project_copy( keyword_params: Dict[str, Any] = { "request_body": request_body, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def post_project_inputs_update(self, project_id: str, **kwargs) -> Tuple[Any, requests.Response]: @@ -2220,6 +2299,7 @@ def post_project_inputs_update(self, project_id: str, **kwargs) -> Tuple[Any, re url_path = f"/projects/{project_id}/rawdata/inputs" http_method = "POST" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def post_project_tasks_update(self, project_id: str, **kwargs) -> Tuple[Any, requests.Response]: @@ -2243,6 +2323,7 @@ def post_project_tasks_update(self, project_id: str, **kwargs) -> Tuple[Any, req url_path = f"/projects/{project_id}/rawdata/tasks" http_method = "POST" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def put_project( @@ -2259,7 +2340,7 @@ def put_project( authorizations: OrganizationAdministrator, ProjectOwner - プロジェクトを新規作成または更新します。 ### 新規作成する場合 ユーザーは、作成するプロジェクトをひもづける組織の [OrganizationAdministrator](#section/Authentication/OrganizationAdministrator) である必要があります。 ### 更新する場合 ユーザーは、更新するプロジェクトの [ProjectOwner](#section/Authentication/ProjectOwner) である必要があります。 また所属組織を変更する場合は、新しくひもづける組織の [OrganizationAdministrator](#section/Authentication/OrganizationAdministrator) である必要があります。 なお、プロジェクト状態を「停止中」にした場合、アノテーションZIPやタスク進捗状況などの集計情報は自動更新されなくなります。 所属組織が変更された場合バックグラウンドジョブが登録されます。ジョブは [getProjectJob](#operation/getProjectJob) APIで確認できます(ジョブ種別は`move-project`)。 + プロジェクトを新規作成または更新します。 ### 新規作成する場合 ユーザーは、作成するプロジェクトをひもづける組織の [OrganizationAdministrator](#section/Authentication/OrganizationAdministrator) である必要があります。 ### 更新する場合 ユーザーは、更新するプロジェクトの [ProjectOwner](#section/Authentication/ProjectOwner) である必要があります。 また所属組織を変更する場合は、新しくひもづける組織の [OrganizationAdministrator](#section/Authentication/OrganizationAdministrator) である必要があります。 なお、プロジェクト状態を「停止中」にした場合、アノテーションZIPやタスク進捗状況などの集計情報は自動更新されなくなります。 所属組織が変更された場合バックグラウンドジョブが登録されます。ジョブは [getProjectJob](#operation/getProjectJob) APIで確認できます(ジョブ種別は`move-project`)。 制限事項として、このAPIでプロジェクト状態を「初期化中」へ変更することはできません。 また、プロジェクト状態が「初期化中」の場合は、所属組織変更をおこなうことはできません。 Args: project_id (str): プロジェクトID。[値の制約についてはこちら。](#section/API-Convention/APIID) (required) @@ -2279,6 +2360,7 @@ def put_project( "query_params": query_params, "request_body": request_body, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) ######################################### @@ -2308,6 +2390,7 @@ def get_project_member(self, project_id: str, user_id: str, **kwargs) -> Tuple[A url_path = f"/projects/{project_id}/members/{user_id}" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_project_members( @@ -2337,6 +2420,7 @@ def get_project_members( keyword_params: Dict[str, Any] = { "query_params": query_params, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def put_project_member( @@ -2367,6 +2451,7 @@ def put_project_member( keyword_params: Dict[str, Any] = { "request_body": request_body, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) ######################################### @@ -2402,6 +2487,7 @@ def get_account_daily_statistics( keyword_params: Dict[str, Any] = { "query_params": query_params, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_account_statistics(self, project_id: str, **kwargs) -> Tuple[Any, requests.Response]: @@ -2431,6 +2517,7 @@ def get_account_statistics(self, project_id: str, **kwargs) -> Tuple[Any, reques url_path = f"/projects/{project_id}/statistics/accounts" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_inspection_daily_statistics( @@ -2461,6 +2548,7 @@ def get_inspection_daily_statistics( keyword_params: Dict[str, Any] = { "query_params": query_params, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_inspection_statistics(self, project_id: str, **kwargs) -> Tuple[Any, requests.Response]: @@ -2490,6 +2578,7 @@ def get_inspection_statistics(self, project_id: str, **kwargs) -> Tuple[Any, req url_path = f"/projects/{project_id}/statistics/inspections" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_label_statistics(self, project_id: str, **kwargs) -> Tuple[Any, requests.Response]: @@ -2513,6 +2602,7 @@ def get_label_statistics(self, project_id: str, **kwargs) -> Tuple[Any, requests url_path = f"/projects/{project_id}/statistics/labels" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_markers(self, project_id: str, **kwargs) -> Tuple[Any, requests.Response]: @@ -2536,6 +2626,7 @@ def get_markers(self, project_id: str, **kwargs) -> Tuple[Any, requests.Response url_path = f"/projects/{project_id}/statistics/markers" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_phase_daily_statistics( @@ -2566,6 +2657,7 @@ def get_phase_daily_statistics( keyword_params: Dict[str, Any] = { "query_params": query_params, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_statistics_available_dates(self, project_id: str, **kwargs) -> Tuple[Any, requests.Response]: @@ -2589,6 +2681,7 @@ def get_statistics_available_dates(self, project_id: str, **kwargs) -> Tuple[Any url_path = f"/projects/{project_id}/statistics/dates" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_task_daily_statistics( @@ -2619,6 +2712,7 @@ def get_task_daily_statistics( keyword_params: Dict[str, Any] = { "query_params": query_params, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_task_phase_statistics(self, project_id: str, **kwargs) -> Tuple[Any, requests.Response]: @@ -2648,6 +2742,7 @@ def get_task_phase_statistics(self, project_id: str, **kwargs) -> Tuple[Any, req url_path = f"/projects/{project_id}/statistics/task-phases" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_task_statistics(self, project_id: str, **kwargs) -> Tuple[Any, requests.Response]: @@ -2677,6 +2772,7 @@ def get_task_statistics(self, project_id: str, **kwargs) -> Tuple[Any, requests. url_path = f"/projects/{project_id}/statistics/tasks" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_worktime_daily_statistics( @@ -2707,6 +2803,7 @@ def get_worktime_daily_statistics( keyword_params: Dict[str, Any] = { "query_params": query_params, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_worktime_daily_statistics_by_account( @@ -2738,6 +2835,7 @@ def get_worktime_daily_statistics_by_account( keyword_params: Dict[str, Any] = { "query_params": query_params, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_worktime_statistics(self, project_id: str, **kwargs) -> Tuple[Any, requests.Response]: @@ -2767,6 +2865,7 @@ def get_worktime_statistics(self, project_id: str, **kwargs) -> Tuple[Any, reque url_path = f"/projects/{project_id}/statistics/worktimes" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def put_markers( @@ -2796,6 +2895,7 @@ def put_markers( keyword_params: Dict[str, Any] = { "request_body": request_body, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) ######################################### @@ -2828,6 +2928,7 @@ def delete_supplementary_data( url_path = f"/projects/{project_id}/inputs/{input_data_id}/supplementary-data/{supplementary_data_id}" http_method = "DELETE" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_supplementary_data_list( @@ -2854,6 +2955,7 @@ def get_supplementary_data_list( url_path = f"/projects/{project_id}/inputs/{input_data_id}/supplementary-data" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def put_supplementary_data( @@ -2890,6 +2992,7 @@ def put_supplementary_data( keyword_params: Dict[str, Any] = { "request_body": request_body, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) ######################################### @@ -2924,6 +3027,7 @@ def assign_tasks( keyword_params: Dict[str, Any] = { "request_body": request_body, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def batch_update_tasks( @@ -2953,6 +3057,7 @@ def batch_update_tasks( keyword_params: Dict[str, Any] = { "request_body": request_body, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def delete_task(self, project_id: str, task_id: str, **kwargs) -> Tuple[Any, requests.Response]: @@ -2977,6 +3082,7 @@ def delete_task(self, project_id: str, task_id: str, **kwargs) -> Tuple[Any, req url_path = f"/projects/{project_id}/tasks/{task_id}" http_method = "DELETE" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_task(self, project_id: str, task_id: str, **kwargs) -> Tuple[Any, requests.Response]: @@ -3001,6 +3107,7 @@ def get_task(self, project_id: str, task_id: str, **kwargs) -> Tuple[Any, reques url_path = f"/projects/{project_id}/tasks/{task_id}" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_task_histories(self, project_id: str, task_id: str, **kwargs) -> Tuple[Any, requests.Response]: @@ -3025,6 +3132,7 @@ def get_task_histories(self, project_id: str, task_id: str, **kwargs) -> Tuple[A url_path = f"/projects/{project_id}/tasks/{task_id}/histories" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_tasks( @@ -3070,6 +3178,7 @@ def get_tasks( keyword_params: Dict[str, Any] = { "query_params": query_params, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def initiate_tasks_generation( @@ -3099,6 +3208,7 @@ def initiate_tasks_generation( keyword_params: Dict[str, Any] = { "request_body": request_body, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def operate_task( @@ -3129,6 +3239,7 @@ def operate_task( keyword_params: Dict[str, Any] = { "request_body": request_body, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def patch_tasks_metadata( @@ -3158,6 +3269,7 @@ def patch_tasks_metadata( keyword_params: Dict[str, Any] = { "request_body": request_body, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def put_task( @@ -3188,6 +3300,7 @@ def put_task( keyword_params: Dict[str, Any] = { "request_body": request_body, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) ######################################### @@ -3217,6 +3330,7 @@ def delete_webhook(self, project_id: str, webhook_id: str, **kwargs) -> Tuple[An url_path = f"/projects/{project_id}/webhooks/{webhook_id}" http_method = "DELETE" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_webhooks(self, project_id: str, **kwargs) -> Tuple[Any, requests.Response]: @@ -3240,6 +3354,7 @@ def get_webhooks(self, project_id: str, **kwargs) -> Tuple[Any, requests.Respons url_path = f"/projects/{project_id}/webhooks" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def put_webhook( @@ -3270,6 +3385,7 @@ def put_webhook( keyword_params: Dict[str, Any] = { "request_body": request_body, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def test_webhook( @@ -3300,4 +3416,5 @@ def test_webhook( keyword_params: Dict[str, Any] = { "request_body": request_body, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) diff --git a/annofabapi/generated_api2.py b/annofabapi/generated_api2.py index ea3b295e..eddb010a 100644 --- a/annofabapi/generated_api2.py +++ b/annofabapi/generated_api2.py @@ -67,6 +67,7 @@ def get_annotation_specs_v2( keyword_params: Dict[str, Any] = { "query_params": query_params, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) ######################################### @@ -102,6 +103,7 @@ def get_organization_member_v2( keyword_params: Dict[str, Any] = { "query_params": query_params, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_organization_members_v2( @@ -131,6 +133,7 @@ def get_organization_members_v2( keyword_params: Dict[str, Any] = { "query_params": query_params, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) ######################################### @@ -165,6 +168,7 @@ def get_organization_by_name_v2( keyword_params: Dict[str, Any] = { "query_params": query_params, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_organization_cache_by_name_v2(self, organization_name: str, **kwargs) -> Tuple[Any, requests.Response]: @@ -188,6 +192,7 @@ def get_organization_cache_by_name_v2(self, organization_name: str, **kwargs) -> url_path = f"/organizations-by-name/{organization_name}/cache" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_organization_cache_v2(self, organization_id: str, **kwargs) -> Tuple[Any, requests.Response]: @@ -211,6 +216,7 @@ def get_organization_cache_v2(self, organization_id: str, **kwargs) -> Tuple[Any url_path = f"/organizations/{organization_id}/cache" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_organization_v2( @@ -240,6 +246,7 @@ def get_organization_v2( keyword_params: Dict[str, Any] = { "query_params": query_params, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_project_task_count_v2( @@ -266,6 +273,7 @@ def get_project_task_count_v2( url_path = f"/organizations/{organization_id}/projects/{project_id}/task-counts" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) ######################################### @@ -301,6 +309,7 @@ def get_project_member_v2( keyword_params: Dict[str, Any] = { "query_params": query_params, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_project_members_v2( @@ -331,6 +340,7 @@ def get_project_members_v2( keyword_params: Dict[str, Any] = { "query_params": query_params, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) ######################################### @@ -359,6 +369,7 @@ def get_project_cache_v2(self, project_id: str, **kwargs) -> Tuple[Any, requests url_path = f"/projects/{project_id}/cache" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) ######################################### @@ -393,6 +404,7 @@ def get_account_statistics_v2( keyword_params: Dict[str, Any] = { "query_params": query_params, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_inspection_statistics_v2( @@ -422,6 +434,7 @@ def get_inspection_statistics_v2( keyword_params: Dict[str, Any] = { "query_params": query_params, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_label_statistics_v2( @@ -451,6 +464,7 @@ def get_label_statistics_v2( keyword_params: Dict[str, Any] = { "query_params": query_params, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_task_phase_statistics_v2( @@ -480,6 +494,7 @@ def get_task_phase_statistics_v2( keyword_params: Dict[str, Any] = { "query_params": query_params, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_task_statistics_v2( @@ -509,6 +524,7 @@ def get_task_statistics_v2( keyword_params: Dict[str, Any] = { "query_params": query_params, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_worktime_statistics_v2( @@ -538,6 +554,7 @@ def get_worktime_statistics_v2( keyword_params: Dict[str, Any] = { "query_params": query_params, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) ######################################### @@ -572,6 +589,7 @@ def get_account_v2( keyword_params: Dict[str, Any] = { "query_params": query_params, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_user_cache_v2(self, account_id: str, **kwargs) -> Tuple[Any, requests.Response]: @@ -595,6 +613,7 @@ def get_user_cache_v2(self, account_id: str, **kwargs) -> Tuple[Any, requests.Re url_path = f"/users/{account_id}/cache" http_method = "GET" keyword_params: Dict[str, Any] = {} + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_user_organizations_v2( @@ -624,6 +643,7 @@ def get_user_organizations_v2( keyword_params: Dict[str, Any] = { "query_params": query_params, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_user_project_members_v2( @@ -653,6 +673,7 @@ def get_user_project_members_v2( keyword_params: Dict[str, Any] = { "query_params": query_params, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) def get_user_projects_v2( @@ -689,4 +710,5 @@ def get_user_projects_v2( keyword_params: Dict[str, Any] = { "query_params": query_params, } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) diff --git a/annofabapi/models.py b/annofabapi/models.py index 3d85ecbc..8fd9e05c 100644 --- a/annofabapi/models.py +++ b/annofabapi/models.py @@ -3351,7 +3351,7 @@ class PricePlan(Enum): class ProjectJobType(Enum): """ - * `copy-project` - プロジェクトのコピー。[initiateProjectCopy](#operation/initiateProjectCopy) APIを実行したときに登録されるジョブ。 * `gen-inputs` - zipファイルから入力データの作成。[putInputData](#operation/putInputData) APIを実行して、zipファイルから入力データを作成したときに登録されるジョブ。 * `gen-tasks` - タスクの一括作成。[initiateTasksGeneration](#operation/initiateTasksGeneration) APIを実行したときに登録されるジョブ。 * `gen-annotation` - アノテーションZIPの更新。[postAnnotationArchiveUpdate](#operation/postAnnotationArchiveUpdate) APIを実行したときに登録されるジョブ。 * `gen-tasks-list` - タスク全件ファイルの更新。[postProjectTasksUpdate](#operation/postProjectTasksUpdate) APIを実行したときに登録されるジョブ。 * `gen-inputs-list` - 入力データ情報全件ファイルの更新。[postProjectInputsUpdate](#operation/postProjectInputsUpdate) APIを実行したときに登録されるジョブ。 * `delete-project` - プロジェクトの削除。[deleteProject](#operation/deleteProject) APIを実行したときに登録されるジョブ。 * `invoke-hook` - Webhookの起動。 * `move-project` - プロジェクトの組織移動。[putProject](#operation/putProject) API で組織を変更したときに登録されるジョブ。 ## ジョブの同時実行制限 AnnoFab上に登録されているデータの整合性を保つため、プロジェクト内で特定のジョブが実行中の間は他のジョブが実行できないよう制限をかけています。 ジョブの同時実行可否はジョブの種別によって異なります。 ### copy-project 次のジョブが実行されている場合、このジョブを実行することはできません。 * `gen-inputs` * `gen-tasks` * `delete-project` * `move-project` ### gen-inputs 次のジョブが実行されている場合、このジョブを実行することはできません。 * `copy-project` * `gen-inputs` * `gen-tasks` * `gen-inputs-list` * `delete-project` * `move-project` ### gen-tasks 次のジョブが実行されている場合、このジョブを実行することはできません。 * `copy-project` * `gen-inputs` * `gen-tasks` * `gen-annotation` * `gen-tasks-list` * `delete-project` * `move-project` ### gen-annotation 次のジョブが実行されている場合、このジョブを実行することはできません。 * `gen-tasks` * `gen-annotation` * `delete-project` * `move-project` ### gen-tasks-list 次のジョブが実行されている場合、このジョブを実行することはできません。 * `gen-tasks` * `gen-tasks-list` * `delete-project` * `move-project` ### gen-inputs-list 次のジョブが実行されている場合、このジョブを実行することはできません。 * `gen-inputs` * `gen-inputs-list` * `delete-project` * `move-project` ### delete-project 他のジョブが実行されていない場合**のみ**実行できます。 ### invoke-hook 次のジョブが実行されている場合、このジョブを実行することはできません。 * `delete-project` * `move-project` ### move-project 他のジョブが実行されていない場合**のみ**実行できます。 + * `copy-project` - プロジェクトのコピー。[initiateProjectCopy](#operation/initiateProjectCopy) APIを実行したときに登録されるジョブ。 * `gen-inputs` - zipファイルから入力データの作成。[putInputData](#operation/putInputData) APIを実行して、zipファイルから入力データを作成したときに登録されるジョブ。 * `gen-tasks` - タスクの一括作成。[initiateTasksGeneration](#operation/initiateTasksGeneration) APIを実行したときに登録されるジョブ。 * `gen-annotation` - アノテーションZIPの更新。[postAnnotationArchiveUpdate](#operation/postAnnotationArchiveUpdate) APIを実行したときに登録されるジョブ。 * `gen-tasks-list` - タスク全件ファイルの更新。[postProjectTasksUpdate](#operation/postProjectTasksUpdate) APIを実行したときに登録されるジョブ。 * `gen-inputs-list` - 入力データ情報全件ファイルの更新。[postProjectInputsUpdate](#operation/postProjectInputsUpdate) APIを実行したときに登録されるジョブ。 * `delete-project` - プロジェクトの削除。[deleteProject](#operation/deleteProject) APIを実行したときに登録されるジョブ。 * `invoke-hook` - Webhookの起動。 * `move-project` - プロジェクトの組織移動。[putProject](#operation/putProject) API で組織を変更したときに登録されるジョブ。 ## ジョブの同時実行制限 AnnoFab上に登録されているデータの整合性を保つため、プロジェクト内で特定のジョブが実行中の間は他のジョブが実行できないよう制限をかけています。 ジョブの同時実行可否はジョブの種別によって異なります。 なお、ジョブを実行するプロジェクトが初期化中 (`project_status = \"initializing\"`) の場合は、どのジョブも実行できません。 ### copy-project 次のジョブが実行されている場合、このジョブを実行することはできません。 * `gen-inputs` * `gen-tasks` * `delete-project` * `move-project` ### gen-inputs 次のジョブが実行されている場合、このジョブを実行することはできません。 * `copy-project` * `gen-inputs` * `gen-tasks` * `gen-inputs-list` * `delete-project` * `move-project` ### gen-tasks 次のジョブが実行されている場合、このジョブを実行することはできません。 * `copy-project` * `gen-inputs` * `gen-tasks` * `gen-annotation` * `gen-tasks-list` * `delete-project` * `move-project` ### gen-annotation 次のジョブが実行されている場合、このジョブを実行することはできません。 * `gen-tasks` * `gen-annotation` * `delete-project` * `move-project` ### gen-tasks-list 次のジョブが実行されている場合、このジョブを実行することはできません。 * `gen-tasks` * `gen-tasks-list` * `delete-project` * `move-project` ### gen-inputs-list 次のジョブが実行されている場合、このジョブを実行することはできません。 * `gen-inputs` * `gen-inputs-list` * `delete-project` * `move-project` ### delete-project 他のジョブが実行されていない場合**のみ**実行できます。 ### invoke-hook 次のジョブが実行されている場合、このジョブを実行することはできません。 * `delete-project` * `move-project` ### move-project 他のジョブが実行されていない場合**のみ**実行できます。 """ COPY_PROJECT = "copy-project" @@ -3478,7 +3478,7 @@ class ProjectMemberStatus(Enum): class ProjectStatus(Enum): """ - プロジェクトの状態 * `active` - プロジェクトが進行中 * `suspended` - プロジェクトが停止中 + プロジェクトの状態 * `active` - プロジェクトが進行中 * `suspended` - プロジェクトが停止中 * `initializing` - プロジェクトが初期化中 """ ACTIVE = "active" diff --git a/annofabapi/resource.py b/annofabapi/resource.py index c3e838ea..00321dad 100644 --- a/annofabapi/resource.py +++ b/annofabapi/resource.py @@ -32,7 +32,7 @@ def __init__(self, login_user_id: str, login_password: str, endpoint_url: str = #: AnnofabApi2 Instance self.api2 = AnnofabApi2(self.api) - logger.debug( + logger.info( "Create annofabapi resource instance :: %s", {"login_user_id": login_user_id, "endpoint_url": endpoint_url} ) diff --git a/annofabapi/utils.py b/annofabapi/utils.py index a531dbdf..4801391f 100644 --- a/annofabapi/utils.py +++ b/annofabapi/utils.py @@ -1,108 +1,13 @@ -import copy import datetime -import json import logging -from functools import wraps -from pathlib import Path -from typing import Any, Dict, List, Optional, Union +from typing import List, Optional -import backoff import dateutil import dateutil.tz -import requests -from requests.structures import CaseInsensitiveDict from annofabapi.models import Task, TaskHistory, TaskHistoryShort, TaskPhase -######################################### -# Private Method -######################################### - - -def _raise_for_status(response: requests.Response) -> None: - """ - HTTP Status CodeがErrorの場合、``requests.exceptions.HTTPError`` を発生させる。 - そのとき ``response.text`` もHTTPErrorに加えて、HTTPError発生時にエラーの原因が分かるようにする。 - - - Args: - response: Response - - Raises: - requests.exceptions.HTTPError: - - """ - try: - response.raise_for_status() - except requests.exceptions.HTTPError as e: - http_error_msg = f"{e.args[0]} , {response.text}" - e.args = (http_error_msg,) - raise e - - -def _log_error_response(arg_logger: logging.Logger, response: requests.Response) -> None: - """ - HTTP Statusが400以上ならば、loggerにresponse/request情報を出力する - - - Args: - arg_logger: logger - response: Response - - """ - RequestBodyHeader = Union[Dict[str, Any], CaseInsensitiveDict] - - def mask_key(d: RequestBodyHeader, key: str) -> RequestBodyHeader: - if key in d: - d[key] = "***" - return d - - def mask_password(d: RequestBodyHeader) -> RequestBodyHeader: - d = mask_key(d, "password") - d = mask_key(d, "old_password") - d = mask_key(d, "new_password") - return d - - if 400 <= response.status_code < 600: - headers = copy.deepcopy(response.request.headers) - - arg_logger.debug("status_code = %s, response.text = %s", response.status_code, response.text) - arg_logger.debug("request.url = %s %s", response.request.method, response.request.url) - - # logにAuthorizationを出力しないようにマスクする - mask_key(headers, "Authorization") - arg_logger.debug("request.headers = %s", headers) - - # request_bodyのpassword関係をマスクして、logに出力する - if response.request.body is None or response.request.body == "": - dict_request_body = {} - else: - dict_request_body = json.loads(response.request.body) - - arg_logger.debug("request.body = %s", mask_password(dict_request_body)) - - -def _download(url: str, dest_path: str) -> requests.Response: - """ - HTTP GETで取得した内容をファイルに保存する(ダウンロードする) - - - Args: - url: ダウンロード対象のURL - dest_path: 保存先ファイルのパス - - Returns: - URLにアクセスしたときのResponse情報 - - """ - response = requests.get(url) - _raise_for_status(response) - - p = Path(dest_path) - p.parent.mkdir(parents=True, exist_ok=True) - with open(dest_path, "wb") as f: - f.write(response.content) - return response +logger = logging.getLogger(__name__) ######################################### @@ -254,93 +159,3 @@ def can_put_annotation(task: Task, my_account_id: str) -> bool: """ # ログインユーザはプロジェクトオーナであること前提 return len(task["histories_by_phase"]) == 0 or task["account_id"] == my_account_id - - -######################################### -# Public Method: Decorator -######################################### - - -def my_backoff(function): - """ - HTTP Status Codeが429 or 5XXのときはリトライする. 最大5分間リトライする。 - """ - - @wraps(function) - def wrapped(*args, **kwargs): - def fatal_code(e): - """ - リトライするかどうか - status codeが5xxのとき、またはToo many Requests(429)のときはリトライする。429以外の4XXはリトライしない - https://requests.kennethreitz.org/en/master/user/quickstart/#errors-and-exceptions - - Args: - e: exception - - Returns: - True: give up(リトライしない), False: リトライする - - """ - if isinstance(e, requests.exceptions.HTTPError): - if e.response is None: - return True - code = e.response.status_code - return 400 <= code < 500 and code != 429 - - else: - # リトライする - return False - - return backoff.on_exception( - backoff.expo, - (requests.exceptions.RequestException, ConnectionError), - jitter=backoff.full_jitter, - max_time=300, - giveup=fatal_code, - )(function)(*args, **kwargs) - - return wrapped - - -def ignore_http_error(status_code_list: List[int]): - """ - HTTPErrorが発生したとき、特定のstatus codeを無視して処理するデコレータ。 - - Args: - status_code_list: 無視するhttp status codeのList - - """ - - def decorator(function): - @wraps(function) - def wrapped(*args, **kwargs): - annofabapi_logger_level = logging.getLogger("annofabapi").level - backoff_logger_level = logging.getLogger("backoff").level - - try: - # 不要なログが出力されないようにする - logging.getLogger("annofabapi").setLevel(level=logging.INFO) - logging.getLogger("backoff").setLevel(level=logging.CRITICAL) - - return function(*args, **kwargs) - - except requests.exceptions.HTTPError as e: - if e.response.status_code in status_code_list: - return None - else: - raise e - finally: - # ロガーの設定を元に戻す - logging.getLogger("annofabapi").setLevel(level=annofabapi_logger_level) - logging.getLogger("backoff").setLevel(level=backoff_logger_level) - - return wrapped - - return decorator - - -allow_404_error = ignore_http_error(status_code_list=[requests.codes.not_found]) -""" -Not Found Error(404)を無視して処理するデコレータ。 -リソースの存在確認などに利用する。 -""" diff --git a/annofabapi/wrapper.py b/annofabapi/wrapper.py index 40109f71..e4401545 100644 --- a/annofabapi/wrapper.py +++ b/annofabapi/wrapper.py @@ -20,6 +20,7 @@ from dateutil.relativedelta import relativedelta from annofabapi import AnnofabApi +from annofabapi.api import _log_error_response, _raise_for_status from annofabapi.exceptions import AnnofabApiException, CheckSumError from annofabapi.models import ( AdditionalData, @@ -47,7 +48,7 @@ TaskStatus, ) from annofabapi.parser import SimpleAnnotationDirParser, SimpleAnnotationParser -from annofabapi.utils import _download, _log_error_response, _raise_for_status, allow_404_error, my_backoff, str_now +from annofabapi.utils import str_now logger = logging.getLogger(__name__) @@ -171,7 +172,7 @@ def _get_all_objects(func_get_list: Callable, limit: int, **kwargs_for_func_get_ """ get_all_XXX関数の共通処理 - Args:c + Args: func_get_list: AnnofabApiのget_XXX関数 limit: 1ページあたりの取得するデータ件数 **kwargs_for_func_get_list: `func_get_list`に渡す引数。 @@ -189,9 +190,8 @@ def _get_all_objects(func_get_list: Callable, limit: int, **kwargs_for_func_get_ kwargs_for_func_get_list["query_params"] = copied_query_params content, _ = func_get_list(**kwargs_for_func_get_list) - logger.debug("%s %d 件 取得します。", func_get_list.__name__, content.get("total_count")) - if content.get("over_limit"): - logger.warning("検索結果が10,000件を超えてますが、Web APIの都合上10,000件までしか取得できません。") + if content["over_limit"]: + logger.warning("calling %s :: 検索結果が10,000件を超えています。Web APIの都合上10,000件までしか取得できません。", func_get_list.__name__) all_objects.extend(content["list"]) @@ -199,33 +199,31 @@ def _get_all_objects(func_get_list: Callable, limit: int, **kwargs_for_func_get_ next_page_no = content["page_no"] + 1 copied_query_params.update({"page": next_page_no}) kwargs_for_func_get_list["query_params"] = copied_query_params + logger.debug("calling '%s' :: %d/%d steps", func_get_list.__name__, next_page_no, content["total_page_no"]) content, _ = func_get_list(**kwargs_for_func_get_list) all_objects.extend(content["list"]) - logger.debug("%s %d / %d page", func_get_list.__name__, content["page_no"], content["total_page_no"]) return all_objects - @my_backoff - def _request_get_wrapper(self, url: str) -> requests.Response: - """ - HTTP GETのリクエスト。 - リトライするためにメソッドを切り出した。 + def _download(self, url: str, dest_path: str) -> requests.Response: """ - return self.api.session.get(url) + 指定したURLからファイルをダウンロードします。 + + Args: + url: ダウンロード対象のURL + dest_path: 保存先ファイルのパス + + Returns: + URLにアクセスしたときのResponse情報 - @my_backoff - def _request_put_wrapper( - self, - url: str, - params: Optional[Dict[str, Any]] = None, - data: Optional[Any] = None, - headers: Optional[Dict[str, Any]] = None, - ) -> requests.Response: - """ - HTTP PUTのリクエスト。 - リトライするためにメソッドを切り出した """ - return self.api.session.put(url, params=params, data=data, headers=headers) + response = self.api._execute_http_request(http_method="get", url=url) + + p = Path(dest_path) + p.parent.mkdir(parents=True, exist_ok=True) + with open(dest_path, "wb") as f: + f.write(response.content) + return response ######################################### # Public Method : Annotation @@ -242,12 +240,15 @@ def download_annotation_archive(self, project_id: str, dest_path: str) -> str: ダウンロード元のURL """ - query_params = None - _, response = self.api.get_annotation_archive(project_id, query_params=query_params) + # 2022/01時点でレスポンスのcontent-typeが"text/plain"なので、contentの型がdictにならない。したがって、Locationヘッダを参照する。 + _, response = self.api.get_annotation_archive(project_id) url = response.headers["Location"] - response2 = _download(url, dest_path) - logger.debug( - f"project_id='{project_id}', type=simple_annotation, Last-Modified={response2.headers.get('Last-Modified')}" + response2 = self._download(url, dest_path) + logger.info( + "SimpleアノテーションZIPファイルをダウンロードしました。 :: project_id='%s', Last-Modified='%s', file='%s'", + project_id, + response2.headers.get("Last-Modified"), + dest_path, ) return url @@ -270,11 +271,14 @@ def download_full_annotation_archive(self, project_id: str, dest_path: str) -> s FutureWarning, stacklevel=2, ) - _, response = self.api.get_archive_full_with_pro_id(project_id) - url = response.headers["Location"] - response2 = _download(url, dest_path) - logger.debug( - f"project_id='{project_id}', type=full_annotation, Last-Modified={response2.headers.get('Last-Modified')}" + content, _ = self.api.get_archive_full_with_pro_id(project_id) + url = content["url"] + response2 = self._download(url, dest_path) + logger.info( + "FullアノテーションZIPファイルをダウンロードしました。 :: project_id='%s', Last-Modified='%s', file='%s'", + project_id, + response2.headers.get("Last-Modified"), + dest_path, ) return url @@ -361,11 +365,10 @@ def __to_dest_annotation_detail( try: outer_file_url = detail["url"] - src_response = self._request_get_wrapper(outer_file_url) + src_response = self.api._execute_http_request("get", outer_file_url) s3_path = self.upload_data_to_s3( dest_project_id, data=src_response.content, content_type=src_response.headers["Content-Type"] ) - logger.debug("project_id='%s', %s に外部アノテーションファイルをアップロードしました。", dest_project_id, s3_path) dest_detail["path"] = s3_path dest_detail["url"] = None dest_detail["etag"] = None @@ -435,7 +438,7 @@ def copy_annotation( src_annotation_details: List[Dict[str, Any]] = src_annotation["details"] if len(src_annotation_details) == 0: - logger.debug(f"コピー元にアノテーションが1つもないため、アノテーションのコピーをスキップします。:: src='{src}'") + logger.warning("コピー元にアノテーションが1つもないため、アノテーションのコピーをスキップします。:: src='{src}'") return False old_dest_annotation, _ = self.api.get_editor_annotation(dest.project_id, dest.task_id, dest.input_data_id) @@ -496,7 +499,9 @@ def __to_additional_data_list(self, attributes: Dict[str, Any], label_info: Labe for key, value in attributes.items(): specs_additional_data = self.__get_additional_data_from_attribute_name(key, label_info) if specs_additional_data is None: - logger.warning(f"アノテーション仕様に attribute_name='{key}' の属性が存在しません。") + logger.warning( + "アノテーション仕様の '%s' ラベルに、attribute_name='%s' である属性が存在しません。", self.__get_label_name_en(label_info), key + ) continue additional_data = dict( @@ -524,7 +529,11 @@ def __to_additional_data_list(self, attributes: Dict[str, Any], label_info: Labe ]: additional_data["choice"] = self._get_choice_id_from_name(value, specs_additional_data["choices"]) else: - logger.warning(f"additional_data_type={additional_data_type}が不正です。") + logger.warning( + "additional_data_type='%s'が不正です。 :: additional_data_definition_id='%s'", + additional_data_type, + specs_additional_data["additional_data_definition_id"], + ) continue additional_data_list.append(additional_data) @@ -554,7 +563,7 @@ def __to_annotation_detail_for_request( """ label_info = self.__get_label_info_from_label_name(detail["label"], annotation_specs_labels) if label_info is None: - logger.warning(f"アノテーション仕様に '{detail['label']}' のラベルが存在しません。") + logger.warning("アノテーション仕様に '%s' のラベルが存在しません。 :: project_id='%s'", {detail["label"]}, project_id) return None additional_data_list: List[AdditionalData] = self.__to_additional_data_list(detail["attributes"], label_info) @@ -582,7 +591,6 @@ def __to_annotation_detail_for_request( try: s3_path = self.upload_data_to_s3(project_id, f, content_type="image/png") dest_obj["path"] = s3_path - logger.debug(f"project_id='{project_id}', {outer_file_path} をS3にアップロードしました。") except CheckSumError as e: message = ( @@ -661,7 +669,14 @@ def put_annotation_for_simple_annotation_json( details = annotation["details"] if len(details) == 0: - logger.warning(f"simple_annotation_json='{simple_annotation_json}'にアノテーション情報は記載されていなかったので、スキップします。") + logger.warning( + "simple_annotation_json='%s'にアノテーション情報は記載されていなかったので、アノテーションの登録処理をスキップします。" + " :: project_id='%s', task_id='%s', input_data_id='%s'", + simple_annotation_json, + project_id, + task_id, + input_data_id, + ) return False request_details: List[Dict[str, Any]] = [] @@ -677,7 +692,14 @@ def put_annotation_for_simple_annotation_json( if request_detail is not None: request_details.append(request_detail) if len(request_details) == 0: - logger.warning(f"simple_annotation_json='{simple_annotation_json}'に、登録できるアノテーションはなかったので、スキップします。") + logger.warning( + "simple_annotation_json='%s'に、登録できるアノテーションはなかったので、アノテーションの登録処理をスキップします。" + " :: project_id='%s', task_id='%s', input_data_id='%s'", + simple_annotation_json, + project_id, + task_id, + input_data_id, + ) return False old_annotation, _ = self.api.get_editor_annotation(project_id, task_id, input_data_id) @@ -816,7 +838,6 @@ def get_annotation_specs_relation(self, src_project_id: str, dest_project_id: st ######################################### # Public Method : Input ######################################### - @allow_404_error def get_input_data_or_none(self, project_id: str, input_data_id: str) -> Optional[InputData]: """ 入力データを取得する。存在しない場合(HTTP 404 Error)はNoneを返す。 @@ -828,8 +849,14 @@ def get_input_data_or_none(self, project_id: str, input_data_id: str) -> Optiona Returns: 入力データ """ - input_data, _ = self.api.get_input_data(project_id, input_data_id) - return input_data + content, response = self.api.get_input_data(project_id, input_data_id, raise_for_status=False) + + if response.status_code == requests.codes.not_found: + return None + else: + _log_error_response(logger, response) + _raise_for_status(response) + return content def get_all_input_data_list( self, project_id: str, query_params: Optional[Dict[str, Any]] = None @@ -914,13 +941,10 @@ def get_md5_value_from_file(fp): s3_url = content["url"].split("?")[0] # アップロード - res_put = self._request_put_wrapper( - url=s3_url, params=query_dict, data=data, headers={"content-type": content_type} + res_put = self.api._execute_http_request( + http_method="put", url=s3_url, params=query_dict, data=data, headers={"content-type": content_type} ) - _log_error_response(logger, res_put) - _raise_for_status(res_put) - # アップロードしたファイルが破損していなかをチェックする if hasattr(data, "read"): # 読み込み位置を先頭に戻す @@ -978,31 +1002,27 @@ def put_input_data_from_file( ######################################### # Public Method : Statistics ######################################### - @my_backoff - def _request_location_header_url(self, response: requests.Response) -> Optional[Any]: + def _get_statistics_content(self, content: Any, response: requests.Response) -> Optional[Any]: """ - Location headerに記載されているURLの中身を返す。 - - Args: + 統計情報webapiのレスポンス情報に格納されているURLにアクセスして、統計情報の中身を取得する。 + 統計情報webapiのレスポンス'url'にアクセスする。 response: Returns: - Location headerに記載されているURLの中身。 - レスポンスヘッダにLocationがない場合は、Noneを返す。 - + 統計情報の中身 """ - url = response.headers.get("Location") + url = content.get("url") if url is None: - # プロジェクト作成直後などが該当する - logger.warning(f"レスポンスヘッダに'Location'がありません。method={response.request.method}, url={response.request.url}") + # プロジェクト作成直後は contentの中身が空になる + logger.warning( + "レスポンスに'url'がないか、または'url'の値がnullです。 :: %s", + {"http_method": response.request.method, "url": response.request.url}, + ) return None - response = self._request_get_wrapper(url) - _log_error_response(logger, response) - + response = self.api._execute_http_request(http_method="get", url=url) response.encoding = "utf-8" - _raise_for_status(response) - # Locationヘッダに記載されているURLの中身はJSONであること前提 + # statistics系のURLLocationヘッダに記載されているURLの中身はJSONであること前提 return response.json() def get_task_statistics(self, project_id: str) -> List[Any]: @@ -1021,8 +1041,7 @@ def get_task_statistics(self, project_id: str) -> List[Any]: warnings.warn( "annofabapi.Wrapper.get_task_statistics() is deprecated and will be removed.", FutureWarning, stacklevel=2 ) - _, response = self.api.get_task_statistics(project_id) - result = self._request_location_header_url(response) + result = self._get_statistics_content(*self.api.get_task_statistics(project_id)) if result is not None: return result else: @@ -1045,8 +1064,7 @@ def get_account_statistics(self, project_id: str) -> List[Any]: FutureWarning, stacklevel=2, ) - _, response = self.api.get_account_statistics(project_id) - result = self._request_location_header_url(response) + result = self._get_statistics_content(*self.api.get_account_statistics(project_id)) if result is not None: return result else: @@ -1069,8 +1087,7 @@ def get_inspection_statistics(self, project_id: str) -> List[Any]: FutureWarning, stacklevel=2, ) - _, response = self.api.get_inspection_statistics(project_id) - result = self._request_location_header_url(response) + result = self._get_statistics_content(*self.api.get_inspection_statistics(project_id)) if result is not None: return result else: @@ -1093,8 +1110,7 @@ def get_task_phase_statistics(self, project_id: str) -> List[Any]: FutureWarning, stacklevel=2, ) - _, response = self.api.get_task_phase_statistics(project_id) - result = self._request_location_header_url(response) + result = self._get_statistics_content(*self.api.get_task_phase_statistics(project_id)) if result is not None: return result else: @@ -1110,8 +1126,7 @@ def get_label_statistics(self, project_id: str) -> List[Any]: Returns: """ - _, response = self.api.get_label_statistics(project_id) - result = self._request_location_header_url(response) + result = self._get_statistics_content(*self.api.get_label_statistics(project_id)) if result is not None: return result else: @@ -1136,8 +1151,7 @@ def get_worktime_statistics(self, project_id: str) -> List[Any]: FutureWarning, stacklevel=2, ) - _, response = self.api.get_worktime_statistics(project_id) - result = self._request_location_header_url(response) + result = self._get_statistics_content(*self.api.get_worktime_statistics(project_id)) if result is not None: return result else: @@ -1182,9 +1196,9 @@ def _get_from_and_to_date_for_statistics_webapi( """statistics webapi用に、from_date, to_dateを取得する。 Args: - project_id (str): プロジェクトID。プロジェクト作成日を取得する際に参照します。 - from_date (Optional[str]): 取得する統計の区間の開始日(YYYY-MM-DD)。Noneの場合は、プロジェクト作成日を開始日とみなします。 - to_date (Optional[str]): 取得する統計の区間の終了日(YYYY-MM-DD)。Noneの場合は、今日の日付を終了日とみなします。 + project_id (str): プロジェクトID。 + from_date (Optional[str]): 取得する統計の区間の開始日(YYYY-MM-DD)。 + to_date (Optional[str]): 取得する統計の区間の終了日(YYYY-MM-DD)。 Returns: Tuple[datetime.date, datetime.date]: [description] @@ -1195,6 +1209,14 @@ def _get_from_and_to_date_for_statistics_webapi( if to_date is None: to_date = str(datetime.datetime.today().date()) + if from_date is None or to_date is None: + dates, _ = self.api.get_statistics_available_dates(project_id) + assert len(dates) > 0 + if from_date is None: + from_date = dates[0]["from"] + if to_date is None: + to_date = dates[-1]["to"] + DATE_FORMAT = "%Y-%m-%d" dt_from_date = datetime.datetime.strptime(from_date, DATE_FORMAT).date() dt_to_date = datetime.datetime.strptime(to_date, DATE_FORMAT).date() @@ -1500,7 +1522,6 @@ def get_all_my_organizations(self) -> List[MyOrganization]: ######################################### # Public Method : Organization ######################################### - @allow_404_error def get_organization_or_none(self, organization_name: str) -> Optional[Organization]: """ 組織情報を取得する。存在しない場合(HTTP 404 Error)はNoneを返す。 @@ -1511,8 +1532,14 @@ def get_organization_or_none(self, organization_name: str) -> Optional[Organizat Returns: 組織情報 """ - content, _ = self.api.get_organization(organization_name) - return content + content, response = self.api.get_organization(organization_name, raise_for_status=False) + + if response.status_code == requests.codes.not_found: + return None + else: + _log_error_response(logger, response) + _raise_for_status(response) + return content def get_all_projects_of_organization( self, organization_name: str, query_params: Optional[Dict[str, Any]] = None @@ -1537,7 +1564,6 @@ def get_all_projects_of_organization( ######################################### # Public Method : OrganizationMember ######################################### - @allow_404_error def get_organization_member_or_none(self, organization_name: str, user_id: str) -> Optional[OrganizationMember]: """ 組織メンバを取得する。存在しない場合(HTTP 404 Error)はNoneを返す。 @@ -1549,8 +1575,13 @@ def get_organization_member_or_none(self, organization_name: str, user_id: str) Returns: 組織メンバ """ - content, _ = self.api.get_organization_member(organization_name, user_id) - return content + content, response = self.api.get_organization_member(organization_name, user_id, raise_for_status=False) + if response.status_code == requests.codes.not_found: + return None + else: + _log_error_response(logger, response) + _raise_for_status(response) + return content def get_all_organization_members(self, organization_name: str) -> List[OrganizationMember]: """ @@ -1570,7 +1601,6 @@ def get_all_organization_members(self, organization_name: str) -> List[Organizat ######################################### # Public Method : Project ######################################### - @allow_404_error def get_project_or_none(self, project_id: str) -> Optional[Project]: """ プロジェクトを取得する。存在しない場合(HTTP 404 Error)はNoneを返す。 @@ -1581,8 +1611,13 @@ def get_project_or_none(self, project_id: str) -> Optional[Project]: Returns: プロジェクト """ - content, _ = self.api.get_project(project_id) - return content + content, response = self.api.get_project(project_id, raise_for_status=False) + if response.status_code == requests.codes.not_found: + return None + else: + _log_error_response(logger, response) + _raise_for_status(response) + return content def download_project_inputs_url(self, project_id: str, dest_path: str) -> str: """ @@ -1599,9 +1634,12 @@ def download_project_inputs_url(self, project_id: str, dest_path: str) -> str: """ content, _ = self.api.get_project_inputs_url(project_id) url = content["url"] - response2 = _download(url, dest_path) - logger.debug( - f"project_id='{project_id}', type=input_data, Last-Modified={response2.headers.get('Last-Modified')}" + response2 = self._download(url, dest_path) + logger.info( + "入力データ全件ファイルをダウンロードしました。 :: project_id='%s', Last-Modified='%s', file='%s'", + project_id, + response2.headers.get("Last-Modified"), + dest_path, ) return url @@ -1621,8 +1659,13 @@ def download_project_tasks_url(self, project_id: str, dest_path: str) -> str: content, _ = self.api.get_project_tasks_url(project_id) url = content["url"] - response2 = _download(url, dest_path) - logger.debug(f"project_id='{project_id}', type=task, Last-Modified={response2.headers.get('Last-Modified')}") + response2 = self._download(url, dest_path) + logger.info( + "タスク全件ファイルをダウンロードしました。 :: project_id='%s', Last-Modified='%s', file='%s'", + project_id, + response2.headers.get("Last-Modified"), + dest_path, + ) return url def download_project_inspections_url(self, project_id: str, dest_path: str) -> str: @@ -1641,10 +1684,12 @@ def download_project_inspections_url(self, project_id: str, dest_path: str) -> s content, _ = self.api.get_project_inspections_url(project_id) url = content["url"] - response2 = _download(url, dest_path) - logger.debug( - f"project_id='{project_id}', type=inspection_comment," - f"Last-Modified={response2.headers.get('Last-Modified')}" + response2 = self._download(url, dest_path) + logger.info( + "検査コメント全件ファイルをダウンロードしました。 :: project_id='%s', Last-Modified='%s', file='%s'", + project_id, + response2.headers.get("Last-Modified"), + dest_path, ) return url @@ -1664,10 +1709,12 @@ def download_project_task_history_events_url(self, project_id: str, dest_path: s content, _ = self.api.get_project_task_history_events_url(project_id) url = content["url"] - response2 = _download(url, dest_path) - logger.debug( - f"project_id='{project_id}', type=task_history_event, " - f"Last-Modified={response2.headers.get('Last-Modified')}" + response2 = self._download(url, dest_path) + logger.info( + "タスク履歴イベント全件ファイルをダウンロードしました。 :: project_id='%s', Last-Modified='%s', file='%s'", + project_id, + response2.headers.get("Last-Modified"), + dest_path, ) return url @@ -1687,16 +1734,18 @@ def download_project_task_histories_url(self, project_id: str, dest_path: str) - content, _ = self.api.get_project_task_histories_url(project_id) url = content["url"] - response2 = _download(url, dest_path) - logger.debug( - f"project_id='{project_id}', type=task_history, Last-Modified={response2.headers.get('Last-Modified')}" + response2 = self._download(url, dest_path) + logger.info( + "タスク履歴全件ファイルをダウンロードしました。 :: project_id='%s', Last-Modified='%s', file='%s'", + project_id, + response2.headers.get("Last-Modified"), + dest_path, ) return url ######################################### # Public Method : ProjectMember ######################################### - @allow_404_error def get_project_member_or_none(self, project_id: str, user_id: str) -> Optional[ProjectMember]: """ プロジェクトメンバを取得する。存在しない場合(HTTP 404 Error)はNoneを返す。 @@ -1708,8 +1757,13 @@ def get_project_member_or_none(self, project_id: str, user_id: str) -> Optional[ Returns: プロジェクトメンバ """ - content, _ = self.api.get_project_member(project_id, user_id) - return content + content, response = self.api.get_project_member(project_id, user_id, raise_for_status=False) + if response.status_code == requests.codes.not_found: + return None + else: + _log_error_response(logger, response) + _raise_for_status(response) + return content def get_all_project_members( self, project_id: str, query_params: Optional[Dict[str, Any]] = None @@ -1755,7 +1809,6 @@ def initiate_tasks_generation_by_csv( } return self.api.initiate_tasks_generation(project_id, request_body=request_body, query_params=query_params)[0] - @allow_404_error def get_task_or_none(self, project_id: str, task_id: str) -> Optional[Task]: """ タスクを取得する。存在しない場合(HTTP 404 Error)はNoneを返す。 @@ -1767,10 +1820,15 @@ def get_task_or_none(self, project_id: str, task_id: str) -> Optional[Task]: Returns: タスク """ - content, _ = self.api.get_task(project_id, task_id) - return content + content, response = self.api.get_task(project_id, task_id, raise_for_status=False) + + if response.status_code == requests.codes.not_found: + return None + else: + _log_error_response(logger, response) + _raise_for_status(response) + return content - @allow_404_error def get_task_histories_or_none(self, project_id: str, task_id: str) -> Optional[Task]: """ タスク履歴一覧を取得する。存在しない場合(HTTP 404 Error)はNoneを返す。 @@ -1782,8 +1840,13 @@ def get_task_histories_or_none(self, project_id: str, task_id: str) -> Optional[ Returns: タスク履歴一覧 """ - content, _ = self.api.get_task_histories(project_id, task_id) - return content + content, response = self.api.get_task_histories(project_id, task_id, raise_for_status=False) + if response.status_code == requests.codes.not_found: + return None + else: + _log_error_response(logger, response) + _raise_for_status(response) + return content def get_all_tasks(self, project_id: str, query_params: Optional[Dict[str, Any]] = None) -> List[Task]: """ @@ -2079,11 +2142,9 @@ def upload_data_as_instruction_image(self, project_id: str, image_id: str, data: s3_url = content["url"].split("?")[0] # アップロード - res_put = self._request_put_wrapper( - url=s3_url, params=query_dict, data=data, headers={"content-type": content_type} + self.api._execute_http_request( + http_method="put", url=s3_url, params=query_dict, data=data, headers={"content-type": content_type} ) - _log_error_response(logger, res_put) - _raise_for_status(res_put) return content["path"] ######################################### @@ -2103,8 +2164,7 @@ def delete_all_succeeded_job(self, project_id: str, job_type: ProjectJobType) -> jobs = self.get_all_project_job(project_id, {"type": job_type.value}) deleted_jobs = [] for job in jobs: - if job["job_status"] == "succeeded": - logger.debug(f"project_id='{project_id}', job_id='{job['job_id']}'のジョブを削除します。") + if job["job_status"] == JobStatus.SUCCEEDED.value: self.api.delete_project_job(project_id, job_type=job_type.value, job_id=job["job_id"]) deleted_jobs.append(job) @@ -2224,12 +2284,12 @@ def get_job_from_job_id(arg_job_id: str) -> Optional[ProjectJobInfo]: # 初回のみ job = get_latest_job() if job is None or job["job_status"] != JobStatus.PROGRESS.value: - logger.debug("project_id='%s', job_type='%s' である進行中のジョブは存在しません。", project_id, job_type.value) + logger.info("project_id='%s', job_type='%s' である進行中のジョブは存在しません。", project_id, job_type.value) return None job_id = job["job_id"] if job is None: - logger.debug( + logger.info( "project_id='%s', job_id='%s', job_type='%s' のジョブは存在しません。", project_id, job_type.value, job_id ) return None @@ -2237,13 +2297,13 @@ def get_job_from_job_id(arg_job_id: str) -> Optional[ProjectJobInfo]: job_access_count += 1 if job["job_status"] == JobStatus.SUCCEEDED.value: - logger.debug( + logger.info( "project_id='%s', job_id='%s', job_type='%s' のジョブが成功しました。", project_id, job_id, job_type.value ) return JobStatus.SUCCEEDED elif job["job_status"] == JobStatus.FAILED.value: - logger.debug( + logger.info( "project_id='%s', job_id='%s', job_type='%s' のジョブが失敗しました。", project_id, job_id, job_type.value ) return JobStatus.FAILED @@ -2251,7 +2311,7 @@ def get_job_from_job_id(arg_job_id: str) -> Optional[ProjectJobInfo]: else: # 進行中 if job_access_count < max_job_access: - logger.debug( + logger.info( "project_id='%s', job_id='%s', job_type='%s' のジョブは進行中です。%d 秒間待ちます。", project_id, job_id, @@ -2260,7 +2320,7 @@ def get_job_from_job_id(arg_job_id: str) -> Optional[ProjectJobInfo]: ) time.sleep(job_access_interval) else: - logger.debug( + logger.info( "project_id='%s', job_id='%s', job_type='%s' のジョブは %.1f 分以上経過しても、終了しませんでした。", project_id, job["job_id"], @@ -2454,7 +2514,7 @@ def _to_new_data(labor: Dict[str, Any]) -> Dict[str, Any]: dt_new_to_date = dt_from_date + datetime.timedelta(days=diff_days // 2) dt_new_from_date = dt_new_to_date + datetime.timedelta(days=1) - logger.debug( + logger.info( f"project_id='{project_id}': 取得対象の期間が広すぎるため、データを取得できませんでした。" f"取得対象の期間を{from_date}~{dt_new_to_date.strftime(DATE_FORMAT)}, " f"{dt_new_from_date.strftime(DATE_FORMAT)}~{to_date}に分割して、再度取得します。" diff --git a/generate/swagger/swagger-api-components.yaml b/generate/swagger/swagger-api-components.yaml index 0c2b6ac8..c04be64a 100644 --- a/generate/swagger/swagger-api-components.yaml +++ b/generate/swagger/swagger-api-components.yaml @@ -1152,6 +1152,7 @@ components: プロジェクトの状態 * `active` - プロジェクトが進行中 * `suspended` - プロジェクトが停止中 + * `initializing` - プロジェクトが初期化中 AssigneeRuleOfResubmittedTask: type: string enum: @@ -1389,6 +1390,8 @@ components: ジョブの同時実行可否はジョブの種別によって異なります。 + なお、ジョブを実行するプロジェクトが初期化中 (`project_status = "initializing"`) の場合は、どのジョブも実行できません。 + ### copy-project 次のジョブが実行されている場合、このジョブを実行することはできません。 diff --git a/generate/swagger/swagger.v2.yaml b/generate/swagger/swagger.v2.yaml index 099fe736..56ed5b21 100644 --- a/generate/swagger/swagger.v2.yaml +++ b/generate/swagger/swagger.v2.yaml @@ -115,7 +115,7 @@ info: 上記例 `account_id_count` は、タスクのフィールド `account_id` でタスクを分類したところ「`account_id` が `c5eee002` であるタスクが9件、`9f110e48` であるタスクが5件、`b25dfeb3` であるタスクが1件」だったという結果を表しています。 - version: 0.128.0 + version: 0.131.0 title: AnnoFab Web API x-logo: url: "https://annofab.com/resource/images/logo_landscape.png" diff --git a/generate/swagger/swagger.yaml b/generate/swagger/swagger.yaml index e92319a2..ffd68a5d 100644 --- a/generate/swagger/swagger.yaml +++ b/generate/swagger/swagger.yaml @@ -116,7 +116,7 @@ info: 上記例 `account_id_count` は、タスクのフィールド `account_id` でタスクを分類したところ「`account_id` が `c5eee002` であるタスクが9件、`9f110e48` であるタスクが5件、`b25dfeb3` であるタスクが1件」だったという結果を表しています。 また、AggregationResultの集約の件数は、合計で10000件以下に制限されており、それを超える件数がある場合は上位10000件が取得されます。もし、省略された部分を取得したい場合は、検索条件を縛って結果に上る集約の数を減らしてください。 - version: 0.128.0 + version: 0.131.0 title: AnnoFab Web API x-logo: url: "https://annofab.com/resource/images/logo_landscape.png" @@ -2007,6 +2007,9 @@ paths: なお、プロジェクト状態を「停止中」にした場合、アノテーションZIPやタスク進捗状況などの集計情報は自動更新されなくなります。 所属組織が変更された場合バックグラウンドジョブが登録されます。ジョブは [getProjectJob](#operation/getProjectJob) APIで確認できます(ジョブ種別は`move-project`)。 + + 制限事項として、このAPIでプロジェクト状態を「初期化中」へ変更することはできません。 + また、プロジェクト状態が「初期化中」の場合は、所属組織変更をおこなうことはできません。 security: - OrganizationAdministrator: [] ProjectOwner: [] diff --git a/generate/template/api.mustache b/generate/template/api.mustache index 9b3e6a51..feebaf05 100644 --- a/generate/template/api.mustache +++ b/generate/template/api.mustache @@ -67,6 +67,7 @@ 'request_body': request_body, {{/hasBodyParam}} } + keyword_params.update(**kwargs) return self._request_wrapper(http_method, url_path, **keyword_params) {{/operation}} {{/operations}} diff --git a/poetry.lock b/poetry.lock index 5cbe9e6b..b344e42a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -16,11 +16,11 @@ python-versions = "*" [[package]] name = "astroid" -version = "2.9.0" +version = "2.9.2" description = "An abstract syntax tree for Python with inference support." category = "dev" optional = false -python-versions = "~=3.6" +python-versions = ">=3.6.2" [package.dependencies] lazy-object-proxy = ">=1.4.0" @@ -38,17 +38,17 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "attrs" -version = "21.2.0" +version = "21.4.0" description = "Classes Without Boilerplate" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] [[package]] name = "autoflake" @@ -105,7 +105,7 @@ lxml = ["lxml"] [[package]] name = "black" -version = "21.11b1" +version = "21.12b0" description = "The uncompromising code formatter." category = "dev" optional = false @@ -113,11 +113,9 @@ python-versions = ">=3.6.2" [package.dependencies] click = ">=7.1.2" -dataclasses = {version = ">=0.6", markers = "python_version < \"3.7\""} mypy-extensions = ">=0.4.3" pathspec = ">=0.9.0,<1" platformdirs = ">=2" -regex = ">=2021.4.4" tomli = ">=0.2.6,<2.0.0" typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""} typing-extensions = [ @@ -142,7 +140,7 @@ python-versions = "*" [[package]] name = "charset-normalizer" -version = "2.0.8" +version = "2.0.9" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false @@ -173,7 +171,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "coverage" -version = "6.1.2" +version = "6.2" description = "Code coverage measurement for Python" category = "dev" optional = false @@ -185,14 +183,6 @@ tomli = {version = "*", optional = true, markers = "extra == \"toml\""} [package.extras] toml = ["tomli"] -[[package]] -name = "dataclasses" -version = "0.8" -description = "A backport of the dataclasses module for Python 3.6" -category = "main" -optional = false -python-versions = ">=3.6, <3.7" - [[package]] name = "dataclasses-json" version = "0.5.6" @@ -202,7 +192,6 @@ optional = false python-versions = ">=3.6" [package.dependencies] -dataclasses = {version = "*", markers = "python_version == \"3.6\""} marshmallow = ">=3.3.0,<4.0.0" marshmallow-enum = ">=1.5.1,<2.0.0" typing-inspect = ">=0.4.0" @@ -293,26 +282,27 @@ python-versions = "*" [[package]] name = "ipython" -version = "7.16.1" +version = "7.30.1" description = "IPython: Productive Interactive Computing" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] appnope = {version = "*", markers = "sys_platform == \"darwin\""} backcall = "*" colorama = {version = "*", markers = "sys_platform == \"win32\""} decorator = "*" -jedi = ">=0.10" -pexpect = {version = "*", markers = "sys_platform != \"win32\""} +jedi = ">=0.16" +matplotlib-inline = "*" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} pickleshare = "*" prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" pygments = "*" traitlets = ">=4.2" [package.extras] -all = ["Sphinx (>=1.3)", "ipykernel", "ipyparallel", "ipywidgets", "nbconvert", "nbformat", "nose (>=0.10.1)", "notebook", "numpy (>=1.14)", "pygments", "qtconsole", "requests", "testpath"] +all = ["Sphinx (>=1.3)", "ipykernel", "ipyparallel", "ipywidgets", "nbconvert", "nbformat", "nose (>=0.10.1)", "notebook", "numpy (>=1.17)", "pygments", "qtconsole", "requests", "testpath"] doc = ["Sphinx (>=1.3)"] kernel = ["ipykernel"] nbconvert = ["nbconvert"] @@ -320,15 +310,7 @@ nbformat = ["nbformat"] notebook = ["notebook", "ipywidgets"] parallel = ["ipyparallel"] qtconsole = ["qtconsole"] -test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy (>=1.14)"] - -[[package]] -name = "ipython-genutils" -version = "0.2.0" -description = "Vestigial utilities from IPython" -category = "dev" -optional = false -python-versions = "*" +test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy (>=1.17)"] [[package]] name = "isort" @@ -375,11 +357,11 @@ i18n = ["Babel (>=2.7)"] [[package]] name = "lazy-object-proxy" -version = "1.6.0" +version = "1.7.1" description = "A fast and thorough lazy object proxy." category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +python-versions = ">=3.6" [[package]] name = "markupsafe" @@ -414,6 +396,17 @@ python-versions = "*" [package.dependencies] marshmallow = ">=2.0.0" +[[package]] +name = "matplotlib-inline" +version = "0.1.3" +description = "Inline Matplotlib backend for Jupyter" +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +traitlets = "*" + [[package]] name = "mccabe" version = "0.6.1" @@ -432,21 +425,21 @@ python-versions = ">=3.5" [[package]] name = "mypy" -version = "0.910" +version = "0.930" description = "Optional static typing for Python" category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" [package.dependencies] -mypy-extensions = ">=0.4.3,<0.5.0" -toml = "*" -typed-ast = {version = ">=1.4.0,<1.5.0", markers = "python_version < \"3.8\""} -typing-extensions = ">=3.7.4" +mypy-extensions = ">=0.4.3" +tomli = ">=1.1.0" +typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""} +typing-extensions = ">=3.10" [package.extras] dmypy = ["psutil (>=4.0)"] -python2 = ["typed-ast (>=1.4.0,<1.5.0)"] +python2 = ["typed-ast (>=1.4.0,<2)"] [[package]] name = "mypy-extensions" @@ -507,11 +500,11 @@ python-versions = "*" [[package]] name = "platformdirs" -version = "2.4.0" +version = "2.4.1" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.extras] docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] @@ -534,7 +527,7 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "prompt-toolkit" -version = "3.0.22" +version = "3.0.24" description = "Library for building powerful interactive command lines in Python" category = "dev" optional = false @@ -594,7 +587,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pygments" -version = "2.10.0" +version = "2.11.1" description = "Pygments is a syntax highlighting package written in Python." category = "dev" optional = false @@ -602,7 +595,7 @@ python-versions = ">=3.5" [[package]] name = "pylint" -version = "2.12.1" +version = "2.12.2" description = "python code static checker" category = "dev" optional = false @@ -667,11 +660,11 @@ testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtuale [[package]] name = "pytest-forked" -version = "1.3.0" +version = "1.4.0" description = "run tests in isolated forked subprocesses" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.6" [package.dependencies] py = "*" @@ -679,7 +672,7 @@ pytest = ">=3.10" [[package]] name = "pytest-xdist" -version = "2.4.0" +version = "2.5.0" description = "pytest xdist plugin for distributed testing and loop-on-failing modes" category = "dev" optional = false @@ -687,7 +680,7 @@ python-versions = ">=3.6" [package.dependencies] execnet = ">=1.1" -pytest = ">=6.0.0" +pytest = ">=6.2.0" pytest-forked = "*" [package.extras] @@ -714,17 +707,9 @@ category = "dev" optional = false python-versions = "*" -[[package]] -name = "regex" -version = "2021.11.10" -description = "Alternative regular expression module, to replace re." -category = "dev" -optional = false -python-versions = "*" - [[package]] name = "requests" -version = "2.26.0" +version = "2.27.0" description = "Python HTTP for Humans." category = "main" optional = false @@ -876,7 +861,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "tomli" -version = "1.2.2" +version = "1.2.3" description = "A lil' TOML parser" category = "dev" optional = false @@ -884,39 +869,26 @@ python-versions = ">=3.6" [[package]] name = "traitlets" -version = "4.3.3" -description = "Traitlets Python config system" +version = "5.1.1" +description = "Traitlets Python configuration system" category = "dev" optional = false -python-versions = "*" - -[package.dependencies] -decorator = "*" -ipython-genutils = "*" -six = "*" +python-versions = ">=3.7" [package.extras] -test = ["pytest", "mock"] +test = ["pytest"] [[package]] name = "typed-ast" -version = "1.4.3" +version = "1.5.1" description = "a fork of Python 2 and 3 ast modules with type comment support" category = "dev" optional = false -python-versions = "*" - -[[package]] -name = "types-dataclasses" -version = "0.6.1" -description = "Typing stubs for dataclasses" -category = "dev" -optional = false -python-versions = "*" +python-versions = ">=3.6" [[package]] name = "types-python-dateutil" -version = "2.8.3" +version = "2.8.4" description = "Typing stubs for python-dateutil" category = "dev" optional = false @@ -924,7 +896,7 @@ python-versions = "*" [[package]] name = "types-requests" -version = "2.26.1" +version = "2.26.3" description = "Typing stubs for requests" category = "dev" optional = false @@ -932,7 +904,7 @@ python-versions = "*" [[package]] name = "typing-extensions" -version = "4.0.0" +version = "4.0.1" description = "Backported and Experimental Type Hints for Python 3.6+" category = "main" optional = false @@ -981,20 +953,20 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [[package]] name = "zipp" -version = "3.6.0" +version = "3.7.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.extras] docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] [metadata] lock-version = "1.1" -python-versions = "^3.6.2" -content-hash = "0d1d206e42c00170c1b14a2cdd6b3fab4c5236ea6d0c4d6db405993f97155036" +python-versions = "^3.7" +content-hash = "199533f970de9922bc5260f775b437d30564156d16945c3e8262c388148564f1" [metadata.files] alabaster = [ @@ -1006,16 +978,16 @@ appnope = [ {file = "appnope-0.1.2.tar.gz", hash = "sha256:dd83cd4b5b460958838f6eb3000c660b1f9caf2a5b1de4264e941512f603258a"}, ] astroid = [ - {file = "astroid-2.9.0-py3-none-any.whl", hash = "sha256:776ca0b748b4ad69c00bfe0fff38fa2d21c338e12c84aa9715ee0d473c422778"}, - {file = "astroid-2.9.0.tar.gz", hash = "sha256:5939cf55de24b92bda00345d4d0659d01b3c7dafb5055165c330bc7c568ba273"}, + {file = "astroid-2.9.2-py3-none-any.whl", hash = "sha256:aa296702f1a5c3102c860de49473aaa90a7f6d221555d5cf2678940a9be32a4e"}, + {file = "astroid-2.9.2.tar.gz", hash = "sha256:72ace9c3333e274e9248168fc4f3e300da8545af1c303bd69197027f49e2bfff"}, ] atomicwrites = [ {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, ] attrs = [ - {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, - {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, + {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, + {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, ] autoflake = [ {file = "autoflake-1.4.tar.gz", hash = "sha256:61a353012cff6ab94ca062823d1fb2f692c4acda51c76ff83a8d77915fba51ea"}, @@ -1037,16 +1009,16 @@ beautifulsoup4 = [ {file = "beautifulsoup4-4.10.0.tar.gz", hash = "sha256:c23ad23c521d818955a4151a67d81580319d4bf548d3d49f4223ae041ff98891"}, ] black = [ - {file = "black-21.11b1-py3-none-any.whl", hash = "sha256:802c6c30b637b28645b7fde282ed2569c0cd777dbe493a41b6a03c1d903f99ac"}, - {file = "black-21.11b1.tar.gz", hash = "sha256:a042adbb18b3262faad5aff4e834ff186bb893f95ba3a8013f09de1e5569def2"}, + {file = "black-21.12b0-py3-none-any.whl", hash = "sha256:a615e69ae185e08fdd73e4715e260e2479c861b5740057fde6e8b4e3b7dd589f"}, + {file = "black-21.12b0.tar.gz", hash = "sha256:77b80f693a569e2e527958459634f18df9b0ba2625ba4e0c2d5da5be42e6f2b3"}, ] certifi = [ {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, ] charset-normalizer = [ - {file = "charset-normalizer-2.0.8.tar.gz", hash = "sha256:735e240d9a8506778cd7a453d97e817e536bb1fc29f4f6961ce297b9c7a917b0"}, - {file = "charset_normalizer-2.0.8-py3-none-any.whl", hash = "sha256:83fcdeb225499d6344c8f7f34684c2981270beacc32ede2e669e94f7fa544405"}, + {file = "charset-normalizer-2.0.9.tar.gz", hash = "sha256:b0b883e8e874edfdece9c28f314e3dd5badf067342e42fb162203335ae61aa2c"}, + {file = "charset_normalizer-2.0.9-py3-none-any.whl", hash = "sha256:1eecaa09422db5be9e29d7fc65664e6c33bd06f9ced7838578ba40d58bdf3721"}, ] click = [ {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, @@ -1057,57 +1029,53 @@ colorama = [ {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] coverage = [ - {file = "coverage-6.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:675adb3b3380967806b3cbb9c5b00ceb29b1c472692100a338730c1d3e59c8b9"}, - {file = "coverage-6.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95a58336aa111af54baa451c33266a8774780242cab3704b7698d5e514840758"}, - {file = "coverage-6.1.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d0a595a781f8e186580ff8e3352dd4953b1944289bec7705377c80c7e36c4d6c"}, - {file = "coverage-6.1.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d3c5f49ce6af61154060640ad3b3281dbc46e2e0ef2fe78414d7f8a324f0b649"}, - {file = "coverage-6.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:310c40bed6b626fd1f463e5a83dba19a61c4eb74e1ac0d07d454ebbdf9047e9d"}, - {file = "coverage-6.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a4d48e42e17d3de212f9af44f81ab73b9378a4b2b8413fd708d0d9023f2bbde4"}, - {file = "coverage-6.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ffa545230ca2ad921ad066bf8fd627e7be43716b6e0fcf8e32af1b8188ccb0ab"}, - {file = "coverage-6.1.2-cp310-cp310-win32.whl", hash = "sha256:cd2d11a59afa5001ff28073ceca24ae4c506da4355aba30d1e7dd2bd0d2206dc"}, - {file = "coverage-6.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:96129e41405887a53a9cc564f960d7f853cc63d178f3a182fdd302e4cab2745b"}, - {file = "coverage-6.1.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:1de9c6f5039ee2b1860b7bad2c7bc3651fbeb9368e4c4d93e98a76358cdcb052"}, - {file = "coverage-6.1.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:80cb70264e9a1d04b519cdba3cd0dc42847bf8e982a4d55c769b9b0ee7cdce1e"}, - {file = "coverage-6.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:ba6125d4e55c0b8e913dad27b22722eac7abdcb1f3eab1bd090eee9105660266"}, - {file = "coverage-6.1.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8492d37acdc07a6eac6489f6c1954026f2260a85a4c2bb1e343fe3d35f5ee21a"}, - {file = "coverage-6.1.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:66af99c7f7b64d050d37e795baadf515b4561124f25aae6e1baa482438ecc388"}, - {file = "coverage-6.1.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ebcc03e1acef4ff44f37f3c61df478d6e469a573aa688e5a162f85d7e4c3860d"}, - {file = "coverage-6.1.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98d44a8136eebbf544ad91fef5bd2b20ef0c9b459c65a833c923d9aa4546b204"}, - {file = "coverage-6.1.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:c18725f3cffe96732ef96f3de1939d81215fd6d7d64900dcc4acfe514ea4fcbf"}, - {file = "coverage-6.1.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:c8e9c4bcaaaa932be581b3d8b88b677489975f845f7714efc8cce77568b6711c"}, - {file = "coverage-6.1.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:06d009e8a29483cbc0520665bc46035ffe9ae0e7484a49f9782c2a716e37d0a0"}, - {file = "coverage-6.1.2-cp36-cp36m-win32.whl", hash = "sha256:e5432d9c329b11c27be45ee5f62cf20a33065d482c8dec1941d6670622a6fb8f"}, - {file = "coverage-6.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:82fdcb64bf08aa5db881db061d96db102c77397a570fbc112e21c48a4d9cb31b"}, - {file = "coverage-6.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:94f558f8555e79c48c422045f252ef41eb43becdd945e9c775b45ebfc0cbd78f"}, - {file = "coverage-6.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:046647b96969fda1ae0605f61288635209dd69dcd27ba3ec0bf5148bc157f954"}, - {file = "coverage-6.1.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:cc799916b618ec9fd00135e576424165691fec4f70d7dc12cfaef09268a2478c"}, - {file = "coverage-6.1.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:62646d98cf0381ffda301a816d6ac6c35fc97aa81b09c4c52d66a15c4bef9d7c"}, - {file = "coverage-6.1.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:27a3df08a855522dfef8b8635f58bab81341b2fb5f447819bc252da3aa4cf44c"}, - {file = "coverage-6.1.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:610c0ba11da8de3a753dc4b1f71894f9f9debfdde6559599f303286e70aeb0c2"}, - {file = "coverage-6.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:35b246ae3a2c042dc8f410c94bcb9754b18179cdb81ff9477a9089dbc9ecc186"}, - {file = "coverage-6.1.2-cp37-cp37m-win32.whl", hash = "sha256:0cde7d9fe2fb55ff68ebe7fb319ef188e9b88e0a3d1c9c5db7dd829cd93d2193"}, - {file = "coverage-6.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:958ac66272ff20e63d818627216e3d7412fdf68a2d25787b89a5c6f1eb7fdd93"}, - {file = "coverage-6.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a300b39c3d5905686c75a369d2a66e68fd01472ea42e16b38c948bd02b29e5bd"}, - {file = "coverage-6.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d3855d5d26292539861f5ced2ed042fc2aa33a12f80e487053aed3bcb6ced13"}, - {file = "coverage-6.1.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:586d38dfc7da4a87f5816b203ff06dd7c1bb5b16211ccaa0e9788a8da2b93696"}, - {file = "coverage-6.1.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a34fccb45f7b2d890183a263578d60a392a1a218fdc12f5bce1477a6a68d4373"}, - {file = "coverage-6.1.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bc1ee1318f703bc6c971da700d74466e9b86e0c443eb85983fb2a1bd20447263"}, - {file = "coverage-6.1.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3f546f48d5d80a90a266769aa613bc0719cb3e9c2ef3529d53f463996dd15a9d"}, - {file = "coverage-6.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd92ece726055e80d4e3f01fff3b91f54b18c9c357c48fcf6119e87e2461a091"}, - {file = "coverage-6.1.2-cp38-cp38-win32.whl", hash = "sha256:24ed38ec86754c4d5a706fbd5b52b057c3df87901a8610d7e5642a08ec07087e"}, - {file = "coverage-6.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:97ef6e9119bd39d60ef7b9cd5deea2b34869c9f0b9777450a7e3759c1ab09b9b"}, - {file = "coverage-6.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6e5a8c947a2a89c56655ecbb789458a3a8e3b0cbf4c04250331df8f647b3de59"}, - {file = "coverage-6.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a39590d1e6acf6a3c435c5d233f72f5d43b585f5be834cff1f21fec4afda225"}, - {file = "coverage-6.1.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9d2c2e3ce7b8cc932a2f918186964bd44de8c84e2f9ef72dc616f5bb8be22e71"}, - {file = "coverage-6.1.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3348865798c077c695cae00da0924136bb5cc501f236cfd6b6d9f7a3c94e0ec4"}, - {file = "coverage-6.1.2-cp39-cp39-win32.whl", hash = "sha256:fae3fe111670e51f1ebbc475823899524e3459ea2db2cb88279bbfb2a0b8a3de"}, - {file = "coverage-6.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:af45eea024c0e3a25462fade161afab4f0d9d9e0d5a5d53e86149f74f0a35ecc"}, - {file = "coverage-6.1.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:eab14fdd410500dae50fd14ccc332e65543e7b39f6fc076fe90603a0e5d2f929"}, - {file = "coverage-6.1.2.tar.gz", hash = "sha256:d9a635114b88c0ab462e0355472d00a180a5fbfd8511e7f18e4ac32652e7d972"}, -] -dataclasses = [ - {file = "dataclasses-0.8-py3-none-any.whl", hash = "sha256:0201d89fa866f68c8ebd9d08ee6ff50c0b255f8ec63a71c16fda7af82bb887bf"}, - {file = "dataclasses-0.8.tar.gz", hash = "sha256:8479067f342acf957dc82ec415d355ab5edb7e7646b90dc6e2fd1d96ad084c97"}, + {file = "coverage-6.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6dbc1536e105adda7a6312c778f15aaabe583b0e9a0b0a324990334fd458c94b"}, + {file = "coverage-6.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:174cf9b4bef0db2e8244f82059a5a72bd47e1d40e71c68ab055425172b16b7d0"}, + {file = "coverage-6.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:92b8c845527eae547a2a6617d336adc56394050c3ed8a6918683646328fbb6da"}, + {file = "coverage-6.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c7912d1526299cb04c88288e148c6c87c0df600eca76efd99d84396cfe00ef1d"}, + {file = "coverage-6.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5d2033d5db1d58ae2d62f095e1aefb6988af65b4b12cb8987af409587cc0739"}, + {file = "coverage-6.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3feac4084291642165c3a0d9eaebedf19ffa505016c4d3db15bfe235718d4971"}, + {file = "coverage-6.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:276651978c94a8c5672ea60a2656e95a3cce2a3f31e9fb2d5ebd4c215d095840"}, + {file = "coverage-6.2-cp310-cp310-win32.whl", hash = "sha256:f506af4f27def639ba45789fa6fde45f9a217da0be05f8910458e4557eed020c"}, + {file = "coverage-6.2-cp310-cp310-win_amd64.whl", hash = "sha256:3f7c17209eef285c86f819ff04a6d4cbee9b33ef05cbcaae4c0b4e8e06b3ec8f"}, + {file = "coverage-6.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:13362889b2d46e8d9f97c421539c97c963e34031ab0cb89e8ca83a10cc71ac76"}, + {file = "coverage-6.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:22e60a3ca5acba37d1d4a2ee66e051f5b0e1b9ac950b5b0cf4aa5366eda41d47"}, + {file = "coverage-6.2-cp311-cp311-win_amd64.whl", hash = "sha256:b637c57fdb8be84e91fac60d9325a66a5981f8086c954ea2772efe28425eaf64"}, + {file = "coverage-6.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f467bbb837691ab5a8ca359199d3429a11a01e6dfb3d9dcc676dc035ca93c0a9"}, + {file = "coverage-6.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2641f803ee9f95b1f387f3e8f3bf28d83d9b69a39e9911e5bfee832bea75240d"}, + {file = "coverage-6.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1219d760ccfafc03c0822ae2e06e3b1248a8e6d1a70928966bafc6838d3c9e48"}, + {file = "coverage-6.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9a2b5b52be0a8626fcbffd7e689781bf8c2ac01613e77feda93d96184949a98e"}, + {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:8e2c35a4c1f269704e90888e56f794e2d9c0262fb0c1b1c8c4ee44d9b9e77b5d"}, + {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:5d6b09c972ce9200264c35a1d53d43ca55ef61836d9ec60f0d44273a31aa9f17"}, + {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e3db840a4dee542e37e09f30859f1612da90e1c5239a6a2498c473183a50e781"}, + {file = "coverage-6.2-cp36-cp36m-win32.whl", hash = "sha256:4e547122ca2d244f7c090fe3f4b5a5861255ff66b7ab6d98f44a0222aaf8671a"}, + {file = "coverage-6.2-cp36-cp36m-win_amd64.whl", hash = "sha256:01774a2c2c729619760320270e42cd9e797427ecfddd32c2a7b639cdc481f3c0"}, + {file = "coverage-6.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb8b8ee99b3fffe4fd86f4c81b35a6bf7e4462cba019997af2fe679365db0c49"}, + {file = "coverage-6.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:619346d57c7126ae49ac95b11b0dc8e36c1dd49d148477461bb66c8cf13bb521"}, + {file = "coverage-6.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0a7726f74ff63f41e95ed3a89fef002916c828bb5fcae83b505b49d81a066884"}, + {file = "coverage-6.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cfd9386c1d6f13b37e05a91a8583e802f8059bebfccde61a418c5808dea6bbfa"}, + {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:17e6c11038d4ed6e8af1407d9e89a2904d573be29d51515f14262d7f10ef0a64"}, + {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c254b03032d5a06de049ce8bca8338a5185f07fb76600afff3c161e053d88617"}, + {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dca38a21e4423f3edb821292e97cec7ad38086f84313462098568baedf4331f8"}, + {file = "coverage-6.2-cp37-cp37m-win32.whl", hash = "sha256:600617008aa82032ddeace2535626d1bc212dfff32b43989539deda63b3f36e4"}, + {file = "coverage-6.2-cp37-cp37m-win_amd64.whl", hash = "sha256:bf154ba7ee2fd613eb541c2bc03d3d9ac667080a737449d1a3fb342740eb1a74"}, + {file = "coverage-6.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f9afb5b746781fc2abce26193d1c817b7eb0e11459510fba65d2bd77fe161d9e"}, + {file = "coverage-6.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edcada2e24ed68f019175c2b2af2a8b481d3d084798b8c20d15d34f5c733fa58"}, + {file = "coverage-6.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a9c8c4283e17690ff1a7427123ffb428ad6a52ed720d550e299e8291e33184dc"}, + {file = "coverage-6.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f614fc9956d76d8a88a88bb41ddc12709caa755666f580af3a688899721efecd"}, + {file = "coverage-6.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9365ed5cce5d0cf2c10afc6add145c5037d3148585b8ae0e77cc1efdd6aa2953"}, + {file = "coverage-6.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8bdfe9ff3a4ea37d17f172ac0dff1e1c383aec17a636b9b35906babc9f0f5475"}, + {file = "coverage-6.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:63c424e6f5b4ab1cf1e23a43b12f542b0ec2e54f99ec9f11b75382152981df57"}, + {file = "coverage-6.2-cp38-cp38-win32.whl", hash = "sha256:49dbff64961bc9bdd2289a2bda6a3a5a331964ba5497f694e2cbd540d656dc1c"}, + {file = "coverage-6.2-cp38-cp38-win_amd64.whl", hash = "sha256:9a29311bd6429be317c1f3fe4bc06c4c5ee45e2fa61b2a19d4d1d6111cb94af2"}, + {file = "coverage-6.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03b20e52b7d31be571c9c06b74746746d4eb82fc260e594dc662ed48145e9efd"}, + {file = "coverage-6.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:215f8afcc02a24c2d9a10d3790b21054b58d71f4b3c6f055d4bb1b15cecce685"}, + {file = "coverage-6.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a4bdeb0a52d1d04123b41d90a4390b096f3ef38eee35e11f0b22c2d031222c6c"}, + {file = "coverage-6.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c332d8f8d448ded473b97fefe4a0983265af21917d8b0cdcb8bb06b2afe632c3"}, + {file = "coverage-6.2-cp39-cp39-win32.whl", hash = "sha256:6e1394d24d5938e561fbeaa0cd3d356207579c28bd1792f25a068743f2d5b282"}, + {file = "coverage-6.2-cp39-cp39-win_amd64.whl", hash = "sha256:86f2e78b1eff847609b1ca8050c9e1fa3bd44ce755b2ec30e70f2d3ba3844644"}, + {file = "coverage-6.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:5829192582c0ec8ca4a2532407bc14c2f338d9878a10442f5d03804a95fac9de"}, + {file = "coverage-6.2.tar.gz", hash = "sha256:e2cad8093172b7d1595b4ad66f24270808658e11acf43a8f95b41276162eb5b8"}, ] dataclasses-json = [ {file = "dataclasses-json-0.5.6.tar.gz", hash = "sha256:1f60be3405dee30b86ffbf6a436db8ba5efaeeb676bfda358e516a97aa7dfce4"}, @@ -1146,12 +1114,8 @@ iniconfig = [ {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] ipython = [ - {file = "ipython-7.16.1-py3-none-any.whl", hash = "sha256:2dbcc8c27ca7d3cfe4fcdff7f45b27f9a8d3edfa70ff8024a71c7a8eb5f09d64"}, - {file = "ipython-7.16.1.tar.gz", hash = "sha256:9f4fcb31d3b2c533333893b9172264e4821c1ac91839500f31bd43f2c59b3ccf"}, -] -ipython-genutils = [ - {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"}, - {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"}, + {file = "ipython-7.30.1-py3-none-any.whl", hash = "sha256:fc60ef843e0863dd4e24ab2bb5698f071031332801ecf8d1aeb4fb622056545c"}, + {file = "ipython-7.30.1.tar.gz", hash = "sha256:cb6aef731bf708a7727ab6cde8df87f0281b1427d41e65d62d4b68934fa54e97"}, ] isort = [ {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, @@ -1166,28 +1130,43 @@ jinja2 = [ {file = "Jinja2-3.0.3.tar.gz", hash = "sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7"}, ] lazy-object-proxy = [ - {file = "lazy-object-proxy-1.6.0.tar.gz", hash = "sha256:489000d368377571c6f982fba6497f2aa13c6d1facc40660963da62f5c379726"}, - {file = "lazy_object_proxy-1.6.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:c6938967f8528b3668622a9ed3b31d145fab161a32f5891ea7b84f6b790be05b"}, - {file = "lazy_object_proxy-1.6.0-cp27-cp27m-win32.whl", hash = "sha256:ebfd274dcd5133e0afae738e6d9da4323c3eb021b3e13052d8cbd0e457b1256e"}, - {file = "lazy_object_proxy-1.6.0-cp27-cp27m-win_amd64.whl", hash = "sha256:ed361bb83436f117f9917d282a456f9e5009ea12fd6de8742d1a4752c3017e93"}, - {file = "lazy_object_proxy-1.6.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d900d949b707778696fdf01036f58c9876a0d8bfe116e8d220cfd4b15f14e741"}, - {file = "lazy_object_proxy-1.6.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5743a5ab42ae40caa8421b320ebf3a998f89c85cdc8376d6b2e00bd12bd1b587"}, - {file = "lazy_object_proxy-1.6.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:bf34e368e8dd976423396555078def5cfc3039ebc6fc06d1ae2c5a65eebbcde4"}, - {file = "lazy_object_proxy-1.6.0-cp36-cp36m-win32.whl", hash = "sha256:b579f8acbf2bdd9ea200b1d5dea36abd93cabf56cf626ab9c744a432e15c815f"}, - {file = "lazy_object_proxy-1.6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:4f60460e9f1eb632584c9685bccea152f4ac2130e299784dbaf9fae9f49891b3"}, - {file = "lazy_object_proxy-1.6.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d7124f52f3bd259f510651450e18e0fd081ed82f3c08541dffc7b94b883aa981"}, - {file = "lazy_object_proxy-1.6.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:22ddd618cefe54305df49e4c069fa65715be4ad0e78e8d252a33debf00f6ede2"}, - {file = "lazy_object_proxy-1.6.0-cp37-cp37m-win32.whl", hash = "sha256:9d397bf41caad3f489e10774667310d73cb9c4258e9aed94b9ec734b34b495fd"}, - {file = "lazy_object_proxy-1.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:24a5045889cc2729033b3e604d496c2b6f588c754f7a62027ad4437a7ecc4837"}, - {file = "lazy_object_proxy-1.6.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:17e0967ba374fc24141738c69736da90e94419338fd4c7c7bef01ee26b339653"}, - {file = "lazy_object_proxy-1.6.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:410283732af311b51b837894fa2f24f2c0039aa7f220135192b38fcc42bd43d3"}, - {file = "lazy_object_proxy-1.6.0-cp38-cp38-win32.whl", hash = "sha256:85fb7608121fd5621cc4377a8961d0b32ccf84a7285b4f1d21988b2eae2868e8"}, - {file = "lazy_object_proxy-1.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:d1c2676e3d840852a2de7c7d5d76407c772927addff8d742b9808fe0afccebdf"}, - {file = "lazy_object_proxy-1.6.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:b865b01a2e7f96db0c5d12cfea590f98d8c5ba64ad222300d93ce6ff9138bcad"}, - {file = "lazy_object_proxy-1.6.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4732c765372bd78a2d6b2150a6e99d00a78ec963375f236979c0626b97ed8e43"}, - {file = "lazy_object_proxy-1.6.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:9698110e36e2df951c7c36b6729e96429c9c32b3331989ef19976592c5f3c77a"}, - {file = "lazy_object_proxy-1.6.0-cp39-cp39-win32.whl", hash = "sha256:1fee665d2638491f4d6e55bd483e15ef21f6c8c2095f235fef72601021e64f61"}, - {file = "lazy_object_proxy-1.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:f5144c75445ae3ca2057faac03fda5a902eff196702b0a24daf1d6ce0650514b"}, + {file = "lazy-object-proxy-1.7.1.tar.gz", hash = "sha256:d609c75b986def706743cdebe5e47553f4a5a1da9c5ff66d76013ef396b5a8a4"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb8c5fd1684d60a9902c60ebe276da1f2281a318ca16c1d0a96db28f62e9166b"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a57d51ed2997e97f3b8e3500c984db50a554bb5db56c50b5dab1b41339b37e36"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd45683c3caddf83abbb1249b653a266e7069a09f486daa8863fb0e7496a9fdb"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8561da8b3dd22d696244d6d0d5330618c993a215070f473b699e00cf1f3f6443"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fccdf7c2c5821a8cbd0a9440a456f5050492f2270bd54e94360cac663398739b"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-win32.whl", hash = "sha256:898322f8d078f2654d275124a8dd19b079080ae977033b713f677afcfc88e2b9"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:85b232e791f2229a4f55840ed54706110c80c0a210d076eee093f2b2e33e1bfd"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:46ff647e76f106bb444b4533bb4153c7370cdf52efc62ccfc1a28bdb3cc95442"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12f3bb77efe1367b2515f8cb4790a11cffae889148ad33adad07b9b55e0ab22c"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c19814163728941bb871240d45c4c30d33b8a2e85972c44d4e63dd7107faba44"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:e40f2013d96d30217a51eeb1db28c9ac41e9d0ee915ef9d00da639c5b63f01a1"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:2052837718516a94940867e16b1bb10edb069ab475c3ad84fd1e1a6dd2c0fcfc"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-win32.whl", hash = "sha256:6a24357267aa976abab660b1d47a34aaf07259a0c3859a34e536f1ee6e76b5bb"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-win_amd64.whl", hash = "sha256:6aff3fe5de0831867092e017cf67e2750c6a1c7d88d84d2481bd84a2e019ec35"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6a6e94c7b02641d1311228a102607ecd576f70734dc3d5e22610111aeacba8a0"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ce15276a1a14549d7e81c243b887293904ad2d94ad767f42df91e75fd7b5b6"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e368b7f7eac182a59ff1f81d5f3802161932a41dc1b1cc45c1f757dc876b5d2c"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6ecbb350991d6434e1388bee761ece3260e5228952b1f0c46ffc800eb313ff42"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:553b0f0d8dbf21890dd66edd771f9b1b5f51bd912fa5f26de4449bfc5af5e029"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-win32.whl", hash = "sha256:c7a683c37a8a24f6428c28c561c80d5f4fd316ddcf0c7cab999b15ab3f5c5c69"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-win_amd64.whl", hash = "sha256:df2631f9d67259dc9620d831384ed7732a198eb434eadf69aea95ad18c587a28"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:07fa44286cda977bd4803b656ffc1c9b7e3bc7dff7d34263446aec8f8c96f88a"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4dca6244e4121c74cc20542c2ca39e5c4a5027c81d112bfb893cf0790f96f57e"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91ba172fc5b03978764d1df5144b4ba4ab13290d7bab7a50f12d8117f8630c38"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:043651b6cb706eee4f91854da4a089816a6606c1428fd391573ef8cb642ae4f7"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b9e89b87c707dd769c4ea91f7a31538888aad05c116a59820f28d59b3ebfe25a"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-win32.whl", hash = "sha256:9d166602b525bf54ac994cf833c385bfcc341b364e3ee71e3bf5a1336e677b55"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:8f3953eb575b45480db6568306893f0bd9d8dfeeebd46812aa09ca9579595148"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dd7ed7429dbb6c494aa9bc4e09d94b778a3579be699f9d67da7e6804c422d3de"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70ed0c2b380eb6248abdef3cd425fc52f0abd92d2b07ce26359fcbc399f636ad"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7096a5e0c1115ec82641afbdd70451a144558ea5cf564a896294e346eb611be1"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f769457a639403073968d118bc70110e7dce294688009f5c24ab78800ae56dc8"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:39b0e26725c5023757fc1ab2a89ef9d7ab23b84f9251e28f9cc114d5b59c1b09"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-win32.whl", hash = "sha256:2130db8ed69a48a3440103d4a520b89d8a9405f1b06e2cc81640509e8bf6548f"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:677ea950bef409b47e51e733283544ac3d660b709cfce7b187f5ace137960d61"}, + {file = "lazy_object_proxy-1.7.1-pp37.pp38-none-any.whl", hash = "sha256:d66906d5785da8e0be7360912e99c9188b70f52c422f9fc18223347235691a84"}, ] markupsafe = [ {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, @@ -1268,6 +1247,10 @@ marshmallow-enum = [ {file = "marshmallow-enum-1.5.1.tar.gz", hash = "sha256:38e697e11f45a8e64b4a1e664000897c659b60aa57bfa18d44e226a9920b6e58"}, {file = "marshmallow_enum-1.5.1-py2.py3-none-any.whl", hash = "sha256:57161ab3dbfde4f57adeb12090f39592e992b9c86d206d02f6bd03ebec60f072"}, ] +matplotlib-inline = [ + {file = "matplotlib-inline-0.1.3.tar.gz", hash = "sha256:a04bfba22e0d1395479f866853ec1ee28eea1485c1d69a6faf00dc3e24ff34ee"}, + {file = "matplotlib_inline-0.1.3-py3-none-any.whl", hash = "sha256:aed605ba3b72462d64d475a21a9296f400a19c4f74a31b59103d2a99ffd5aa5c"}, +] mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, @@ -1277,29 +1260,26 @@ more-itertools = [ {file = "more_itertools-8.12.0-py3-none-any.whl", hash = "sha256:43e6dd9942dffd72661a2c4ef383ad7da1e6a3e968a927ad7a6083ab410a688b"}, ] mypy = [ - {file = "mypy-0.910-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:a155d80ea6cee511a3694b108c4494a39f42de11ee4e61e72bc424c490e46457"}, - {file = "mypy-0.910-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b94e4b785e304a04ea0828759172a15add27088520dc7e49ceade7834275bedb"}, - {file = "mypy-0.910-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:088cd9c7904b4ad80bec811053272986611b84221835e079be5bcad029e79dd9"}, - {file = "mypy-0.910-cp35-cp35m-win_amd64.whl", hash = "sha256:adaeee09bfde366d2c13fe6093a7df5df83c9a2ba98638c7d76b010694db760e"}, - {file = "mypy-0.910-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ecd2c3fe726758037234c93df7e98deb257fd15c24c9180dacf1ef829da5f921"}, - {file = "mypy-0.910-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d9dd839eb0dc1bbe866a288ba3c1afc33a202015d2ad83b31e875b5905a079b6"}, - {file = "mypy-0.910-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:3e382b29f8e0ccf19a2df2b29a167591245df90c0b5a2542249873b5c1d78212"}, - {file = "mypy-0.910-cp36-cp36m-win_amd64.whl", hash = "sha256:53fd2eb27a8ee2892614370896956af2ff61254c275aaee4c230ae771cadd885"}, - {file = "mypy-0.910-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b6fb13123aeef4a3abbcfd7e71773ff3ff1526a7d3dc538f3929a49b42be03f0"}, - {file = "mypy-0.910-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e4dab234478e3bd3ce83bac4193b2ecd9cf94e720ddd95ce69840273bf44f6de"}, - {file = "mypy-0.910-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:7df1ead20c81371ccd6091fa3e2878559b5c4d4caadaf1a484cf88d93ca06703"}, - {file = "mypy-0.910-cp37-cp37m-win_amd64.whl", hash = "sha256:0aadfb2d3935988ec3815952e44058a3100499f5be5b28c34ac9d79f002a4a9a"}, - {file = "mypy-0.910-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ec4e0cd079db280b6bdabdc807047ff3e199f334050db5cbb91ba3e959a67504"}, - {file = "mypy-0.910-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:119bed3832d961f3a880787bf621634ba042cb8dc850a7429f643508eeac97b9"}, - {file = "mypy-0.910-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:866c41f28cee548475f146aa4d39a51cf3b6a84246969f3759cb3e9c742fc072"}, - {file = "mypy-0.910-cp38-cp38-win_amd64.whl", hash = "sha256:ceb6e0a6e27fb364fb3853389607cf7eb3a126ad335790fa1e14ed02fba50811"}, - {file = "mypy-0.910-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1a85e280d4d217150ce8cb1a6dddffd14e753a4e0c3cf90baabb32cefa41b59e"}, - {file = "mypy-0.910-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:42c266ced41b65ed40a282c575705325fa7991af370036d3f134518336636f5b"}, - {file = "mypy-0.910-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:3c4b8ca36877fc75339253721f69603a9c7fdb5d4d5a95a1a1b899d8b86a4de2"}, - {file = "mypy-0.910-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:c0df2d30ed496a08de5daed2a9ea807d07c21ae0ab23acf541ab88c24b26ab97"}, - {file = "mypy-0.910-cp39-cp39-win_amd64.whl", hash = "sha256:c6c2602dffb74867498f86e6129fd52a2770c48b7cd3ece77ada4fa38f94eba8"}, - {file = "mypy-0.910-py3-none-any.whl", hash = "sha256:ef565033fa5a958e62796867b1df10c40263ea9ded87164d67572834e57a174d"}, - {file = "mypy-0.910.tar.gz", hash = "sha256:704098302473cb31a218f1775a873b376b30b4c18229421e9e9dc8916fd16150"}, + {file = "mypy-0.930-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:221cc94dc6a801ccc2be7c0c9fd791c5e08d1fa2c5e1c12dec4eab15b2469871"}, + {file = "mypy-0.930-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:db3a87376a1380f396d465bed462e76ea89f838f4c5e967d68ff6ee34b785c31"}, + {file = "mypy-0.930-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1d2296f35aae9802eeb1327058b550371ee382d71374b3e7d2804035ef0b830b"}, + {file = "mypy-0.930-cp310-cp310-win_amd64.whl", hash = "sha256:959319b9a3cafc33a8185f440a433ba520239c72e733bf91f9efd67b0a8e9b30"}, + {file = "mypy-0.930-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:45a4dc21c789cfd09b8ccafe114d6de66f0b341ad761338de717192f19397a8c"}, + {file = "mypy-0.930-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1e689e92cdebd87607a041585f1dc7339aa2e8a9f9bad9ba7e6ece619431b20c"}, + {file = "mypy-0.930-cp36-cp36m-win_amd64.whl", hash = "sha256:ed4e0ea066bb12f56b2812a15ff223c57c0a44eca817ceb96b214bb055c7051f"}, + {file = "mypy-0.930-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a9d8dffefba634b27d650e0de2564379a1a367e2e08d6617d8f89261a3bf63b2"}, + {file = "mypy-0.930-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b419e9721260161e70d054a15abbd50603c16f159860cfd0daeab647d828fc29"}, + {file = "mypy-0.930-cp37-cp37m-win_amd64.whl", hash = "sha256:601f46593f627f8a9b944f74fd387c9b5f4266b39abad77471947069c2fc7651"}, + {file = "mypy-0.930-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1ea7199780c1d7940b82dbc0a4e37722b4e3851264dbba81e01abecc9052d8a7"}, + {file = "mypy-0.930-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:70b197dd8c78fc5d2daf84bd093e8466a2b2e007eedaa85e792e513a820adbf7"}, + {file = "mypy-0.930-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5feb56f8bb280468fe5fc8e6f56f48f99aa0df9eed3c507a11505ee4657b5380"}, + {file = "mypy-0.930-cp38-cp38-win_amd64.whl", hash = "sha256:2e9c5409e9cb81049bb03fa1009b573dea87976713e3898561567a86c4eaee01"}, + {file = "mypy-0.930-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:554873e45c1ca20f31ddf873deb67fa5d2e87b76b97db50669f0468ccded8fae"}, + {file = "mypy-0.930-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0feb82e9fa849affca7edd24713dbe809dce780ced9f3feca5ed3d80e40b777f"}, + {file = "mypy-0.930-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bc1a0607ea03c30225347334af66b0af12eefba018a89a88c209e02b7065ea95"}, + {file = "mypy-0.930-cp39-cp39-win_amd64.whl", hash = "sha256:f9f665d69034b1fcfdbcd4197480d26298bbfb5d2dfe206245b6498addb34999"}, + {file = "mypy-0.930-py3-none-any.whl", hash = "sha256:bf4a44e03040206f7c058d1f5ba02ef2d1820720c88bc4285c7d9a4269f54173"}, + {file = "mypy-0.930.tar.gz", hash = "sha256:51426262ae4714cc7dd5439814676e0992b55bcc0f6514eccb4cf8e0678962c2"}, ] mypy-extensions = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, @@ -1326,16 +1306,16 @@ pickleshare = [ {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, ] platformdirs = [ - {file = "platformdirs-2.4.0-py3-none-any.whl", hash = "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"}, - {file = "platformdirs-2.4.0.tar.gz", hash = "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2"}, + {file = "platformdirs-2.4.1-py3-none-any.whl", hash = "sha256:1d7385c7db91728b83efd0ca99a5afb296cab9d0ed8313a45ed8ba17967ecfca"}, + {file = "platformdirs-2.4.1.tar.gz", hash = "sha256:440633ddfebcc36264232365d7840a970e75e1018d15b4327d11f91909045fda"}, ] pluggy = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] prompt-toolkit = [ - {file = "prompt_toolkit-3.0.22-py3-none-any.whl", hash = "sha256:48d85cdca8b6c4f16480c7ce03fd193666b62b0a21667ca56b4bb5ad679d1170"}, - {file = "prompt_toolkit-3.0.22.tar.gz", hash = "sha256:449f333dd120bd01f5d296a8ce1452114ba3a71fae7288d2f0ae2c918764fa72"}, + {file = "prompt_toolkit-3.0.24-py3-none-any.whl", hash = "sha256:e56f2ff799bacecd3e88165b1e2f5ebf9bcd59e80e06d395fa0cc4b8bd7bb506"}, + {file = "prompt_toolkit-3.0.24.tar.gz", hash = "sha256:1bb05628c7d87b645974a1bad3f17612be0c29fa39af9f7688030163f680bad6"}, ] ptyprocess = [ {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, @@ -1358,12 +1338,12 @@ pyflakes = [ {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, ] pygments = [ - {file = "Pygments-2.10.0-py3-none-any.whl", hash = "sha256:b8e67fe6af78f492b3c4b3e2970c0624cbf08beb1e493b2c99b9fa1b67a20380"}, - {file = "Pygments-2.10.0.tar.gz", hash = "sha256:f398865f7eb6874156579fdf36bc840a03cab64d1cde9e93d68f46a425ec52c6"}, + {file = "Pygments-2.11.1-py3-none-any.whl", hash = "sha256:9135c1af61eec0f650cd1ea1ed8ce298e54d56bcd8cc2ef46edd7702c171337c"}, + {file = "Pygments-2.11.1.tar.gz", hash = "sha256:59b895e326f0fb0d733fd28c6839bd18ad0687ba20efc26d4277fd1d30b971f4"}, ] pylint = [ - {file = "pylint-2.12.1-py3-none-any.whl", hash = "sha256:b4b5a7b6d04e914a11c198c816042af1fb2d3cda29bb0c98a9c637010da2a5c5"}, - {file = "pylint-2.12.1.tar.gz", hash = "sha256:4f4a52b132c05b49094b28e109febcec6bfb7bc6961c7485a5ad0a0f961df289"}, + {file = "pylint-2.12.2-py3-none-any.whl", hash = "sha256:daabda3f7ed9d1c60f52d563b1b854632fd90035bcf01443e234d3dc794e3b74"}, + {file = "pylint-2.12.2.tar.gz", hash = "sha256:9d945a73640e1fec07ee34b42f5669b770c759acd536ec7b16d7e4b87a9c9ff9"}, ] pyparsing = [ {file = "pyparsing-3.0.6-py3-none-any.whl", hash = "sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4"}, @@ -1378,12 +1358,12 @@ pytest-cov = [ {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, ] pytest-forked = [ - {file = "pytest-forked-1.3.0.tar.gz", hash = "sha256:6aa9ac7e00ad1a539c41bec6d21011332de671e938c7637378ec9710204e37ca"}, - {file = "pytest_forked-1.3.0-py2.py3-none-any.whl", hash = "sha256:dc4147784048e70ef5d437951728825a131b81714b398d5d52f17c7c144d8815"}, + {file = "pytest-forked-1.4.0.tar.gz", hash = "sha256:8b67587c8f98cbbadfdd804539ed5455b6ed03802203485dd2f53c1422d7440e"}, + {file = "pytest_forked-1.4.0-py3-none-any.whl", hash = "sha256:bbbb6717efc886b9d64537b41fb1497cfaf3c9601276be8da2cccfea5a3c8ad8"}, ] pytest-xdist = [ - {file = "pytest-xdist-2.4.0.tar.gz", hash = "sha256:89b330316f7fc475f999c81b577c2b926c9569f3d397ae432c0c2e2496d61ff9"}, - {file = "pytest_xdist-2.4.0-py3-none-any.whl", hash = "sha256:7b61ebb46997a0820a263553179d6d1e25a8c50d8a8620cd1aa1e20e3be99168"}, + {file = "pytest-xdist-2.5.0.tar.gz", hash = "sha256:4580deca3ff04ddb2ac53eba39d76cb5dd5edeac050cb6fbc768b0dd712b4edf"}, + {file = "pytest_xdist-2.5.0-py3-none-any.whl", hash = "sha256:6fe5c74fec98906deb8f2d2b616b5c782022744978e7bd4695d39c8f42d0ce65"}, ] python-dateutil = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, @@ -1393,60 +1373,9 @@ pytz = [ {file = "pytz-2021.3-py2.py3-none-any.whl", hash = "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c"}, {file = "pytz-2021.3.tar.gz", hash = "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326"}, ] -regex = [ - {file = "regex-2021.11.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9345b6f7ee578bad8e475129ed40123d265464c4cfead6c261fd60fc9de00bcf"}, - {file = "regex-2021.11.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:416c5f1a188c91e3eb41e9c8787288e707f7d2ebe66e0a6563af280d9b68478f"}, - {file = "regex-2021.11.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0538c43565ee6e703d3a7c3bdfe4037a5209250e8502c98f20fea6f5fdf2965"}, - {file = "regex-2021.11.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ee1227cf08b6716c85504aebc49ac827eb88fcc6e51564f010f11a406c0a667"}, - {file = "regex-2021.11.10-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6650f16365f1924d6014d2ea770bde8555b4a39dc9576abb95e3cd1ff0263b36"}, - {file = "regex-2021.11.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30ab804ea73972049b7a2a5c62d97687d69b5a60a67adca07eb73a0ddbc9e29f"}, - {file = "regex-2021.11.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68a067c11463de2a37157930d8b153005085e42bcb7ad9ca562d77ba7d1404e0"}, - {file = "regex-2021.11.10-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:162abfd74e88001d20cb73ceaffbfe601469923e875caf9118333b1a4aaafdc4"}, - {file = "regex-2021.11.10-cp310-cp310-win32.whl", hash = "sha256:98ba568e8ae26beb726aeea2273053c717641933836568c2a0278a84987b2a1a"}, - {file = "regex-2021.11.10-cp310-cp310-win_amd64.whl", hash = "sha256:780b48456a0f0ba4d390e8b5f7c661fdd218934388cde1a974010a965e200e12"}, - {file = "regex-2021.11.10-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:dba70f30fd81f8ce6d32ddeef37d91c8948e5d5a4c63242d16a2b2df8143aafc"}, - {file = "regex-2021.11.10-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1f54b9b4b6c53369f40028d2dd07a8c374583417ee6ec0ea304e710a20f80a0"}, - {file = "regex-2021.11.10-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fbb9dc00e39f3e6c0ef48edee202f9520dafb233e8b51b06b8428cfcb92abd30"}, - {file = "regex-2021.11.10-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666abff54e474d28ff42756d94544cdfd42e2ee97065857413b72e8a2d6a6345"}, - {file = "regex-2021.11.10-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5537f71b6d646f7f5f340562ec4c77b6e1c915f8baae822ea0b7e46c1f09b733"}, - {file = "regex-2021.11.10-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed2e07c6a26ed4bea91b897ee2b0835c21716d9a469a96c3e878dc5f8c55bb23"}, - {file = "regex-2021.11.10-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ca5f18a75e1256ce07494e245cdb146f5a9267d3c702ebf9b65c7f8bd843431e"}, - {file = "regex-2021.11.10-cp36-cp36m-win32.whl", hash = "sha256:93a5051fcf5fad72de73b96f07d30bc29665697fb8ecdfbc474f3452c78adcf4"}, - {file = "regex-2021.11.10-cp36-cp36m-win_amd64.whl", hash = "sha256:b483c9d00a565633c87abd0aaf27eb5016de23fed952e054ecc19ce32f6a9e7e"}, - {file = "regex-2021.11.10-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fff55f3ce50a3ff63ec8e2a8d3dd924f1941b250b0aac3d3d42b687eeff07a8e"}, - {file = "regex-2021.11.10-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e32d2a2b02ccbef10145df9135751abea1f9f076e67a4e261b05f24b94219e36"}, - {file = "regex-2021.11.10-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53db2c6be8a2710b359bfd3d3aa17ba38f8aa72a82309a12ae99d3c0c3dcd74d"}, - {file = "regex-2021.11.10-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2207ae4f64ad3af399e2d30dde66f0b36ae5c3129b52885f1bffc2f05ec505c8"}, - {file = "regex-2021.11.10-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5ca078bb666c4a9d1287a379fe617a6dccd18c3e8a7e6c7e1eb8974330c626a"}, - {file = "regex-2021.11.10-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd33eb9bdcfbabab3459c9ee651d94c842bc8a05fabc95edf4ee0c15a072495e"}, - {file = "regex-2021.11.10-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:05b7d6d7e64efe309972adab77fc2af8907bb93217ec60aa9fe12a0dad35874f"}, - {file = "regex-2021.11.10-cp37-cp37m-win32.whl", hash = "sha256:e71255ba42567d34a13c03968736c5d39bb4a97ce98188fafb27ce981115beec"}, - {file = "regex-2021.11.10-cp37-cp37m-win_amd64.whl", hash = "sha256:07856afef5ffcc052e7eccf3213317fbb94e4a5cd8177a2caa69c980657b3cb4"}, - {file = "regex-2021.11.10-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ba05430e819e58544e840a68b03b28b6d328aff2e41579037e8bab7653b37d83"}, - {file = "regex-2021.11.10-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7f301b11b9d214f83ddaf689181051e7f48905568b0c7017c04c06dfd065e244"}, - {file = "regex-2021.11.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aaa4e0705ef2b73dd8e36eeb4c868f80f8393f5f4d855e94025ce7ad8525f50"}, - {file = "regex-2021.11.10-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:788aef3549f1924d5c38263104dae7395bf020a42776d5ec5ea2b0d3d85d6646"}, - {file = "regex-2021.11.10-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f8af619e3be812a2059b212064ea7a640aff0568d972cd1b9e920837469eb3cb"}, - {file = "regex-2021.11.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85bfa6a5413be0ee6c5c4a663668a2cad2cbecdee367630d097d7823041bdeec"}, - {file = "regex-2021.11.10-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f23222527b307970e383433daec128d769ff778d9b29343fb3496472dc20dabe"}, - {file = "regex-2021.11.10-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:da1a90c1ddb7531b1d5ff1e171b4ee61f6345119be7351104b67ff413843fe94"}, - {file = "regex-2021.11.10-cp38-cp38-win32.whl", hash = "sha256:0617383e2fe465732af4509e61648b77cbe3aee68b6ac8c0b6fe934db90be5cc"}, - {file = "regex-2021.11.10-cp38-cp38-win_amd64.whl", hash = "sha256:a3feefd5e95871872673b08636f96b61ebef62971eab044f5124fb4dea39919d"}, - {file = "regex-2021.11.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f7f325be2804246a75a4f45c72d4ce80d2443ab815063cdf70ee8fb2ca59ee1b"}, - {file = "regex-2021.11.10-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:537ca6a3586931b16a85ac38c08cc48f10fc870a5b25e51794c74df843e9966d"}, - {file = "regex-2021.11.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eef2afb0fd1747f33f1ee3e209bce1ed582d1896b240ccc5e2697e3275f037c7"}, - {file = "regex-2021.11.10-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:432bd15d40ed835a51617521d60d0125867f7b88acf653e4ed994a1f8e4995dc"}, - {file = "regex-2021.11.10-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b43c2b8a330a490daaef5a47ab114935002b13b3f9dc5da56d5322ff218eeadb"}, - {file = "regex-2021.11.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:962b9a917dd7ceacbe5cd424556914cb0d636001e393b43dc886ba31d2a1e449"}, - {file = "regex-2021.11.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fa8c626d6441e2d04b6ee703ef2d1e17608ad44c7cb75258c09dd42bacdfc64b"}, - {file = "regex-2021.11.10-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3c5fb32cc6077abad3bbf0323067636d93307c9fa93e072771cf9a64d1c0f3ef"}, - {file = "regex-2021.11.10-cp39-cp39-win32.whl", hash = "sha256:3b5df18db1fccd66de15aa59c41e4f853b5df7550723d26aa6cb7f40e5d9da5a"}, - {file = "regex-2021.11.10-cp39-cp39-win_amd64.whl", hash = "sha256:83ee89483672b11f8952b158640d0c0ff02dc43d9cb1b70c1564b49abe92ce29"}, - {file = "regex-2021.11.10.tar.gz", hash = "sha256:f341ee2df0999bfdf7a95e448075effe0db212a59387de1a70690e4acb03d4c6"}, -] requests = [ - {file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"}, - {file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"}, + {file = "requests-2.27.0-py2.py3-none-any.whl", hash = "sha256:f71a09d7feba4a6b64ffd8e9d9bc60f9bf7d7e19fd0e04362acb1cfc2e3d98df"}, + {file = "requests-2.27.0.tar.gz", hash = "sha256:8e5643905bf20a308e25e4c1dd379117c09000bf8a82ebccc462cfb1b34a16b5"}, ] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, @@ -1493,60 +1422,45 @@ toml = [ {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] tomli = [ - {file = "tomli-1.2.2-py3-none-any.whl", hash = "sha256:f04066f68f5554911363063a30b108d2b5a5b1a010aa8b6132af78489fe3aade"}, - {file = "tomli-1.2.2.tar.gz", hash = "sha256:c6ce0015eb38820eaf32b5db832dbc26deb3dd427bd5f6556cf0acac2c214fee"}, + {file = "tomli-1.2.3-py3-none-any.whl", hash = "sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c"}, + {file = "tomli-1.2.3.tar.gz", hash = "sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f"}, ] traitlets = [ - {file = "traitlets-4.3.3-py2.py3-none-any.whl", hash = "sha256:70b4c6a1d9019d7b4f6846832288f86998aa3b9207c6821f3578a6a6a467fe44"}, - {file = "traitlets-4.3.3.tar.gz", hash = "sha256:d023ee369ddd2763310e4c3eae1ff649689440d4ae59d7485eb4cfbbe3e359f7"}, + {file = "traitlets-5.1.1-py3-none-any.whl", hash = "sha256:2d313cc50a42cd6c277e7d7dc8d4d7fedd06a2c215f78766ae7b1a66277e0033"}, + {file = "traitlets-5.1.1.tar.gz", hash = "sha256:059f456c5a7c1c82b98c2e8c799f39c9b8128f6d0d46941ee118daace9eb70c7"}, ] typed-ast = [ - {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, - {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"}, - {file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"}, - {file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"}, - {file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"}, - {file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"}, - {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"}, - {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"}, - {file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"}, - {file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"}, - {file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"}, - {file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"}, - {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"}, - {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"}, - {file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"}, - {file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"}, - {file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"}, - {file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"}, - {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"}, - {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"}, - {file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"}, - {file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"}, - {file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"}, - {file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"}, - {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"}, - {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"}, - {file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"}, - {file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"}, - {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"}, - {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, -] -types-dataclasses = [ - {file = "types-dataclasses-0.6.1.tar.gz", hash = "sha256:6568532fed11f854e4db2eb48063385b323b93ecadd09f10a215d56246c306d7"}, - {file = "types_dataclasses-0.6.1-py3-none-any.whl", hash = "sha256:aa45bb0dacdba09e3195a36ff8337bba45eac03b6f31c4645e87b4a2a47830dd"}, + {file = "typed_ast-1.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d8314c92414ce7481eee7ad42b353943679cf6f30237b5ecbf7d835519e1212"}, + {file = "typed_ast-1.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b53ae5de5500529c76225d18eeb060efbcec90ad5e030713fe8dab0fb4531631"}, + {file = "typed_ast-1.5.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:24058827d8f5d633f97223f5148a7d22628099a3d2efe06654ce872f46f07cdb"}, + {file = "typed_ast-1.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:a6d495c1ef572519a7bac9534dbf6d94c40e5b6a608ef41136133377bba4aa08"}, + {file = "typed_ast-1.5.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:de4ecae89c7d8b56169473e08f6bfd2df7f95015591f43126e4ea7865928677e"}, + {file = "typed_ast-1.5.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:256115a5bc7ea9e665c6314ed6671ee2c08ca380f9d5f130bd4d2c1f5848d695"}, + {file = "typed_ast-1.5.1-cp36-cp36m-win_amd64.whl", hash = "sha256:7c42707ab981b6cf4b73490c16e9d17fcd5227039720ca14abe415d39a173a30"}, + {file = "typed_ast-1.5.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:71dcda943a471d826ea930dd449ac7e76db7be778fcd722deb63642bab32ea3f"}, + {file = "typed_ast-1.5.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4f30a2bcd8e68adbb791ce1567fdb897357506f7ea6716f6bbdd3053ac4d9471"}, + {file = "typed_ast-1.5.1-cp37-cp37m-win_amd64.whl", hash = "sha256:ca9e8300d8ba0b66d140820cf463438c8e7b4cdc6fd710c059bfcfb1531d03fb"}, + {file = "typed_ast-1.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9caaf2b440efb39ecbc45e2fabde809cbe56272719131a6318fd9bf08b58e2cb"}, + {file = "typed_ast-1.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c9bcad65d66d594bffab8575f39420fe0ee96f66e23c4d927ebb4e24354ec1af"}, + {file = "typed_ast-1.5.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:591bc04e507595887160ed7aa8d6785867fb86c5793911be79ccede61ae96f4d"}, + {file = "typed_ast-1.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:a80d84f535642420dd17e16ae25bb46c7f4c16ee231105e7f3eb43976a89670a"}, + {file = "typed_ast-1.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:38cf5c642fa808300bae1281460d4f9b7617cf864d4e383054a5ef336e344d32"}, + {file = "typed_ast-1.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5b6ab14c56bc9c7e3c30228a0a0b54b915b1579613f6e463ba6f4eb1382e7fd4"}, + {file = "typed_ast-1.5.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a2b8d7007f6280e36fa42652df47087ac7b0a7d7f09f9468f07792ba646aac2d"}, + {file = "typed_ast-1.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:b6d17f37f6edd879141e64a5db17b67488cfeffeedad8c5cec0392305e9bc775"}, + {file = "typed_ast-1.5.1.tar.gz", hash = "sha256:484137cab8ecf47e137260daa20bafbba5f4e3ec7fda1c1e69ab299b75fa81c5"}, ] types-python-dateutil = [ - {file = "types-python-dateutil-2.8.3.tar.gz", hash = "sha256:d94e7c7ecd9f0e23b3a78087eae12c0d7aa4af9e067a8ea963ad03ed0abd1cb7"}, - {file = "types_python_dateutil-2.8.3-py3-none-any.whl", hash = "sha256:42262d0b8f8ecb06cdc5c458956685eb3b27c74f170adf541d1cc5ee4ff68bdc"}, + {file = "types-python-dateutil-2.8.4.tar.gz", hash = "sha256:29b85e25b832170f8d5ed383bf68cf5d9ee9066e2354221c6e9dd381dab71c5c"}, + {file = "types_python_dateutil-2.8.4-py3-none-any.whl", hash = "sha256:82a6160961d7b24418eeb383a5356f262229bd1450e0ccd82ad40f8a67baaafd"}, ] types-requests = [ - {file = "types-requests-2.26.1.tar.gz", hash = "sha256:0893e112e1510bbb67f537941c92192de7472e51bf7f236e0e583866f0ed933e"}, - {file = "types_requests-2.26.1-py3-none-any.whl", hash = "sha256:853571b3accc188976c0f4feffcaebf6cdfc170082b5e43f3358aa78de61f531"}, + {file = "types-requests-2.26.3.tar.gz", hash = "sha256:d63fa617846dcefff5aa2d59e47ab4ffd806e4bb0567115f7adbb5e438302fe4"}, + {file = "types_requests-2.26.3-py3-none-any.whl", hash = "sha256:ad18284931c5ddbf050ccdd138f200d18fd56f88aa3567019d8da9b2d4fe0344"}, ] typing-extensions = [ - {file = "typing_extensions-4.0.0-py3-none-any.whl", hash = "sha256:829704698b22e13ec9eaf959122315eabb370b0884400e9818334d8b677023d9"}, - {file = "typing_extensions-4.0.0.tar.gz", hash = "sha256:2cdf80e4e04866a9b3689a51869016d36db0814d84b8d8a568d22781d45d27ed"}, + {file = "typing_extensions-4.0.1-py3-none-any.whl", hash = "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"}, + {file = "typing_extensions-4.0.1.tar.gz", hash = "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e"}, ] typing-inspect = [ {file = "typing_inspect-0.7.1-py2-none-any.whl", hash = "sha256:b1f56c0783ef0f25fb064a01be6e5407e54cf4a4bf4f3ba3fe51e0bd6dcea9e5"}, @@ -1615,6 +1529,6 @@ wrapt = [ {file = "wrapt-1.13.3.tar.gz", hash = "sha256:1fea9cd438686e6682271d36f3481a9f3636195578bab9ca3382e2f5f01fc185"}, ] zipp = [ - {file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"}, - {file = "zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832"}, + {file = "zipp-3.7.0-py3-none-any.whl", hash = "sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375"}, + {file = "zipp-3.7.0.tar.gz", hash = "sha256:9f50f446828eb9d45b267433fd3e9da8d801f614129124863f9c51ebceafb87d"}, ] diff --git a/pylintrc b/pylintrc index 8a97cfcd..927c329c 100644 --- a/pylintrc +++ b/pylintrc @@ -1,6 +1,5 @@ [pylint] disable = - logging-fstring-interpolation, # ===== May not modify ===== fixme, # TODO remains line-too-long, # [flake8] @@ -38,5 +37,6 @@ disable = protected-access, f-string-without-interpolation, # f-stringに変数を使わない場合もあるため C0330, + logging-fstring-interpolation ignored-argument-names=arg|args|kwargs \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 69f641d3..955db210 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "annofabapi" -version = "0.51.0" +version = "0.52.0" description = "Python Clinet Library of AnnoFab WebAPI (https://annofab.com/docs/api/)" authors = ["yuji38kwmt"] license = "MIT" @@ -13,7 +13,6 @@ classifiers = [ "License :: OSI Approved :: MIT License", "Programming Language :: Python", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", @@ -23,12 +22,11 @@ classifiers = [ [tool.poetry.dependencies] -python = "^3.6.2" +python = "^3.7" requests = "*" python-dateutil = "*" backoff="*" dataclasses-json="*" -dataclasses = { version = "*", python = "<3.7" } [tool.poetry.dev-dependencies] @@ -76,6 +74,8 @@ skip="annofabapi/__init__.py" [tool.mypy] # スタブが無いパッケージのエラーは無視させる.サードパーティのライブラリに型情報がないケースもあるため ignore_missing_imports = true +# 型ヒントが付いていない関数もチェックする +check_untyped_defs = true [build-system] requires = ["poetry>=0.12"] diff --git a/tests/test_api.py b/tests/test_api.py index dbecc730..b42aee78 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -17,6 +17,7 @@ import annofabapi import annofabapi.utils +from annofabapi.exceptions import NotLoggedInError from annofabapi.models import GraphType, ProjectJobType from annofabapi.wrapper import TaskFrameKey from tests.utils_for_test import WrapperForTest, create_csv_for_task @@ -300,9 +301,13 @@ def test_login(self): assert type(api.logout()[0]) == dict - assert api.refresh_token() is None, "ログアウト状態では、refresh_tokenメソッドはNoneを返す" + # ログアウト状態では、refresh_tokenメソッドはExceptionをスローする + with pytest.raises(NotLoggedInError): + api.refresh_token() - assert api.logout() is None, "ログアウト状態では、logoutメソッドはNoneを返す" + # ログアウト状態では、logoutメソッドはNoneを返す + with pytest.raises(NotLoggedInError): + api.logout() class TestMy: @@ -504,7 +509,7 @@ def test_graph_marker(self): assert type(api.put_markers(project_id, request_body=request_body)[0]) == dict def test_get_statistics_available_dates(self): - content, _ = api.s.api.get_statistics_available_dates(project_id) + content, _ = api.get_statistics_available_dates(project_id) assert type(content) == list diff --git a/tests/test_local_utils.py b/tests/test_local_utils.py index 73097a85..36478764 100644 --- a/tests/test_local_utils.py +++ b/tests/test_local_utils.py @@ -1,12 +1,8 @@ -import pytest -import requests - from annofabapi.models import TaskPhase from annofabapi.utils import ( get_number_of_rejections, get_task_history_index_skipped_acceptance, get_task_history_index_skipped_inspection, - my_backoff, ) @@ -616,87 +612,3 @@ def test_get_task_history_index_skipped_inspection_検査1回_教師付で提出 actual = get_task_history_index_skipped_inspection(task_history_list) expected = [] assert all([a == b for a, b in zip(actual, expected)]) - - -class TestMyBackoff: - @my_backoff - def requestexception_connectionerror_then_true(self, log): - if len(log) == 2: - return True - - if len(log) == 0: - e = requests.exceptions.RequestException() - elif len(log) == 1: - e = ConnectionError() - log.append(e) - raise e - - def test_assert_retry(self): - log = [] - assert self.requestexception_connectionerror_then_true(log) is True - assert 2 == len(log) - print(log) - assert type(log[0]) == requests.exceptions.RequestException - assert type(log[1]) == ConnectionError - - @my_backoff - def chunkedencodingerror_requestsconnectionerror_then_true(self, log): - if len(log) == 2: - return True - if len(log) == 0: - e = requests.exceptions.ChunkedEncodingError() - log.append(e) - raise e - elif len(log) == 1: - e = requests.exceptions.ConnectionError() - log.append(e) - raise e - - def test_assert_retry2(self): - log = [] - assert self.chunkedencodingerror_requestsconnectionerror_then_true(log) is True - assert 2 == len(log) - print(log) - assert type(log[0]) == requests.exceptions.ChunkedEncodingError - assert type(log[1]) == requests.exceptions.ConnectionError - - @my_backoff - def httperror_then_true(self, log): - if len(log) == 2: - return True - response = requests.Response() - if len(log) == 0: - response.status_code = 429 - e = requests.exceptions.HTTPError(response=response) - elif len(log) == 1: - response.status_code = 500 - e = requests.exceptions.HTTPError(response=response) - log.append(e) - raise e - - def test_assert_retry_with_httperror(self): - log = [] - assert self.httperror_then_true(log) is True - assert 2 == len(log) - print(log) - assert type(log[0]) == requests.exceptions.HTTPError - assert log[0].response.status_code == 429 - assert type(log[1]) == requests.exceptions.HTTPError - assert log[1].response.status_code == 500 - - @my_backoff - def httperror_with_400(self, log): - if len(log) == 1: - return True - response = requests.Response() - if len(log) == 0: - response.status_code = 400 - e = requests.exceptions.HTTPError(response=response) - log.append(e) - raise e - - def test_assert_not_retry(self): - log = [] - with pytest.raises(requests.exceptions.HTTPError): - self.httperror_with_400(log) - assert 1 == len(log) diff --git a/tests/tests_local_api.py b/tests/tests_local_api.py new file mode 100644 index 00000000..a2ca479d --- /dev/null +++ b/tests/tests_local_api.py @@ -0,0 +1,104 @@ +import pytest +import requests + +from annofabapi.api import _create_request_body_for_logger, my_backoff + + +class TestMyBackoff: + @my_backoff + def requestexception_connectionerror_then_true(self, log): + if len(log) == 2: + return True + + if len(log) == 0: + e = requests.exceptions.RequestException() + elif len(log) == 1: + e = ConnectionError() + log.append(e) + raise e + + def test_assert_retry(self): + log = [] + assert self.requestexception_connectionerror_then_true(log) is True + assert 2 == len(log) + print(log) + assert type(log[0]) == requests.exceptions.RequestException + assert type(log[1]) == ConnectionError + + @my_backoff + def chunkedencodingerror_requestsconnectionerror_then_true(self, log): + if len(log) == 2: + return True + if len(log) == 0: + e = requests.exceptions.ChunkedEncodingError() + log.append(e) + raise e + elif len(log) == 1: + e = requests.exceptions.ConnectionError() + log.append(e) + raise e + + def test_assert_retry2(self): + log = [] + assert self.chunkedencodingerror_requestsconnectionerror_then_true(log) is True + assert 2 == len(log) + print(log) + assert type(log[0]) == requests.exceptions.ChunkedEncodingError + assert type(log[1]) == requests.exceptions.ConnectionError + + @my_backoff + def httperror_then_true(self, log): + if len(log) == 2: + return True + response = requests.Response() + if len(log) == 0: + response.status_code = 429 + e = requests.exceptions.HTTPError(response=response) + elif len(log) == 1: + response.status_code = 500 + e = requests.exceptions.HTTPError(response=response) + log.append(e) + raise e + + def test_assert_retry_with_httperror(self): + log = [] + assert self.httperror_then_true(log) is True + assert 2 == len(log) + print(log) + assert type(log[0]) == requests.exceptions.HTTPError + assert log[0].response.status_code == 429 + assert type(log[1]) == requests.exceptions.HTTPError + assert log[1].response.status_code == 500 + + @my_backoff + def httperror_with_400(self, log): + if len(log) == 1: + return True + response = requests.Response() + if len(log) == 0: + response.status_code = 400 + e = requests.exceptions.HTTPError(response=response) + log.append(e) + raise e + + def test_assert_not_retry(self): + log = [] + with pytest.raises(requests.exceptions.HTTPError): + self.httperror_with_400(log) + assert 1 == len(log) + + +class Test__create_request_body_for_logger: + def test_data_dict(self): + actual = _create_request_body_for_logger( + {"foo": "1", "password": "x", "new_password": "y", "old_password": "z"} + ) + assert actual == {"foo": "1", "password": "***", "new_password": "***", "old_password": "***"} + + def test_data_dict2(self): + actual = _create_request_body_for_logger({"foo": "1"}) + assert actual == {"foo": "1"} + + def test_data_list(self): + actual = _create_request_body_for_logger([{"foo": "1"}]) + assert actual == [{"foo": "1"}]