Skip to content

Commit

Permalink
Merge pull request #147 from kurusugawa-computer/support-annofab-pat
Browse files Browse the repository at this point in the history
Annofabのパーソナルアクセストークンに対応しました
  • Loading branch information
seraphr authored Oct 10, 2024
2 parents 1f7e5af + 420234e commit ec2a578
Show file tree
Hide file tree
Showing 8 changed files with 524 additions and 249 deletions.
3 changes: 2 additions & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
"--init",
"--net=host",
"--env=ANNOFAB_USER_ID",
"--env=ANNOFAB_PASSWORD"
"--env=ANNOFAB_PASSWORD",
"--env=ANNOFAB_PAT"
],
"workspaceMount": "source=${localWorkspaceFolder},target=/workspaces,type=bind,consistency=cached",
"workspaceFolder": "/workspaces",
Expand Down
5 changes: 4 additions & 1 deletion .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,7 @@ disable=
cyclic-import,
similarities, # Similar lines in 2 files
# ===== 修正すべき項目 =====
empty-docstring,
empty-docstring,
too-many-positional-arguments, # コマンドに対応するメソッドでは引数の数が多くなるため
non-ascii-name, # テストメソッドの名前に日本語を利用するため

54 changes: 46 additions & 8 deletions anno3d/annofab/client.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,64 @@
from contextlib import contextmanager
from typing import Generator, Optional
from dataclasses import dataclass
from typing import Generator, Optional, Union

import annofabapi
from annofabapi import AnnofabApi, Resource

from anno3d.util.type_util import assert_noreturn


@dataclass(frozen=True)
class IdPass:
"""ユーザIDとパスワード"""

user_id: str
password: str


@dataclass(frozen=True)
class Pat:
"""Personal Access Token"""

token: str


Credential = Union[IdPass, Pat]


class ClientLoader:
_annofab_id: str
_annofab_pass: str
_annofab_endpoint: Optional[str]
_credential: Credential

def __init__(self, annofab_id: str, annofab_pass: str, endpoint: Optional[str]) -> None:
self._annofab_id = annofab_id
self._annofab_pass = annofab_pass
def __init__(
self,
credential: Credential,
endpoint: Optional[str],
) -> None:
self._credential = credential
self._annofab_endpoint = endpoint

@contextmanager
def open_resource(self) -> Generator[Resource, None, None]:
endpoint = (
self._annofab_endpoint if self._annofab_endpoint is not None else annofabapi.resource.DEFAULT_ENDPOINT_URL
)
resource = annofabapi.build(self._annofab_id, self._annofab_pass, endpoint_url=endpoint)
annofab_id = None
annofab_pass = None
annofab_pat = None
if isinstance(self._credential, IdPass):
annofab_id = self._credential.user_id
annofab_pass = self._credential.password
elif isinstance(self._credential, Pat):
annofab_pat = self._credential.token
else:
assert_noreturn(self._credential)

resource = annofabapi.build(
annofab_id,
annofab_pass,
annofab_pat,
endpoint_url=endpoint,
)
try:
yield resource
finally:
Expand Down
255 changes: 173 additions & 82 deletions anno3d/app.py

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions anno3d/util/type_util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from typing import NoReturn


def assert_noreturn(x: NoReturn) -> NoReturn:
"""python 3.11 以降に追加されたassert_neverの代わり"""
raise AssertionError(f"Invalid value: {x!r}") # x!rは repr(x)と等価
324 changes: 168 additions & 156 deletions poetry.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ packages = [

[tool.poetry.dependencies]
python = "^3.9"
annofabapi = ">=0.71.1"
annofabapi = "^1"
dataclasses-json = ">=0.5.7,<1"
fire = ">=0.3.1,<1"
more-itertools = "^8.5.0"
Expand Down
124 changes: 124 additions & 0 deletions tests/test_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import os

import pytest

from anno3d.annofab.client import IdPass, Pat
from anno3d.app import InvalidCredentialError, get_annofab_credential


class Test_get_annofab_credential:
@pytest.fixture(autouse=True)
def setup(self):
os.environ.pop("ANNOFAB_USER_ID", None)
os.environ.pop("ANNOFAB_PASSWORD", None)
os.environ.pop("ANNOFAB_PAT", None)

def test_コマンドライン引数で指定されたパーソナルアクセストークンの優先順位は1番目(
self,
):
os.environ["ANNOFAB_USER_ID"] = "env_user_id"
os.environ["ANNOFAB_PAT"] = "env_pat"
credential = get_annofab_credential(
cli_annofab_id="cli_user_id",
cli_annofab_pass="cli_password",
cli_annofab_pat="cli_pat",
)
assert isinstance(credential, Pat)
assert credential.token == "cli_pat"

def test_コマンドライン引数で指定されたユーザーIDの優先順位は2番目(
self,
):
os.environ["ANNOFAB_USER_ID"] = "env_user_id"
os.environ["ANNOFAB_PAT"] = "env_pat"
credential = get_annofab_credential(
cli_annofab_id="cli_user_id",
cli_annofab_pass="cli_password",
cli_annofab_pat=None,
)
assert isinstance(credential, IdPass)
assert credential.user_id == "cli_user_id"
assert credential.password == "cli_password"

def test_環境変数で指定されたパーソナルアクセストークンの優先順位は3番目(
self,
):
os.environ["ANNOFAB_USER_ID"] = "env_user_id"
os.environ["ANNOFAB_PAT"] = "env_pat"
credential = get_annofab_credential(
cli_annofab_id=None,
cli_annofab_pass="cli_password",
cli_annofab_pat=None,
)
assert isinstance(credential, Pat)
assert credential.token == "env_pat"

def test_環境変数で指定されたユーザーIDの優先順位は4番目(
self,
):
os.environ["ANNOFAB_USER_ID"] = "env_user_id"
credential = get_annofab_credential(
cli_annofab_id=None,
cli_annofab_pass="cli_password",
cli_annofab_pat=None,
)
assert isinstance(credential, IdPass)
assert credential.user_id == "env_user_id"
assert credential.password == "cli_password"

def test_コマンドライン引数で指定されたパスワードは環境変数で指定されたパスワードより優先順位が高い(
self,
):
os.environ["ANNOFAB_PASSWORD"] = "env_password"
credential = get_annofab_credential(
cli_annofab_id="cli_user_id",
cli_annofab_pass="cli_password",
cli_annofab_pat=None,
)
assert isinstance(credential, IdPass)
assert credential.user_id == "cli_user_id"
assert credential.password == "cli_password"

def test_環境変数で指定されたパスワードが参照されること(
self,
):
os.environ["ANNOFAB_PASSWORD"] = "env_password"
credential = get_annofab_credential(
cli_annofab_id="cli_user_id",
cli_annofab_pass=None,
cli_annofab_pat=None,
)
assert isinstance(credential, IdPass)
assert credential.user_id == "cli_user_id"
assert credential.password == "env_password"

def test_コマンドライン引数でユーザーIDが指定されている状態でパスワードが指定されないとInvalidCredentialErrorがraiseされる(
self,
):
with pytest.raises(InvalidCredentialError):
get_annofab_credential(
cli_annofab_id="cli_user_id",
cli_annofab_pass=None,
cli_annofab_pat=None,
)

def test_環境変数でユーザーIDが指定されている状態でパスワードが指定されないとInvalidCredentialErrorがraiseされる(
self,
):
os.environ["ANNOFAB_USER_ID"] = "env_user_id"
with pytest.raises(InvalidCredentialError):
get_annofab_credential(
cli_annofab_id=None,
cli_annofab_pass=None,
cli_annofab_pat=None,
)

def test_ユーザーIDとパーソナルアクセストークンの両方が指定されていないとInvalidCredentialErrorがraiseされる(
self,
):
with pytest.raises(InvalidCredentialError):
get_annofab_credential(
cli_annofab_id=None,
cli_annofab_pass=None,
cli_annofab_pat=None,
)

0 comments on commit ec2a578

Please sign in to comment.