cvat-cli 2.64.0__tar.gz → 2.66.0__tar.gz

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.
Files changed (25) hide show
  1. {cvat_cli-2.64.0 → cvat_cli-2.66.0}/PKG-INFO +6 -2
  2. {cvat_cli-2.64.0 → cvat_cli-2.66.0}/README.md +4 -0
  3. {cvat_cli-2.64.0 → cvat_cli-2.66.0}/pyproject.toml +1 -1
  4. cvat_cli-2.66.0/src/cvat_cli/_internal/command_base.py +302 -0
  5. {cvat_cli-2.64.0 → cvat_cli-2.66.0}/src/cvat_cli/_internal/commands_projects.py +33 -1
  6. {cvat_cli-2.64.0 → cvat_cli-2.66.0}/src/cvat_cli/_internal/commands_tasks.py +18 -145
  7. cvat_cli-2.66.0/src/cvat_cli/version.py +1 -0
  8. {cvat_cli-2.64.0 → cvat_cli-2.66.0}/src/cvat_cli.egg-info/PKG-INFO +6 -2
  9. {cvat_cli-2.64.0 → cvat_cli-2.66.0}/src/cvat_cli.egg-info/requires.txt +1 -1
  10. cvat_cli-2.64.0/src/cvat_cli/_internal/command_base.py +0 -124
  11. cvat_cli-2.64.0/src/cvat_cli/version.py +0 -1
  12. {cvat_cli-2.64.0 → cvat_cli-2.66.0}/setup.cfg +0 -0
  13. {cvat_cli-2.64.0 → cvat_cli-2.66.0}/src/cvat_cli/__init__.py +0 -0
  14. {cvat_cli-2.64.0 → cvat_cli-2.66.0}/src/cvat_cli/__main__.py +0 -0
  15. {cvat_cli-2.64.0 → cvat_cli-2.66.0}/src/cvat_cli/_internal/__init__.py +0 -0
  16. {cvat_cli-2.64.0 → cvat_cli-2.66.0}/src/cvat_cli/_internal/agent.py +0 -0
  17. {cvat_cli-2.64.0 → cvat_cli-2.66.0}/src/cvat_cli/_internal/commands_all.py +0 -0
  18. {cvat_cli-2.64.0 → cvat_cli-2.66.0}/src/cvat_cli/_internal/commands_functions.py +0 -0
  19. {cvat_cli-2.64.0 → cvat_cli-2.66.0}/src/cvat_cli/_internal/common.py +0 -0
  20. {cvat_cli-2.64.0 → cvat_cli-2.66.0}/src/cvat_cli/_internal/parsers.py +0 -0
  21. {cvat_cli-2.64.0 → cvat_cli-2.66.0}/src/cvat_cli/_internal/utils.py +0 -0
  22. {cvat_cli-2.64.0 → cvat_cli-2.66.0}/src/cvat_cli.egg-info/SOURCES.txt +0 -0
  23. {cvat_cli-2.64.0 → cvat_cli-2.66.0}/src/cvat_cli.egg-info/dependency_links.txt +0 -0
  24. {cvat_cli-2.64.0 → cvat_cli-2.66.0}/src/cvat_cli.egg-info/entry_points.txt +0 -0
  25. {cvat_cli-2.64.0 → cvat_cli-2.66.0}/src/cvat_cli.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cvat-cli
3
- Version: 2.64.0
3
+ Version: 2.66.0
4
4
  Summary: Command-line client for CVAT
5
5
  Author-email: "CVAT.ai Corporation" <support@cvat.ai>
6
6
  License-Expression: MIT
@@ -9,7 +9,7 @@ Classifier: Programming Language :: Python :: 3
9
9
  Classifier: Operating System :: OS Independent
10
10
  Requires-Python: >=3.10
11
11
  Description-Content-Type: text/markdown
12
- Requires-Dist: cvat-sdk==2.64.0
12
+ Requires-Dist: cvat-sdk==2.66.0
13
13
  Requires-Dist: attrs>=24.2.0
14
14
  Requires-Dist: Pillow>=10.3.0
15
15
 
@@ -22,8 +22,12 @@ comprehensive CVAT administration tool in the future.
22
22
  The following subcommands are supported:
23
23
 
24
24
  - Projects:
25
+ - `backup` - back up a project
25
26
  - `create` - create a new project
27
+ - `create-from-backup` - create a project from a backup file
26
28
  - `delete` - delete projects
29
+ - `export-dataset` - export a project as a dataset
30
+ - `import-dataset` - import annotations into a project from a dataset
27
31
  - `ls` - list all projects
28
32
 
29
33
  - Tasks:
@@ -7,8 +7,12 @@ comprehensive CVAT administration tool in the future.
7
7
  The following subcommands are supported:
8
8
 
9
9
  - Projects:
10
+ - `backup` - back up a project
10
11
  - `create` - create a new project
12
+ - `create-from-backup` - create a project from a backup file
11
13
  - `delete` - delete projects
14
+ - `export-dataset` - export a project as a dataset
15
+ - `import-dataset` - import annotations into a project from a dataset
12
16
  - `ls` - list all projects
13
17
 
14
18
  - Tasks:
@@ -16,7 +16,7 @@ classifiers = [
16
16
  ]
17
17
  requires-python = ">=3.10"
18
18
  dependencies = [
19
- "cvat-sdk==2.64.0",
19
+ "cvat-sdk==2.66.0",
20
20
 
21
21
  "attrs>=24.2.0",
22
22
  "Pillow>=10.3.0",
@@ -0,0 +1,302 @@
1
+ # Copyright (C) CVAT.ai Corporation
2
+ #
3
+ # SPDX-License-Identifier: MIT
4
+
5
+ import argparse
6
+ import json
7
+ import os
8
+ import textwrap
9
+ import types
10
+ from abc import ABCMeta, abstractmethod
11
+ from collections.abc import Callable, Mapping, Sequence
12
+ from typing import Protocol
13
+
14
+ from attr.converters import to_bool
15
+ from cvat_sdk import Client
16
+ from cvat_sdk.core.helpers import DeferredTqdmProgressReporter
17
+ from cvat_sdk.core.proxies.types import Location
18
+
19
+
20
+ class Command(Protocol):
21
+ @property
22
+ def description(self) -> str: ...
23
+
24
+ def configure_parser(self, parser: argparse.ArgumentParser) -> None: ...
25
+
26
+ # The exact parameters accepted by `execute` vary between commands,
27
+ # so we're forced to declare it like this instead of as a method.
28
+ @property
29
+ def execute(self) -> Callable[..., None]: ...
30
+
31
+
32
+ class CommandGroup:
33
+ def __init__(self, *, description: str) -> None:
34
+ self._commands: dict[str, Command] = {}
35
+ self.description = description
36
+
37
+ def command_class(self, name: str):
38
+ def decorator(cls: type):
39
+ self._commands[name] = cls()
40
+ return cls
41
+
42
+ return decorator
43
+
44
+ def add_command(self, name: str, command: Command) -> None:
45
+ self._commands[name] = command
46
+
47
+ @property
48
+ def commands(self) -> Mapping[str, Command]:
49
+ return types.MappingProxyType(self._commands)
50
+
51
+ def configure_parser(self, parser: argparse.ArgumentParser) -> None:
52
+ subparsers = parser.add_subparsers(required=True)
53
+
54
+ for name, command in self._commands.items():
55
+ subparser = subparsers.add_parser(name, description=command.description)
56
+ subparser.set_defaults(_executor=command.execute)
57
+ command.configure_parser(subparser)
58
+
59
+ def execute(self) -> None:
60
+ # It should be impossible for a command group to be executed,
61
+ # because configure_parser requires that a subcommand is specified.
62
+ assert False, "unreachable code"
63
+
64
+
65
+ class DeprecatedAlias:
66
+ def __init__(self, command: Command, replacement: str) -> None:
67
+ self._command = command
68
+ self._replacement = replacement
69
+
70
+ @property
71
+ def description(self) -> str:
72
+ return textwrap.dedent(f"""\
73
+ {self._command.description}
74
+ (Deprecated; use "{self._replacement}" instead.)
75
+ """)
76
+
77
+ def configure_parser(self, parser: argparse.ArgumentParser) -> None:
78
+ self._command.configure_parser(parser)
79
+
80
+ def execute(self, client: Client, **kwargs) -> None:
81
+ client.logger.warning('This command is deprecated. Use "%s" instead.', self._replacement)
82
+ self._command.execute(client, **kwargs)
83
+
84
+
85
+ class GenericCommand(metaclass=ABCMeta):
86
+ @abstractmethod
87
+ def repo(self, client: Client): ...
88
+
89
+ @property
90
+ @abstractmethod
91
+ def resource_type_str(self) -> str: ...
92
+
93
+
94
+ class GenericListCommand(GenericCommand):
95
+ @property
96
+ def description(self) -> str:
97
+ return f"List all CVAT {self.resource_type_str}s in either basic or JSON format."
98
+
99
+ def configure_parser(self, parser: argparse.ArgumentParser) -> None:
100
+ parser.add_argument(
101
+ "--json",
102
+ dest="use_json_output",
103
+ default=False,
104
+ action="store_true",
105
+ help="output JSON data",
106
+ )
107
+
108
+ def execute(self, client: Client, *, use_json_output: bool = False):
109
+ results = self.repo(client).list(return_json=use_json_output)
110
+ if use_json_output:
111
+ print(json.dumps(json.loads(results), indent=2))
112
+ else:
113
+ for r in results:
114
+ print(r.id)
115
+
116
+
117
+ class GenericDeleteCommand(GenericCommand):
118
+ @property
119
+ def description(self):
120
+ return f"Delete a list of {self.resource_type_str}s, ignoring those which don't exist."
121
+
122
+ def configure_parser(self, parser: argparse.ArgumentParser) -> None:
123
+ parser.add_argument(
124
+ "ids", type=int, help=f"list of {self.resource_type_str} IDs", nargs="+"
125
+ )
126
+
127
+ def execute(self, client: Client, *, ids: Sequence[int]) -> None:
128
+ self.repo(client).remove_by_ids(ids)
129
+
130
+
131
+ class GenericDownloadBackupCommand(GenericCommand):
132
+ @property
133
+ def description(self) -> str:
134
+ return f"Download a {self.resource_type_str} backup."
135
+
136
+ def configure_parser(self, parser: argparse.ArgumentParser) -> None:
137
+ parser.add_argument(
138
+ "resource_id",
139
+ metavar=f"{self.resource_type_str}_id",
140
+ type=int,
141
+ help=f"{self.resource_type_str} ID",
142
+ )
143
+ parser.add_argument(
144
+ "filename",
145
+ type=str,
146
+ nargs="?",
147
+ default="",
148
+ help="output file or directory (default: current directory)",
149
+ )
150
+ parser.add_argument(
151
+ "--completion_verification_period",
152
+ dest="status_check_period",
153
+ default=2,
154
+ type=float,
155
+ help="time interval between checks if archive building has been finished, in seconds",
156
+ )
157
+
158
+ def execute(
159
+ self, client: Client, *, resource_id: int, filename: str, status_check_period: float
160
+ ) -> None:
161
+ if not filename:
162
+ filename = os.getcwd()
163
+
164
+ if filename.endswith((os.sep, os.altsep or os.sep)):
165
+ os.makedirs(filename, exist_ok=True)
166
+
167
+ self.repo(client).retrieve(obj_id=resource_id).download_backup(
168
+ filename=filename,
169
+ status_check_period=status_check_period,
170
+ pbar=DeferredTqdmProgressReporter(),
171
+ location=Location.LOCAL,
172
+ )
173
+
174
+
175
+ class GenericCreateFromBackupCommand(GenericCommand):
176
+ @property
177
+ def description(self) -> str:
178
+ return f"Create a {self.resource_type_str} from a backup file."
179
+
180
+ def configure_parser(self, parser: argparse.ArgumentParser) -> None:
181
+ parser.add_argument("filename", type=str, help="upload file")
182
+ parser.add_argument(
183
+ "--completion_verification_period",
184
+ dest="status_check_period",
185
+ default=2,
186
+ type=float,
187
+ help="time interval between checks if archive processing was finished, in seconds",
188
+ )
189
+
190
+ def execute(self, client: Client, *, filename: str, status_check_period: float) -> None:
191
+ resource = self.repo(client).create_from_backup(
192
+ filename=filename,
193
+ status_check_period=status_check_period,
194
+ pbar=DeferredTqdmProgressReporter(),
195
+ )
196
+ print(resource.id)
197
+
198
+
199
+ class GenericExportDatasetCommand(GenericCommand):
200
+ @property
201
+ def description(self) -> str:
202
+ return textwrap.dedent(f"""\
203
+ Export a {self.resource_type_str} as a dataset in the specified format
204
+ (e.g. 'YOLO 1.1').
205
+ """)
206
+
207
+ def configure_parser(self, parser: argparse.ArgumentParser) -> None:
208
+ parser.add_argument(
209
+ "resource_id",
210
+ metavar=f"{self.resource_type_str}_id",
211
+ type=int,
212
+ help=f"{self.resource_type_str} ID",
213
+ )
214
+ parser.add_argument(
215
+ "filename",
216
+ type=str,
217
+ nargs="?",
218
+ default="",
219
+ help="output file or directory (default: current directory)",
220
+ )
221
+ parser.add_argument(
222
+ "--format",
223
+ dest="fileformat",
224
+ type=str,
225
+ default="CVAT for images 1.1",
226
+ help="annotation format (default: %(default)s)",
227
+ )
228
+ parser.add_argument(
229
+ "--completion_verification_period",
230
+ dest="status_check_period",
231
+ default=2,
232
+ type=float,
233
+ help="number of seconds to wait until checking if dataset building finished",
234
+ )
235
+ parser.add_argument(
236
+ "--with-images",
237
+ type=to_bool,
238
+ default=False,
239
+ dest="include_images",
240
+ help="Whether to include images or not (default: %(default)s)",
241
+ )
242
+
243
+ def execute(
244
+ self,
245
+ client: Client,
246
+ *,
247
+ fileformat: str,
248
+ filename: str,
249
+ status_check_period: int,
250
+ include_images: bool,
251
+ resource_id: int,
252
+ ) -> None:
253
+ if not filename:
254
+ filename = os.getcwd()
255
+
256
+ if filename.endswith((os.sep, os.altsep or os.sep)):
257
+ os.makedirs(filename, exist_ok=True)
258
+
259
+ self.repo(client).retrieve(obj_id=resource_id).export_dataset(
260
+ format_name=fileformat,
261
+ filename=filename,
262
+ pbar=DeferredTqdmProgressReporter(),
263
+ status_check_period=status_check_period,
264
+ include_images=include_images,
265
+ location=Location.LOCAL,
266
+ )
267
+
268
+
269
+ class GenericImportDatasetCommand(GenericCommand):
270
+ import_method_name = "import_dataset"
271
+
272
+ def configure_parser(self, parser: argparse.ArgumentParser) -> None:
273
+ parser.add_argument(
274
+ "resource_id",
275
+ metavar=f"{self.resource_type_str}_id",
276
+ type=int,
277
+ help=f"{self.resource_type_str} ID",
278
+ )
279
+ parser.add_argument("filename", type=str, help="upload file")
280
+ parser.add_argument(
281
+ "--format",
282
+ dest="fileformat",
283
+ type=str,
284
+ default="CVAT 1.1",
285
+ help="annotation format (default: %(default)s)",
286
+ )
287
+
288
+ def execute(
289
+ self,
290
+ client: Client,
291
+ *,
292
+ fileformat: str,
293
+ filename: str,
294
+ resource_id: int,
295
+ ) -> None:
296
+ resource = self.repo(client).retrieve(obj_id=resource_id)
297
+ import_dataset = getattr(resource, self.import_method_name)
298
+ import_dataset(
299
+ format_name=fileformat,
300
+ filename=filename,
301
+ pbar=DeferredTqdmProgressReporter(),
302
+ )
@@ -7,7 +7,16 @@ import textwrap
7
7
 
8
8
  from cvat_sdk import Client, models
9
9
 
10
- from .command_base import CommandGroup, GenericCommand, GenericDeleteCommand, GenericListCommand
10
+ from .command_base import (
11
+ CommandGroup,
12
+ GenericCommand,
13
+ GenericCreateFromBackupCommand,
14
+ GenericDeleteCommand,
15
+ GenericDownloadBackupCommand,
16
+ GenericExportDatasetCommand,
17
+ GenericImportDatasetCommand,
18
+ GenericListCommand,
19
+ )
11
20
  from .parsers import parse_label_arg
12
21
 
13
22
  COMMANDS = CommandGroup(description="Perform operations on CVAT projects.")
@@ -87,3 +96,26 @@ class ProjectCreate:
87
96
  @COMMANDS.command_class("delete")
88
97
  class ProjectDelete(GenericDeleteCommand, GenericProjectCommand):
89
98
  pass
99
+
100
+
101
+ @COMMANDS.command_class("backup")
102
+ class ProjectBackup(GenericDownloadBackupCommand, GenericProjectCommand):
103
+ pass
104
+
105
+
106
+ @COMMANDS.command_class("create-from-backup")
107
+ class ProjectCreateFromBackup(GenericCreateFromBackupCommand, GenericProjectCommand):
108
+ pass
109
+
110
+
111
+ @COMMANDS.command_class("export-dataset")
112
+ class ProjectExportDataset(GenericExportDatasetCommand, GenericProjectCommand):
113
+ pass
114
+
115
+
116
+ @COMMANDS.command_class("import-dataset")
117
+ class ProjectImportDataset(GenericImportDatasetCommand, GenericProjectCommand):
118
+ description = textwrap.dedent("""\
119
+ Create tasks in a project from a dataset in the specified format
120
+ (e.g. 'YOLO 1.1'), including images and annotations.
121
+ """)
@@ -5,18 +5,24 @@
5
5
  from __future__ import annotations
6
6
 
7
7
  import argparse
8
- import os
9
8
  import textwrap
10
9
  from collections.abc import Sequence
11
10
 
12
11
  import cvat_sdk.auto_annotation as cvataa
13
- from attr.converters import to_bool
14
12
  from cvat_sdk import Client, models
15
13
  from cvat_sdk.core.helpers import DeferredTqdmProgressReporter
16
14
  from cvat_sdk.core.proxies.tasks import ResourceType
17
- from cvat_sdk.core.proxies.types import Location
18
15
 
19
- from .command_base import CommandGroup, GenericCommand, GenericDeleteCommand, GenericListCommand
16
+ from .command_base import (
17
+ CommandGroup,
18
+ GenericCommand,
19
+ GenericCreateFromBackupCommand,
20
+ GenericDeleteCommand,
21
+ GenericDownloadBackupCommand,
22
+ GenericExportDatasetCommand,
23
+ GenericImportDatasetCommand,
24
+ GenericListCommand,
25
+ )
20
26
  from .common import FunctionLoader, configure_function_implementation_arguments
21
27
  from .parsers import parse_label_arg, parse_resource_type, parse_threshold
22
28
 
@@ -254,160 +260,27 @@ class TaskFrames:
254
260
 
255
261
 
256
262
  @COMMANDS.command_class("export-dataset")
257
- class TaskExportDataset:
258
- description = textwrap.dedent("""\
259
- Export a task as a dataset in the specified format (e.g. 'YOLO 1.1').
260
- """)
261
-
262
- def configure_parser(self, parser: argparse.ArgumentParser) -> None:
263
- parser.add_argument("task_id", type=int, help="task ID")
264
- parser.add_argument(
265
- "filename",
266
- type=str,
267
- nargs="?",
268
- default="",
269
- help="output file or directory (default: current directory)",
270
- )
271
- parser.add_argument(
272
- "--format",
273
- dest="fileformat",
274
- type=str,
275
- default="CVAT for images 1.1",
276
- help="annotation format (default: %(default)s)",
277
- )
278
- parser.add_argument(
279
- "--completion_verification_period",
280
- dest="status_check_period",
281
- default=2,
282
- type=float,
283
- help="number of seconds to wait until checking if dataset building finished",
284
- )
285
- parser.add_argument(
286
- "--with-images",
287
- type=to_bool,
288
- default=False,
289
- dest="include_images",
290
- help="Whether to include images or not (default: %(default)s)",
291
- )
292
-
293
- def execute(
294
- self,
295
- client: Client,
296
- *,
297
- task_id: int,
298
- fileformat: str,
299
- filename: str,
300
- status_check_period: int,
301
- include_images: bool,
302
- ) -> None:
303
- if not filename:
304
- filename = os.getcwd()
305
-
306
- if filename.endswith((os.sep, os.altsep or os.sep)):
307
- os.makedirs(filename, exist_ok=True)
308
-
309
- client.tasks.retrieve(obj_id=task_id).export_dataset(
310
- format_name=fileformat,
311
- filename=filename,
312
- pbar=DeferredTqdmProgressReporter(),
313
- status_check_period=status_check_period,
314
- include_images=include_images,
315
- location=Location.LOCAL,
316
- )
263
+ class TaskExportDataset(GenericExportDatasetCommand, GenericTaskCommand):
264
+ pass
317
265
 
318
266
 
319
267
  @COMMANDS.command_class("import-dataset")
320
- class TaskImportDataset:
268
+ class TaskImportDataset(GenericImportDatasetCommand, GenericTaskCommand):
321
269
  description = textwrap.dedent("""\
322
270
  Import annotations into a task from a dataset in the specified format
323
271
  (e.g. 'YOLO 1.1').
324
272
  """)
325
-
326
- def configure_parser(self, parser: argparse.ArgumentParser) -> None:
327
- parser.add_argument("task_id", type=int, help="task ID")
328
- parser.add_argument("filename", type=str, help="upload file")
329
- parser.add_argument(
330
- "--format",
331
- dest="fileformat",
332
- type=str,
333
- default="CVAT 1.1",
334
- help="annotation format (default: %(default)s)",
335
- )
336
-
337
- def execute(
338
- self,
339
- client: Client,
340
- *,
341
- task_id: int,
342
- fileformat: str,
343
- filename: str,
344
- ) -> None:
345
- client.tasks.retrieve(obj_id=task_id).import_annotations(
346
- format_name=fileformat,
347
- filename=filename,
348
- pbar=DeferredTqdmProgressReporter(),
349
- )
273
+ import_method_name = "import_annotations"
350
274
 
351
275
 
352
276
  @COMMANDS.command_class("backup")
353
- class TaskBackup:
354
- description = """Download a task backup."""
355
-
356
- def configure_parser(self, parser: argparse.ArgumentParser) -> None:
357
- parser.add_argument("task_id", type=int, help="task ID")
358
- parser.add_argument(
359
- "filename",
360
- type=str,
361
- nargs="?",
362
- default="",
363
- help="output file or directory (default: current directory)",
364
- )
365
- parser.add_argument(
366
- "--completion_verification_period",
367
- dest="status_check_period",
368
- default=2,
369
- type=float,
370
- help="time interval between checks if archive building has been finished, in seconds",
371
- )
372
-
373
- def execute(
374
- self, client: Client, *, task_id: int, filename: str, status_check_period: int
375
- ) -> None:
376
- if not filename:
377
- filename = os.getcwd()
378
-
379
- if filename.endswith((os.sep, os.altsep or os.sep)):
380
- os.makedirs(filename, exist_ok=True)
381
-
382
- client.tasks.retrieve(obj_id=task_id).download_backup(
383
- filename=filename,
384
- status_check_period=status_check_period,
385
- pbar=DeferredTqdmProgressReporter(),
386
- location=Location.LOCAL,
387
- )
277
+ class TaskBackup(GenericDownloadBackupCommand, GenericTaskCommand):
278
+ pass
388
279
 
389
280
 
390
281
  @COMMANDS.command_class("create-from-backup")
391
- class TaskCreateFromBackup:
392
- description = """Create a task from a backup file."""
393
-
394
- def configure_parser(self, parser: argparse.ArgumentParser) -> None:
395
- parser.add_argument("filename", type=str, help="upload file")
396
- parser.add_argument(
397
- "--completion_verification_period",
398
- dest="status_check_period",
399
- default=2,
400
- type=float,
401
- help="time interval between checks if archive processing was finished, in seconds",
402
- )
403
-
404
- def execute(self, client: Client, *, filename: str, status_check_period: int) -> None:
405
- task = client.tasks.create_from_backup(
406
- filename=filename,
407
- status_check_period=status_check_period,
408
- pbar=DeferredTqdmProgressReporter(),
409
- )
410
- print(task.id)
282
+ class TaskCreateFromBackup(GenericCreateFromBackupCommand, GenericTaskCommand):
283
+ pass
411
284
 
412
285
 
413
286
  @COMMANDS.command_class("auto-annotate")
@@ -0,0 +1 @@
1
+ VERSION = "2.66.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cvat-cli
3
- Version: 2.64.0
3
+ Version: 2.66.0
4
4
  Summary: Command-line client for CVAT
5
5
  Author-email: "CVAT.ai Corporation" <support@cvat.ai>
6
6
  License-Expression: MIT
@@ -9,7 +9,7 @@ Classifier: Programming Language :: Python :: 3
9
9
  Classifier: Operating System :: OS Independent
10
10
  Requires-Python: >=3.10
11
11
  Description-Content-Type: text/markdown
12
- Requires-Dist: cvat-sdk==2.64.0
12
+ Requires-Dist: cvat-sdk==2.66.0
13
13
  Requires-Dist: attrs>=24.2.0
14
14
  Requires-Dist: Pillow>=10.3.0
15
15
 
@@ -22,8 +22,12 @@ comprehensive CVAT administration tool in the future.
22
22
  The following subcommands are supported:
23
23
 
24
24
  - Projects:
25
+ - `backup` - back up a project
25
26
  - `create` - create a new project
27
+ - `create-from-backup` - create a project from a backup file
26
28
  - `delete` - delete projects
29
+ - `export-dataset` - export a project as a dataset
30
+ - `import-dataset` - import annotations into a project from a dataset
27
31
  - `ls` - list all projects
28
32
 
29
33
  - Tasks:
@@ -1,3 +1,3 @@
1
- cvat-sdk==2.64.0
1
+ cvat-sdk==2.66.0
2
2
  attrs>=24.2.0
3
3
  Pillow>=10.3.0
@@ -1,124 +0,0 @@
1
- # Copyright (C) CVAT.ai Corporation
2
- #
3
- # SPDX-License-Identifier: MIT
4
-
5
- import argparse
6
- import json
7
- import textwrap
8
- import types
9
- from abc import ABCMeta, abstractmethod
10
- from collections.abc import Callable, Mapping, Sequence
11
- from typing import Protocol
12
-
13
- from cvat_sdk import Client
14
-
15
-
16
- class Command(Protocol):
17
- @property
18
- def description(self) -> str: ...
19
-
20
- def configure_parser(self, parser: argparse.ArgumentParser) -> None: ...
21
-
22
- # The exact parameters accepted by `execute` vary between commands,
23
- # so we're forced to declare it like this instead of as a method.
24
- @property
25
- def execute(self) -> Callable[..., None]: ...
26
-
27
-
28
- class CommandGroup:
29
- def __init__(self, *, description: str) -> None:
30
- self._commands: dict[str, Command] = {}
31
- self.description = description
32
-
33
- def command_class(self, name: str):
34
- def decorator(cls: type):
35
- self._commands[name] = cls()
36
- return cls
37
-
38
- return decorator
39
-
40
- def add_command(self, name: str, command: Command) -> None:
41
- self._commands[name] = command
42
-
43
- @property
44
- def commands(self) -> Mapping[str, Command]:
45
- return types.MappingProxyType(self._commands)
46
-
47
- def configure_parser(self, parser: argparse.ArgumentParser) -> None:
48
- subparsers = parser.add_subparsers(required=True)
49
-
50
- for name, command in self._commands.items():
51
- subparser = subparsers.add_parser(name, description=command.description)
52
- subparser.set_defaults(_executor=command.execute)
53
- command.configure_parser(subparser)
54
-
55
- def execute(self) -> None:
56
- # It should be impossible for a command group to be executed,
57
- # because configure_parser requires that a subcommand is specified.
58
- assert False, "unreachable code"
59
-
60
-
61
- class DeprecatedAlias:
62
- def __init__(self, command: Command, replacement: str) -> None:
63
- self._command = command
64
- self._replacement = replacement
65
-
66
- @property
67
- def description(self) -> str:
68
- return textwrap.dedent(f"""\
69
- {self._command.description}
70
- (Deprecated; use "{self._replacement}" instead.)
71
- """)
72
-
73
- def configure_parser(self, parser: argparse.ArgumentParser) -> None:
74
- self._command.configure_parser(parser)
75
-
76
- def execute(self, client: Client, **kwargs) -> None:
77
- client.logger.warning('This command is deprecated. Use "%s" instead.', self._replacement)
78
- self._command.execute(client, **kwargs)
79
-
80
-
81
- class GenericCommand(metaclass=ABCMeta):
82
- @abstractmethod
83
- def repo(self, client: Client): ...
84
-
85
- @property
86
- @abstractmethod
87
- def resource_type_str(self) -> str: ...
88
-
89
-
90
- class GenericListCommand(GenericCommand):
91
- @property
92
- def description(self) -> str:
93
- return f"List all CVAT {self.resource_type_str}s in either basic or JSON format."
94
-
95
- def configure_parser(self, parser: argparse.ArgumentParser) -> None:
96
- parser.add_argument(
97
- "--json",
98
- dest="use_json_output",
99
- default=False,
100
- action="store_true",
101
- help="output JSON data",
102
- )
103
-
104
- def execute(self, client: Client, *, use_json_output: bool = False):
105
- results = self.repo(client).list(return_json=use_json_output)
106
- if use_json_output:
107
- print(json.dumps(json.loads(results), indent=2))
108
- else:
109
- for r in results:
110
- print(r.id)
111
-
112
-
113
- class GenericDeleteCommand(GenericCommand):
114
- @property
115
- def description(self):
116
- return f"Delete a list of {self.resource_type_str}s, ignoring those which don't exist."
117
-
118
- def configure_parser(self, parser: argparse.ArgumentParser) -> None:
119
- parser.add_argument(
120
- "ids", type=int, help=f"list of {self.resource_type_str} IDs", nargs="+"
121
- )
122
-
123
- def execute(self, client: Client, *, ids: Sequence[int]) -> None:
124
- self.repo(client).remove_by_ids(ids)
@@ -1 +0,0 @@
1
- VERSION = "2.64.0"
File without changes