kleinkram 0.38.1.dev20241212075157__py3-none-any.whl → 0.38.1.dev20250207122632__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.
Potentially problematic release.
This version of kleinkram might be problematic. Click here for more details.
- kleinkram/__init__.py +33 -2
- kleinkram/api/client.py +21 -16
- kleinkram/api/deser.py +165 -0
- kleinkram/api/file_transfer.py +13 -24
- kleinkram/api/pagination.py +56 -0
- kleinkram/api/query.py +111 -0
- kleinkram/api/routes.py +266 -97
- kleinkram/auth.py +21 -20
- kleinkram/cli/__init__.py +0 -0
- kleinkram/{commands/download.py → cli/_download.py} +18 -44
- kleinkram/cli/_endpoint.py +58 -0
- kleinkram/{commands/list.py → cli/_list.py} +25 -38
- kleinkram/cli/_mission.py +153 -0
- kleinkram/cli/_project.py +99 -0
- kleinkram/cli/_upload.py +84 -0
- kleinkram/cli/_verify.py +56 -0
- kleinkram/{app.py → cli/app.py} +57 -25
- kleinkram/cli/error_handling.py +67 -0
- kleinkram/config.py +141 -107
- kleinkram/core.py +251 -3
- kleinkram/errors.py +13 -45
- kleinkram/main.py +1 -1
- kleinkram/models.py +48 -149
- kleinkram/printing.py +325 -0
- kleinkram/py.typed +0 -0
- kleinkram/types.py +9 -0
- kleinkram/utils.py +88 -29
- kleinkram/wrappers.py +401 -0
- {kleinkram-0.38.1.dev20241212075157.dist-info → kleinkram-0.38.1.dev20250207122632.dist-info}/METADATA +3 -3
- kleinkram-0.38.1.dev20250207122632.dist-info/RECORD +49 -0
- {kleinkram-0.38.1.dev20241212075157.dist-info → kleinkram-0.38.1.dev20250207122632.dist-info}/WHEEL +1 -1
- {kleinkram-0.38.1.dev20241212075157.dist-info → kleinkram-0.38.1.dev20250207122632.dist-info}/top_level.txt +1 -0
- testing/__init__.py +0 -0
- testing/backend_fixtures.py +67 -0
- tests/conftest.py +7 -0
- tests/test_config.py +115 -0
- tests/test_core.py +165 -0
- tests/test_end_to_end.py +29 -39
- tests/test_error_handling.py +44 -0
- tests/test_fixtures.py +34 -0
- tests/test_printing.py +62 -0
- tests/test_query.py +138 -0
- tests/test_utils.py +46 -24
- tests/test_wrappers.py +71 -0
- kleinkram/api/parsing.py +0 -86
- kleinkram/commands/__init__.py +0 -1
- kleinkram/commands/endpoint.py +0 -62
- kleinkram/commands/mission.py +0 -69
- kleinkram/commands/project.py +0 -24
- kleinkram/commands/upload.py +0 -164
- kleinkram/commands/verify.py +0 -142
- kleinkram/consts.py +0 -8
- kleinkram/enums.py +0 -10
- kleinkram/resources.py +0 -158
- kleinkram-0.38.1.dev20241212075157.dist-info/LICENSE +0 -674
- kleinkram-0.38.1.dev20241212075157.dist-info/RECORD +0 -37
- tests/test_resources.py +0 -137
- {kleinkram-0.38.1.dev20241212075157.dist-info → kleinkram-0.38.1.dev20250207122632.dist-info}/entry_points.txt +0 -0
kleinkram/commands/upload.py
DELETED
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
from typing import List
|
|
5
|
-
from typing import Optional
|
|
6
|
-
|
|
7
|
-
import typer
|
|
8
|
-
from kleinkram.api.client import AuthenticatedClient
|
|
9
|
-
from kleinkram.api.file_transfer import upload_files
|
|
10
|
-
from kleinkram.api.routes import _create_mission as _create_mission_api
|
|
11
|
-
from kleinkram.config import get_shared_state
|
|
12
|
-
from kleinkram.errors import MissionNotFound
|
|
13
|
-
from kleinkram.errors import ProjectNotFound
|
|
14
|
-
from kleinkram.models import Mission
|
|
15
|
-
from kleinkram.resources import check_mission_spec_is_creatable
|
|
16
|
-
from kleinkram.resources import get_missions_by_spec
|
|
17
|
-
from kleinkram.resources import get_projects_by_spec
|
|
18
|
-
from kleinkram.resources import InvalidMissionSpec
|
|
19
|
-
from kleinkram.resources import mission_spec_is_unique
|
|
20
|
-
from kleinkram.resources import MissionSpec
|
|
21
|
-
from kleinkram.resources import ProjectSpec
|
|
22
|
-
from kleinkram.utils import check_file_paths
|
|
23
|
-
from kleinkram.utils import get_filename_map
|
|
24
|
-
from kleinkram.utils import load_metadata
|
|
25
|
-
from kleinkram.utils import split_args
|
|
26
|
-
from rich.console import Console
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
HELP = """\
|
|
30
|
-
Upload files to kleinkram.
|
|
31
|
-
"""
|
|
32
|
-
|
|
33
|
-
upload_typer = typer.Typer(
|
|
34
|
-
name="upload",
|
|
35
|
-
no_args_is_help=True,
|
|
36
|
-
invoke_without_command=True,
|
|
37
|
-
help=HELP,
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
# TODO: move this to core
|
|
42
|
-
def _create_mission(
|
|
43
|
-
client: AuthenticatedClient,
|
|
44
|
-
mission_spec: MissionSpec,
|
|
45
|
-
metadata_path: Optional[Path],
|
|
46
|
-
ignore_missing_tags: bool,
|
|
47
|
-
) -> Mission:
|
|
48
|
-
check_mission_spec_is_creatable(mission_spec)
|
|
49
|
-
mission_name = mission_spec.patterns[0]
|
|
50
|
-
|
|
51
|
-
# get the metadata
|
|
52
|
-
metadata_dct = {}
|
|
53
|
-
if metadata_path is not None:
|
|
54
|
-
metadata_dct = load_metadata(metadata_path)
|
|
55
|
-
|
|
56
|
-
# get project
|
|
57
|
-
projects = get_projects_by_spec(client, mission_spec.project_spec)
|
|
58
|
-
|
|
59
|
-
if not projects:
|
|
60
|
-
raise ProjectNotFound(f"project {mission_spec.project_spec} does not exist")
|
|
61
|
-
elif len(projects) > 1:
|
|
62
|
-
raise AssertionError("unreachable")
|
|
63
|
-
|
|
64
|
-
parsed_project = projects[0]
|
|
65
|
-
|
|
66
|
-
_create_mission_api(
|
|
67
|
-
client,
|
|
68
|
-
parsed_project.id,
|
|
69
|
-
mission_name,
|
|
70
|
-
metadata=metadata_dct,
|
|
71
|
-
ignore_missing_tags=ignore_missing_tags,
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
missions = get_missions_by_spec(client, mission_spec)
|
|
75
|
-
assert len(missions) is not None, "unreachable, the ghost is back"
|
|
76
|
-
|
|
77
|
-
return missions[0]
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
@upload_typer.callback()
|
|
81
|
-
def upload(
|
|
82
|
-
files: List[str] = typer.Argument(help="files to upload"),
|
|
83
|
-
project: Optional[str] = typer.Option(
|
|
84
|
-
None, "--project", "-p", help="project id or name"
|
|
85
|
-
),
|
|
86
|
-
mission: str = typer.Option(..., "--mission", "-m", help="mission id or name"),
|
|
87
|
-
create: bool = typer.Option(False, help="create mission if it does not exist"),
|
|
88
|
-
metadata: Optional[str] = typer.Option(
|
|
89
|
-
None, help="path to metadata file (json or yaml)"
|
|
90
|
-
),
|
|
91
|
-
fix_filenames: bool = typer.Option(False, help="fix filenames"),
|
|
92
|
-
ignore_missing_tags: bool = typer.Option(False, help="ignore mission tags"),
|
|
93
|
-
) -> None:
|
|
94
|
-
# check files and `fix` filenames
|
|
95
|
-
if files is None:
|
|
96
|
-
files = []
|
|
97
|
-
|
|
98
|
-
file_paths = [Path(file) for file in files]
|
|
99
|
-
check_file_paths(file_paths)
|
|
100
|
-
files_map = get_filename_map(file_paths)
|
|
101
|
-
|
|
102
|
-
if not fix_filenames:
|
|
103
|
-
for name, path in files_map.items():
|
|
104
|
-
if name != path.name:
|
|
105
|
-
raise ValueError(
|
|
106
|
-
f"invalid filename format {path.name}, use `--fix-filenames`"
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
mission_ids, mission_patterns = split_args([mission])
|
|
110
|
-
project_ids, project_patterns = split_args([project] if project else [])
|
|
111
|
-
|
|
112
|
-
project_spec = ProjectSpec(ids=project_ids, patterns=project_patterns)
|
|
113
|
-
mission_spec = MissionSpec(
|
|
114
|
-
ids=mission_ids,
|
|
115
|
-
patterns=mission_patterns,
|
|
116
|
-
project_spec=project_spec,
|
|
117
|
-
)
|
|
118
|
-
|
|
119
|
-
if not mission_spec_is_unique(mission_spec):
|
|
120
|
-
raise InvalidMissionSpec(f"mission spec is not unique: {mission_spec}")
|
|
121
|
-
|
|
122
|
-
client = AuthenticatedClient()
|
|
123
|
-
missions = get_missions_by_spec(client, mission_spec)
|
|
124
|
-
|
|
125
|
-
if len(missions) > 1:
|
|
126
|
-
raise AssertionError("unreachable")
|
|
127
|
-
|
|
128
|
-
if not create and not missions:
|
|
129
|
-
raise MissionNotFound(f"mission: {mission_spec} does not exist, use `--create`")
|
|
130
|
-
|
|
131
|
-
# create mission if it does not exist
|
|
132
|
-
mission_parsed = (
|
|
133
|
-
missions[0]
|
|
134
|
-
if missions
|
|
135
|
-
else _create_mission(
|
|
136
|
-
client,
|
|
137
|
-
mission_spec,
|
|
138
|
-
metadata_path=Path(metadata) if metadata else None,
|
|
139
|
-
ignore_missing_tags=ignore_missing_tags,
|
|
140
|
-
)
|
|
141
|
-
)
|
|
142
|
-
|
|
143
|
-
console = Console()
|
|
144
|
-
filtered_files_map = {}
|
|
145
|
-
remote_file_names = [file.name for file in mission_parsed.files]
|
|
146
|
-
for name, path in files_map.items():
|
|
147
|
-
if name in remote_file_names:
|
|
148
|
-
console.print(
|
|
149
|
-
f"file: {name} (path: {path}) already exists in mission", style="yellow"
|
|
150
|
-
)
|
|
151
|
-
else:
|
|
152
|
-
filtered_files_map[name] = path
|
|
153
|
-
|
|
154
|
-
if not filtered_files_map:
|
|
155
|
-
console.print("\nNO FILES UPLOADED", style="yellow")
|
|
156
|
-
return
|
|
157
|
-
|
|
158
|
-
upload_files(
|
|
159
|
-
client,
|
|
160
|
-
filtered_files_map,
|
|
161
|
-
mission_parsed.id,
|
|
162
|
-
n_workers=2,
|
|
163
|
-
verbose=get_shared_state().verbose,
|
|
164
|
-
)
|
kleinkram/commands/verify.py
DELETED
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import logging
|
|
4
|
-
import sys
|
|
5
|
-
from enum import Enum
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
from typing import Dict
|
|
8
|
-
from typing import List
|
|
9
|
-
from typing import Optional
|
|
10
|
-
|
|
11
|
-
import typer
|
|
12
|
-
from kleinkram.api.client import AuthenticatedClient
|
|
13
|
-
from kleinkram.config import get_shared_state
|
|
14
|
-
from kleinkram.errors import InvalidMissionSpec
|
|
15
|
-
from kleinkram.errors import MissionNotFound
|
|
16
|
-
from kleinkram.models import FileState
|
|
17
|
-
from kleinkram.resources import FileSpec
|
|
18
|
-
from kleinkram.resources import get_files_by_spec
|
|
19
|
-
from kleinkram.resources import get_missions_by_spec
|
|
20
|
-
from kleinkram.resources import mission_spec_is_unique
|
|
21
|
-
from kleinkram.resources import MissionSpec
|
|
22
|
-
from kleinkram.resources import ProjectSpec
|
|
23
|
-
from kleinkram.utils import b64_md5
|
|
24
|
-
from kleinkram.utils import check_file_paths
|
|
25
|
-
from kleinkram.utils import get_filename_map
|
|
26
|
-
from kleinkram.utils import split_args
|
|
27
|
-
from rich.console import Console
|
|
28
|
-
from rich.table import Table
|
|
29
|
-
from rich.text import Text
|
|
30
|
-
from tqdm import tqdm
|
|
31
|
-
|
|
32
|
-
logger = logging.getLogger(__name__)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
class FileVerificationStatus(str, Enum):
|
|
36
|
-
UPLAODED = "uploaded"
|
|
37
|
-
UPLOADING = "uploading"
|
|
38
|
-
COMPUTING_HASH = "computing hash"
|
|
39
|
-
MISSING = "missing"
|
|
40
|
-
MISMATCHED_HASH = "hash mismatch"
|
|
41
|
-
UNKNOWN = "unknown"
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
FILE_STATUS_STYLES = {
|
|
45
|
-
FileVerificationStatus.UPLAODED: "green",
|
|
46
|
-
FileVerificationStatus.UPLOADING: "yellow",
|
|
47
|
-
FileVerificationStatus.MISSING: "yellow",
|
|
48
|
-
FileVerificationStatus.MISMATCHED_HASH: "red",
|
|
49
|
-
FileVerificationStatus.UNKNOWN: "gray",
|
|
50
|
-
FileVerificationStatus.COMPUTING_HASH: "purple",
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
HELP = """\
|
|
55
|
-
Verify if files were uploaded correctly.
|
|
56
|
-
"""
|
|
57
|
-
|
|
58
|
-
verify_typer = typer.Typer(name="verify", invoke_without_command=True, help=HELP)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
@verify_typer.callback()
|
|
62
|
-
def verify(
|
|
63
|
-
files: List[str] = typer.Argument(help="files to upload"),
|
|
64
|
-
project: Optional[str] = typer.Option(
|
|
65
|
-
None, "--project", "-p", help="project id or name"
|
|
66
|
-
),
|
|
67
|
-
mission: str = typer.Option(..., "--mission", "-m", help="mission id or name"),
|
|
68
|
-
skip_hash: bool = typer.Option(False, help="skip hash check"),
|
|
69
|
-
) -> None:
|
|
70
|
-
# get all filepaths
|
|
71
|
-
if files is None:
|
|
72
|
-
files = []
|
|
73
|
-
|
|
74
|
-
file_paths = [Path(file) for file in files]
|
|
75
|
-
check_file_paths(file_paths)
|
|
76
|
-
files_map = get_filename_map(file_paths)
|
|
77
|
-
|
|
78
|
-
# get the mission by the provided spec
|
|
79
|
-
mission_ids, mission_patterns = split_args([mission])
|
|
80
|
-
project_ids, project_patterns = split_args([project] if project else [])
|
|
81
|
-
|
|
82
|
-
project_spec = ProjectSpec(ids=project_ids, patterns=project_patterns)
|
|
83
|
-
mission_spec = MissionSpec(
|
|
84
|
-
ids=mission_ids, patterns=mission_patterns, project_spec=project_spec
|
|
85
|
-
)
|
|
86
|
-
file_spec = FileSpec(mission_spec=mission_spec)
|
|
87
|
-
|
|
88
|
-
client = AuthenticatedClient()
|
|
89
|
-
|
|
90
|
-
# check first that the mission even exists, the mission could be empty
|
|
91
|
-
if not mission_spec_is_unique(mission_spec):
|
|
92
|
-
raise InvalidMissionSpec(f"mission spec is not unique: {mission_spec}")
|
|
93
|
-
missions = get_missions_by_spec(client, mission_spec)
|
|
94
|
-
if len(missions) > 1:
|
|
95
|
-
raise AssertionError("unreachable")
|
|
96
|
-
if not missions:
|
|
97
|
-
raise MissionNotFound(f"mission: {mission_spec} does not exist")
|
|
98
|
-
|
|
99
|
-
# get all files from that mission
|
|
100
|
-
remote_files = {file.name: file for file in get_files_by_spec(client, file_spec)}
|
|
101
|
-
|
|
102
|
-
# verify files
|
|
103
|
-
file_status: Dict[Path, FileVerificationStatus] = {}
|
|
104
|
-
for name, file in tqdm(
|
|
105
|
-
files_map.items(),
|
|
106
|
-
desc="verifying files",
|
|
107
|
-
unit="file",
|
|
108
|
-
disable=not get_shared_state().verbose,
|
|
109
|
-
):
|
|
110
|
-
if name not in remote_files:
|
|
111
|
-
file_status[file] = FileVerificationStatus.MISSING
|
|
112
|
-
continue
|
|
113
|
-
|
|
114
|
-
remote_file = remote_files[name]
|
|
115
|
-
|
|
116
|
-
if remote_file.state == FileState.UPLOADING:
|
|
117
|
-
file_status[file] = FileVerificationStatus.UPLOADING
|
|
118
|
-
elif remote_file.state == FileState.OK:
|
|
119
|
-
if remote_file.hash is None:
|
|
120
|
-
file_status[file] = FileVerificationStatus.COMPUTING_HASH
|
|
121
|
-
elif skip_hash or remote_file.hash == b64_md5(file):
|
|
122
|
-
file_status[file] = FileVerificationStatus.UPLAODED
|
|
123
|
-
else:
|
|
124
|
-
file_status[file] = FileVerificationStatus.MISMATCHED_HASH
|
|
125
|
-
else:
|
|
126
|
-
file_status[file] = FileVerificationStatus.UNKNOWN
|
|
127
|
-
|
|
128
|
-
if get_shared_state().verbose:
|
|
129
|
-
table = Table(title="file status")
|
|
130
|
-
table.add_column("filename", style="cyan")
|
|
131
|
-
table.add_column("status", style="green")
|
|
132
|
-
|
|
133
|
-
for path, status in file_status.items():
|
|
134
|
-
table.add_row(str(path), Text(status, style=FILE_STATUS_STYLES[status]))
|
|
135
|
-
|
|
136
|
-
Console().print(table)
|
|
137
|
-
else:
|
|
138
|
-
for path, status in file_status.items():
|
|
139
|
-
stream = (
|
|
140
|
-
sys.stdout if status == FileVerificationStatus.UPLAODED else sys.stderr
|
|
141
|
-
)
|
|
142
|
-
print(path, file=stream)
|
kleinkram/consts.py
DELETED
kleinkram/enums.py
DELETED
kleinkram/resources.py
DELETED
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from concurrent.futures import ThreadPoolExecutor
|
|
4
|
-
from dataclasses import dataclass
|
|
5
|
-
from dataclasses import field
|
|
6
|
-
from itertools import chain
|
|
7
|
-
from typing import List
|
|
8
|
-
from uuid import UUID
|
|
9
|
-
|
|
10
|
-
from kleinkram.api.client import AuthenticatedClient
|
|
11
|
-
from kleinkram.api.routes import _get_files_by_mission
|
|
12
|
-
from kleinkram.api.routes import _get_missions_by_project
|
|
13
|
-
from kleinkram.api.routes import _get_projects
|
|
14
|
-
from kleinkram.errors import InvalidMissionSpec
|
|
15
|
-
from kleinkram.errors import InvalidProjectSpec
|
|
16
|
-
from kleinkram.models import File
|
|
17
|
-
from kleinkram.models import Mission
|
|
18
|
-
from kleinkram.models import Project
|
|
19
|
-
from kleinkram.utils import filtered_by_patterns
|
|
20
|
-
|
|
21
|
-
MAX_PARALLEL_REQUESTS = 32
|
|
22
|
-
SPECIAL_PATTERN_CHARS = ["*", "?", "[", "]"]
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
@dataclass
|
|
26
|
-
class ProjectSpec:
|
|
27
|
-
patterns: List[str] = field(default_factory=list)
|
|
28
|
-
ids: List[UUID] = field(default_factory=list)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
@dataclass
|
|
32
|
-
class MissionSpec:
|
|
33
|
-
patterns: List[str] = field(default_factory=list)
|
|
34
|
-
ids: List[UUID] = field(default_factory=list)
|
|
35
|
-
project_spec: ProjectSpec = field(default=ProjectSpec())
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
@dataclass
|
|
39
|
-
class FileSpec:
|
|
40
|
-
patterns: List[str] = field(default_factory=list)
|
|
41
|
-
ids: List[UUID] = field(default_factory=list)
|
|
42
|
-
mission_spec: MissionSpec = field(default=MissionSpec())
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def check_mission_spec_is_creatable(spec: MissionSpec) -> None:
|
|
46
|
-
if not mission_spec_is_unique(spec):
|
|
47
|
-
raise InvalidMissionSpec(f"Mission spec is not unique: {spec}")
|
|
48
|
-
# cant create a missing by id
|
|
49
|
-
if spec.ids:
|
|
50
|
-
raise InvalidMissionSpec(f"cant create mission by id: {spec}")
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def check_project_spec_is_creatable(spec: ProjectSpec) -> None:
|
|
54
|
-
if not project_spec_is_unique(spec):
|
|
55
|
-
raise InvalidProjectSpec(f"Project spec is not unique: {spec}")
|
|
56
|
-
# cant create a missing by id
|
|
57
|
-
if spec.ids:
|
|
58
|
-
raise InvalidProjectSpec(f"cant create project by id: {spec}")
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def _pattern_is_unique(pattern: str) -> bool:
|
|
62
|
-
for char in SPECIAL_PATTERN_CHARS:
|
|
63
|
-
if char in pattern:
|
|
64
|
-
return False
|
|
65
|
-
return True
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
def project_spec_is_unique(spec: ProjectSpec) -> bool:
|
|
69
|
-
# a single project id is specified
|
|
70
|
-
if len(spec.ids) == 1 and not spec.patterns:
|
|
71
|
-
return True
|
|
72
|
-
|
|
73
|
-
# a single project name is specified
|
|
74
|
-
if len(spec.patterns) == 1 and _pattern_is_unique(spec.patterns[0]):
|
|
75
|
-
return True
|
|
76
|
-
return False
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
def mission_spec_is_unique(spec: MissionSpec) -> bool:
|
|
80
|
-
# a single mission id is specified
|
|
81
|
-
if len(spec.ids) == 1 and not spec.patterns:
|
|
82
|
-
return True
|
|
83
|
-
|
|
84
|
-
# a single mission name a unique project spec are specified
|
|
85
|
-
if (
|
|
86
|
-
project_spec_is_unique(spec.project_spec)
|
|
87
|
-
and len(spec.patterns) == 1
|
|
88
|
-
and _pattern_is_unique(spec.patterns[0])
|
|
89
|
-
):
|
|
90
|
-
return True
|
|
91
|
-
return False
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
def get_projects_by_spec(
|
|
95
|
-
client: AuthenticatedClient, spec: ProjectSpec
|
|
96
|
-
) -> List[Project]:
|
|
97
|
-
projects = _get_projects(client)
|
|
98
|
-
|
|
99
|
-
matched_names = filtered_by_patterns(
|
|
100
|
-
[project.name for project in projects], spec.patterns
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
if not spec.patterns and not spec.ids:
|
|
104
|
-
return projects
|
|
105
|
-
|
|
106
|
-
return [
|
|
107
|
-
project
|
|
108
|
-
for project in projects
|
|
109
|
-
if project.name in matched_names or project.id in spec.ids
|
|
110
|
-
]
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
def get_missions_by_spec(
|
|
114
|
-
client: AuthenticatedClient, spec: MissionSpec
|
|
115
|
-
) -> List[Mission]:
|
|
116
|
-
projects = get_projects_by_spec(client, spec.project_spec)
|
|
117
|
-
|
|
118
|
-
with ThreadPoolExecutor(max_workers=MAX_PARALLEL_REQUESTS) as executor:
|
|
119
|
-
missions = chain.from_iterable(
|
|
120
|
-
executor.map(
|
|
121
|
-
lambda project: _get_missions_by_project(client, project), projects
|
|
122
|
-
)
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
missions = list(missions)
|
|
126
|
-
|
|
127
|
-
if not spec.patterns and not spec.ids:
|
|
128
|
-
return list(missions)
|
|
129
|
-
|
|
130
|
-
matched_names = filtered_by_patterns(
|
|
131
|
-
[mission.name for mission in missions], spec.patterns
|
|
132
|
-
)
|
|
133
|
-
|
|
134
|
-
filter = [
|
|
135
|
-
mission
|
|
136
|
-
for mission in missions
|
|
137
|
-
if mission.name in matched_names or mission.id in spec.patterns
|
|
138
|
-
]
|
|
139
|
-
|
|
140
|
-
return filter
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
def get_files_by_spec(client: AuthenticatedClient, spec: FileSpec) -> List[File]:
|
|
144
|
-
missions = get_missions_by_spec(client, spec.mission_spec)
|
|
145
|
-
|
|
146
|
-
# collect files
|
|
147
|
-
with ThreadPoolExecutor(max_workers=MAX_PARALLEL_REQUESTS) as executor:
|
|
148
|
-
files = chain.from_iterable(
|
|
149
|
-
executor.map(
|
|
150
|
-
lambda mission: _get_files_by_mission(client, mission), missions
|
|
151
|
-
)
|
|
152
|
-
)
|
|
153
|
-
|
|
154
|
-
if not spec.patterns and not spec.ids:
|
|
155
|
-
return list(files)
|
|
156
|
-
matched_names = filtered_by_patterns([file.name for file in files], spec.patterns)
|
|
157
|
-
|
|
158
|
-
return [file for file in files if file.name in matched_names or file.id in spec.ids]
|