arcsecond 3.7.2__tar.gz → 3.8.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 (117) hide show
  1. arcsecond-3.8.0/.docker/Dockerfile_postgres +2 -0
  2. arcsecond-3.8.0/.docker/Dockerfile_redis +2 -0
  3. arcsecond-3.8.0/.github/dependabot.yml +11 -0
  4. arcsecond-3.8.0/.github/workflows/docsdeploy.yml +30 -0
  5. arcsecond-3.8.0/.github/workflows/pythonpublish.yml +57 -0
  6. arcsecond-3.8.0/.github/workflows/tests.yml +90 -0
  7. arcsecond-3.8.0/.gitignore +22 -0
  8. arcsecond-3.8.0/Makefile +63 -0
  9. {arcsecond-3.7.2 → arcsecond-3.8.0}/PKG-INFO +10 -16
  10. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/__init__.py +10 -1
  11. arcsecond-3.8.0/arcsecond/__version__.py +11 -0
  12. arcsecond-3.8.0/arcsecond/api/__init__.py +11 -0
  13. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/api/endpoint.py +77 -4
  14. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/api/main.py +5 -0
  15. arcsecond-3.8.0/arcsecond/api/resources.py +114 -0
  16. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/cli.py +4 -0
  17. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/hosting/docker/docker-compose.yml +6 -0
  18. arcsecond-3.8.0/arcsecond/hosting/postgres/init-db.sh +8 -0
  19. arcsecond-3.8.0/arcsecond/targets.py +211 -0
  20. arcsecond-3.8.0/arcsecond/webcam/commands.py +101 -0
  21. arcsecond-3.8.0/arcsecond/webcam/proxy.py +135 -0
  22. arcsecond-3.8.0/deploy.sh +30 -0
  23. arcsecond-3.8.0/docs/.vitepress/config.js +21 -0
  24. arcsecond-3.8.0/docs/.vitepress/theme/custom.css +4 -0
  25. arcsecond-3.8.0/docs/.vitepress/theme/index.js +4 -0
  26. arcsecond-3.8.0/docs/api-basics.md +91 -0
  27. arcsecond-3.8.0/docs/img/logo-circle.png +0 -0
  28. arcsecond-3.8.0/docs/index.md +156 -0
  29. arcsecond-3.8.0/docs/install.md +67 -0
  30. arcsecond-3.8.0/docs/resources.md +335 -0
  31. arcsecond-3.8.0/examples/example_upload_files.py +25 -0
  32. arcsecond-3.8.0/examples/example_upload_images.py +24 -0
  33. arcsecond-3.8.0/package-lock.json +2627 -0
  34. arcsecond-3.8.0/package.json +27 -0
  35. arcsecond-3.8.0/poetry.lock +471 -0
  36. {arcsecond-3.7.2 → arcsecond-3.8.0}/pyproject.toml +6 -1
  37. arcsecond-3.8.0/requirements.txt +3 -0
  38. {arcsecond-3.7.2 → arcsecond-3.8.0}/setup.cfg +0 -5
  39. arcsecond-3.8.0/sonar-project.properties +23 -0
  40. {arcsecond-3.7.2 → arcsecond-3.8.0}/tests/api/test_api_endpoint.py +50 -0
  41. arcsecond-3.8.0/tests/api/test_targets.py +196 -0
  42. arcsecond-3.8.0/tests/cloud/uploader/allskycameraimages/__init__.py +0 -0
  43. arcsecond-3.8.0/tests/cloud/uploader/datafiles/__init__.py +0 -0
  44. arcsecond-3.8.0/tests/conftest.py +54 -0
  45. arcsecond-3.8.0/tests/fixtures/file1.fits +0 -0
  46. arcsecond-3.8.0/tests/test_targets_planning.py +86 -0
  47. arcsecond-3.8.0/tests/utils.py +141 -0
  48. arcsecond-3.7.2/arcsecond/__version__.py +0 -2
  49. arcsecond-3.7.2/arcsecond/api/__init__.py +0 -5
  50. arcsecond-3.7.2/arcsecond.egg-info/PKG-INFO +0 -86
  51. arcsecond-3.7.2/arcsecond.egg-info/SOURCES.txt +0 -78
  52. arcsecond-3.7.2/arcsecond.egg-info/dependency_links.txt +0 -1
  53. arcsecond-3.7.2/arcsecond.egg-info/entry_points.txt +0 -2
  54. arcsecond-3.7.2/arcsecond.egg-info/not-zip-safe +0 -1
  55. arcsecond-3.7.2/arcsecond.egg-info/requires.txt +0 -7
  56. arcsecond-3.7.2/arcsecond.egg-info/top_level.txt +0 -2
  57. {arcsecond-3.7.2 → arcsecond-3.8.0}/LICENSE +0 -0
  58. {arcsecond-3.7.2 → arcsecond-3.8.0}/README.md +0 -0
  59. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/api/config.py +0 -0
  60. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/api/constants.py +0 -0
  61. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/cloud/__init__.py +0 -0
  62. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/cloud/auth.py +0 -0
  63. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/cloud/resources.py +0 -0
  64. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/cloud/uploader/__init__.py +0 -0
  65. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/cloud/uploader/allskycameraimages/__init__.py +0 -0
  66. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/cloud/uploader/allskycameraimages/context.py +0 -0
  67. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/cloud/uploader/allskycameraimages/errors.py +0 -0
  68. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/cloud/uploader/allskycameraimages/uploader.py +0 -0
  69. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/cloud/uploader/allskycameraimages/utils.py +0 -0
  70. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/cloud/uploader/constants.py +0 -0
  71. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/cloud/uploader/context.py +0 -0
  72. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/cloud/uploader/datafiles/__init__.py +0 -0
  73. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/cloud/uploader/datafiles/context.py +0 -0
  74. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/cloud/uploader/datafiles/errors.py +0 -0
  75. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/cloud/uploader/datafiles/uploader.py +0 -0
  76. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/cloud/uploader/datafiles/utils.py +0 -0
  77. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/cloud/uploader/errors.py +0 -0
  78. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/cloud/uploader/logger.py +0 -0
  79. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/cloud/uploader/uploader.py +0 -0
  80. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/cloud/uploader/utils.py +0 -0
  81. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/cloud/uploader/walker.py +0 -0
  82. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/cloud/uploads.py +0 -0
  83. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/errors.py +0 -0
  84. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/hosting/__init__.py +0 -0
  85. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/hosting/checks.py +0 -0
  86. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/hosting/constants.py +0 -0
  87. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/hosting/docker/__init__.py +0 -0
  88. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/hosting/docker/constants.py +0 -0
  89. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/hosting/docker/containers.py +0 -0
  90. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/hosting/docker/images.py +0 -0
  91. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/hosting/docker/utils.py +0 -0
  92. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/hosting/keygen/__init__.py +0 -0
  93. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/hosting/keygen/client.py +0 -0
  94. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/hosting/keygen/utils.py +0 -0
  95. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/hosting/local.py +0 -0
  96. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/hosting/main.py +0 -0
  97. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/hosting/setup.py +0 -0
  98. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/hosting/utils.py +0 -0
  99. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/hosting/validation.py +0 -0
  100. {arcsecond-3.7.2 → arcsecond-3.8.0}/arcsecond/options.py +0 -0
  101. {arcsecond-3.7.2/tests/api → arcsecond-3.8.0/arcsecond/webcam}/__init__.py +0 -0
  102. {arcsecond-3.7.2 → arcsecond-3.8.0}/setup.py +0 -0
  103. {arcsecond-3.7.2/tests/cloud → arcsecond-3.8.0/tests}/__init__.py +0 -0
  104. {arcsecond-3.7.2/tests/cloud/uploader → arcsecond-3.8.0/tests/api}/__init__.py +0 -0
  105. {arcsecond-3.7.2 → arcsecond-3.8.0}/tests/api/test_api.py +0 -0
  106. {arcsecond-3.7.2 → arcsecond-3.8.0}/tests/api/test_config.py +0 -0
  107. {arcsecond-3.7.2/tests/cloud/uploader/allskycameraimages → arcsecond-3.8.0/tests/cloud}/__init__.py +0 -0
  108. {arcsecond-3.7.2/tests/cloud/uploader/datafiles → arcsecond-3.8.0/tests/cloud/uploader}/__init__.py +0 -0
  109. {arcsecond-3.7.2 → arcsecond-3.8.0}/tests/cloud/uploader/allskycameraimages/test_context.py +0 -0
  110. {arcsecond-3.7.2 → arcsecond-3.8.0}/tests/cloud/uploader/allskycameraimages/test_uploader_full_process.py +0 -0
  111. {arcsecond-3.7.2 → arcsecond-3.8.0}/tests/cloud/uploader/datafiles/test_uploader_errors.py +0 -0
  112. {arcsecond-3.7.2 → arcsecond-3.8.0}/tests/cloud/uploader/datafiles/test_uploader_full_process.py +0 -0
  113. {arcsecond-3.7.2 → arcsecond-3.8.0}/tests/cloud/uploader/datafiles/test_uploader_init.py +0 -0
  114. {arcsecond-3.7.2 → arcsecond-3.8.0}/tests/cloud/uploader/datafiles/test_uploader_prepare.py +0 -0
  115. {arcsecond-3.7.2 → arcsecond-3.8.0}/tests/cloud/uploader/datafiles/test_uploader_upload.py +0 -0
  116. {arcsecond-3.7.2 → arcsecond-3.8.0}/tests/test_cli.py +0 -0
  117. {arcsecond-3.7.2 → arcsecond-3.8.0}/tests/test_hosting_local.py +0 -0
@@ -0,0 +1,2 @@
1
+ ARG BASE_IMAGE=postgres:16
2
+ FROM ${BASE_IMAGE}
@@ -0,0 +1,2 @@
1
+ ARG BASE_IMAGE=redis:7.4
2
+ FROM ${BASE_IMAGE}
@@ -0,0 +1,11 @@
1
+ # To get started with Dependabot version updates, you'll need to specify which
2
+ # package ecosystems to update and where the package manifests are located.
3
+ # Please see the documentation for all configuration options:
4
+ # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5
+
6
+ version: 2
7
+ updates:
8
+ - package-ecosystem: "pip"
9
+ directory: "/" # Location of package manifests
10
+ schedule:
11
+ interval: "weekly"
@@ -0,0 +1,30 @@
1
+ name: Deploy Docs
2
+
3
+ on: [push]
4
+
5
+ jobs:
6
+ build:
7
+ runs-on: ubuntu-latest
8
+ steps:
9
+ - uses: actions/checkout@v4
10
+ - name: Setup node
11
+ uses: actions/setup-node@v4
12
+ with:
13
+ node-version: '20.x'
14
+ - name: Cache node modules
15
+ uses: actions/cache@v4
16
+ with:
17
+ path: ~/.npm
18
+ key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
19
+ restore-keys: |
20
+ ${{ runner.os }}-node-
21
+ - run: npm ci
22
+ env:
23
+ NODE_AUTH_TOKEN: ${{ secrets.READ_PACKAGE_TOKEN }}
24
+ CI: true
25
+ node-version: '20.x'
26
+ cache: 'npm'
27
+ - name: Deploy docs
28
+ env:
29
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
30
+ run: ./deploy.sh
@@ -0,0 +1,57 @@
1
+ name: Upload Python Package to Pypi
2
+
3
+ on:
4
+ release:
5
+ types: [ published ]
6
+
7
+ jobs:
8
+ deploy:
9
+ runs-on: ubuntu-latest
10
+ permissions:
11
+ contents: read
12
+ id-token: write
13
+
14
+ steps:
15
+ - name: Validate release tag and version (X.Y.Z and matches pyproject)
16
+ shell: bash
17
+ run: |
18
+ TAG="${{ github.event.release.tag_name }}"
19
+ echo "Release tag: $TAG"
20
+ if ! [[ "$TAG" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
21
+ echo "Tag must be X.Y.Z (e.g. 1.2.3). Got: $TAG"
22
+ exit 1
23
+ fi
24
+
25
+ - uses: actions/checkout@v4
26
+
27
+ - name: Validate pyproject.toml version matches tag
28
+ run: |
29
+ python - <<'PY'
30
+ import pathlib
31
+ import tomllib
32
+
33
+ tag = "${{ github.event.release.tag_name }}"
34
+
35
+ data = tomllib.loads(pathlib.Path("pyproject.toml").read_text(encoding="utf-8"))
36
+ py_ver = data["project"]["version"]
37
+
38
+ print(f"pyproject.toml version: {py_ver}")
39
+ if py_ver != tag:
40
+ raise SystemExit(f"Version mismatch: tag={tag} but pyproject.toml={py_ver}")
41
+ PY
42
+
43
+ - name: Set up Python
44
+ uses: actions/setup-python@v5
45
+ with:
46
+ python-version: '3.12'
47
+
48
+ - name: Build distributions
49
+ run: |
50
+ python -m pip install --upgrade pip
51
+ pip install build
52
+ python -m build
53
+
54
+ - name: Publish to PyPI (OIDC)
55
+ uses: pypa/gh-action-pypi-publish@release/v1
56
+ with:
57
+ packages-dir: dist
@@ -0,0 +1,90 @@
1
+ name: Run Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [ '*' ]
6
+ pull_request:
7
+ branches: [ '*' ]
8
+
9
+ jobs:
10
+ tests:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: [ '3.10', '3.11', '3.12', '3.13' ]
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Set up Python ${{ matrix.python-version }}
20
+ uses: actions/setup-python@v4
21
+ with:
22
+ python-version: ${{ matrix.python-version }}
23
+
24
+ - name: Install dependencies
25
+ run: |
26
+ python -m pip install --upgrade pip
27
+ if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
28
+ pip install pytest pytest-cov respx
29
+ pip install -e .
30
+
31
+ - name: Test with coverage (only on 3.12)
32
+ if: matrix.python-version == '3.12'
33
+ run: |
34
+ pytest -vvs tests/ --cov=arcsecond --cov-report=xml:coverage.xml
35
+
36
+ - name: Test without coverage
37
+ if: matrix.python-version != '3.12'
38
+ run: pytest -vvs tests/
39
+
40
+ - name: Upload coverage artifact (only on 3.12)
41
+ if: matrix.python-version == '3.12'
42
+ uses: actions/upload-artifact@v4
43
+ with:
44
+ name: coverage
45
+ path: coverage.xml
46
+
47
+ scan:
48
+ runs-on: ubuntu-latest
49
+ needs: tests
50
+
51
+ steps:
52
+ - uses: actions/checkout@v4
53
+
54
+ - name: Download coverage artifact
55
+ uses: actions/download-artifact@v4
56
+ with:
57
+ name: coverage
58
+ path: .
59
+
60
+ - name: SonarQube Scan
61
+ uses: SonarSource/sonarcloud-github-action@master
62
+ env:
63
+ SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
64
+
65
+ lint:
66
+ runs-on: ubuntu-latest
67
+ steps:
68
+ - uses: actions/checkout@v4
69
+
70
+ - name: Set up Python
71
+ uses: actions/setup-python@v4
72
+ with:
73
+ python-version: '3.12'
74
+
75
+ - name: Install dependencies
76
+ run: |
77
+ python -m pip install --upgrade pip
78
+ pip install flake8 black isort
79
+
80
+ - name: Lint with flake8
81
+ run: |
82
+ flake8 arcsecond tests --count --select=E9,F63,F7,F82 --show-source --statistics
83
+
84
+ - name: Check formatting with black
85
+ run: |
86
+ black --check arcsecond tests
87
+
88
+ - name: Check imports with isort
89
+ run: |
90
+ isort --check-only --profile black arcsecond tests
@@ -0,0 +1,22 @@
1
+ *.pyc
2
+
3
+ dist/
4
+ build/
5
+ *.egg-info/
6
+
7
+ .tox/
8
+ .coverage
9
+ .pytest_cache
10
+ .idea
11
+
12
+ venv
13
+ /env
14
+ .DS_Store
15
+ node_modules
16
+
17
+ /docs/.vitepress/cache
18
+
19
+ *.tar
20
+
21
+ /coverage.xml
22
+ .env
@@ -0,0 +1,63 @@
1
+ ARCH ?= amd64
2
+ TAG ?= latest
3
+
4
+ IMAGE_API = arcsecond-api-linux-$(ARCH)
5
+ IMAGE_WEB = arcsecond-web-linux-$(ARCH)
6
+ IMAGE_REDIS = arcsecond-redis-linux-$(ARCH)
7
+ IMAGE_POSTGRES = arcsecond-postgres-linux-$(ARCH)
8
+
9
+ IMAGE_API_OCI = arcsecond-api
10
+ IMAGE_WEB_OCI = arcsecond-web
11
+ IMAGE_REDIS_OCI = arcsecond-redis
12
+ IMAGE_POSTGRES_OCI = arcsecond-postgres
13
+
14
+ # Paths
15
+ DOCKERFILE_API = ../arcsecond-back/.docker/Dockerfile
16
+ CONTEXT_API = ../arcsecond-back
17
+
18
+ DOCKERFILE_WEB = ../arcsecond-front/.docker/Dockerfile
19
+ CONTEXT_WEB = ../arcsecond-front
20
+
21
+ DOCKERFILE_REDIS = ./.docker/Dockerfile_redis
22
+ DOCKERFILE_POSTGRES = ./.docker/Dockerfile_postgres
23
+
24
+ # Build and save each image
25
+
26
+ arcsecond-api-linux:
27
+ docker build --platform linux/$(ARCH) -t $(IMAGE_API):$(TAG) -f $(DOCKERFILE_API) $(CONTEXT_API)
28
+ docker save $(IMAGE_API):$(TAG) -o tars/$(IMAGE_API)_$(TAG).tar
29
+
30
+ arcsecond-web-linux:
31
+ docker build --platform linux/$(ARCH) -t $(IMAGE_WEB):$(TAG) -f $(DOCKERFILE_WEB) $(CONTEXT_WEB)
32
+ docker save $(IMAGE_WEB):$(TAG) -o tars/$(IMAGE_WEB)_$(TAG).tar
33
+
34
+ arcsecond-redis-linux:
35
+ docker build --platform linux/$(ARCH) -t $(IMAGE_REDIS):7.4 -f $(DOCKERFILE_REDIS) .
36
+ docker save $(IMAGE_REDIS):7.4 -o tars/$(IMAGE_REDIS)_7.4.tar
37
+
38
+ arcsecond-postgres-linux:
39
+ docker build --platform linux/$(ARCH) -t $(IMAGE_POSTGRES):16 -f $(DOCKERFILE_POSTGRES) .
40
+ docker save $(IMAGE_POSTGRES):16 -o tars/$(IMAGE_POSTGRES)_16.tar
41
+
42
+ arcsecond-api-oci:
43
+ docker buildx build --platform 'linux/amd64,linux/arm64' -o 'type=oci,dest=-' -t $(IMAGE_API_OCI):$(TAG) -f $(DOCKERFILE_API) $(CONTEXT_API) > tars/$(IMAGE_API_OCI)_$(TAG).tar
44
+
45
+ arcsecond-web-oci:
46
+ docker buildx build --platform 'linux/amd64,linux/arm64' -o 'type=oci,dest=-' -t $(IMAGE_WEB_OCI):$(TAG) -f $(DOCKERFILE_WEB) $(CONTEXT_WEB) > tars/$(IMAGE_WEB_OCI)_$(TAG).tar
47
+
48
+ arcsecond-redis-oci:
49
+ docker buildx build --platform 'linux/amd64,linux/arm64' -o 'type=oci,dest=-' -t $(IMAGE_REDIS_OCI):$(TAG) -f $(DOCKERFILE_REDIS) . > tars/$(IMAGE_REDIS_OCI)_$(TAG).tar
50
+
51
+ arcsecond-postgres-oci:
52
+ docker buildx build --platform 'linux/amd64,linux/arm64' -o 'type=oci,dest=-' -t $(IMAGE_POSTGRES_OCI):$(TAG) -f $(DOCKERFILE_POSTGRES) . > tars/$(IMAGE_POSTGRES_OCI)_$(TAG).tar
53
+
54
+ .PHONY: core aux clean
55
+
56
+ core: arcsecond-api-linux arcsecond-web-linux
57
+
58
+ aux: arcsecond-redis-linux arcsecond-postgres-linux
59
+
60
+ oci: arcsecond-api-oci arcsecond-web-oci arcsecond-redis-oci arcsecond-postgres-oci
61
+
62
+ clean:
63
+ rm -f arcsecond-*.tar tars/*
@@ -1,9 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arcsecond
3
- Version: 3.7.2
3
+ Version: 3.8.0
4
4
  Summary: CLI for arcsecond.io
5
- Home-page: https://github.com/arcsecond-io/cli
6
- Author: Cedric Foellmi
5
+ Project-URL: Homepage, https://github.com/arcsecond-io/cli
6
+ Project-URL: Issues, https://github.com/arcsecond-io/cli/issues
7
+ Project-URL: Documentation, https://docs.arcsecond.io
7
8
  Author-email: Cedric Foellmi <cedric@arcsecond.io>
8
9
  License: MIT License
9
10
 
@@ -26,11 +27,7 @@ License: MIT License
26
27
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
28
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
29
  SOFTWARE.
29
-
30
- Project-URL: Homepage, https://github.com/arcsecond-io/cli
31
- Project-URL: Issues, https://github.com/arcsecond-io/cli/issues
32
- Project-URL: Documentation, https://docs.arcsecond.io
33
- Platform: any
30
+ License-File: LICENSE
34
31
  Classifier: Development Status :: 5 - Production/Stable
35
32
  Classifier: Environment :: Console
36
33
  Classifier: Intended Audience :: Developers
@@ -39,19 +36,16 @@ Classifier: Operating System :: OS Independent
39
36
  Classifier: Programming Language :: Python :: 3
40
37
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
41
38
  Requires-Python: >=3.9
42
- Description-Content-Type: text/markdown
43
- License-File: LICENSE
39
+ Requires-Dist: aiohttp>=3.9
44
40
  Requires-Dist: click>=8
45
41
  Requires-Dist: configparser
46
42
  Requires-Dist: docker
47
43
  Requires-Dist: httpx
48
- Requires-Dist: tqdm<5.0.0,>=4.67.1
44
+ Requires-Dist: opencv-python-headless<5,>=4.10
49
45
  Requires-Dist: py-machineid
46
+ Requires-Dist: tqdm<5.0.0,>=4.67.1
50
47
  Requires-Dist: wait-for-it
51
- Dynamic: author
52
- Dynamic: home-page
53
- Dynamic: license-file
54
- Dynamic: platform
48
+ Description-Content-Type: text/markdown
55
49
 
56
50
  [![Deploy Docs](https://github.com/arcsecond-io/cli/actions/workflows/docsdeploy.yml/badge.svg)](https://github.com/arcsecond-io/cli/actions/workflows/docsdeploy.yml) [![Downloads](http://pepy.tech/badge/arcsecond)](http://pepy.tech/project/arcsecond)
57
51
  [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Farcsecond-io%2Fcli.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Farcsecond-io%2Fcli?ref=badge_shield)
@@ -83,4 +77,4 @@ env/bin/activate` is needed when you restart a debugging session.
83
77
 
84
78
 
85
79
  ## License
86
- [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Farcsecond-io%2Fcli.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Farcsecond-io%2Fcli?ref=badge_large)
80
+ [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Farcsecond-io%2Fcli.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Farcsecond-io%2Fcli?ref=badge_large)
@@ -1,4 +1,9 @@
1
- from .api import ArcsecondAPI, ArcsecondAPIEndpoint, ArcsecondConfig
1
+ from .api import (
2
+ ArcsecondAPI,
3
+ ArcsecondAPIEndpoint,
4
+ ArcsecondConfig,
5
+ ArcsecondTargetListsResource,
6
+ )
2
7
  from .cloud.uploader import (
3
8
  AllSkyCameraImageFileUploader,
4
9
  AllSkyCameraImageUploadContext,
@@ -7,6 +12,7 @@ from .cloud.uploader import (
7
12
  )
8
13
  from .cloud.uploader.walker import walk_folder_and_upload_files
9
14
  from .errors import ArcsecondError
15
+ from .targets import ArcsecondTargetPayloadPlan, plan_target_payload
10
16
 
11
17
  name = "arcsecond"
12
18
 
@@ -15,9 +21,12 @@ __all__ = [
15
21
  "ArcsecondError",
16
22
  "ArcsecondConfig",
17
23
  "ArcsecondAPIEndpoint",
24
+ "ArcsecondTargetListsResource",
25
+ "ArcsecondTargetPayloadPlan",
18
26
  "DatasetUploadContext",
19
27
  "DatasetFileUploader",
20
28
  "AllSkyCameraImageFileUploader",
21
29
  "AllSkyCameraImageUploadContext",
30
+ "plan_target_payload",
22
31
  "walk_folder_and_upload_files",
23
32
  ]
@@ -0,0 +1,11 @@
1
+ """
2
+ Version is defined in pyproject.toml (project.version).
3
+ """
4
+
5
+ from importlib.metadata import PackageNotFoundError, version
6
+
7
+ try:
8
+ __version__ = version("arcsecond")
9
+ except PackageNotFoundError:
10
+ # Fallback for running from a source checkout without installing.
11
+ __version__ = "0.0.0"
@@ -0,0 +1,11 @@
1
+ from .config import ArcsecondConfig
2
+ from .endpoint import ArcsecondAPIEndpoint
3
+ from .main import ArcsecondAPI
4
+ from .resources import ArcsecondTargetListsResource
5
+
6
+ __all__ = [
7
+ "ArcsecondAPI",
8
+ "ArcsecondConfig",
9
+ "ArcsecondAPIEndpoint",
10
+ "ArcsecondTargetListsResource",
11
+ ]
@@ -12,6 +12,13 @@ WRITABLE_MEMBERSHIPS = ["superadmin", "admin", "member"]
12
12
 
13
13
 
14
14
  class ArcsecondAPIEndpoint(object):
15
+ """
16
+ Generic REST endpoint wrapper for Arcsecond resources.
17
+
18
+ It owns transport-level CRUD plus resource-agnostic conveniences such as
19
+ payload merging, `find_one()`, and `upsert()`.
20
+ """
21
+
15
22
  def __init__(
16
23
  self,
17
24
  config: ArcsecondConfig,
@@ -57,6 +64,30 @@ class ArcsecondAPIEndpoint(object):
57
64
  def _detail_url(self, uuid_or_id):
58
65
  return self._build_url(self.__path, str(uuid_or_id))
59
66
 
67
+ def _build_payload(self, json=None, **fields):
68
+ payload = {}
69
+ if json:
70
+ payload.update(json)
71
+ payload.update({key: value for key, value in fields.items() if value is not None})
72
+ return payload or None
73
+
74
+ def _extract_results(self, response):
75
+ if isinstance(response, dict):
76
+ if isinstance(response.get("results"), list):
77
+ return response["results"]
78
+ if response:
79
+ return [response]
80
+ elif isinstance(response, list):
81
+ return response
82
+ return []
83
+
84
+ def _extract_identifier(self, resource, identifier_fields=("uuid", "id", "pk")):
85
+ for key in identifier_fields:
86
+ value = resource.get(key)
87
+ if value is not None:
88
+ return value
89
+ return None
90
+
60
91
  def list(self, **filters):
61
92
  return self._perform_request(self._list_url(**filters), "get")
62
93
 
@@ -65,16 +96,20 @@ class ArcsecondAPIEndpoint(object):
65
96
  self._detail_url(id_name_uuid), "get", headers=headers
66
97
  )
67
98
 
68
- def create(self, json=None, files=None, headers=None):
99
+ def create(self, json=None, files=None, headers=None, **fields):
69
100
  return self._perform_request(
70
- self._list_url(), "post", json=json, files=files, headers=headers
101
+ self._list_url(),
102
+ "post",
103
+ json=self._build_payload(json=json, **fields),
104
+ files=files,
105
+ headers=headers,
71
106
  )
72
107
 
73
- def update(self, id_name_uuid, json=None, files=None, headers=None):
108
+ def update(self, id_name_uuid, json=None, files=None, headers=None, **fields):
74
109
  return self._perform_request(
75
110
  self._detail_url(id_name_uuid),
76
111
  "patch",
77
- json=json,
112
+ json=self._build_payload(json=json, **fields),
78
113
  files=files,
79
114
  headers=headers,
80
115
  )
@@ -82,6 +117,44 @@ class ArcsecondAPIEndpoint(object):
82
117
  def delete(self, id_name_uuid):
83
118
  return self._perform_request(self._detail_url(id_name_uuid), "delete")
84
119
 
120
+ def find_one(self, **filters):
121
+ response, error = self.list(**filters)
122
+ if error:
123
+ return None, error
124
+
125
+ results = self._extract_results(response)
126
+ if len(results) == 0:
127
+ return None, None
128
+ if len(results) > 1:
129
+ return (
130
+ None,
131
+ ArcsecondError(
132
+ f"Expected one '{self.path}' match for filters {filters}, got {len(results)}."
133
+ ),
134
+ )
135
+ return results[0], None
136
+
137
+ def upsert(self, match_field="name", json=None, **fields):
138
+ payload = self._build_payload(json=json, **fields)
139
+ if payload is None:
140
+ return None, ArcsecondError("Cannot upsert an empty payload.")
141
+
142
+ match_value = payload.get(match_field)
143
+ if match_value in (None, ""):
144
+ return self.create(json=payload)
145
+
146
+ existing, error = self.find_one(**{match_field: match_value})
147
+ if error:
148
+ return None, error
149
+ if existing is None:
150
+ return self.create(json=payload)
151
+
152
+ identifier = self._extract_identifier(existing)
153
+ if identifier is None:
154
+ return None, ArcsecondError(f"Could not find an identifier for '{match_value}'.")
155
+
156
+ return self.update(identifier, json=payload)
157
+
85
158
  def _perform_request(self, url, method_name, json=None, files=None, headers=None):
86
159
  if self.__config.verbose:
87
160
  click.echo(f"Sending {method_name} request to {url}")
@@ -8,6 +8,7 @@ from arcsecond.options import State
8
8
  from .config import ArcsecondConfig
9
9
  from .constants import API_AUTH_PATH_VERIFY
10
10
  from .endpoint import ArcsecondAPIEndpoint
11
+ from .resources import ArcsecondTargetListsResource
11
12
 
12
13
  __all__ = [
13
14
  "ArcsecondAPI",
@@ -40,6 +41,10 @@ class ArcsecondAPI(object):
40
41
  self.calibrations = ArcsecondAPIEndpoint(
41
42
  self.config, "calibrations", self.subdomain
42
43
  )
44
+ self.targets = ArcsecondAPIEndpoint(self.config, "targets", self.subdomain)
45
+ self.targetlists = ArcsecondTargetListsResource(
46
+ self.config, "targetlists", self.subdomain
47
+ )
43
48
 
44
49
  self.datapackages = ArcsecondAPIEndpoint(
45
50
  self.config, "datapackages", self.subdomain
@@ -0,0 +1,114 @@
1
+ from arcsecond.errors import ArcsecondError
2
+
3
+ from .endpoint import ArcsecondAPIEndpoint
4
+
5
+
6
+ class ArcsecondTargetListsResource(ArcsecondAPIEndpoint):
7
+ """Target-list specific helpers built on top of the generic endpoint contract."""
8
+
9
+ target_relation_keys = ("targets", "target_uuids", "target_ids")
10
+
11
+ def _ensure_iterable(self, values):
12
+ if values is None:
13
+ return None
14
+ if isinstance(values, (str, int)):
15
+ return [values]
16
+ return list(values)
17
+
18
+ def _normalise_target_references(self, targets):
19
+ values = self._ensure_iterable(targets)
20
+ if values is None:
21
+ return None
22
+
23
+ refs = []
24
+ for target in values:
25
+ if isinstance(target, dict):
26
+ ref = (
27
+ target.get("uuid")
28
+ or target.get("id")
29
+ or target.get("pk")
30
+ or target.get("name")
31
+ )
32
+ if ref is None:
33
+ raise ArcsecondError(
34
+ "Target dictionaries must include one of: uuid, id, pk or name."
35
+ )
36
+ refs.append(ref)
37
+ else:
38
+ refs.append(target)
39
+ return refs
40
+
41
+ def _target_key_from_payload(self, payload, target_key=None):
42
+ if target_key:
43
+ return target_key
44
+ for key in self.target_relation_keys:
45
+ if payload and key in payload:
46
+ return key
47
+ return self.target_relation_keys[0]
48
+
49
+ def _build_payload(self, json=None, targets=None, target_key=None, **fields):
50
+ payload = super()._build_payload(json=json, **fields) or {}
51
+ normalised_targets = self._normalise_target_references(targets)
52
+ if normalised_targets is not None:
53
+ payload[self._target_key_from_payload(payload, target_key=target_key)] = (
54
+ normalised_targets
55
+ )
56
+ return payload or None
57
+
58
+ def create(self, json=None, targets=None, target_key=None, **fields):
59
+ payload = self._build_payload(
60
+ json=json, targets=targets, target_key=target_key, **fields
61
+ )
62
+ return ArcsecondAPIEndpoint.create(self, json=payload)
63
+
64
+ def update(self, id_name_uuid, json=None, targets=None, target_key=None, **fields):
65
+ payload = self._build_payload(
66
+ json=json, targets=targets, target_key=target_key, **fields
67
+ )
68
+ return ArcsecondAPIEndpoint.update(self, id_name_uuid, json=payload)
69
+
70
+ def upsert(self, match_field="name", json=None, targets=None, target_key=None, **fields):
71
+ payload = self._build_payload(
72
+ json=json, targets=targets, target_key=target_key, **fields
73
+ )
74
+ return super().upsert(match_field=match_field, json=payload)
75
+
76
+ def _read_target_refs(self, target_list, target_key=None):
77
+ key = self._target_key_from_payload(target_list or {}, target_key=target_key)
78
+ raw_targets = (target_list or {}).get(key, [])
79
+ refs = self._normalise_target_references(raw_targets) or []
80
+ return key, refs
81
+
82
+ def set_targets(self, id_name_uuid, targets, target_key=None):
83
+ target_key = self._target_key_from_payload({}, target_key=target_key)
84
+ return self.update(id_name_uuid, **{target_key: self._normalise_target_references(targets)})
85
+
86
+ def clear_targets(self, id_name_uuid, target_key=None):
87
+ return self.set_targets(id_name_uuid, [], target_key=target_key)
88
+
89
+ def add_targets(self, id_name_uuid, targets, target_key=None):
90
+ target_list, error = self.read(id_name_uuid)
91
+ if error:
92
+ return None, error
93
+
94
+ key, current_refs = self._read_target_refs(target_list, target_key=target_key)
95
+ for ref in self._normalise_target_references(targets) or []:
96
+ if ref not in current_refs:
97
+ current_refs.append(ref)
98
+ return self.update(id_name_uuid, **{key: current_refs})
99
+
100
+ def remove_targets(self, id_name_uuid, targets, target_key=None):
101
+ target_list, error = self.read(id_name_uuid)
102
+ if error:
103
+ return None, error
104
+
105
+ key, current_refs = self._read_target_refs(target_list, target_key=target_key)
106
+ refs_to_remove = set(self._normalise_target_references(targets) or [])
107
+ remaining_refs = [ref for ref in current_refs if ref not in refs_to_remove]
108
+ return self.update(id_name_uuid, **{key: remaining_refs})
109
+
110
+ def add_target(self, id_name_uuid, target, target_key=None):
111
+ return self.add_targets(id_name_uuid, [target], target_key=target_key)
112
+
113
+ def remove_target(self, id_name_uuid, target, target_key=None):
114
+ return self.remove_targets(id_name_uuid, [target], target_key=target_key)
@@ -10,6 +10,7 @@ from arcsecond.cloud import (
10
10
  upload_data,
11
11
  )
12
12
  from arcsecond.hosting import setup
13
+ from arcsecond.webcam import commands as webcam
13
14
 
14
15
  from . import __version__
15
16
  from .options import State
@@ -59,3 +60,6 @@ main.add_command(upload_data)
59
60
 
60
61
  # Allow to try arcsecond by installing a local version
61
62
  main.add_command(setup)
63
+
64
+ # Native webcam proxy — lets Docker containers reach USB webcams on the host.
65
+ main.add_command(webcam.webcam)
@@ -35,6 +35,12 @@ services:
35
35
  depends_on:
36
36
  - db
37
37
  - broker
38
+ # Allows the backend to reach the host machine via host.docker.internal.
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
+ extra_hosts:
43
+ - "host.docker.internal:host-gateway"
38
44
  env_file:
39
45
  # You must have a .env file with secret keys beside this yml file.
40
46
  - .env