arcsecond 4.3.1__tar.gz → 4.3.2__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 (121) hide show
  1. {arcsecond-4.3.1 → arcsecond-4.3.2}/PKG-INFO +1 -1
  2. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/hosting/stack.py +28 -2
  3. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/hosting/token.py +15 -8
  4. {arcsecond-4.3.1 → arcsecond-4.3.2}/pyproject.toml +1 -1
  5. {arcsecond-4.3.1 → arcsecond-4.3.2}/tests/test_hosting_token.py +72 -1
  6. {arcsecond-4.3.1 → arcsecond-4.3.2}/uv.lock +1 -1
  7. {arcsecond-4.3.1 → arcsecond-4.3.2}/.docker/Dockerfile_postgres +0 -0
  8. {arcsecond-4.3.1 → arcsecond-4.3.2}/.docker/Dockerfile_redis +0 -0
  9. {arcsecond-4.3.1 → arcsecond-4.3.2}/.flake8 +0 -0
  10. {arcsecond-4.3.1 → arcsecond-4.3.2}/.gitattributes +0 -0
  11. {arcsecond-4.3.1 → arcsecond-4.3.2}/.github/dependabot.yml +0 -0
  12. {arcsecond-4.3.1 → arcsecond-4.3.2}/.github/workflows/pythonpublish.yml +0 -0
  13. {arcsecond-4.3.1 → arcsecond-4.3.2}/.github/workflows/tests.yml +0 -0
  14. {arcsecond-4.3.1 → arcsecond-4.3.2}/.gitignore +0 -0
  15. {arcsecond-4.3.1 → arcsecond-4.3.2}/LICENSE +0 -0
  16. {arcsecond-4.3.1 → arcsecond-4.3.2}/Makefile +0 -0
  17. {arcsecond-4.3.1 → arcsecond-4.3.2}/README.md +0 -0
  18. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/__init__.py +0 -0
  19. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/__version__.py +0 -0
  20. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/alpaca/__init__.py +0 -0
  21. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/alpaca/commands.py +0 -0
  22. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/alpaca/dome_probe.py +0 -0
  23. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/alpaca/probe.py +0 -0
  24. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/alpaca/telescope_probe.py +0 -0
  25. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/api/__init__.py +0 -0
  26. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/api/config.py +0 -0
  27. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/api/constants.py +0 -0
  28. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/api/endpoint.py +0 -0
  29. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/api/main.py +0 -0
  30. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/api/resources.py +0 -0
  31. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/cli.py +0 -0
  32. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/cloud/__init__.py +0 -0
  33. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/cloud/auth.py +0 -0
  34. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/cloud/resources.py +0 -0
  35. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/cloud/uploader/__init__.py +0 -0
  36. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/cloud/uploader/constants.py +0 -0
  37. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/cloud/uploader/context.py +0 -0
  38. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/cloud/uploader/datafiles/__init__.py +0 -0
  39. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/cloud/uploader/datafiles/context.py +0 -0
  40. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/cloud/uploader/datafiles/errors.py +0 -0
  41. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/cloud/uploader/datafiles/uploader.py +0 -0
  42. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/cloud/uploader/datafiles/utils.py +0 -0
  43. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/cloud/uploader/errors.py +0 -0
  44. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/cloud/uploader/logger.py +0 -0
  45. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/cloud/uploader/uploader.py +0 -0
  46. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/cloud/uploader/utils.py +0 -0
  47. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/cloud/uploader/walker.py +0 -0
  48. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/cloud/uploads.py +0 -0
  49. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/docgen.py +0 -0
  50. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/errors.py +0 -0
  51. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/hosting/__init__.py +0 -0
  52. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/hosting/backups.py +0 -0
  53. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/hosting/check.py +0 -0
  54. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/hosting/composedoc.py +0 -0
  55. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/hosting/database.py +0 -0
  56. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/hosting/docker/__init__.py +0 -0
  57. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/hosting/docker/docker-compose.yml +0 -0
  58. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/hosting/lifecycle.py +0 -0
  59. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/hosting/local.py +0 -0
  60. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/hosting/utils.py +0 -0
  61. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/imagesources/__init__.py +0 -0
  62. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/imagesources/autostart.py +0 -0
  63. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/imagesources/commands.py +0 -0
  64. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/imagesources/detection.py +0 -0
  65. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/imagesources/proxy.py +0 -0
  66. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/imagesources/registry.py +0 -0
  67. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/imagesources/runtime.py +0 -0
  68. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/imagesources/sources/__init__.py +0 -0
  69. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/imagesources/sources/base.py +0 -0
  70. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/imagesources/sources/filewatch.py +0 -0
  71. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/imagesources/sources/mdns.py +0 -0
  72. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/imagesources/sources/network.py +0 -0
  73. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/imagesources/sources/opencv.py +0 -0
  74. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/imagesources/store.py +0 -0
  75. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/options.py +0 -0
  76. {arcsecond-4.3.1 → arcsecond-4.3.2}/arcsecond/targets.py +0 -0
  77. {arcsecond-4.3.1 → arcsecond-4.3.2}/examples/example_upload_files.py +0 -0
  78. {arcsecond-4.3.1 → arcsecond-4.3.2}/requirements.txt +0 -0
  79. {arcsecond-4.3.1 → arcsecond-4.3.2}/setup.cfg +0 -0
  80. {arcsecond-4.3.1 → arcsecond-4.3.2}/setup.py +0 -0
  81. {arcsecond-4.3.1 → arcsecond-4.3.2}/sonar-project.properties +0 -0
  82. {arcsecond-4.3.1 → arcsecond-4.3.2}/tests/__init__.py +0 -0
  83. {arcsecond-4.3.1 → arcsecond-4.3.2}/tests/api/__init__.py +0 -0
  84. {arcsecond-4.3.1 → arcsecond-4.3.2}/tests/api/test_api.py +0 -0
  85. {arcsecond-4.3.1 → arcsecond-4.3.2}/tests/api/test_api_endpoint.py +0 -0
  86. {arcsecond-4.3.1 → arcsecond-4.3.2}/tests/api/test_config.py +0 -0
  87. {arcsecond-4.3.1 → arcsecond-4.3.2}/tests/api/test_config_pointer.py +0 -0
  88. {arcsecond-4.3.1 → arcsecond-4.3.2}/tests/api/test_targets.py +0 -0
  89. {arcsecond-4.3.1 → arcsecond-4.3.2}/tests/cloud/__init__.py +0 -0
  90. {arcsecond-4.3.1 → arcsecond-4.3.2}/tests/cloud/uploader/__init__.py +0 -0
  91. {arcsecond-4.3.1 → arcsecond-4.3.2}/tests/cloud/uploader/datafiles/__init__.py +0 -0
  92. {arcsecond-4.3.1 → arcsecond-4.3.2}/tests/cloud/uploader/datafiles/test_uploader_errors.py +0 -0
  93. {arcsecond-4.3.1 → arcsecond-4.3.2}/tests/cloud/uploader/datafiles/test_uploader_full_process.py +0 -0
  94. {arcsecond-4.3.1 → arcsecond-4.3.2}/tests/cloud/uploader/datafiles/test_uploader_init.py +0 -0
  95. {arcsecond-4.3.1 → arcsecond-4.3.2}/tests/cloud/uploader/datafiles/test_uploader_prepare.py +0 -0
  96. {arcsecond-4.3.1 → arcsecond-4.3.2}/tests/cloud/uploader/datafiles/test_uploader_upload.py +0 -0
  97. {arcsecond-4.3.1 → arcsecond-4.3.2}/tests/conftest.py +0 -0
  98. {arcsecond-4.3.1 → arcsecond-4.3.2}/tests/fixtures/file1.fits +0 -0
  99. {arcsecond-4.3.1 → arcsecond-4.3.2}/tests/test_alpaca_probe.py +0 -0
  100. {arcsecond-4.3.1 → arcsecond-4.3.2}/tests/test_cli.py +0 -0
  101. {arcsecond-4.3.1 → arcsecond-4.3.2}/tests/test_cli_api_commands.py +0 -0
  102. {arcsecond-4.3.1 → arcsecond-4.3.2}/tests/test_docgen.py +0 -0
  103. {arcsecond-4.3.1 → arcsecond-4.3.2}/tests/test_hosting_backups_install_dir.py +0 -0
  104. {arcsecond-4.3.1 → arcsecond-4.3.2}/tests/test_hosting_backups_restore.py +0 -0
  105. {arcsecond-4.3.1 → arcsecond-4.3.2}/tests/test_hosting_check.py +0 -0
  106. {arcsecond-4.3.1 → arcsecond-4.3.2}/tests/test_hosting_composedoc.py +0 -0
  107. {arcsecond-4.3.1 → arcsecond-4.3.2}/tests/test_hosting_database.py +0 -0
  108. {arcsecond-4.3.1 → arcsecond-4.3.2}/tests/test_hosting_lifecycle.py +0 -0
  109. {arcsecond-4.3.1 → arcsecond-4.3.2}/tests/test_hosting_local.py +0 -0
  110. {arcsecond-4.3.1 → arcsecond-4.3.2}/tests/test_hosting_stack.py +0 -0
  111. {arcsecond-4.3.1 → arcsecond-4.3.2}/tests/test_hosting_utils_secret_key.py +0 -0
  112. {arcsecond-4.3.1 → arcsecond-4.3.2}/tests/test_imagesources_autostart.py +0 -0
  113. {arcsecond-4.3.1 → arcsecond-4.3.2}/tests/test_imagesources_commands.py +0 -0
  114. {arcsecond-4.3.1 → arcsecond-4.3.2}/tests/test_imagesources_detection.py +0 -0
  115. {arcsecond-4.3.1 → arcsecond-4.3.2}/tests/test_imagesources_mdns.py +0 -0
  116. {arcsecond-4.3.1 → arcsecond-4.3.2}/tests/test_imagesources_network.py +0 -0
  117. {arcsecond-4.3.1 → arcsecond-4.3.2}/tests/test_imagesources_proxy.py +0 -0
  118. {arcsecond-4.3.1 → arcsecond-4.3.2}/tests/test_imagesources_store.py +0 -0
  119. {arcsecond-4.3.1 → arcsecond-4.3.2}/tests/test_imagesources_webcam.py +0 -0
  120. {arcsecond-4.3.1 → arcsecond-4.3.2}/tests/test_targets_planning.py +0 -0
  121. {arcsecond-4.3.1 → arcsecond-4.3.2}/tests/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: arcsecond
3
- Version: 4.3.1
3
+ Version: 4.3.2
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
@@ -229,6 +229,22 @@ def compose_streaming(
229
229
  )
230
230
 
231
231
 
232
+ REGISTRY_REFUSED = (
233
+ "Docker could not download an Arcsecond image: the registry refused.\n"
234
+ "Enter the token Arcsecond gave your observatory: arcsecond token set"
235
+ )
236
+ REGISTRY_REFUSED_NO_TOKEN = (
237
+ "Docker could not download an Arcsecond image: this machine has no token "
238
+ "saved, so it asked without one.\n"
239
+ "Enter the token Arcsecond gave your observatory: arcsecond token set\n"
240
+ 'It must answer "Token accepted." before Arcsecond.local can be downloaded.'
241
+ )
242
+ REGISTRY_REFUSED_WITH_TOKEN = (
243
+ "Docker could not download an Arcsecond image: the token this machine holds "
244
+ "was refused. It may have expired or been replaced — enter the current one "
245
+ "with arcsecond token set, or ask team@arcsecond.io for a new one."
246
+ )
247
+
232
248
  # The compose failures the troubleshooting page used to explain, matched on
233
249
  # the daemon's own words so `start` can answer them on the spot.
234
250
  _KNOWN_FAILURES = (
@@ -267,8 +283,7 @@ _KNOWN_FAILURES = (
267
283
  ),
268
284
  (
269
285
  ("denied", "unauthorized", "pull access denied"),
270
- "Docker could not download an Arcsecond image: the registry refused.\n"
271
- "Enter the token Arcsecond gave your observatory: arcsecond token set",
286
+ REGISTRY_REFUSED,
272
287
  ),
273
288
  )
274
289
 
@@ -284,6 +299,17 @@ def explain_compose_failure(stderr: str) -> Optional[str]:
284
299
 
285
300
  def raise_compose_failure(what: str, result: subprocess.CompletedProcess) -> None:
286
301
  explanation = explain_compose_failure(result.stderr)
302
+ if explanation == REGISTRY_REFUSED:
303
+ # "Unauthorized" reads the same whether the token is wrong or was never
304
+ # saved (a password store that could not write it, a first `start`
305
+ # before `token set`); the two call for different words.
306
+ from . import token # token imports this module
307
+
308
+ explanation = (
309
+ REGISTRY_REFUSED_WITH_TOKEN
310
+ if token.has_token()
311
+ else REGISTRY_REFUSED_NO_TOKEN
312
+ )
287
313
  lines = [f"`{what}` failed (exit code {result.returncode})."]
288
314
  if explanation:
289
315
  lines += ["", explanation]
@@ -53,21 +53,23 @@ def docker_config_path() -> Path:
53
53
  def has_token() -> bool:
54
54
  """Whether Docker on this machine holds a credential for the images.
55
55
 
56
- Docker lists the registry under `auths` in its config even when the secret
57
- itself lives in a credential store (Docker Desktop's, the OS keychain), so
58
- the key's presence is the answer in the common case. When the config names
59
- a store and lists nothing, the store is asked.
56
+ When the config names a credential store (Docker Desktop's, `pass`, the OS
57
+ keychain), the store is the truth and is asked: the empty entry Docker
58
+ leaves under `auths` survives a store being emptied or replaced, and a
59
+ machine whose `pass` was never initialised can carry one from an older
60
+ login. Without a store, the secret sits inline under `auths`. Only when
61
+ the store cannot answer — its helper missing, or waiting on a passphrase
62
+ prompt nobody sees — does the entry's presence stand in for it.
60
63
  """
61
64
  try:
62
65
  config = json.loads(docker_config_path().read_text(encoding="utf-8") or "{}")
63
66
  except (OSError, ValueError):
64
67
  return False
65
68
  auths = config.get("auths") or {}
66
- if any(REGISTRY in key for key in auths):
67
- return True
69
+ entries = [value for key, value in auths.items() if REGISTRY in key]
68
70
  store = (config.get("credHelpers") or {}).get(REGISTRY) or config.get("credsStore")
69
71
  if not store:
70
- return False
72
+ return any(isinstance(entry, dict) and entry.get("auth") for entry in entries)
71
73
  try:
72
74
  probe = subprocess.run(
73
75
  [f"docker-credential-{store}", "get"],
@@ -78,8 +80,13 @@ def has_token() -> bool:
78
80
  timeout=15,
79
81
  )
80
82
  except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
83
+ return bool(entries)
84
+ if probe.returncode != 0:
85
+ return False
86
+ try:
87
+ return bool(json.loads(probe.stdout or "{}").get("Secret"))
88
+ except ValueError:
81
89
  return False
82
- return probe.returncode == 0 and REGISTRY_USER in (probe.stdout or "")
83
90
 
84
91
 
85
92
  def store_token(token: str) -> None:
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "arcsecond"
7
- version = "4.3.1"
7
+ version = "4.3.2"
8
8
  authors = [
9
9
  { name = "Cedric Foellmi", email = "cedric@arcsecond.io" },
10
10
  ]
@@ -7,6 +7,7 @@ import pytest
7
7
  from click.testing import CliRunner
8
8
 
9
9
  from arcsecond import cli
10
+ from arcsecond.errors import ArcsecondError
10
11
  from arcsecond.hosting import local, stack, token
11
12
 
12
13
 
@@ -117,12 +118,61 @@ def test_status_reads_dockers_config(docker, tmp_path):
117
118
  result = _invoke("token")
118
119
  assert result.exit_code == 1 and "no token yet" in result.output
119
120
  (tmp_path / "config.json").write_text(
120
- json.dumps({"auths": {"ghcr.io": {}}, "credsStore": "desktop"})
121
+ json.dumps({"auths": {"ghcr.io": {"auth": "YXJjc2Vjb25kLWlvOng="}}})
121
122
  )
122
123
  result = _invoke("token")
123
124
  assert result.exit_code == 0 and "has the token" in result.output
124
125
 
125
126
 
127
+ def _store_answers(monkeypatch, tmp_path, config, returncode, stdout=""):
128
+ monkeypatch.setattr(token, "docker_config_path", lambda: tmp_path / "config.json")
129
+ (tmp_path / "config.json").write_text(json.dumps(config))
130
+
131
+ def fake_run(cmd, input=None, **kwargs):
132
+ return subprocess.CompletedProcess(cmd, returncode, stdout=stdout)
133
+
134
+ monkeypatch.setattr(token.subprocess, "run", fake_run)
135
+
136
+
137
+ def test_an_empty_entry_is_not_a_token_when_the_store_holds_nothing(
138
+ monkeypatch, tmp_path
139
+ ):
140
+ # JB's machine: `pass` never initialised, an `auths` entry left behind.
141
+ _store_answers(
142
+ monkeypatch,
143
+ tmp_path,
144
+ {"auths": {"ghcr.io": {}}, "credsStore": "desktop"},
145
+ returncode=1,
146
+ stdout="credentials not found in native keychain",
147
+ )
148
+ assert token.has_token() is False
149
+
150
+
151
+ def test_a_token_saved_under_another_username_still_counts(monkeypatch, tmp_path):
152
+ # Before 4.2 the documentation had people log in under their own name.
153
+ _store_answers(
154
+ monkeypatch,
155
+ tmp_path,
156
+ {"credsStore": "pass"},
157
+ returncode=0,
158
+ stdout=json.dumps({"Username": "jb", "Secret": "x"}),
159
+ )
160
+ assert token.has_token() is True
161
+
162
+
163
+ def test_a_store_that_cannot_answer_falls_back_on_the_entry(monkeypatch, tmp_path):
164
+ monkeypatch.setattr(token, "docker_config_path", lambda: tmp_path / "config.json")
165
+ (tmp_path / "config.json").write_text(
166
+ json.dumps({"auths": {"ghcr.io": {}}, "credsStore": "pass"})
167
+ )
168
+
169
+ def hangs(cmd, **kwargs):
170
+ raise subprocess.TimeoutExpired(cmd, 15)
171
+
172
+ monkeypatch.setattr(token.subprocess, "run", hangs)
173
+ assert token.has_token() is True
174
+
175
+
126
176
  def test_has_token_asks_the_credential_store_when_the_config_lists_nothing(
127
177
  monkeypatch, tmp_path
128
178
  ):
@@ -150,6 +200,27 @@ def test_start_points_at_the_token_when_the_pull_is_denied():
150
200
  )
151
201
 
152
202
 
203
+ @pytest.mark.parametrize(
204
+ "saved, says, never",
205
+ [
206
+ (False, "no token saved", "expired"),
207
+ (True, "was refused", "no token saved"),
208
+ ],
209
+ )
210
+ def test_start_tells_a_missing_token_from_a_refused_one(
211
+ monkeypatch, saved, says, never
212
+ ):
213
+ monkeypatch.setattr(token, "has_token", lambda: saved)
214
+ result = subprocess.CompletedProcess(
215
+ [], 1, stderr="Error response from daemon: error from registry: unauthorized"
216
+ )
217
+ with pytest.raises(ArcsecondError) as excinfo:
218
+ stack.raise_compose_failure("docker compose up", result)
219
+ message = str(excinfo.value)
220
+ assert says in message and never not in message
221
+ assert "arcsecond token set" in message
222
+
223
+
153
224
  # --- setup asks for it -----------------------------------------------------------
154
225
 
155
226
 
@@ -155,7 +155,7 @@ wheels = [
155
155
 
156
156
  [[package]]
157
157
  name = "arcsecond"
158
- version = "4.3.1"
158
+ version = "4.3.2"
159
159
  source = { editable = "." }
160
160
  dependencies = [
161
161
  { name = "alpyca" },
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
File without changes
File without changes
File without changes
File without changes