awcs-scripts 1.0.2__tar.gz → 1.1.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.
- {awcs_scripts-1.0.2 → awcs_scripts-1.1.0}/PKG-INFO +11 -8
- {awcs_scripts-1.0.2 → awcs_scripts-1.1.0}/README.md +10 -7
- {awcs_scripts-1.0.2 → awcs_scripts-1.1.0}/pyproject.toml +1 -1
- {awcs_scripts-1.0.2 → awcs_scripts-1.1.0}/src/awcs_scripts/__init__.py +1 -1
- {awcs_scripts-1.0.2 → awcs_scripts-1.1.0}/src/awcs_scripts/api.py +34 -0
- {awcs_scripts-1.0.2 → awcs_scripts-1.1.0}/src/awcs_scripts/cli.py +51 -1
- {awcs_scripts-1.0.2 → awcs_scripts-1.1.0}/tests/test_api.py +90 -2
- {awcs_scripts-1.0.2 → awcs_scripts-1.1.0}/uv.lock +1 -1
- {awcs_scripts-1.0.2 → awcs_scripts-1.1.0}/.gitignore +0 -0
- {awcs_scripts-1.0.2 → awcs_scripts-1.1.0}/.gitlab-ci.yml +0 -0
- {awcs_scripts-1.0.2 → awcs_scripts-1.1.0}/.vscode/tasks.json +0 -0
- {awcs_scripts-1.0.2 → awcs_scripts-1.1.0}/LICENSE +0 -0
- {awcs_scripts-1.0.2 → awcs_scripts-1.1.0}/bash/README.md +0 -0
- {awcs_scripts-1.0.2 → awcs_scripts-1.1.0}/scripts/release.py +0 -0
- {awcs_scripts-1.0.2 → awcs_scripts-1.1.0}/src/awcs_scripts/config.py +0 -0
- {awcs_scripts-1.0.2 → awcs_scripts-1.1.0}/src/awcs_scripts/mrmap/__init__.py +0 -0
- {awcs_scripts-1.0.2 → awcs_scripts-1.1.0}/src/awcs_scripts/mrmap/mrmap-to-bg.py +0 -0
- {awcs_scripts-1.0.2 → awcs_scripts-1.1.0}/src/awcs_scripts/mrmap/mrmap-to-raster.py +0 -0
- {awcs_scripts-1.0.2 → awcs_scripts-1.1.0}/tests/test_cli.py +0 -0
- {awcs_scripts-1.0.2 → awcs_scripts-1.1.0}/tests/test_raster.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: awcs-scripts
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: Command line scripts for WCS maps and the ARMS API
|
|
5
5
|
Project-URL: Repository, https://gitlab.sprydev.ru/automacon/agv/wcs/awcs_scripts
|
|
6
6
|
Author: AWCS
|
|
@@ -43,7 +43,7 @@ uv tool install .
|
|
|
43
43
|
|
|
44
44
|
```
|
|
45
45
|
uvx --from awcs-scripts awcs help
|
|
46
|
-
uvx --from awcs-scripts awcs mrmap-to-raster path/to/map.mrmap --resolution 0.05 --out-dir path/to/out
|
|
46
|
+
uvx --from awcs-scripts awcs mrmap-to-raster path/to/map.mrmap [--resolution 0.05] [--out-dir path/to/out]
|
|
47
47
|
uvx --from awcs-scripts awcs users me
|
|
48
48
|
```
|
|
49
49
|
|
|
@@ -59,8 +59,8 @@ awcs version
|
|
|
59
59
|
#### Карты
|
|
60
60
|
|
|
61
61
|
```
|
|
62
|
-
awcs mrmap-to-raster path/to/map.mrmap --out-dir path/to/out
|
|
63
|
-
awcs mrmap-to-bg path/to/map.mrmap path/to/layout.lif --layout_id layoutId --resolution 0.05 --output path/to/layout.out.lif
|
|
62
|
+
awcs mrmap-to-raster path/to/map.mrmap [--out-dir path/to/out]
|
|
63
|
+
awcs mrmap-to-bg path/to/map.mrmap path/to/layout.lif --layout_id layoutId [--resolution 0.05] [--output path/to/layout.out.lif]
|
|
64
64
|
```
|
|
65
65
|
|
|
66
66
|
`awcs mrmap-to-raster` пишет PNG, GeoTIFF и JSON. `--resolution` задаёт метры на пиксель, по умолчанию берётся `resolution_2d` из карты. `awcs mrmap-to-bg` подставляет этот растр в `backgroundImage` одного layout. Без `--output` исходный `.lif` перезаписывается.
|
|
@@ -74,16 +74,19 @@ awcs users me
|
|
|
74
74
|
awcs mission_templates list
|
|
75
75
|
awcs robots list
|
|
76
76
|
awcs missions list
|
|
77
|
-
awcs missions create --template_code PickPallet --params "{\"zone\":\"A\"}"
|
|
78
|
-
awcs mission_templates execute PickPallet --params "{\"zone\":\"A\"}"
|
|
77
|
+
awcs missions create --template_code PickPallet [--params "{\"zone\":\"A\"}"]
|
|
78
|
+
awcs mission_templates execute PickPallet [--params "{\"zone\":\"A\"}"]
|
|
79
79
|
awcs projects list
|
|
80
|
-
awcs projects create --lif_json path/to/layout.lif -m MAIRUI -v 1.0.0 --desc "line 1" --sync
|
|
80
|
+
awcs projects create --lif_json path/to/layout.lif -m MAIRUI -v 1.0.0 [--desc "line 1"] [--sync]
|
|
81
|
+
awcs projects upload --lif_json path/to/layout.lif -m MAIRUI -v 1.0.0 [--desc "line 1"] [--sync]
|
|
82
|
+
awcs projects download
|
|
83
|
+
awcs projects download 12 -o path/to/layout.lif
|
|
81
84
|
awcs projects activate 12
|
|
82
85
|
```
|
|
83
86
|
|
|
84
87
|
`awcs login` проверяет ключ через `GET /users/me` и только после ответа 200 сохраняет origin и ключ. Профиль лежит вне репозитория: путь из `AWCS_CONFIG`, иначе `%APPDATA%\awcs\config.ini` на Windows и `~/.config/awcs/config.ini` в остальных системах. Ключ в вывод не печатается. Клиент сам дописывает `/api/v1` и шлёт заголовок `X-Api-Key`.
|
|
85
88
|
|
|
86
|
-
У списков есть `--page`, `--per_page`, `--filter`, `--sort_by`. `awcs missions create` принимает `--template_id` или `--template_code` и необязательный `--params`. `awcs mission_templates execute` отправляет `--params` плоским JSON-объектом. `awcs projects create` загружает LIF как `multipart/form-data`: `-m` это `--manufacturer`, `-v` и `--version` это `--project_version`, `--desc` это `--description`, `--sync` это `--synchronous`.
|
|
89
|
+
У списков есть `--page`, `--per_page`, `--filter`, `--sort_by`. `awcs missions create` принимает `--template_id` или `--template_code` и необязательный `--params`. `awcs mission_templates execute` отправляет `--params` плоским JSON-объектом. `awcs projects create` загружает LIF как `multipart/form-data`: `-m` это `--manufacturer`, `-v` и `--version` это `--project_version`, `--desc` это `--description`, `--sync` это `--synchronous`. `awcs projects upload` делает то же самое. `awcs projects download` без id забирает активный проект (`GET /projects/active`), с id — этот проект, и сохраняет исходный LIF. `-o` задаёт файл или существующую папку; без него файл пишется в текущий каталог под именем вложения. Путь печатается в stdout.
|
|
87
90
|
|
|
88
91
|
### Удаление
|
|
89
92
|
|
|
@@ -28,7 +28,7 @@ uv tool install .
|
|
|
28
28
|
|
|
29
29
|
```
|
|
30
30
|
uvx --from awcs-scripts awcs help
|
|
31
|
-
uvx --from awcs-scripts awcs mrmap-to-raster path/to/map.mrmap --resolution 0.05 --out-dir path/to/out
|
|
31
|
+
uvx --from awcs-scripts awcs mrmap-to-raster path/to/map.mrmap [--resolution 0.05] [--out-dir path/to/out]
|
|
32
32
|
uvx --from awcs-scripts awcs users me
|
|
33
33
|
```
|
|
34
34
|
|
|
@@ -44,8 +44,8 @@ awcs version
|
|
|
44
44
|
#### Карты
|
|
45
45
|
|
|
46
46
|
```
|
|
47
|
-
awcs mrmap-to-raster path/to/map.mrmap --out-dir path/to/out
|
|
48
|
-
awcs mrmap-to-bg path/to/map.mrmap path/to/layout.lif --layout_id layoutId --resolution 0.05 --output path/to/layout.out.lif
|
|
47
|
+
awcs mrmap-to-raster path/to/map.mrmap [--out-dir path/to/out]
|
|
48
|
+
awcs mrmap-to-bg path/to/map.mrmap path/to/layout.lif --layout_id layoutId [--resolution 0.05] [--output path/to/layout.out.lif]
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
`awcs mrmap-to-raster` пишет PNG, GeoTIFF и JSON. `--resolution` задаёт метры на пиксель, по умолчанию берётся `resolution_2d` из карты. `awcs mrmap-to-bg` подставляет этот растр в `backgroundImage` одного layout. Без `--output` исходный `.lif` перезаписывается.
|
|
@@ -59,16 +59,19 @@ awcs users me
|
|
|
59
59
|
awcs mission_templates list
|
|
60
60
|
awcs robots list
|
|
61
61
|
awcs missions list
|
|
62
|
-
awcs missions create --template_code PickPallet --params "{\"zone\":\"A\"}"
|
|
63
|
-
awcs mission_templates execute PickPallet --params "{\"zone\":\"A\"}"
|
|
62
|
+
awcs missions create --template_code PickPallet [--params "{\"zone\":\"A\"}"]
|
|
63
|
+
awcs mission_templates execute PickPallet [--params "{\"zone\":\"A\"}"]
|
|
64
64
|
awcs projects list
|
|
65
|
-
awcs projects create --lif_json path/to/layout.lif -m MAIRUI -v 1.0.0 --desc "line 1" --sync
|
|
65
|
+
awcs projects create --lif_json path/to/layout.lif -m MAIRUI -v 1.0.0 [--desc "line 1"] [--sync]
|
|
66
|
+
awcs projects upload --lif_json path/to/layout.lif -m MAIRUI -v 1.0.0 [--desc "line 1"] [--sync]
|
|
67
|
+
awcs projects download
|
|
68
|
+
awcs projects download 12 -o path/to/layout.lif
|
|
66
69
|
awcs projects activate 12
|
|
67
70
|
```
|
|
68
71
|
|
|
69
72
|
`awcs login` проверяет ключ через `GET /users/me` и только после ответа 200 сохраняет origin и ключ. Профиль лежит вне репозитория: путь из `AWCS_CONFIG`, иначе `%APPDATA%\awcs\config.ini` на Windows и `~/.config/awcs/config.ini` в остальных системах. Ключ в вывод не печатается. Клиент сам дописывает `/api/v1` и шлёт заголовок `X-Api-Key`.
|
|
70
73
|
|
|
71
|
-
У списков есть `--page`, `--per_page`, `--filter`, `--sort_by`. `awcs missions create` принимает `--template_id` или `--template_code` и необязательный `--params`. `awcs mission_templates execute` отправляет `--params` плоским JSON-объектом. `awcs projects create` загружает LIF как `multipart/form-data`: `-m` это `--manufacturer`, `-v` и `--version` это `--project_version`, `--desc` это `--description`, `--sync` это `--synchronous`.
|
|
74
|
+
У списков есть `--page`, `--per_page`, `--filter`, `--sort_by`. `awcs missions create` принимает `--template_id` или `--template_code` и необязательный `--params`. `awcs mission_templates execute` отправляет `--params` плоским JSON-объектом. `awcs projects create` загружает LIF как `multipart/form-data`: `-m` это `--manufacturer`, `-v` и `--version` это `--project_version`, `--desc` это `--description`, `--sync` это `--synchronous`. `awcs projects upload` делает то же самое. `awcs projects download` без id забирает активный проект (`GET /projects/active`), с id — этот проект, и сохраняет исходный LIF. `-o` задаёт файл или существующую папку; без него файл пишется в текущий каталог под именем вложения. Путь печатается в stdout.
|
|
72
75
|
|
|
73
76
|
### Удаление
|
|
74
77
|
|
|
@@ -24,6 +24,13 @@ def origin_of(url: str) -> str:
|
|
|
24
24
|
return value
|
|
25
25
|
|
|
26
26
|
|
|
27
|
+
def _strip_api_prefix(path: str) -> str:
|
|
28
|
+
"""Drop a leading /api/v1 so the client base URL does not double it."""
|
|
29
|
+
if path.startswith(_API_PREFIX + "/"):
|
|
30
|
+
return path[len(_API_PREFIX) :]
|
|
31
|
+
return path
|
|
32
|
+
|
|
33
|
+
|
|
27
34
|
class Client:
|
|
28
35
|
def __init__(
|
|
29
36
|
self,
|
|
@@ -64,3 +71,30 @@ class Client:
|
|
|
64
71
|
if response.status_code >= 400:
|
|
65
72
|
raise ApiError(response.status_code, response.text)
|
|
66
73
|
return response
|
|
74
|
+
|
|
75
|
+
def get(self, url_or_path: str) -> httpx.Response:
|
|
76
|
+
"""GET an API path or an absolute URL.
|
|
77
|
+
|
|
78
|
+
A leading /api/v1 is removed so the client base URL does not double it.
|
|
79
|
+
An absolute URL on this origin is sent with the API key. An absolute URL
|
|
80
|
+
on another host is fetched without the key.
|
|
81
|
+
"""
|
|
82
|
+
if url_or_path.startswith(("http://", "https://")):
|
|
83
|
+
return self._get_absolute(url_or_path)
|
|
84
|
+
return self.request("GET", _strip_api_prefix(url_or_path))
|
|
85
|
+
|
|
86
|
+
def _get_absolute(self, url: str) -> httpx.Response:
|
|
87
|
+
target = httpx.URL(url)
|
|
88
|
+
origin = httpx.URL(self.origin)
|
|
89
|
+
same_origin = (
|
|
90
|
+
target.scheme == origin.scheme
|
|
91
|
+
and target.host == origin.host
|
|
92
|
+
and target.port == origin.port
|
|
93
|
+
)
|
|
94
|
+
if not same_origin:
|
|
95
|
+
response = httpx.get(url, timeout=self._client.timeout, follow_redirects=True)
|
|
96
|
+
if response.status_code >= 400:
|
|
97
|
+
raise ApiError(response.status_code, response.text)
|
|
98
|
+
return response
|
|
99
|
+
params = dict(target.params) or None
|
|
100
|
+
return self.request("GET", _strip_api_prefix(target.path), params=params)
|
|
@@ -158,6 +158,45 @@ def _cmd_projects_activate(args: argparse.Namespace) -> None:
|
|
|
158
158
|
_emit(client.request("POST", f"/projects/{args.id}/activate"))
|
|
159
159
|
|
|
160
160
|
|
|
161
|
+
def _download_name(source: dict[str, Any], project_id: object) -> str:
|
|
162
|
+
raw = str(source.get("filename") or "").replace("\\", "/")
|
|
163
|
+
name = Path(raw).name
|
|
164
|
+
if name and name not in {".", ".."}:
|
|
165
|
+
return name
|
|
166
|
+
return f"project-{project_id}.lif"
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _download_destination(output: Path | None, filename: str) -> Path:
|
|
170
|
+
if output is None:
|
|
171
|
+
return Path(filename)
|
|
172
|
+
output = output.expanduser()
|
|
173
|
+
if output.is_dir():
|
|
174
|
+
return output / filename
|
|
175
|
+
return output
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _cmd_projects_download(args: argparse.Namespace) -> None:
|
|
179
|
+
with _open_client() as client:
|
|
180
|
+
if args.id is None:
|
|
181
|
+
project = client.request("GET", "/projects/active").json()
|
|
182
|
+
else:
|
|
183
|
+
project = client.request("GET", f"/projects/{args.id}").json()
|
|
184
|
+
source = project.get("source") if isinstance(project, dict) else None
|
|
185
|
+
if not isinstance(source, dict):
|
|
186
|
+
source = {}
|
|
187
|
+
download_url = source.get("download_url")
|
|
188
|
+
if not download_url:
|
|
189
|
+
attachment_id = source.get("id")
|
|
190
|
+
if not attachment_id:
|
|
191
|
+
_fail("project has no map file")
|
|
192
|
+
download_url = f"/attachments/{attachment_id}/download"
|
|
193
|
+
response = client.get(str(download_url))
|
|
194
|
+
project_id = project.get("id", args.id) if isinstance(project, dict) else args.id
|
|
195
|
+
dest = _download_destination(args.output, _download_name(source, project_id))
|
|
196
|
+
dest.write_bytes(response.content)
|
|
197
|
+
print(dest)
|
|
198
|
+
|
|
199
|
+
|
|
161
200
|
_TOOL_COMMANDS = {
|
|
162
201
|
"mrmap-to-raster": "mrmap-to-raster.py",
|
|
163
202
|
"mrmap-to-bg": "mrmap-to-bg.py",
|
|
@@ -229,13 +268,24 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
229
268
|
projects_list = projects_sub.add_parser("list", help="GET /projects")
|
|
230
269
|
_add_list_flags(projects_list)
|
|
231
270
|
projects_list.set_defaults(func=_cmd_projects_list)
|
|
232
|
-
projects_create = projects_sub.add_parser(
|
|
271
|
+
projects_create = projects_sub.add_parser(
|
|
272
|
+
"create",
|
|
273
|
+
aliases=["upload"],
|
|
274
|
+
help="POST /projects with a LIF file (alias: upload)",
|
|
275
|
+
)
|
|
233
276
|
projects_create.add_argument("--lif_json", type=Path, required=True, help="Path to a .lif file")
|
|
234
277
|
projects_create.add_argument("--manufacturer", "-m", required=True)
|
|
235
278
|
projects_create.add_argument("--project_version", "--version", "-v", required=True, dest="project_version")
|
|
236
279
|
projects_create.add_argument("--description", "--desc", dest="description")
|
|
237
280
|
projects_create.add_argument("--synchronous", "--sync", action="store_true", dest="synchronous")
|
|
238
281
|
projects_create.set_defaults(func=_cmd_projects_create)
|
|
282
|
+
projects_download = projects_sub.add_parser(
|
|
283
|
+
"download",
|
|
284
|
+
help="Save a project LIF file; the active project when id is omitted",
|
|
285
|
+
)
|
|
286
|
+
projects_download.add_argument("id", type=int, nargs="?", help="Project id; omit to download the active project")
|
|
287
|
+
projects_download.add_argument("--output", "-o", type=Path, help="Output file, or an existing directory")
|
|
288
|
+
projects_download.set_defaults(func=_cmd_projects_download)
|
|
239
289
|
projects_activate = projects_sub.add_parser("activate", help="POST /projects/{id}/activate")
|
|
240
290
|
projects_activate.add_argument("id", type=int)
|
|
241
291
|
projects_activate.set_defaults(func=_cmd_projects_activate)
|
|
@@ -7,6 +7,8 @@ from awcs_scripts.api import Client, origin_of
|
|
|
7
7
|
from awcs_scripts.cli import main
|
|
8
8
|
from awcs_scripts.config import load_profile
|
|
9
9
|
|
|
10
|
+
_LIF = b'{"layouts":[]}'
|
|
11
|
+
|
|
10
12
|
|
|
11
13
|
def test_origin_strips_api_prefix():
|
|
12
14
|
assert origin_of("https://dev.wcs.automacon.ru/") == "https://dev.wcs.automacon.ru"
|
|
@@ -100,7 +102,8 @@ def test_template_execute_body(tmp_path, monkeypatch):
|
|
|
100
102
|
assert seen["body"] == {"zone": "A"}
|
|
101
103
|
|
|
102
104
|
|
|
103
|
-
|
|
105
|
+
@pytest.mark.parametrize("command", ["create", "upload"])
|
|
106
|
+
def test_projects_create_and_activate(tmp_path, monkeypatch, command):
|
|
104
107
|
_profile(tmp_path, monkeypatch)
|
|
105
108
|
lif = tmp_path / "layout.lif"
|
|
106
109
|
lif.write_text("{}", encoding="utf-8")
|
|
@@ -113,7 +116,7 @@ def test_projects_create_and_activate(tmp_path, monkeypatch):
|
|
|
113
116
|
_install(monkeypatch, handler)
|
|
114
117
|
main([
|
|
115
118
|
"projects",
|
|
116
|
-
|
|
119
|
+
command,
|
|
117
120
|
"--lif_json",
|
|
118
121
|
str(lif),
|
|
119
122
|
"-m",
|
|
@@ -143,6 +146,91 @@ def test_projects_create_and_activate(tmp_path, monkeypatch):
|
|
|
143
146
|
assert seen[1].url.path == "/api/v1/projects/12/activate"
|
|
144
147
|
|
|
145
148
|
|
|
149
|
+
def test_projects_download_active_uses_source_filename(tmp_path, monkeypatch, capsys):
|
|
150
|
+
_profile(tmp_path, monkeypatch)
|
|
151
|
+
monkeypatch.chdir(tmp_path)
|
|
152
|
+
seen = []
|
|
153
|
+
|
|
154
|
+
def handler(request: httpx.Request) -> httpx.Response:
|
|
155
|
+
seen.append(request.url.path)
|
|
156
|
+
if request.url.path == "/api/v1/projects/active":
|
|
157
|
+
return httpx.Response(200, json={
|
|
158
|
+
"id": 4,
|
|
159
|
+
"source": {
|
|
160
|
+
"id": 9,
|
|
161
|
+
"filename": "nested/line.lif",
|
|
162
|
+
"download_url": "/api/v1/attachments/9/download",
|
|
163
|
+
},
|
|
164
|
+
})
|
|
165
|
+
assert request.headers["x-api-key"] == "secret-key"
|
|
166
|
+
return httpx.Response(200, content=_LIF)
|
|
167
|
+
|
|
168
|
+
_install(monkeypatch, handler)
|
|
169
|
+
main(["projects", "download"])
|
|
170
|
+
assert seen == ["/api/v1/projects/active", "/api/v1/attachments/9/download"]
|
|
171
|
+
assert (tmp_path / "line.lif").read_bytes() == _LIF
|
|
172
|
+
assert capsys.readouterr().out.strip() == "line.lif"
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def test_projects_download_id_to_output(tmp_path, monkeypatch, capsys):
|
|
176
|
+
_profile(tmp_path, monkeypatch)
|
|
177
|
+
dest = tmp_path / "saved.lif"
|
|
178
|
+
folder = tmp_path / "maps"
|
|
179
|
+
folder.mkdir()
|
|
180
|
+
|
|
181
|
+
def handler(request: httpx.Request) -> httpx.Response:
|
|
182
|
+
if request.url.path == "/api/v1/projects/12":
|
|
183
|
+
return httpx.Response(200, json={
|
|
184
|
+
"id": 12,
|
|
185
|
+
"source": {"id": 3, "filename": "layout.lif"},
|
|
186
|
+
})
|
|
187
|
+
assert request.url.path == "/api/v1/attachments/3/download"
|
|
188
|
+
return httpx.Response(200, content=_LIF)
|
|
189
|
+
|
|
190
|
+
_install(monkeypatch, handler)
|
|
191
|
+
main(["projects", "download", "12", "-o", str(dest)])
|
|
192
|
+
assert dest.read_bytes() == _LIF
|
|
193
|
+
assert capsys.readouterr().out.strip() == str(dest)
|
|
194
|
+
|
|
195
|
+
main(["projects", "download", "12", "-o", str(folder)])
|
|
196
|
+
assert (folder / "layout.lif").read_bytes() == _LIF
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def test_projects_download_without_source_fails(tmp_path, monkeypatch):
|
|
200
|
+
_profile(tmp_path, monkeypatch)
|
|
201
|
+
_install(monkeypatch, lambda request: httpx.Response(200, json={"id": 1, "source": None}))
|
|
202
|
+
with pytest.raises(SystemExit) as caught:
|
|
203
|
+
main(["projects", "download", "1"])
|
|
204
|
+
assert caught.value.code == 1
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def test_client_get_same_origin_keeps_query():
|
|
208
|
+
def handler(request: httpx.Request) -> httpx.Response:
|
|
209
|
+
assert request.url.path == "/api/v1/attachments/9/download"
|
|
210
|
+
assert request.url.params["x"] == "1"
|
|
211
|
+
assert request.headers["x-api-key"] == "secret-key"
|
|
212
|
+
return httpx.Response(200, content=_LIF)
|
|
213
|
+
|
|
214
|
+
with Client("https://dev.wcs.automacon.ru", "secret-key", transport=httpx.MockTransport(handler)) as client:
|
|
215
|
+
response = client.get("https://dev.wcs.automacon.ru/api/v1/attachments/9/download?x=1")
|
|
216
|
+
assert response.content == _LIF
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def test_client_get_external_omits_api_key(monkeypatch):
|
|
220
|
+
seen = {}
|
|
221
|
+
|
|
222
|
+
def fake_get(url, **kwargs):
|
|
223
|
+
seen["url"] = url
|
|
224
|
+
seen["headers"] = kwargs.get("headers")
|
|
225
|
+
return httpx.Response(200, content=_LIF)
|
|
226
|
+
|
|
227
|
+
monkeypatch.setattr("awcs_scripts.api.httpx.get", fake_get)
|
|
228
|
+
with Client("https://dev.wcs.automacon.ru", "secret-key") as client:
|
|
229
|
+
response = client.get("https://files.example/map.lif")
|
|
230
|
+
assert response.content == _LIF
|
|
231
|
+
assert seen == {"url": "https://files.example/map.lif", "headers": None}
|
|
232
|
+
|
|
233
|
+
|
|
146
234
|
def _profile(tmp_path, monkeypatch):
|
|
147
235
|
config = tmp_path / "config.ini"
|
|
148
236
|
config.write_text("[default]\nurl = https://dev.wcs.automacon.ru\napikey = secret-key\n", encoding="utf-8")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|