quantilica-cli 0.10.0__tar.gz → 0.10.1__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 (27) hide show
  1. {quantilica_cli-0.10.0 → quantilica_cli-0.10.1}/CHANGELOG.md +8 -0
  2. {quantilica_cli-0.10.0 → quantilica_cli-0.10.1}/PKG-INFO +2 -2
  3. {quantilica_cli-0.10.0 → quantilica_cli-0.10.1}/pyproject.toml +2 -2
  4. {quantilica_cli-0.10.0 → quantilica_cli-0.10.1}/src/quantilica/cli/sdk.py +10 -3
  5. {quantilica_cli-0.10.0 → quantilica_cli-0.10.1}/tests/test_sdk.py +62 -0
  6. {quantilica_cli-0.10.0 → quantilica_cli-0.10.1}/.githooks/pre-commit +0 -0
  7. {quantilica_cli-0.10.0 → quantilica_cli-0.10.1}/.githooks/pre-push +0 -0
  8. {quantilica_cli-0.10.0 → quantilica_cli-0.10.1}/.github/workflows/publish.yml +0 -0
  9. {quantilica_cli-0.10.0 → quantilica_cli-0.10.1}/.github/workflows/test.yml +0 -0
  10. {quantilica_cli-0.10.0 → quantilica_cli-0.10.1}/.gitignore +0 -0
  11. {quantilica_cli-0.10.0 → quantilica_cli-0.10.1}/LICENSE +0 -0
  12. {quantilica_cli-0.10.0 → quantilica_cli-0.10.1}/README.md +0 -0
  13. {quantilica_cli-0.10.0 → quantilica_cli-0.10.1}/src/quantilica/cli/__init__.py +0 -0
  14. {quantilica_cli-0.10.0 → quantilica_cli-0.10.1}/src/quantilica/cli/cli.py +0 -0
  15. {quantilica_cli-0.10.0 → quantilica_cli-0.10.1}/src/quantilica/cli/health.py +0 -0
  16. {quantilica_cli-0.10.0 → quantilica_cli-0.10.1}/src/quantilica/cli/manifests.py +0 -0
  17. {quantilica_cli-0.10.0 → quantilica_cli-0.10.1}/src/quantilica/cli/progress.py +0 -0
  18. {quantilica_cli-0.10.0 → quantilica_cli-0.10.1}/src/quantilica/cli/sources.py +0 -0
  19. {quantilica_cli-0.10.0 → quantilica_cli-0.10.1}/src/quantilica/cli/ui.py +0 -0
  20. {quantilica_cli-0.10.0 → quantilica_cli-0.10.1}/src/quantilica/py.typed +0 -0
  21. {quantilica_cli-0.10.0 → quantilica_cli-0.10.1}/tests/__init__.py +0 -0
  22. {quantilica_cli-0.10.0 → quantilica_cli-0.10.1}/tests/test_health.py +0 -0
  23. {quantilica_cli-0.10.0 → quantilica_cli-0.10.1}/tests/test_manifests.py +0 -0
  24. {quantilica_cli-0.10.0 → quantilica_cli-0.10.1}/tests/test_package_hygiene.py +0 -0
  25. {quantilica_cli-0.10.0 → quantilica_cli-0.10.1}/tests/test_sdk_onda_a2.py +0 -0
  26. {quantilica_cli-0.10.0 → quantilica_cli-0.10.1}/tests/test_sources.py +0 -0
  27. {quantilica_cli-0.10.0 → quantilica_cli-0.10.1}/tests/test_ui.py +0 -0
@@ -5,6 +5,14 @@ Todas as mudanças notáveis deste projeto serão documentadas neste arquivo.
5
5
  O formato segue [Keep a Changelog](https://keepachangelog.com/pt-BR/1.1.0/),
6
6
  e este projeto adere ao [Semantic Versioning](https://semver.org/lang/pt-BR/).
7
7
 
8
+ ## [0.10.1] - 2026-10-08
9
+
10
+ ### Corrigido
11
+
12
+ - `sdk`: `check_entry` considera `local.exists() or sidecar.exists()`, preservando
13
+ o status de `manifest-fresh` em ambientes de ingestão efêmera sem forçar
14
+ re-download redundante quando o dado bruto já foi espelhado.
15
+
8
16
  ## [0.10.0] - 2026-10-07
9
17
 
10
18
  ### Adicionado
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: quantilica-cli
3
- Version: 0.10.0
3
+ Version: 0.10.1
4
4
  Summary: Unified CLI for Quantilica open data fetchers
5
5
  Author-email: "Komesu, D.K." <daniel@dkko.me>
6
6
  License-Expression: MIT
@@ -16,7 +16,7 @@ Classifier: Programming Language :: Python :: 3.12
16
16
  Classifier: Programming Language :: Python :: 3.13
17
17
  Classifier: Typing :: Typed
18
18
  Requires-Python: >=3.12
19
- Requires-Dist: quantilica-core>=0.10.0
19
+ Requires-Dist: quantilica-core>=0.10.1
20
20
  Requires-Dist: rich>=13.0.0
21
21
  Requires-Dist: typer>=0.15.0
22
22
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "quantilica-cli"
3
- version = "0.10.0"
3
+ version = "0.10.1"
4
4
  description = "Unified CLI for Quantilica open data fetchers"
5
5
  readme = "README.md"
6
6
  authors = [{ name = "Komesu, D.K.", email = "daniel@dkko.me" }]
@@ -20,7 +20,7 @@ classifiers = [
20
20
  "Programming Language :: Python :: 3.13",
21
21
  ]
22
22
  dependencies = [
23
- "quantilica-core>=0.10.0",
23
+ "quantilica-core>=0.10.1",
24
24
  "rich>=13.0.0",
25
25
  "typer>=0.15.0",
26
26
  ]
@@ -28,6 +28,7 @@ from quantilica.core.http import (
28
28
  resolve_verify_from_env,
29
29
  )
30
30
  from quantilica.core.logging import get_logger
31
+ from quantilica.core.manifests import manifest_sidecar_path
31
32
  from quantilica.core.storage import (
32
33
  StampedDataRepository,
33
34
  build_stamped_filename,
@@ -166,7 +167,9 @@ class CheckPlanItem:
166
167
  remote_etag: Remote ETag header, or None.
167
168
  remote_last_modified: Remote Last-Modified header, or None.
168
169
  remote_size: Remote Content-Length, or None.
169
- local_exists: Whether the local file already exists.
170
+ local_exists: Whether the artifact already exists locally — this
171
+ covers either the raw file itself or its ephemeral ``.manifest.json``
172
+ sidecar marker (file deleted for retention, sidecar still fresh).
170
173
  action: ``"download"`` or ``"skip-up-to-date"``.
171
174
  reason: Machine-readable reason for the action.
172
175
  entry: Original dataset entry (allows ``sync --from-plan`` round-trip).
@@ -983,9 +986,13 @@ class FetcherApp:
983
986
  size = None
984
987
  last_modified = _parse_head_date(head.headers.get("Last-Modified"))
985
988
  local = self.path_builder(output_dir, entry, last_modified)
986
- exists = local.exists()
989
+ sidecar = manifest_sidecar_path(local)
990
+ exists = local.exists() or sidecar.exists()
987
991
  if exists and not is_remote_more_recent(head, local):
988
- action, reason = "skip-up-to-date", "local-fresh"
992
+ if local.exists():
993
+ action, reason = "skip-up-to-date", "local-fresh"
994
+ else:
995
+ action, reason = "skip-up-to-date", "manifest-fresh"
989
996
  else:
990
997
  action = "download"
991
998
  reason = "not-present" if not exists else "remote-newer"
@@ -435,3 +435,65 @@ def test_default_client_attempts_env_override(monkeypatch):
435
435
  monkeypatch.delenv("QUANTILICA_CA_BUNDLE", raising=False)
436
436
  monkeypatch.setenv("QUANTILICA_HTTP_ATTEMPTS", "9")
437
437
  assert default_client().attempts == 9
438
+
439
+
440
+ def test_check_entry_ephemeral_manifest_fresh(tmp_path: Path):
441
+ """Raw file deleted but sidecar manifest survives -> manifest-fresh skip."""
442
+ from quantilica.core.manifests import (
443
+ DownloadManifest,
444
+ SourceMetadata,
445
+ write_manifest_sidecar,
446
+ )
447
+
448
+ app = make_app(client=_mock_client())
449
+ entry = sample_list_datasets("exp")[0]
450
+ local = default_path_builder(tmp_path, entry, dt.date(2024, 1, 1))
451
+ local.parent.mkdir(parents=True, exist_ok=True)
452
+ write_manifest_sidecar(
453
+ local,
454
+ DownloadManifest(
455
+ source_id="src",
456
+ dataset_id="ds",
457
+ url=entry["url"],
458
+ fetched_at="2024-01-02T00:00:00+00:00",
459
+ sha256="0" * 64,
460
+ size_bytes=10,
461
+ source_meta=SourceMetadata(
462
+ etag='"abc123"',
463
+ last_modified="Mon, 01 Jan 2024 00:00:00 GMT",
464
+ ),
465
+ ),
466
+ )
467
+ item = app.check_entry(entry, tmp_path)
468
+ assert item.action == "skip-up-to-date"
469
+ assert item.reason == "manifest-fresh"
470
+ assert item.local_exists is True
471
+
472
+
473
+ def test_check_entry_ephemeral_stale_downloads(tmp_path: Path):
474
+ """Sidecar with stale metadata (remote ETag differs) -> download."""
475
+ from quantilica.core.manifests import (
476
+ DownloadManifest,
477
+ SourceMetadata,
478
+ write_manifest_sidecar,
479
+ )
480
+
481
+ app = make_app(client=_mock_client())
482
+ entry = sample_list_datasets("exp")[0]
483
+ local = default_path_builder(tmp_path, entry, dt.date(2024, 1, 1))
484
+ local.parent.mkdir(parents=True, exist_ok=True)
485
+ write_manifest_sidecar(
486
+ local,
487
+ DownloadManifest(
488
+ source_id="src",
489
+ dataset_id="ds",
490
+ url=entry["url"],
491
+ fetched_at="2023-12-31T00:00:00+00:00",
492
+ sha256="0" * 64,
493
+ size_bytes=10,
494
+ source_meta=SourceMetadata(etag='"outdated"', last_modified=None),
495
+ ),
496
+ )
497
+ item = app.check_entry(entry, tmp_path)
498
+ assert item.action == "download"
499
+ assert item.reason == "remote-newer"
File without changes