arcsecond 3.8.0__tar.gz → 3.9.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.
- arcsecond-3.9.0/.github/workflows/docsdeploy.yml +28 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/.github/workflows/pythonpublish.yml +3 -3
- {arcsecond-3.8.0 → arcsecond-3.9.0}/.github/workflows/tests.yml +8 -8
- {arcsecond-3.8.0 → arcsecond-3.9.0}/.gitignore +1 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/PKG-INFO +4 -3
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/api/resources.py +73 -38
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/hosting/docker/docker-compose.yml +0 -2
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/hosting/local.py +2 -6
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/webcam/proxy.py +12 -2
- {arcsecond-3.8.0 → arcsecond-3.9.0}/docs/.vitepress/config.js +15 -3
- arcsecond-3.9.0/docs/.vitepress/theme/custom.css +35 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/docs/api-basics.md +11 -5
- arcsecond-3.9.0/docs/img/icon-logo.png +0 -0
- arcsecond-3.9.0/docs/index.md +36 -0
- arcsecond-3.9.0/docs/install.md +78 -0
- arcsecond-3.9.0/docs/public/img/icon-logo.png +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/docs/resources.md +52 -28
- arcsecond-3.9.0/docs/upload.md +128 -0
- arcsecond-3.9.0/docs/webcam.md +116 -0
- arcsecond-3.9.0/package-lock.json +3528 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/package.json +5 -3
- {arcsecond-3.8.0 → arcsecond-3.9.0}/pyproject.toml +7 -4
- {arcsecond-3.8.0 → arcsecond-3.9.0}/tests/api/test_targets.py +58 -11
- arcsecond-3.8.0/.github/workflows/docsdeploy.yml +0 -30
- arcsecond-3.8.0/docs/.vitepress/theme/custom.css +0 -4
- arcsecond-3.8.0/docs/img/logo-circle.png +0 -0
- arcsecond-3.8.0/docs/index.md +0 -156
- arcsecond-3.8.0/docs/install.md +0 -67
- arcsecond-3.8.0/package-lock.json +0 -2627
- {arcsecond-3.8.0 → arcsecond-3.9.0}/.docker/Dockerfile_postgres +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/.docker/Dockerfile_redis +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/.github/dependabot.yml +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/LICENSE +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/Makefile +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/README.md +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/__init__.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/__version__.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/api/__init__.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/api/config.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/api/constants.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/api/endpoint.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/api/main.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/cli.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/cloud/__init__.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/cloud/auth.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/cloud/resources.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/cloud/uploader/__init__.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/cloud/uploader/allskycameraimages/__init__.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/cloud/uploader/allskycameraimages/context.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/cloud/uploader/allskycameraimages/errors.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/cloud/uploader/allskycameraimages/uploader.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/cloud/uploader/allskycameraimages/utils.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/cloud/uploader/constants.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/cloud/uploader/context.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/cloud/uploader/datafiles/__init__.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/cloud/uploader/datafiles/context.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/cloud/uploader/datafiles/errors.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/cloud/uploader/datafiles/uploader.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/cloud/uploader/datafiles/utils.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/cloud/uploader/errors.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/cloud/uploader/logger.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/cloud/uploader/uploader.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/cloud/uploader/utils.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/cloud/uploader/walker.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/cloud/uploads.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/errors.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/hosting/__init__.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/hosting/checks.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/hosting/constants.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/hosting/docker/__init__.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/hosting/docker/constants.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/hosting/docker/containers.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/hosting/docker/images.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/hosting/docker/utils.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/hosting/keygen/__init__.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/hosting/keygen/client.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/hosting/keygen/utils.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/hosting/main.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/hosting/postgres/init-db.sh +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/hosting/setup.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/hosting/utils.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/hosting/validation.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/options.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/targets.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/webcam/__init__.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/arcsecond/webcam/commands.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/deploy.sh +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/docs/.vitepress/theme/index.js +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/examples/example_upload_files.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/examples/example_upload_images.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/poetry.lock +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/requirements.txt +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/setup.cfg +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/setup.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/sonar-project.properties +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/tests/__init__.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/tests/api/__init__.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/tests/api/test_api.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/tests/api/test_api_endpoint.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/tests/api/test_config.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/tests/cloud/__init__.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/tests/cloud/uploader/__init__.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/tests/cloud/uploader/allskycameraimages/__init__.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/tests/cloud/uploader/allskycameraimages/test_context.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/tests/cloud/uploader/allskycameraimages/test_uploader_full_process.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/tests/cloud/uploader/datafiles/__init__.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/tests/cloud/uploader/datafiles/test_uploader_errors.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/tests/cloud/uploader/datafiles/test_uploader_full_process.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/tests/cloud/uploader/datafiles/test_uploader_init.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/tests/cloud/uploader/datafiles/test_uploader_prepare.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/tests/cloud/uploader/datafiles/test_uploader_upload.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/tests/conftest.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/tests/fixtures/file1.fits +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/tests/test_cli.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/tests/test_hosting_local.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/tests/test_targets_planning.py +0 -0
- {arcsecond-3.8.0 → arcsecond-3.9.0}/tests/utils.py +0 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: Deploy Docs
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- master
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
build:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v5
|
|
16
|
+
- name: Setup node
|
|
17
|
+
uses: actions/setup-node@v5
|
|
18
|
+
with:
|
|
19
|
+
node-version: '24'
|
|
20
|
+
cache: 'npm'
|
|
21
|
+
- run: npm ci
|
|
22
|
+
env:
|
|
23
|
+
NODE_AUTH_TOKEN: ${{ secrets.READ_PACKAGE_TOKEN }}
|
|
24
|
+
CI: true
|
|
25
|
+
- name: Deploy docs
|
|
26
|
+
env:
|
|
27
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
28
|
+
run: ./deploy.sh
|
|
@@ -22,7 +22,7 @@ jobs:
|
|
|
22
22
|
exit 1
|
|
23
23
|
fi
|
|
24
24
|
|
|
25
|
-
- uses: actions/checkout@
|
|
25
|
+
- uses: actions/checkout@v5
|
|
26
26
|
|
|
27
27
|
- name: Validate pyproject.toml version matches tag
|
|
28
28
|
run: |
|
|
@@ -41,7 +41,7 @@ jobs:
|
|
|
41
41
|
PY
|
|
42
42
|
|
|
43
43
|
- name: Set up Python
|
|
44
|
-
uses: actions/setup-python@
|
|
44
|
+
uses: actions/setup-python@v6
|
|
45
45
|
with:
|
|
46
46
|
python-version: '3.12'
|
|
47
47
|
|
|
@@ -54,4 +54,4 @@ jobs:
|
|
|
54
54
|
- name: Publish to PyPI (OIDC)
|
|
55
55
|
uses: pypa/gh-action-pypi-publish@release/v1
|
|
56
56
|
with:
|
|
57
|
-
packages-dir: dist
|
|
57
|
+
packages-dir: dist
|
|
@@ -14,10 +14,10 @@ jobs:
|
|
|
14
14
|
python-version: [ '3.10', '3.11', '3.12', '3.13' ]
|
|
15
15
|
|
|
16
16
|
steps:
|
|
17
|
-
- uses: actions/checkout@
|
|
17
|
+
- uses: actions/checkout@v5
|
|
18
18
|
|
|
19
19
|
- name: Set up Python ${{ matrix.python-version }}
|
|
20
|
-
uses: actions/setup-python@
|
|
20
|
+
uses: actions/setup-python@v6
|
|
21
21
|
with:
|
|
22
22
|
python-version: ${{ matrix.python-version }}
|
|
23
23
|
|
|
@@ -39,7 +39,7 @@ jobs:
|
|
|
39
39
|
|
|
40
40
|
- name: Upload coverage artifact (only on 3.12)
|
|
41
41
|
if: matrix.python-version == '3.12'
|
|
42
|
-
uses: actions/upload-artifact@
|
|
42
|
+
uses: actions/upload-artifact@v6
|
|
43
43
|
with:
|
|
44
44
|
name: coverage
|
|
45
45
|
path: coverage.xml
|
|
@@ -49,10 +49,10 @@ jobs:
|
|
|
49
49
|
needs: tests
|
|
50
50
|
|
|
51
51
|
steps:
|
|
52
|
-
- uses: actions/checkout@
|
|
52
|
+
- uses: actions/checkout@v5
|
|
53
53
|
|
|
54
54
|
- name: Download coverage artifact
|
|
55
|
-
uses: actions/download-artifact@
|
|
55
|
+
uses: actions/download-artifact@v5
|
|
56
56
|
with:
|
|
57
57
|
name: coverage
|
|
58
58
|
path: .
|
|
@@ -65,10 +65,10 @@ jobs:
|
|
|
65
65
|
lint:
|
|
66
66
|
runs-on: ubuntu-latest
|
|
67
67
|
steps:
|
|
68
|
-
- uses: actions/checkout@
|
|
68
|
+
- uses: actions/checkout@v5
|
|
69
69
|
|
|
70
70
|
- name: Set up Python
|
|
71
|
-
uses: actions/setup-python@
|
|
71
|
+
uses: actions/setup-python@v6
|
|
72
72
|
with:
|
|
73
73
|
python-version: '3.12'
|
|
74
74
|
|
|
@@ -87,4 +87,4 @@ jobs:
|
|
|
87
87
|
|
|
88
88
|
- name: Check imports with isort
|
|
89
89
|
run: |
|
|
90
|
-
isort --check-only --profile black arcsecond tests
|
|
90
|
+
isort --check-only --profile black arcsecond tests
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: arcsecond
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.9.0
|
|
4
4
|
Summary: CLI for arcsecond.io
|
|
5
5
|
Project-URL: Homepage, https://github.com/arcsecond-io/cli
|
|
6
6
|
Project-URL: Issues, https://github.com/arcsecond-io/cli/issues
|
|
@@ -36,15 +36,16 @@ Classifier: Operating System :: OS Independent
|
|
|
36
36
|
Classifier: Programming Language :: Python :: 3
|
|
37
37
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
38
38
|
Requires-Python: >=3.9
|
|
39
|
-
Requires-Dist: aiohttp>=3.9
|
|
40
39
|
Requires-Dist: click>=8
|
|
41
40
|
Requires-Dist: configparser
|
|
42
41
|
Requires-Dist: docker
|
|
43
42
|
Requires-Dist: httpx
|
|
44
|
-
Requires-Dist: opencv-python-headless<5,>=4.10
|
|
45
43
|
Requires-Dist: py-machineid
|
|
46
44
|
Requires-Dist: tqdm<5.0.0,>=4.67.1
|
|
47
45
|
Requires-Dist: wait-for-it
|
|
46
|
+
Provides-Extra: webcam
|
|
47
|
+
Requires-Dist: aiohttp>=3.9; extra == 'webcam'
|
|
48
|
+
Requires-Dist: opencv-python-headless<5,>=4.10; extra == 'webcam'
|
|
48
49
|
Description-Content-Type: text/markdown
|
|
49
50
|
|
|
50
51
|
[](https://github.com/arcsecond-io/cli/actions/workflows/docsdeploy.yml) [](http://pepy.tech/project/arcsecond)
|
|
@@ -6,53 +6,76 @@ from .endpoint import ArcsecondAPIEndpoint
|
|
|
6
6
|
class ArcsecondTargetListsResource(ArcsecondAPIEndpoint):
|
|
7
7
|
"""Target-list specific helpers built on top of the generic endpoint contract."""
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
target_relation_key = "targets"
|
|
10
|
+
target_writable_fields = (
|
|
11
|
+
"id",
|
|
12
|
+
"pk",
|
|
13
|
+
"object",
|
|
14
|
+
"name",
|
|
15
|
+
"identifier",
|
|
16
|
+
"target_class",
|
|
17
|
+
"mode",
|
|
18
|
+
"color",
|
|
19
|
+
"notes",
|
|
20
|
+
"tags",
|
|
21
|
+
"profile",
|
|
22
|
+
"organisation",
|
|
23
|
+
)
|
|
10
24
|
|
|
11
25
|
def _ensure_iterable(self, values):
|
|
12
26
|
if values is None:
|
|
13
27
|
return None
|
|
28
|
+
if isinstance(values, dict):
|
|
29
|
+
return [values]
|
|
14
30
|
if isinstance(values, (str, int)):
|
|
15
31
|
return [values]
|
|
16
32
|
return list(values)
|
|
17
33
|
|
|
18
|
-
def
|
|
34
|
+
def _target_payload_identity(self, target):
|
|
35
|
+
if target.get("id") is not None:
|
|
36
|
+
return ("id", target["id"])
|
|
37
|
+
if target.get("pk") is not None:
|
|
38
|
+
return ("pk", target["pk"])
|
|
39
|
+
return (
|
|
40
|
+
"composite",
|
|
41
|
+
target.get("target_class"),
|
|
42
|
+
target.get("identifier"),
|
|
43
|
+
target.get("name"),
|
|
44
|
+
target.get("mode"),
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
def _normalise_target_payloads(self, targets):
|
|
19
48
|
values = self._ensure_iterable(targets)
|
|
20
49
|
if values is None:
|
|
21
50
|
return None
|
|
22
51
|
|
|
23
|
-
|
|
52
|
+
payloads = []
|
|
24
53
|
for target in values:
|
|
25
|
-
if isinstance(target, dict):
|
|
26
|
-
|
|
27
|
-
target.
|
|
28
|
-
or target
|
|
29
|
-
|
|
30
|
-
or target.get("name")
|
|
54
|
+
if not isinstance(target, dict):
|
|
55
|
+
raise ArcsecondError(
|
|
56
|
+
"Target list helpers expect target payload dictionaries, not scalar IDs or UUIDs. "
|
|
57
|
+
"Pass dictionaries such as `plan_target_payload(...).payload` or target objects returned "
|
|
58
|
+
"by `api.targets.read()/list()/upsert()`."
|
|
31
59
|
)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if payload and key in payload:
|
|
46
|
-
return key
|
|
47
|
-
return self.target_relation_keys[0]
|
|
60
|
+
|
|
61
|
+
payload = {
|
|
62
|
+
key: value
|
|
63
|
+
for key, value in target.items()
|
|
64
|
+
if key in self.target_writable_fields and value is not None
|
|
65
|
+
}
|
|
66
|
+
if not payload:
|
|
67
|
+
raise ArcsecondError(
|
|
68
|
+
"Target dictionaries must include at least one writable target field."
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
payloads.append(payload)
|
|
72
|
+
return payloads
|
|
48
73
|
|
|
49
74
|
def _build_payload(self, json=None, targets=None, target_key=None, **fields):
|
|
50
75
|
payload = super()._build_payload(json=json, **fields) or {}
|
|
51
|
-
normalised_targets = self.
|
|
76
|
+
normalised_targets = self._normalise_target_payloads(targets)
|
|
52
77
|
if normalised_targets is not None:
|
|
53
|
-
payload[self.
|
|
54
|
-
normalised_targets
|
|
55
|
-
)
|
|
78
|
+
payload[target_key or self.target_relation_key] = normalised_targets
|
|
56
79
|
return payload or None
|
|
57
80
|
|
|
58
81
|
def create(self, json=None, targets=None, target_key=None, **fields):
|
|
@@ -74,14 +97,14 @@ class ArcsecondTargetListsResource(ArcsecondAPIEndpoint):
|
|
|
74
97
|
return super().upsert(match_field=match_field, json=payload)
|
|
75
98
|
|
|
76
99
|
def _read_target_refs(self, target_list, target_key=None):
|
|
77
|
-
key =
|
|
100
|
+
key = target_key or self.target_relation_key
|
|
78
101
|
raw_targets = (target_list or {}).get(key, [])
|
|
79
|
-
refs = self.
|
|
102
|
+
refs = self._normalise_target_payloads(raw_targets) or []
|
|
80
103
|
return key, refs
|
|
81
104
|
|
|
82
105
|
def set_targets(self, id_name_uuid, targets, target_key=None):
|
|
83
|
-
target_key = self.
|
|
84
|
-
return self.update(id_name_uuid, **{target_key: self.
|
|
106
|
+
target_key = target_key or self.target_relation_key
|
|
107
|
+
return self.update(id_name_uuid, **{target_key: self._normalise_target_payloads(targets)})
|
|
85
108
|
|
|
86
109
|
def clear_targets(self, id_name_uuid, target_key=None):
|
|
87
110
|
return self.set_targets(id_name_uuid, [], target_key=target_key)
|
|
@@ -92,9 +115,14 @@ class ArcsecondTargetListsResource(ArcsecondAPIEndpoint):
|
|
|
92
115
|
return None, error
|
|
93
116
|
|
|
94
117
|
key, current_refs = self._read_target_refs(target_list, target_key=target_key)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
118
|
+
current_identities = {
|
|
119
|
+
self._target_payload_identity(target): target for target in current_refs
|
|
120
|
+
}
|
|
121
|
+
for target in self._normalise_target_payloads(targets) or []:
|
|
122
|
+
identity = self._target_payload_identity(target)
|
|
123
|
+
if identity not in current_identities:
|
|
124
|
+
current_refs.append(target)
|
|
125
|
+
current_identities[identity] = target
|
|
98
126
|
return self.update(id_name_uuid, **{key: current_refs})
|
|
99
127
|
|
|
100
128
|
def remove_targets(self, id_name_uuid, targets, target_key=None):
|
|
@@ -103,8 +131,15 @@ class ArcsecondTargetListsResource(ArcsecondAPIEndpoint):
|
|
|
103
131
|
return None, error
|
|
104
132
|
|
|
105
133
|
key, current_refs = self._read_target_refs(target_list, target_key=target_key)
|
|
106
|
-
refs_to_remove =
|
|
107
|
-
|
|
134
|
+
refs_to_remove = {
|
|
135
|
+
self._target_payload_identity(target)
|
|
136
|
+
for target in (self._normalise_target_payloads(targets) or [])
|
|
137
|
+
}
|
|
138
|
+
remaining_refs = [
|
|
139
|
+
ref
|
|
140
|
+
for ref in current_refs
|
|
141
|
+
if self._target_payload_identity(ref) not in refs_to_remove
|
|
142
|
+
]
|
|
108
143
|
return self.update(id_name_uuid, **{key: remaining_refs})
|
|
109
144
|
|
|
110
145
|
def add_target(self, id_name_uuid, target, target_key=None):
|
|
@@ -37,8 +37,6 @@ services:
|
|
|
37
37
|
- broker
|
|
38
38
|
# Allows the backend to reach the host machine via host.docker.internal.
|
|
39
39
|
# Required on Linux; Docker Desktop on Windows/macOS adds this automatically.
|
|
40
|
-
# Used by the webcam proxy: set WEBCAM_PROXY_URL=http://host.docker.internal:8765
|
|
41
|
-
# in your .env file and run `arcsecond webcam start` on the host.
|
|
42
40
|
extra_hosts:
|
|
43
41
|
- "host.docker.internal:host-gateway"
|
|
44
42
|
env_file:
|
|
@@ -125,12 +125,8 @@ def write_docker_compose_file() -> Path:
|
|
|
125
125
|
print("docker-compose.yml is already up to date.")
|
|
126
126
|
return dest
|
|
127
127
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
print(
|
|
131
|
-
"docker-compose.yml differs from the latest packaged version. "
|
|
132
|
-
f"Wrote new template to: {latest_dest}"
|
|
133
|
-
)
|
|
128
|
+
dest.write_bytes(expected_content)
|
|
129
|
+
print("docker-compose.yml has been updated to the latest version.")
|
|
134
130
|
return dest
|
|
135
131
|
|
|
136
132
|
|
|
@@ -68,10 +68,16 @@ def _detect_webcams_sync(max_index: int = _MAX_PROBE) -> list[WebcamInfo]:
|
|
|
68
68
|
# aiohttp request handlers
|
|
69
69
|
# ---------------------------------------------------------------------------
|
|
70
70
|
|
|
71
|
+
async def handle_health(request):
|
|
72
|
+
"""GET /health — simple liveness check."""
|
|
73
|
+
from aiohttp import web
|
|
74
|
+
return web.json_response({'status': 'ok'})
|
|
75
|
+
|
|
76
|
+
|
|
71
77
|
async def handle_detect(request):
|
|
72
78
|
"""GET /detect — return JSON list of attached webcams."""
|
|
73
79
|
from aiohttp import web
|
|
74
|
-
loop = asyncio.
|
|
80
|
+
loop = asyncio.get_running_loop()
|
|
75
81
|
webcams = await loop.run_in_executor(None, _detect_webcams_sync)
|
|
76
82
|
return web.json_response([asdict(w) for w in webcams])
|
|
77
83
|
|
|
@@ -86,7 +92,7 @@ async def handle_stream(request):
|
|
|
86
92
|
await ws.prepare(request)
|
|
87
93
|
logger.info("Webcam proxy: client connected to stream for device index %d", index)
|
|
88
94
|
|
|
89
|
-
loop = asyncio.
|
|
95
|
+
loop = asyncio.get_running_loop()
|
|
90
96
|
cap: cv2.VideoCapture = await loop.run_in_executor(None, cv2.VideoCapture, index)
|
|
91
97
|
|
|
92
98
|
if not await loop.run_in_executor(None, cap.isOpened):
|
|
@@ -109,9 +115,12 @@ async def handle_stream(request):
|
|
|
109
115
|
b64 = await loop.run_in_executor(None, _read_and_encode)
|
|
110
116
|
if b64 is None:
|
|
111
117
|
logger.warning("Webcam proxy: cap.read() failed for device %d, stopping.", index)
|
|
118
|
+
await ws.send_str(json.dumps({'type': 'error', 'message': f'Device lost at index {index}.'}))
|
|
112
119
|
break
|
|
113
120
|
await ws.send_str(json.dumps({'type': 'frame', 'format': 'jpeg/base64', 'data': b64}))
|
|
114
121
|
await asyncio.sleep(_FRAME_INTERVAL)
|
|
122
|
+
except (ConnectionResetError, ConnectionError):
|
|
123
|
+
logger.info("Webcam proxy: client disconnected from device %d.", index)
|
|
115
124
|
finally:
|
|
116
125
|
await loop.run_in_executor(None, cap.release)
|
|
117
126
|
logger.info("Webcam proxy: device %d released.", index)
|
|
@@ -128,6 +137,7 @@ def run(host: str = '0.0.0.0', port: int = 8765):
|
|
|
128
137
|
from aiohttp import web
|
|
129
138
|
|
|
130
139
|
app = web.Application()
|
|
140
|
+
app.router.add_get('/health', handle_health)
|
|
131
141
|
app.router.add_get('/detect', handle_detect)
|
|
132
142
|
app.router.add_get('/stream/{index}', handle_stream)
|
|
133
143
|
|
|
@@ -2,16 +2,28 @@ module.exports = {
|
|
|
2
2
|
title: 'Arcsecond CLI',
|
|
3
3
|
description: 'The command-line / Python module of Arcsecond.',
|
|
4
4
|
base: '/cli/',
|
|
5
|
+
cleanUrls: true,
|
|
5
6
|
themeConfig: {
|
|
6
7
|
nav: [
|
|
7
|
-
{ text: 'Home', link: '/' },
|
|
8
8
|
{ text: 'Arcsecond Docs', link: 'https://docs.arcsecond.io' }
|
|
9
9
|
],
|
|
10
10
|
sidebar: [
|
|
11
11
|
{
|
|
12
|
-
text: '
|
|
12
|
+
text: 'Getting Started',
|
|
13
|
+
items: [
|
|
14
|
+
{ text: 'Install & Login', link: '/install' },
|
|
15
|
+
{ text: 'Data Upload', link: '/upload' }
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
text: 'Self-Hosting',
|
|
20
|
+
items: [
|
|
21
|
+
{ text: 'Webcam Proxy', link: '/webcam' }
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
text: 'Python API',
|
|
13
26
|
items: [
|
|
14
|
-
{ text: 'Install', link: '/install' },
|
|
15
27
|
{ text: 'API Basics', link: '/api-basics' },
|
|
16
28
|
{ text: 'Resources', link: '/resources' }
|
|
17
29
|
]
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--vp-c-brand-1: #081c4d;
|
|
3
|
+
--vp-c-brand-2: #0b2462;
|
|
4
|
+
--vp-c-brand-3: #0e2d77;
|
|
5
|
+
--vp-c-brand-soft: rgba(8, 28, 77, 0.14);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.dark {
|
|
9
|
+
--vp-c-brand-1: #6ba6dc;
|
|
10
|
+
--vp-c-brand-2: #82b4e3;
|
|
11
|
+
--vp-c-brand-3: #528fc8;
|
|
12
|
+
--vp-c-brand-soft: rgba(107, 166, 220, 0.18);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.VPHomeHero .image-container {
|
|
16
|
+
width: 220px;
|
|
17
|
+
height: 220px;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.VPHomeHero .image-src {
|
|
21
|
+
max-width: 120px;
|
|
22
|
+
max-height: 120px;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@media (min-width: 640px) {
|
|
26
|
+
.VPHomeHero .image-container {
|
|
27
|
+
width: 260px;
|
|
28
|
+
height: 260px;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.VPHomeHero .image-src {
|
|
32
|
+
max-width: 120px;
|
|
33
|
+
max-height: 120px;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -25,7 +25,7 @@ api = ArcsecondAPI(config, subdomain="my-observatory")
|
|
|
25
25
|
|
|
26
26
|
Authentication with the Python module currently relies on your Arcsecond keys.
|
|
27
27
|
|
|
28
|
-
###
|
|
28
|
+
### Reuse CLI credentials
|
|
29
29
|
|
|
30
30
|
Login once from the command line:
|
|
31
31
|
|
|
@@ -33,18 +33,19 @@ Login once from the command line:
|
|
|
33
33
|
arcsecond login
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
This stores your credentials locally in `~/.config/arcsecond/config.ini
|
|
36
|
+
This stores your credentials locally in `~/.config/arcsecond/config.ini`, which
|
|
37
|
+
`ArcsecondConfig()` will load automatically.
|
|
37
38
|
|
|
38
39
|
To skip prompts:
|
|
39
40
|
|
|
40
41
|
```bash
|
|
41
|
-
arcsecond login --username <username> --
|
|
42
|
+
arcsecond login --username <username> --type access --key <access-key>
|
|
42
43
|
```
|
|
43
44
|
|
|
44
45
|
or:
|
|
45
46
|
|
|
46
47
|
```bash
|
|
47
|
-
arcsecond login --username <username> --
|
|
48
|
+
arcsecond login --username <username> --type upload --key <upload-key>
|
|
48
49
|
```
|
|
49
50
|
|
|
50
51
|
Once that is done, Python code can reuse the stored configuration:
|
|
@@ -56,7 +57,7 @@ config = ArcsecondConfig()
|
|
|
56
57
|
api = ArcsecondAPI(config)
|
|
57
58
|
```
|
|
58
59
|
|
|
59
|
-
###
|
|
60
|
+
### Authenticate in Python code
|
|
60
61
|
|
|
61
62
|
You can also authenticate directly in Python code:
|
|
62
63
|
|
|
@@ -89,3 +90,8 @@ backend or local automation contexts, never in browser-side code.
|
|
|
89
90
|
|
|
90
91
|
For Python scripts that only need to upload data, prefer an Upload Key. For broader
|
|
91
92
|
resource management, use an Access Key.
|
|
93
|
+
|
|
94
|
+
## Next Step
|
|
95
|
+
|
|
96
|
+
Once authenticated, move to [Resources](/resources) for the generic CRUD helpers,
|
|
97
|
+
target planning utilities, and target list management helpers.
|
|
Binary file
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: home
|
|
3
|
+
title: Arcsecond CLI
|
|
4
|
+
description: Arcsecond CLI
|
|
5
|
+
hero:
|
|
6
|
+
name: Arcsecond CLI
|
|
7
|
+
tagline: A lightweight command-line and Python client for Arcsecond resources, target workflows, and data uploads.
|
|
8
|
+
image:
|
|
9
|
+
src: /img/icon-logo.png
|
|
10
|
+
alt: Arcsecond CLI
|
|
11
|
+
actions:
|
|
12
|
+
- theme: brand
|
|
13
|
+
text: Install & Login
|
|
14
|
+
link: /install
|
|
15
|
+
- theme: alt
|
|
16
|
+
text: Python API
|
|
17
|
+
link: /api-basics
|
|
18
|
+
- theme: alt
|
|
19
|
+
text: Resources
|
|
20
|
+
link: /resources
|
|
21
|
+
features:
|
|
22
|
+
- title: Lightweight CLI
|
|
23
|
+
details: Install the package, log in once, and use simple commands for resource access and uploads.
|
|
24
|
+
- title: Python API
|
|
25
|
+
details: Reuse the same credentials in code, work with generic CRUD endpoints, and plan target payloads safely.
|
|
26
|
+
- title: Data Upload
|
|
27
|
+
details: Upload datasets and all-sky camera images from the CLI or from Python automation.
|
|
28
|
+
footer: MIT Licensed | Copyright © 2018-present Arcsecond.io (F52 Tech).
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
Arcsecond CLI gives you two entry points:
|
|
32
|
+
|
|
33
|
+
- the `arcsecond` command-line tool for day-to-day operations
|
|
34
|
+
- the `arcsecond` Python module for automation and integration
|
|
35
|
+
|
|
36
|
+
Use the sidebar to move directly to installation, authentication, uploads, and the Python resource helpers.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar: true
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Install & Login
|
|
6
|
+
|
|
7
|
+
Simply issue the following in a Terminal:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install arcsecond
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
To upgrade an existing Arcsecond installation:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install --upgrade arcsecond
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The help is available like any other command-line tool:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
arcsecond --help
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
For subcommands:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
arcsecond <command> --help
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The Arcsecond CLI works like a tool such as `git`: `arcsecond` is the main entry
|
|
32
|
+
point, followed by a command. Many commands directly map to Arcsecond resources.
|
|
33
|
+
|
|
34
|
+
## Authentication
|
|
35
|
+
|
|
36
|
+
To use the CLI, you need an Arcsecond account. In your settings page on
|
|
37
|
+
[arcsecond.io](https://www.arcsecond.io) you will find two kinds of credentials:
|
|
38
|
+
|
|
39
|
+
- an Access Key for broad access to your resources
|
|
40
|
+
- an Upload Key for upload-only workflows
|
|
41
|
+
|
|
42
|
+
Use an Access Key only on trusted computers. If you only need to upload files,
|
|
43
|
+
prefer an Upload Key.
|
|
44
|
+
|
|
45
|
+
### Interactive login
|
|
46
|
+
|
|
47
|
+
Run:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
arcsecond login
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The CLI will prompt for:
|
|
54
|
+
|
|
55
|
+
- `username`
|
|
56
|
+
- `type` (`access` or `upload`)
|
|
57
|
+
- `key`
|
|
58
|
+
|
|
59
|
+
Your credential is stored locally in `~/.config/arcsecond/config.ini`.
|
|
60
|
+
|
|
61
|
+
### Non-interactive login
|
|
62
|
+
|
|
63
|
+
To skip prompts, pass all values explicitly:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
arcsecond login --username <username> --type access --key <access-key>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
or:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
arcsecond login --username <username> --type upload --key <upload-key>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Logging in again overwrites the stored credential if the login succeeds.
|
|
76
|
+
|
|
77
|
+
If you think a key is compromised, regenerate it from your profile settings on
|
|
78
|
+
[arcsecond.io](https://www.arcsecond.io). The CLI cannot regenerate keys for you.
|
|
Binary file
|