annofabcli 1.107.1__py3-none-any.whl → 1.109.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,308 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import enum
5
+ import logging
6
+ import multiprocessing
7
+ import sys
8
+ from dataclasses import dataclass
9
+ from enum import Enum
10
+ from functools import partial
11
+ from pathlib import Path
12
+ from typing import Optional
13
+
14
+ import annofabapi
15
+ import pandas
16
+ from dataclasses_json import DataClassJsonMixin
17
+
18
+ import annofabcli
19
+ import annofabcli.common.cli
20
+ from annofabcli.common.cli import (
21
+ COMMAND_LINE_ERROR_STATUS_CODE,
22
+ PARALLELISM_CHOICES,
23
+ ArgumentParser,
24
+ CommandLine,
25
+ CommandLineWithConfirm,
26
+ build_annofabapi_resource_and_login,
27
+ get_json_from_args,
28
+ )
29
+ from annofabcli.common.facade import AnnofabApiFacade
30
+
31
+ logger = logging.getLogger(__name__)
32
+
33
+
34
+ class UpdateResult(Enum):
35
+ """更新結果の種類"""
36
+
37
+ SUCCESS = enum.auto()
38
+ """更新に成功した"""
39
+ SKIPPED = enum.auto()
40
+ """更新を実行しなかった(存在しないinput_data_id、ユーザー拒否等)"""
41
+ FAILED = enum.auto()
42
+ """更新を試みたが例外で失敗"""
43
+
44
+
45
+ @dataclass
46
+ class UpdatedInputData(DataClassJsonMixin):
47
+ """
48
+ 更新される入力データ
49
+ """
50
+
51
+ input_data_id: str
52
+ """更新対象の入力データを表すID"""
53
+ input_data_name: Optional[str] = None
54
+ """変更後の入力データ名(指定した場合のみ更新)"""
55
+ input_data_path: Optional[str] = None
56
+ """変更後の入力データパス(指定した場合のみ更新)"""
57
+
58
+
59
+ class UpdateInputDataMain(CommandLineWithConfirm):
60
+ def __init__(self, service: annofabapi.Resource, *, all_yes: bool = False) -> None:
61
+ self.service = service
62
+ CommandLineWithConfirm.__init__(self, all_yes)
63
+
64
+ def update_input_data(
65
+ self,
66
+ project_id: str,
67
+ input_data_id: str,
68
+ *,
69
+ new_input_data_name: Optional[str] = None,
70
+ new_input_data_path: Optional[str] = None,
71
+ input_data_index: Optional[int] = None,
72
+ ) -> UpdateResult:
73
+ """
74
+ 1個の入力データを更新します。
75
+ """
76
+ # ログメッセージの先頭の変数
77
+ log_prefix = f"input_data_id='{input_data_id}' :: "
78
+ if input_data_index is not None:
79
+ log_prefix = f"{input_data_index + 1}件目 :: {log_prefix}"
80
+
81
+ old_input_data = self.service.wrapper.get_input_data_or_none(project_id, input_data_id)
82
+ if old_input_data is None:
83
+ logger.warning(f"{log_prefix}入力データは存在しません。")
84
+ return UpdateResult.SKIPPED
85
+
86
+ # 更新する内容の確認メッセージを作成
87
+ changes = []
88
+ if new_input_data_name is not None:
89
+ changes.append(f"input_data_name='{old_input_data['input_data_name']}'を'{new_input_data_name}'に変更")
90
+ if new_input_data_path is not None:
91
+ changes.append(f"input_data_path='{old_input_data['input_data_path']}'を'{new_input_data_path}'に変更")
92
+
93
+ if len(changes) == 0:
94
+ logger.warning(f"{log_prefix}更新する内容が指定されていません。")
95
+ return UpdateResult.SKIPPED
96
+
97
+ change_message = "、".join(changes)
98
+ if not self.confirm_processing(f"{log_prefix}{change_message}しますか?"):
99
+ return UpdateResult.SKIPPED
100
+
101
+ request_body = old_input_data
102
+ request_body["last_updated_datetime"] = old_input_data["updated_datetime"]
103
+
104
+ if new_input_data_name is not None:
105
+ request_body["input_data_name"] = new_input_data_name
106
+ if new_input_data_path is not None:
107
+ request_body["input_data_path"] = new_input_data_path
108
+
109
+ self.service.api.put_input_data(project_id, input_data_id, request_body=request_body)
110
+ logger.debug(f"{log_prefix} :: 入力データを更新しました。 :: {changes}")
111
+ return UpdateResult.SUCCESS
112
+
113
+ def update_input_data_list_sequentially(
114
+ self,
115
+ project_id: str,
116
+ updated_input_data_list: list[UpdatedInputData],
117
+ ) -> None:
118
+ """複数の入力データを逐次的に更新します。"""
119
+ success_count = 0
120
+ skipped_count = 0 # 更新を実行しなかった個数
121
+ failed_count = 0 # 更新に失敗した個数
122
+
123
+ logger.info(f"{len(updated_input_data_list)} 件の入力データを更新します。")
124
+
125
+ for input_data_index, updated_input_data in enumerate(updated_input_data_list):
126
+ current_num = input_data_index + 1
127
+
128
+ # 進捗ログ出力
129
+ if current_num % 1000 == 0:
130
+ logger.info(f"{current_num} / {len(updated_input_data_list)} 件目の入力データを処理中...")
131
+
132
+ try:
133
+ result = self.update_input_data(
134
+ project_id,
135
+ updated_input_data.input_data_id,
136
+ new_input_data_name=updated_input_data.input_data_name,
137
+ new_input_data_path=updated_input_data.input_data_path,
138
+ input_data_index=input_data_index,
139
+ )
140
+ if result == UpdateResult.SUCCESS:
141
+ success_count += 1
142
+ elif result == UpdateResult.SKIPPED:
143
+ skipped_count += 1
144
+ except Exception:
145
+ logger.warning(f"{current_num}件目 :: input_data_id='{updated_input_data.input_data_id}'の入力データを更新するのに失敗しました。", exc_info=True)
146
+ failed_count += 1
147
+ continue
148
+
149
+ logger.info(f"{success_count} / {len(updated_input_data_list)} 件の入力データを更新しました。(成功: {success_count}件, スキップ: {skipped_count}件, 失敗: {failed_count}件)")
150
+
151
+ def _update_input_data_wrapper(self, args: tuple[int, UpdatedInputData], project_id: str) -> UpdateResult:
152
+ index, updated_input_data = args
153
+ try:
154
+ return self.update_input_data(
155
+ project_id,
156
+ input_data_id=updated_input_data.input_data_id,
157
+ new_input_data_name=updated_input_data.input_data_name,
158
+ new_input_data_path=updated_input_data.input_data_path,
159
+ input_data_index=index,
160
+ )
161
+ except Exception:
162
+ logger.warning(f"{index + 1}件目 :: input_data_id='{updated_input_data.input_data_id}'の入力データを更新するのに失敗しました。", exc_info=True)
163
+ return UpdateResult.FAILED
164
+
165
+ def update_input_data_list_in_parallel(
166
+ self,
167
+ project_id: str,
168
+ updated_input_data_list: list[UpdatedInputData],
169
+ parallelism: int,
170
+ ) -> None:
171
+ """複数の入力データを並列的に更新します。"""
172
+
173
+ logger.info(f"{len(updated_input_data_list)} 件の入力データを更新します。{parallelism}個のプロセスを使用して並列実行します。")
174
+
175
+ partial_func = partial(self._update_input_data_wrapper, project_id=project_id)
176
+ with multiprocessing.Pool(parallelism) as pool:
177
+ result_list = pool.map(partial_func, enumerate(updated_input_data_list))
178
+ success_count = len([e for e in result_list if e == UpdateResult.SUCCESS])
179
+ skipped_count = len([e for e in result_list if e == UpdateResult.SKIPPED])
180
+ failed_count = len([e for e in result_list if e == UpdateResult.FAILED])
181
+
182
+ logger.info(f"{success_count} / {len(updated_input_data_list)} 件の入力データを更新しました。(成功: {success_count}件, スキップ: {skipped_count}件, 失敗: {failed_count}件)")
183
+
184
+
185
+ def create_updated_input_data_list_from_dict(input_data_dict_list: list[dict[str, str]]) -> list[UpdatedInputData]:
186
+ return [UpdatedInputData.from_dict(e) for e in input_data_dict_list]
187
+
188
+
189
+ def create_updated_input_data_list_from_csv(csv_file: Path) -> list[UpdatedInputData]:
190
+ """入力データの情報が記載されているCSVを読み込み、UpdatedInputDataのlistを返します。
191
+ CSVには以下の列が存在します。
192
+ * input_data_id (必須)
193
+ * input_data_name (任意)
194
+ * input_data_path (任意)
195
+
196
+ Args:
197
+ csv_file (Path): CSVファイルのパス
198
+
199
+ Returns:
200
+ 更新対象の入力データのlist
201
+ """
202
+ df_input_data = pandas.read_csv(
203
+ csv_file,
204
+ # 文字列として読み込むようにする
205
+ dtype={"input_data_id": "string", "input_data_name": "string", "input_data_path": "string"},
206
+ )
207
+
208
+ input_data_dict_list = df_input_data.to_dict("records")
209
+ return [UpdatedInputData.from_dict(e) for e in input_data_dict_list]
210
+
211
+
212
+ CLI_COMMON_MESSAGE = "annofabcli input_data update: error:"
213
+
214
+
215
+ class UpdateInputData(CommandLine):
216
+ @staticmethod
217
+ def validate(args: argparse.Namespace) -> bool:
218
+ if args.parallelism is not None and not args.yes:
219
+ print( # noqa: T201
220
+ f"{CLI_COMMON_MESSAGE} argument --parallelism: '--parallelism'を指定するときは、必ず ``--yes`` を指定してください。",
221
+ file=sys.stderr,
222
+ )
223
+ return False
224
+
225
+ return True
226
+
227
+ def main(self) -> None:
228
+ args = self.args
229
+ if not self.validate(args):
230
+ sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
231
+
232
+ main_obj = UpdateInputDataMain(self.service, all_yes=self.all_yes)
233
+
234
+ if args.csv is not None:
235
+ updated_input_data_list = create_updated_input_data_list_from_csv(args.csv)
236
+
237
+ elif args.json is not None:
238
+ input_data_dict_list = get_json_from_args(args.json)
239
+ if not isinstance(input_data_dict_list, list):
240
+ print(f"{CLI_COMMON_MESSAGE} JSON形式が不正です。オブジェクトの配列を指定してください。", file=sys.stderr) # noqa: T201
241
+ sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
242
+ updated_input_data_list = create_updated_input_data_list_from_dict(input_data_dict_list)
243
+ else:
244
+ raise RuntimeError("argparse により相互排他が保証されているため、ここには到達しません")
245
+
246
+ project_id: str = args.project_id
247
+ if args.parallelism is not None:
248
+ main_obj.update_input_data_list_in_parallel(project_id, updated_input_data_list=updated_input_data_list, parallelism=args.parallelism)
249
+ else:
250
+ main_obj.update_input_data_list_sequentially(project_id, updated_input_data_list=updated_input_data_list)
251
+
252
+
253
+ def main(args: argparse.Namespace) -> None:
254
+ service = build_annofabapi_resource_and_login(args)
255
+ facade = AnnofabApiFacade(service)
256
+ UpdateInputData(service, facade, args).main()
257
+
258
+
259
+ def parse_args(parser: argparse.ArgumentParser) -> None:
260
+ argument_parser = ArgumentParser(parser)
261
+ argument_parser.add_project_id()
262
+
263
+ file_group = parser.add_mutually_exclusive_group(required=True)
264
+ file_group.add_argument(
265
+ "--csv",
266
+ type=Path,
267
+ help=(
268
+ "更新対象の入力データと更新後の値が記載されたCSVファイルのパスを指定します。\n"
269
+ "CSVのフォーマットは以下の通りです。"
270
+ "\n"
271
+ " * ヘッダ行あり, カンマ区切り\n"
272
+ " * input_data_id (required)\n"
273
+ " * input_data_name (optional)\n"
274
+ " * input_data_path (optional)\n"
275
+ "更新しないプロパティは、セルの値を空欄にしてください。\n"
276
+ ),
277
+ )
278
+
279
+ JSON_SAMPLE = '[{"input_data_id":"id1","input_data_name":"new_name1"},{"input_data_id":"id2","input_data_path":"new_path2"}]' # noqa: N806
280
+ file_group.add_argument(
281
+ "--json",
282
+ type=str,
283
+ help=(
284
+ "更新対象の入力データと更新後の値をJSON形式で指定します。\n"
285
+ "JSONの各キーは ``--csv`` に渡すCSVの各列に対応しています。\n"
286
+ "``file://`` を先頭に付けるとjsonファイルを指定できます。\n"
287
+ f"(ex) ``{JSON_SAMPLE}`` \n"
288
+ "更新しないプロパティは、キーを記載しないか値をnullにしてください。\n"
289
+ ),
290
+ )
291
+
292
+ parser.add_argument(
293
+ "--parallelism",
294
+ type=int,
295
+ choices=PARALLELISM_CHOICES,
296
+ help="使用するプロセス数(並列度)。指定しない場合は、逐次的に処理します。指定する場合は ``--yes`` も一緒に指定する必要があります。",
297
+ )
298
+
299
+ parser.set_defaults(subcommand_func=main)
300
+
301
+
302
+ def add_parser(subparsers: Optional[argparse._SubParsersAction] = None) -> argparse.ArgumentParser:
303
+ subcommand_name = "update"
304
+ subcommand_help = "入力データの名前または入力データのパスを更新します。"
305
+ epilog = "オーナロールを持つユーザで実行してください。"
306
+ parser = annofabcli.common.cli.add_parser(subparsers, subcommand_name, subcommand_help, epilog=epilog)
307
+ parse_args(parser)
308
+ return parser
@@ -0,0 +1,151 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import logging
5
+ import sys
6
+ import uuid
7
+ from enum import Enum
8
+ from typing import Any, Optional
9
+
10
+ from annofabapi.models import InputDataType
11
+ from annofabapi.plugin import EditorPluginId, ExtendSpecsPluginId
12
+
13
+ import annofabcli
14
+ from annofabcli.common.cli import (
15
+ COMMAND_LINE_ERROR_STATUS_CODE,
16
+ CommandLine,
17
+ build_annofabapi_resource_and_login,
18
+ get_json_from_args,
19
+ )
20
+ from annofabcli.common.facade import AnnofabApiFacade
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ class CustomProjectType(Enum):
26
+ """
27
+ カスタムプロジェクトの種類
28
+ """
29
+
30
+ THREE_DIMENSION = "3d"
31
+ """3次元データ"""
32
+
33
+
34
+ class CreateProject(CommandLine):
35
+ def create_project( # noqa: ANN201
36
+ self,
37
+ organization: str,
38
+ title: str,
39
+ input_data_type: InputDataType,
40
+ *,
41
+ project_id: Optional[str],
42
+ overview: Optional[str],
43
+ editor_plugin_id: Optional[str],
44
+ custom_project_type: Optional[CustomProjectType],
45
+ configuration: Optional[dict[str, Any]],
46
+ ):
47
+ new_project_id = project_id if project_id is not None else str(uuid.uuid4())
48
+ if configuration is None:
49
+ configuration = {}
50
+
51
+ if input_data_type == InputDataType.CUSTOM and custom_project_type is not None:
52
+ assert editor_plugin_id is None
53
+ editor_plugin_id = EditorPluginId.THREE_DIMENSION.value
54
+ configuration.update({"extended_specs_plugin_id": ExtendSpecsPluginId.THREE_DIMENSION.value})
55
+
56
+ configuration.update({"plugin_id": editor_plugin_id})
57
+
58
+ request_body = {
59
+ "title": title,
60
+ "organization_name": organization,
61
+ "input_data_type": input_data_type.value,
62
+ "overview": overview,
63
+ "status": "active",
64
+ "configuration": configuration,
65
+ }
66
+ new_project, _ = self.service.api.put_project(new_project_id, request_body=request_body)
67
+ logger.info(
68
+ f"'{organization}'組織に、project_id='{new_project['project_id']}'のプロジェクトを作成しました。 :: title='{new_project['title']}', input_data_type='{new_project['input_data_type']}'"
69
+ )
70
+
71
+ COMMON_MESSAGE = "annofabcli project create: error:"
72
+
73
+ def validate(self, args: argparse.Namespace) -> bool:
74
+ if args.input_data_type == InputDataType.CUSTOM.value: # noqa: SIM102
75
+ if args.plugin_id is None and args.custom_project_type is None:
76
+ print( # noqa: T201
77
+ f"{self.COMMON_MESSAGE} '--input_data_type custom' を指定した場合は、'--plugin_id' または '--custom_project_type' が必須です。",
78
+ file=sys.stderr,
79
+ )
80
+ return False
81
+
82
+ return True
83
+
84
+ def main(self) -> None:
85
+ args = self.args
86
+ if not self.validate(args):
87
+ sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
88
+
89
+ self.create_project(
90
+ args.organization,
91
+ args.title,
92
+ InputDataType(args.input_data_type),
93
+ project_id=args.project_id,
94
+ overview=args.overview,
95
+ editor_plugin_id=args.plugin_id,
96
+ custom_project_type=CustomProjectType(args.custom_project_type) if args.custom_project_type is not None else None,
97
+ configuration=get_json_from_args(args.configuration),
98
+ )
99
+
100
+
101
+ def main(args: argparse.Namespace) -> None:
102
+ service = build_annofabapi_resource_and_login(args)
103
+ facade = AnnofabApiFacade(service)
104
+ CreateProject(service, facade, args).main()
105
+
106
+
107
+ def parse_args(parser: argparse.ArgumentParser) -> None:
108
+ parser.add_argument("-org", "--organization", type=str, required=True, help="プロジェクトの所属先組織")
109
+
110
+ parser.add_argument("--title", type=str, required=True, help="作成するプロジェクトのタイトル")
111
+ parser.add_argument(
112
+ "--input_data_type",
113
+ type=str,
114
+ choices=[e.value for e in InputDataType],
115
+ required=True,
116
+ help=f"プロジェクトに登録する入力データの種類\n\n * {InputDataType.IMAGE.value} : 画像\n * {InputDataType.MOVIE.value} : 動画\n * {InputDataType.CUSTOM.value} : カスタム(点群など)",
117
+ )
118
+
119
+ parser.add_argument("-p", "--project_id", type=str, required=False, help="作成するプロジェクトのproject_id。未指定の場合はUUIDv4になります。")
120
+ parser.add_argument("--overview", type=str, help="作成するプロジェクトの概要")
121
+
122
+ group = parser.add_mutually_exclusive_group()
123
+ group.add_argument("--plugin_id", type=str, help="アノテーションエディタプラグインのplugin_id")
124
+ group.add_argument(
125
+ "--custom_project_type",
126
+ type=str,
127
+ choices=[e.value for e in CustomProjectType],
128
+ help="カスタムプロジェクトの種類。 ``--input_data_type custom`` を指定したときのみ有効です。"
129
+ "指定した値に対応するエディタプラグインが適用されるため、 `--plugin_id`` と同時には指定できません。\n"
130
+ " * 3d : 3次元データ",
131
+ )
132
+
133
+ parser.add_argument(
134
+ "--configuration",
135
+ type=str,
136
+ help="プロジェクトの設定情報。JSON形式で指定します。"
137
+ "JSONの構造については https://annofab.com/docs/api/#operation/putProject のリクエストボディを参照してください。\n"
138
+ "``file://`` を先頭に付けると、JSON形式のファイルを指定できます。",
139
+ )
140
+
141
+ parser.set_defaults(subcommand_func=main)
142
+
143
+
144
+ def add_parser(subparsers: Optional[argparse._SubParsersAction] = None) -> argparse.ArgumentParser:
145
+ subcommand_name = "create"
146
+ subcommand_help = "プロジェクトを作成します。"
147
+ epilog = "組織管理者、組織オーナを持つユーザで実行してください。"
148
+
149
+ parser = annofabcli.common.cli.add_parser(subparsers, subcommand_name, subcommand_help, epilog=epilog)
150
+ parse_args(parser)
151
+ return parser
@@ -1,151 +1,36 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import argparse
4
- import logging
5
4
  import sys
6
- import uuid
7
- from enum import Enum
8
- from typing import Any, Optional
9
-
10
- from annofabapi.models import InputDataType
11
- from annofabapi.plugin import EditorPluginId, ExtendSpecsPluginId
5
+ from logging import getLogger
6
+ from typing import Optional
12
7
 
13
8
  import annofabcli
14
- from annofabcli.common.cli import (
15
- COMMAND_LINE_ERROR_STATUS_CODE,
16
- CommandLine,
17
- build_annofabapi_resource_and_login,
18
- get_json_from_args,
19
- )
20
- from annofabcli.common.facade import AnnofabApiFacade
21
-
22
- logger = logging.getLogger(__name__)
23
-
24
-
25
- class CustomProjectType(Enum):
26
- """
27
- カスタムプロジェクトの種類
28
- """
29
-
30
- THREE_DIMENSION = "3d"
31
- """3次元データ"""
32
-
33
-
34
- class PutProject(CommandLine):
35
- def put_project( # noqa: ANN201
36
- self,
37
- organization: str,
38
- title: str,
39
- input_data_type: InputDataType,
40
- *,
41
- project_id: Optional[str],
42
- overview: Optional[str],
43
- editor_plugin_id: Optional[str],
44
- custom_project_type: Optional[CustomProjectType],
45
- configuration: Optional[dict[str, Any]],
46
- ):
47
- new_project_id = project_id if project_id is not None else str(uuid.uuid4())
48
- if configuration is None:
49
- configuration = {}
50
-
51
- if input_data_type == InputDataType.CUSTOM and custom_project_type is not None:
52
- assert editor_plugin_id is None
53
- editor_plugin_id = EditorPluginId.THREE_DIMENSION.value
54
- configuration.update({"extended_specs_plugin_id": ExtendSpecsPluginId.THREE_DIMENSION.value})
55
-
56
- configuration.update({"plugin_id": editor_plugin_id})
57
-
58
- request_body = {
59
- "title": title,
60
- "organization_name": organization,
61
- "input_data_type": input_data_type.value,
62
- "overview": overview,
63
- "status": "active",
64
- "configuration": configuration,
65
- }
66
- new_project, _ = self.service.api.put_project(new_project_id, request_body=request_body)
67
- logger.info(
68
- f"'{organization}'組織に、project_id='{new_project['project_id']}'のプロジェクトを作成しました。 :: title='{new_project['title']}', input_data_type='{new_project['input_data_type']}'"
69
- )
9
+ from annofabcli.project import create_project
70
10
 
71
- COMMON_MESSAGE = "annofabcli project put: error:"
72
-
73
- def validate(self, args: argparse.Namespace) -> bool:
74
- if args.input_data_type == InputDataType.CUSTOM.value: # noqa: SIM102
75
- if args.plugin_id is None and args.custom_project_type is None:
76
- print( # noqa: T201
77
- f"{self.COMMON_MESSAGE} '--input_data_type custom' を指定した場合は、'--plugin_id' または '--custom_project_type' が必須です。",
78
- file=sys.stderr,
79
- )
80
- return False
81
-
82
- return True
83
-
84
- def main(self) -> None:
85
- args = self.args
86
- if not self.validate(args):
87
- sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
88
-
89
- self.put_project(
90
- args.organization,
91
- args.title,
92
- InputDataType(args.input_data_type),
93
- project_id=args.project_id,
94
- overview=args.overview,
95
- editor_plugin_id=args.plugin_id,
96
- custom_project_type=CustomProjectType(args.custom_project_type) if args.custom_project_type is not None else None,
97
- configuration=get_json_from_args(args.configuration),
98
- )
11
+ logger = getLogger(__name__)
99
12
 
100
13
 
101
14
  def main(args: argparse.Namespace) -> None:
102
- service = build_annofabapi_resource_and_login(args)
103
- facade = AnnofabApiFacade(service)
104
- PutProject(service, facade, args).main()
15
+ print("[DEPRECATED] :: `project put` コマンドは非推奨です。代わりに `project create` コマンドを使用してください。`project put` コマンドは2026年01月01日以降に廃止予定です。", file=sys.stderr) # noqa: T201
16
+ # create_project.py の実装を使用
17
+ create_project.main(args)
105
18
 
106
19
 
107
20
  def parse_args(parser: argparse.ArgumentParser) -> None:
108
- parser.add_argument("-org", "--organization", type=str, required=True, help="プロジェクトの所属先組織")
109
-
110
- parser.add_argument("--title", type=str, required=True, help="作成するプロジェクトのタイトル")
111
- parser.add_argument(
112
- "--input_data_type",
113
- type=str,
114
- choices=[e.value for e in InputDataType],
115
- required=True,
116
- help=f"プロジェクトに登録する入力データの種類\n\n * {InputDataType.IMAGE.value} : 画像\n * {InputDataType.MOVIE.value} : 動画\n * {InputDataType.CUSTOM.value} : カスタム(点群など)",
117
- )
118
-
119
- parser.add_argument("-p", "--project_id", type=str, required=False, help="作成するプロジェクトのproject_id。未指定の場合はUUIDv4になります。")
120
- parser.add_argument("--overview", type=str, help="作成するプロジェクトの概要")
121
-
122
- group = parser.add_mutually_exclusive_group()
123
- group.add_argument("--plugin_id", type=str, help="アノテーションエディタプラグインのplugin_id")
124
- group.add_argument(
125
- "--custom_project_type",
126
- type=str,
127
- choices=[e.value for e in CustomProjectType],
128
- help="カスタムプロジェクトの種類。 ``--input_data_type custom`` を指定したときのみ有効です。"
129
- "指定した値に対応するエディタプラグインが適用されるため、 `--plugin_id`` と同時には指定できません。\n"
130
- " * 3d : 3次元データ",
131
- )
132
-
133
- parser.add_argument(
134
- "--configuration",
135
- type=str,
136
- help="プロジェクトの設定情報。JSON形式で指定します。"
137
- "JSONの構造については https://annofab.com/docs/api/#operation/putProject のリクエストボディを参照してください。\n"
138
- "``file://`` を先頭に付けると、JSON形式のファイルを指定できます。",
139
- )
140
-
21
+ # create_project.py のparse_argsと同じ実装を使用
22
+ create_project.parse_args(parser)
23
+ # main関数のみ差し替え
141
24
  parser.set_defaults(subcommand_func=main)
142
25
 
143
26
 
144
27
  def add_parser(subparsers: Optional[argparse._SubParsersAction] = None) -> argparse.ArgumentParser:
145
28
  subcommand_name = "put"
146
- subcommand_help = "プロジェクトを作成します。"
29
+ subcommand_help = "[DEPRECATED] プロジェクトを作成します。"
30
+ subcommand_description = subcommand_help + "\n`project put` コマンドは非推奨です。代わりに 'project create'コマンドを使用してください。`project put` コマンドは2026年01月01日以降に廃止予定です。"
31
+
147
32
  epilog = "組織管理者、組織オーナを持つユーザで実行してください。"
148
33
 
149
- parser = annofabcli.common.cli.add_parser(subparsers, subcommand_name, subcommand_help, epilog=epilog)
34
+ parser = annofabcli.common.cli.add_parser(subparsers, subcommand_name, subcommand_help, description=subcommand_description, epilog=epilog)
150
35
  parse_args(parser)
151
36
  return parser
@@ -4,9 +4,12 @@ from typing import Optional
4
4
  import annofabcli.project.change_organization_of_project
5
5
  import annofabcli.project.change_project_status
6
6
  import annofabcli.project.copy_project
7
+ import annofabcli.project.create_project
7
8
  import annofabcli.project.diff_projects
8
9
  import annofabcli.project.list_project
9
10
  import annofabcli.project.put_project
11
+ import annofabcli.project.update_configuration
12
+ import annofabcli.project.update_project
10
13
  from annofabcli.common.cli import add_parser as common_add_parser
11
14
 
12
15
 
@@ -17,9 +20,12 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
17
20
  annofabcli.project.change_organization_of_project.add_parser(subparsers)
18
21
  annofabcli.project.change_project_status.add_parser(subparsers)
19
22
  annofabcli.project.copy_project.add_parser(subparsers)
23
+ annofabcli.project.create_project.add_parser(subparsers)
20
24
  annofabcli.project.diff_projects.add_parser(subparsers)
21
25
  annofabcli.project.list_project.add_parser(subparsers)
22
26
  annofabcli.project.put_project.add_parser(subparsers)
27
+ annofabcli.project.update_configuration.add_parser(subparsers)
28
+ annofabcli.project.update_project.add_parser(subparsers)
23
29
 
24
30
 
25
31
  def add_parser(subparsers: Optional[argparse._SubParsersAction] = None) -> argparse.ArgumentParser: