fairagro-middleware-shared 11.0.1.dev29__tar.gz → 11.0.1.dev30__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 (34) hide show
  1. {fairagro_middleware_shared-11.0.1.dev29 → fairagro_middleware_shared-11.0.1.dev30}/PKG-INFO +1 -1
  2. {fairagro_middleware_shared-11.0.1.dev29 → fairagro_middleware_shared-11.0.1.dev30}/src/middleware/shared/report/model.py +14 -8
  3. {fairagro_middleware_shared-11.0.1.dev29 → fairagro_middleware_shared-11.0.1.dev30}/tests/unit/test_harvest_report.py +21 -5
  4. {fairagro_middleware_shared-11.0.1.dev29 → fairagro_middleware_shared-11.0.1.dev30}/.gitignore +0 -0
  5. {fairagro_middleware_shared-11.0.1.dev29 → fairagro_middleware_shared-11.0.1.dev30}/README.md +0 -0
  6. {fairagro_middleware_shared-11.0.1.dev29 → fairagro_middleware_shared-11.0.1.dev30}/pyproject.toml +0 -0
  7. {fairagro_middleware_shared-11.0.1.dev29 → fairagro_middleware_shared-11.0.1.dev30}/src/middleware/shared/__init__.py +0 -0
  8. {fairagro_middleware_shared-11.0.1.dev29 → fairagro_middleware_shared-11.0.1.dev30}/src/middleware/shared/api_models/__init__.py +0 -0
  9. {fairagro_middleware_shared-11.0.1.dev29 → fairagro_middleware_shared-11.0.1.dev30}/src/middleware/shared/api_models/common/__init__.py +0 -0
  10. {fairagro_middleware_shared-11.0.1.dev29 → fairagro_middleware_shared-11.0.1.dev30}/src/middleware/shared/api_models/common/models.py +0 -0
  11. {fairagro_middleware_shared-11.0.1.dev29 → fairagro_middleware_shared-11.0.1.dev30}/src/middleware/shared/api_models/common/rocrate.py +0 -0
  12. {fairagro_middleware_shared-11.0.1.dev29 → fairagro_middleware_shared-11.0.1.dev30}/src/middleware/shared/api_models/py.typed +0 -0
  13. {fairagro_middleware_shared-11.0.1.dev29 → fairagro_middleware_shared-11.0.1.dev30}/src/middleware/shared/api_models/v1/__init__.py +0 -0
  14. {fairagro_middleware_shared-11.0.1.dev29 → fairagro_middleware_shared-11.0.1.dev30}/src/middleware/shared/api_models/v1/models.py +0 -0
  15. {fairagro_middleware_shared-11.0.1.dev29 → fairagro_middleware_shared-11.0.1.dev30}/src/middleware/shared/api_models/v2/__init__.py +0 -0
  16. {fairagro_middleware_shared-11.0.1.dev29 → fairagro_middleware_shared-11.0.1.dev30}/src/middleware/shared/api_models/v2/models.py +0 -0
  17. {fairagro_middleware_shared-11.0.1.dev29 → fairagro_middleware_shared-11.0.1.dev30}/src/middleware/shared/api_models/v3/__init__.py +0 -0
  18. {fairagro_middleware_shared-11.0.1.dev29 → fairagro_middleware_shared-11.0.1.dev30}/src/middleware/shared/api_models/v3/models.py +0 -0
  19. {fairagro_middleware_shared-11.0.1.dev29 → fairagro_middleware_shared-11.0.1.dev30}/src/middleware/shared/config/__init__.py +0 -0
  20. {fairagro_middleware_shared-11.0.1.dev29 → fairagro_middleware_shared-11.0.1.dev30}/src/middleware/shared/config/config_base.py +0 -0
  21. {fairagro_middleware_shared-11.0.1.dev29 → fairagro_middleware_shared-11.0.1.dev30}/src/middleware/shared/config/config_wrapper.py +0 -0
  22. {fairagro_middleware_shared-11.0.1.dev29 → fairagro_middleware_shared-11.0.1.dev30}/src/middleware/shared/config/logging.py +0 -0
  23. {fairagro_middleware_shared-11.0.1.dev29 → fairagro_middleware_shared-11.0.1.dev30}/src/middleware/shared/py.typed +0 -0
  24. {fairagro_middleware_shared-11.0.1.dev29 → fairagro_middleware_shared-11.0.1.dev30}/src/middleware/shared/report/__init__.py +0 -0
  25. {fairagro_middleware_shared-11.0.1.dev29 → fairagro_middleware_shared-11.0.1.dev30}/src/middleware/shared/report/formats/__init__.py +0 -0
  26. {fairagro_middleware_shared-11.0.1.dev29 → fairagro_middleware_shared-11.0.1.dev30}/src/middleware/shared/report/formats/base.py +0 -0
  27. {fairagro_middleware_shared-11.0.1.dev29 → fairagro_middleware_shared-11.0.1.dev30}/src/middleware/shared/report/formats/jsonld.py +0 -0
  28. {fairagro_middleware_shared-11.0.1.dev29 → fairagro_middleware_shared-11.0.1.dev30}/src/middleware/shared/tracing.py +0 -0
  29. {fairagro_middleware_shared-11.0.1.dev29 → fairagro_middleware_shared-11.0.1.dev30}/tests/unit/test_api_models.py +0 -0
  30. {fairagro_middleware_shared-11.0.1.dev29 → fairagro_middleware_shared-11.0.1.dev30}/tests/unit/test_config_base.py +0 -0
  31. {fairagro_middleware_shared-11.0.1.dev29 → fairagro_middleware_shared-11.0.1.dev30}/tests/unit/test_config_wrapper.py +0 -0
  32. {fairagro_middleware_shared-11.0.1.dev29 → fairagro_middleware_shared-11.0.1.dev30}/tests/unit/test_logging.py +0 -0
  33. {fairagro_middleware_shared-11.0.1.dev29 → fairagro_middleware_shared-11.0.1.dev30}/tests/unit/test_rocrate.py +0 -0
  34. {fairagro_middleware_shared-11.0.1.dev29 → fairagro_middleware_shared-11.0.1.dev30}/tests/unit/test_tracing.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fairagro-middleware-shared
3
- Version: 11.0.1.dev29
3
+ Version: 11.0.1.dev30
4
4
  Summary: The FAIRagro advanced middleware shared components
5
5
  Requires-Python: >=3.12
6
6
  Requires-Dist: opentelemetry-api>=1.26.0
@@ -8,10 +8,10 @@ from datetime import UTC, datetime
8
8
 
9
9
 
10
10
  def _require_aware_utc(value: datetime, *, what: str) -> datetime:
11
- """Reject naive datetimes; callers must pass timezone-aware UTC timestamps."""
11
+ """Reject naive datetimes and normalize timezone-aware values to UTC."""
12
12
  if value.tzinfo is None:
13
13
  raise ValueError(f"{what} must be timezone-aware UTC")
14
- return value
14
+ return value.astimezone(UTC)
15
15
 
16
16
 
17
17
  @dataclass(frozen=True)
@@ -76,7 +76,7 @@ class RepositoryScope:
76
76
 
77
77
  @property
78
78
  def rdi(self) -> str:
79
- """Return the RDI identifier for this scope."""
79
+ """RDI identifier for this scope."""
80
80
  return self._rdi
81
81
 
82
82
  def close(self, *, closed_at: datetime | None = None) -> None:
@@ -183,12 +183,12 @@ class HarvestReport:
183
183
 
184
184
  @property
185
185
  def start_time(self) -> datetime:
186
- """Return the run start timestamp."""
186
+ """Run start timestamp."""
187
187
  return self._start_time
188
188
 
189
189
  @property
190
190
  def end_time(self) -> datetime:
191
- """Return the run end timestamp.
191
+ """Run end timestamp.
192
192
 
193
193
  Raises:
194
194
  ValueError: If :meth:`finish` has not been called yet.
@@ -199,12 +199,12 @@ class HarvestReport:
199
199
 
200
200
  @property
201
201
  def duration_seconds(self) -> float:
202
- """Return the total harvest run duration in seconds."""
202
+ """Total harvest run duration in seconds."""
203
203
  return (self.end_time - self.start_time).total_seconds()
204
204
 
205
205
  @property
206
206
  def repository_reports(self) -> tuple[RepositoryReport, ...]:
207
- """Return immutable snapshots of all repository scopes in open order."""
207
+ """Immutable snapshots of all repository scopes in open order."""
208
208
  with self._scopes_lock:
209
209
  scopes = list(self._scopes)
210
210
  return tuple(scope.snapshot() for scope in scopes)
@@ -217,7 +217,13 @@ class HarvestReport:
217
217
  return scope
218
218
 
219
219
  def finish(self, *, end_time: datetime | None = None) -> None:
220
- """Finish the harvest run, close open scopes, and record the end timestamp."""
220
+ """Finish the harvest run, close open scopes, and record the end timestamp.
221
+
222
+ Raises:
223
+ ValueError: If :meth:`finish` has already been called.
224
+ """
225
+ if self._end_time is not None:
226
+ raise ValueError("HarvestReport.finish() has already been called")
221
227
  if end_time is None:
222
228
  finished_at = datetime.now(UTC)
223
229
  else:
@@ -8,6 +8,7 @@ import threading
8
8
  from collections.abc import Callable
9
9
  from concurrent.futures import ThreadPoolExecutor
10
10
  from datetime import UTC, datetime, timedelta
11
+ from zoneinfo import ZoneInfo
11
12
 
12
13
  import pytest
13
14
 
@@ -203,11 +204,26 @@ def test_finish_closes_open_scopes_deterministically() -> None:
203
204
  scope = report.open_repository("bonares", opened_at=opened)
204
205
  report.finish(end_time=_END)
205
206
  assert scope.snapshot().duration_seconds == (_END - opened).total_seconds()
206
- # Second finish must not change an already-closed scope.
207
- later = _END + timedelta(seconds=30)
208
- report.finish(end_time=later)
209
- assert scope.snapshot().duration_seconds == (_END - opened).total_seconds()
210
- assert report.end_time == later
207
+ assert report.end_time == _END
208
+
209
+
210
+ def test_finish_is_single_shot() -> None:
211
+ """Calling finish() twice raises so run and scope durations stay aligned."""
212
+ report = HarvestReport(start_time=_START)
213
+ report.open_repository("bonares", opened_at=_START)
214
+ report.finish(end_time=_END)
215
+ with pytest.raises(ValueError, match="already been called"):
216
+ report.finish(end_time=_END + timedelta(seconds=30))
217
+ assert report.end_time == _END
218
+
219
+
220
+ def test_require_aware_utc_normalizes_non_utc() -> None:
221
+ """Timezone-aware non-UTC timestamps are stored as UTC equivalents."""
222
+ berlin = ZoneInfo("Europe/Berlin")
223
+ local_start = datetime(2026, 5, 6, 16, 0, 0, tzinfo=berlin) # UTC+2 in May
224
+ report = HarvestReport(start_time=local_start)
225
+ assert report.start_time == datetime(2026, 5, 6, 14, 0, 0, tzinfo=UTC)
226
+ assert report.start_time.tzinfo == UTC
211
227
 
212
228
 
213
229
  def test_jsonld_context_and_types() -> None: