pam-python 0.2.10__tar.gz → 0.2.12__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. {pam_python-0.2.10/pam_python.egg-info → pam_python-0.2.12}/PKG-INFO +1 -1
  2. {pam_python-0.2.10 → pam_python-0.2.12}/pam/__init__.py +1 -1
  3. {pam_python-0.2.10 → pam_python-0.2.12}/pam/interface_task_manager.py +1 -0
  4. {pam_python-0.2.10 → pam_python-0.2.12}/pam/reports/__init__.py +2 -0
  5. {pam_python-0.2.10 → pam_python-0.2.12}/pam/reports/area.py +10 -0
  6. {pam_python-0.2.10 → pam_python-0.2.12}/pam/reports/bar.py +10 -0
  7. {pam_python-0.2.10 → pam_python-0.2.12}/pam/reports/definition.py +16 -0
  8. {pam_python-0.2.10 → pam_python-0.2.12}/pam/reports/donut.py +6 -0
  9. {pam_python-0.2.10 → pam_python-0.2.12}/pam/reports/line.py +10 -0
  10. {pam_python-0.2.10 → pam_python-0.2.12}/pam/reports/manager.py +30 -2
  11. {pam_python-0.2.10 → pam_python-0.2.12}/pam/reports/metric.py +10 -0
  12. {pam_python-0.2.10 → pam_python-0.2.12}/pam/reports/pie.py +6 -0
  13. {pam_python-0.2.10 → pam_python-0.2.12}/pam/reports/polar_area.py +6 -0
  14. {pam_python-0.2.10 → pam_python-0.2.12}/pam/reports/radial_bar.py +10 -0
  15. {pam_python-0.2.10 → pam_python-0.2.12}/pam/reports/store.py +53 -10
  16. {pam_python-0.2.10 → pam_python-0.2.12}/pam/reports/table.py +10 -0
  17. pam_python-0.2.12/pam/reports/temporal.py +55 -0
  18. {pam_python-0.2.10 → pam_python-0.2.12}/pam/reports/transport.py +1 -0
  19. {pam_python-0.2.10 → pam_python-0.2.12}/pam/task_manager.py +2 -1
  20. {pam_python-0.2.10 → pam_python-0.2.12}/pam/templates/init/AGENT.md +112 -9
  21. {pam_python-0.2.10 → pam_python-0.2.12}/pam/templates/init/REPORTS.md +152 -17
  22. {pam_python-0.2.10 → pam_python-0.2.12}/pam/tester_task.py +22 -1
  23. {pam_python-0.2.10 → pam_python-0.2.12/pam_python.egg-info}/PKG-INFO +1 -1
  24. {pam_python-0.2.10 → pam_python-0.2.12}/pam_python.egg-info/SOURCES.txt +2 -0
  25. {pam_python-0.2.10 → pam_python-0.2.12}/tests/test_cli_templates.py +40 -0
  26. {pam_python-0.2.10 → pam_python-0.2.12}/tests/test_report_store.py +40 -6
  27. pam_python-0.2.12/tests/test_temporal_mode.py +116 -0
  28. {pam_python-0.2.10 → pam_python-0.2.12}/LICENSE.txt +0 -0
  29. {pam_python-0.2.10 → pam_python-0.2.12}/README.md +0 -0
  30. {pam_python-0.2.10 → pam_python-0.2.12}/pam/api.py +0 -0
  31. {pam_python-0.2.10 → pam_python-0.2.12}/pam/cli.py +0 -0
  32. {pam_python-0.2.10 → pam_python-0.2.12}/pam/datetime_utils.py +0 -0
  33. {pam_python-0.2.10 → pam_python-0.2.12}/pam/logger.py +0 -0
  34. {pam_python-0.2.10 → pam_python-0.2.12}/pam/models/__init__.py +0 -0
  35. {pam_python-0.2.10 → pam_python-0.2.12}/pam/models/data_input_v2.py +0 -0
  36. {pam_python-0.2.10 → pam_python-0.2.12}/pam/models/request_command.py +0 -0
  37. {pam_python-0.2.10 → pam_python-0.2.12}/pam/models/sqlite_download.py +0 -0
  38. {pam_python-0.2.10 → pam_python-0.2.12}/pam/reports/axis.py +0 -0
  39. {pam_python-0.2.10 → pam_python-0.2.12}/pam/reports/box_plot.py +0 -0
  40. {pam_python-0.2.10 → pam_python-0.2.12}/pam/reports/bubble.py +0 -0
  41. {pam_python-0.2.10 → pam_python-0.2.12}/pam/reports/candlestick.py +0 -0
  42. {pam_python-0.2.10 → pam_python-0.2.12}/pam/reports/collection.py +0 -0
  43. {pam_python-0.2.10 → pam_python-0.2.12}/pam/reports/errors.py +0 -0
  44. {pam_python-0.2.10 → pam_python-0.2.12}/pam/reports/fields.py +0 -0
  45. {pam_python-0.2.10 → pam_python-0.2.12}/pam/reports/heatmap.py +0 -0
  46. {pam_python-0.2.10 → pam_python-0.2.12}/pam/reports/radar.py +0 -0
  47. {pam_python-0.2.10 → pam_python-0.2.12}/pam/reports/range_area.py +0 -0
  48. {pam_python-0.2.10 → pam_python-0.2.12}/pam/reports/range_bar.py +0 -0
  49. {pam_python-0.2.10 → pam_python-0.2.12}/pam/reports/range_transform.py +0 -0
  50. {pam_python-0.2.10 → pam_python-0.2.12}/pam/reports/record.py +0 -0
  51. {pam_python-0.2.10 → pam_python-0.2.12}/pam/reports/report.py +0 -0
  52. {pam_python-0.2.10 → pam_python-0.2.12}/pam/reports/scatter.py +0 -0
  53. {pam_python-0.2.10 → pam_python-0.2.12}/pam/reports/schema.py +0 -0
  54. {pam_python-0.2.10 → pam_python-0.2.12}/pam/reports/treemap.py +0 -0
  55. {pam_python-0.2.10 → pam_python-0.2.12}/pam/request_file_format.py +0 -0
  56. {pam_python-0.2.10 → pam_python-0.2.12}/pam/result_batch_uploader.py +0 -0
  57. {pam_python-0.2.10 → pam_python-0.2.12}/pam/server.py +0 -0
  58. {pam_python-0.2.10 → pam_python-0.2.12}/pam/service.py +0 -0
  59. {pam_python-0.2.10 → pam_python-0.2.12}/pam/sqlite.py +0 -0
  60. {pam_python-0.2.10 → pam_python-0.2.12}/pam/temp_file_utils.py +0 -0
  61. {pam_python-0.2.10 → pam_python-0.2.12}/pam/templates/buildcmd/pamb +0 -0
  62. {pam_python-0.2.10 → pam_python-0.2.12}/pam/templates/buildcmd/pamb-base.sh +0 -0
  63. {pam_python-0.2.10 → pam_python-0.2.12}/pam/templates/init/dockerignore.tmpl +0 -0
  64. {pam_python-0.2.10 → pam_python-0.2.12}/pam/templates/init/gitignore.tmpl +0 -0
  65. {pam_python-0.2.10 → pam_python-0.2.12}/pam/templates/init/main.tmpl +0 -0
  66. {pam_python-0.2.10 → pam_python-0.2.12}/pam/templates/init/pylintrc.tmpl +0 -0
  67. {pam_python-0.2.10 → pam_python-0.2.12}/pam/templates/init/run_unit_test.bat +0 -0
  68. {pam_python-0.2.10 → pam_python-0.2.12}/pam/templates/init/run_unit_test.ps1 +0 -0
  69. {pam_python-0.2.10 → pam_python-0.2.12}/pam/templates/init/run_unit_test.sh +0 -0
  70. {pam_python-0.2.10 → pam_python-0.2.12}/pam/templates/project/pip/Dockerfile +0 -0
  71. {pam_python-0.2.10 → pam_python-0.2.12}/pam/templates/project/pip/requirements.txt +0 -0
  72. {pam_python-0.2.10 → pam_python-0.2.12}/pam/templates/project/uv/Dockerfile +0 -0
  73. {pam_python-0.2.10 → pam_python-0.2.12}/pam/templates/project/uv/pyproject.toml +0 -0
  74. {pam_python-0.2.10 → pam_python-0.2.12}/pam/templates/project/uv/python-version +0 -0
  75. {pam_python-0.2.10 → pam_python-0.2.12}/pam/templates/service/common/service.yaml +0 -0
  76. {pam_python-0.2.10 → pam_python-0.2.12}/pam/templates/service/pandas/functions.tmpl +0 -0
  77. {pam_python-0.2.10 → pam_python-0.2.12}/pam/templates/service/pandas/service.test.tmpl +0 -0
  78. {pam_python-0.2.10 → pam_python-0.2.12}/pam/templates/service/pandas/service_class.tmpl +0 -0
  79. {pam_python-0.2.10 → pam_python-0.2.12}/pam/templates/service/polars/functions.tmpl +0 -0
  80. {pam_python-0.2.10 → pam_python-0.2.12}/pam/templates/service/polars/service.test.tmpl +0 -0
  81. {pam_python-0.2.10 → pam_python-0.2.12}/pam/templates/service/polars/service_class.tmpl +0 -0
  82. {pam_python-0.2.10 → pam_python-0.2.12}/pam/utils.py +0 -0
  83. {pam_python-0.2.10 → pam_python-0.2.12}/pam_python.egg-info/dependency_links.txt +0 -0
  84. {pam_python-0.2.10 → pam_python-0.2.12}/pam_python.egg-info/entry_points.txt +0 -0
  85. {pam_python-0.2.10 → pam_python-0.2.12}/pam_python.egg-info/requires.txt +0 -0
  86. {pam_python-0.2.10 → pam_python-0.2.12}/pam_python.egg-info/top_level.txt +0 -0
  87. {pam_python-0.2.10 → pam_python-0.2.12}/pyproject.toml +0 -0
  88. {pam_python-0.2.10 → pam_python-0.2.12}/setup.cfg +0 -0
  89. {pam_python-0.2.10 → pam_python-0.2.12}/tests/test_api.py +0 -0
  90. {pam_python-0.2.10 → pam_python-0.2.12}/tests/test_data_error_v2.py +0 -0
  91. {pam_python-0.2.10 → pam_python-0.2.12}/tests/test_data_input_v2.py +0 -0
  92. {pam_python-0.2.10 → pam_python-0.2.12}/tests/test_data_timeout_v2.py +0 -0
  93. {pam_python-0.2.10 → pam_python-0.2.12}/tests/test_datetime_utils.py +0 -0
  94. {pam_python-0.2.10 → pam_python-0.2.12}/tests/test_range_transform.py +0 -0
  95. {pam_python-0.2.10 → pam_python-0.2.12}/tests/test_request_data_v2.py +0 -0
  96. {pam_python-0.2.10 → pam_python-0.2.12}/tests/test_result_batch_uploader.py +0 -0
  97. {pam_python-0.2.10 → pam_python-0.2.12}/tests/test_service_dataframe_output.py +0 -0
  98. {pam_python-0.2.10 → pam_python-0.2.12}/tests/test_sqlite_download_v2.py +0 -0
  99. {pam_python-0.2.10 → pam_python-0.2.12}/tests/test_temp_file_utils.py +0 -0
  100. {pam_python-0.2.10 → pam_python-0.2.12}/tests/test_typed_area_report.py +0 -0
  101. {pam_python-0.2.10 → pam_python-0.2.12}/tests/test_typed_bar_report.py +0 -0
  102. {pam_python-0.2.10 → pam_python-0.2.12}/tests/test_typed_box_plot_report.py +0 -0
  103. {pam_python-0.2.10 → pam_python-0.2.12}/tests/test_typed_bubble_report.py +0 -0
  104. {pam_python-0.2.10 → pam_python-0.2.12}/tests/test_typed_candlestick_report.py +0 -0
  105. {pam_python-0.2.10 → pam_python-0.2.12}/tests/test_typed_donut_report.py +0 -0
  106. {pam_python-0.2.10 → pam_python-0.2.12}/tests/test_typed_heatmap_report.py +0 -0
  107. {pam_python-0.2.10 → pam_python-0.2.12}/tests/test_typed_line_report.py +0 -0
  108. {pam_python-0.2.10 → pam_python-0.2.12}/tests/test_typed_metric_report.py +0 -0
  109. {pam_python-0.2.10 → pam_python-0.2.12}/tests/test_typed_pie_report.py +0 -0
  110. {pam_python-0.2.10 → pam_python-0.2.12}/tests/test_typed_polar_area_report.py +0 -0
  111. {pam_python-0.2.10 → pam_python-0.2.12}/tests/test_typed_radar_report.py +0 -0
  112. {pam_python-0.2.10 → pam_python-0.2.12}/tests/test_typed_radial_bar_report.py +0 -0
  113. {pam_python-0.2.10 → pam_python-0.2.12}/tests/test_typed_range_area_report.py +0 -0
  114. {pam_python-0.2.10 → pam_python-0.2.12}/tests/test_typed_range_bar_report.py +0 -0
  115. {pam_python-0.2.10 → pam_python-0.2.12}/tests/test_typed_scatter_report.py +0 -0
  116. {pam_python-0.2.10 → pam_python-0.2.12}/tests/test_typed_table_report.py +0 -0
  117. {pam_python-0.2.10 → pam_python-0.2.12}/tests/test_typed_treemap_report.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pam-python
3
- Version: 0.2.10
3
+ Version: 0.2.12
4
4
  Summary: Pam Python Library
5
5
  Author-email: Narongrit Kanhanoi <narongrit@pams.ai>
6
6
  Project-URL: Homepage, https://github.com/heart/pam-python
@@ -1,3 +1,3 @@
1
1
  """PAM Data Plugin framework."""
2
2
 
3
- __version__ = "0.2.10"
3
+ __version__ = "0.2.12"
@@ -79,5 +79,6 @@ class ITaskManager(ABC):
79
79
  service: Service,
80
80
  report_key: str,
81
81
  pointer: dict,
82
+ tracker_name: str,
82
83
  ):
83
84
  pass
@@ -31,6 +31,7 @@ from pam.reports.range_bar import RangeBarChartReport
31
31
  from pam.reports.scatter import ScatterChartReport
32
32
  from pam.reports.store import ReportStore
33
33
  from pam.reports.table import TableReport
34
+ from pam.reports.temporal import TemporalMode
34
35
  from pam.reports.treemap import TreemapReport
35
36
 
36
37
  __all__ = [
@@ -68,6 +69,7 @@ __all__ = [
68
69
  "ScatterChartReport",
69
70
  "Sum",
70
71
  "TableReport",
72
+ "TemporalMode",
71
73
  "TimeRangeTransform",
72
74
  "TreemapReport",
73
75
  ]
@@ -8,6 +8,7 @@ from pam.reports.definition import ReportDefinition
8
8
  from pam.reports.errors import ReportStoreValidationError
9
9
  from pam.reports.fields import Dimension, Measure
10
10
  from pam.reports.range_transform import TimeRangeTransform, normalize_range_transform
11
+ from pam.reports.temporal import TemporalMode, normalize_temporal_mode
11
12
 
12
13
 
13
14
  AREA_RENDERER = "area"
@@ -50,6 +51,7 @@ class AreaChartReport(TypedCollectionReport):
50
51
  series: Optional[Dimension] = None,
51
52
  stacked: bool = False,
52
53
  range_transform: Optional[TimeRangeTransform] = None,
54
+ temporal_mode: Optional[TemporalMode] = None,
53
55
  ):
54
56
  if not isinstance(stacked, bool):
55
57
  raise ReportStoreValidationError("Area stacked must be boolean")
@@ -61,6 +63,13 @@ class AreaChartReport(TypedCollectionReport):
61
63
  normalized_range_transform = normalize_range_transform(
62
64
  range_transform, granularity, normalized_values, "Area Chart"
63
65
  )
66
+ normalized_temporal_mode = normalize_temporal_mode(
67
+ temporal_mode,
68
+ granularity,
69
+ normalized_values,
70
+ "Area Chart",
71
+ range_transform,
72
+ )
64
73
  definition = ReportDefinition.create(
65
74
  report_type="area",
66
75
  contract_version=AREA_CONTRACT_VERSION,
@@ -72,6 +81,7 @@ class AreaChartReport(TypedCollectionReport):
72
81
  },
73
82
  options={"stacked": stacked},
74
83
  range_transform=normalized_range_transform,
84
+ temporal_mode=normalized_temporal_mode,
75
85
  )
76
86
  report = store._declare_typed_report(
77
87
  key=key,
@@ -8,6 +8,7 @@ from pam.reports.definition import ReportDefinition
8
8
  from pam.reports.errors import ReportStoreValidationError
9
9
  from pam.reports.fields import Dimension, Measure
10
10
  from pam.reports.range_transform import TimeRangeTransform, normalize_range_transform
11
+ from pam.reports.temporal import TemporalMode, normalize_temporal_mode
11
12
 
12
13
 
13
14
  BAR_RENDERER = "bar"
@@ -56,6 +57,7 @@ class BarChartReport(TypedCollectionReport):
56
57
  orientation: str = "vertical",
57
58
  stacked: bool = False,
58
59
  range_transform: Optional[TimeRangeTransform] = None,
60
+ temporal_mode: Optional[TemporalMode] = None,
59
61
  ):
60
62
  if not isinstance(category, Dimension):
61
63
  raise ReportStoreValidationError(
@@ -110,6 +112,13 @@ class BarChartReport(TypedCollectionReport):
110
112
  normalized_range_transform = normalize_range_transform(
111
113
  range_transform, granularity, normalized_values, "Bar Chart"
112
114
  )
115
+ normalized_temporal_mode = normalize_temporal_mode(
116
+ temporal_mode,
117
+ granularity,
118
+ normalized_values,
119
+ "Bar Chart",
120
+ range_transform,
121
+ )
113
122
  definition = ReportDefinition.create(
114
123
  report_type="bar",
115
124
  contract_version=BAR_CONTRACT_VERSION,
@@ -121,6 +130,7 @@ class BarChartReport(TypedCollectionReport):
121
130
  },
122
131
  options={"orientation": orientation, "stacked": stacked},
123
132
  range_transform=normalized_range_transform,
133
+ temporal_mode=normalized_temporal_mode,
124
134
  )
125
135
  report = store._declare_typed_report(
126
136
  key=key,
@@ -18,6 +18,7 @@ class ReportDefinition:
18
18
  binding: Dict[str, Any]
19
19
  options: Dict[str, Any] = field(default_factory=dict)
20
20
  range_transform: Optional[Dict[str, Any]] = None
21
+ temporal_mode: Optional[str] = None
21
22
 
22
23
  def __post_init__(self) -> None:
23
24
  if not isinstance(self.report_type, str) or not self.report_type:
@@ -41,6 +42,17 @@ class ReportDefinition:
41
42
  raise ReportStoreValidationError(
42
43
  "range_transform must be a dictionary or None"
43
44
  )
45
+ if self.temporal_mode is not None and self.temporal_mode not in {
46
+ "AGGREGATE",
47
+ "SNAPSHOT",
48
+ }:
49
+ raise ReportStoreValidationError(
50
+ "temporal_mode must be AGGREGATE, SNAPSHOT, or None"
51
+ )
52
+ if self.temporal_mode is not None and self.range_transform is not None:
53
+ raise ReportStoreValidationError(
54
+ "temporal_mode and range_transform cannot be declared together"
55
+ )
44
56
 
45
57
  # Validate serializability while the declaration is still close to its caller.
46
58
  canonical_json(self.to_dict())
@@ -54,6 +66,7 @@ class ReportDefinition:
54
66
  binding: Dict[str, Any],
55
67
  options: Dict[str, Any] = None,
56
68
  range_transform: Dict[str, Any] = None,
69
+ temporal_mode: str = None,
57
70
  ):
58
71
  return cls(
59
72
  report_type=report_type,
@@ -64,6 +77,7 @@ class ReportDefinition:
64
77
  range_transform=(
65
78
  dict(range_transform) if range_transform is not None else None
66
79
  ),
80
+ temporal_mode=temporal_mode,
67
81
  )
68
82
 
69
83
  def to_dict(self) -> Dict[str, Any]:
@@ -76,6 +90,8 @@ class ReportDefinition:
76
90
  }
77
91
  if self.range_transform is not None:
78
92
  definition["range_transform"] = self.range_transform
93
+ if self.temporal_mode is not None:
94
+ definition["temporal_mode"] = self.temporal_mode
79
95
  return definition
80
96
 
81
97
  def to_json(self) -> str:
@@ -8,6 +8,7 @@ from pam.reports.definition import ReportDefinition
8
8
  from pam.reports.errors import ReportStoreValidationError
9
9
  from pam.reports.fields import Dimension, Measure
10
10
  from pam.reports.range_transform import TimeRangeTransform, normalize_range_transform
11
+ from pam.reports.temporal import TemporalMode, normalize_temporal_mode
11
12
 
12
13
 
13
14
  DONUT_RENDERER = "donut"
@@ -51,6 +52,7 @@ class DonutChartReport(TypedCollectionReport):
51
52
  category: Dimension,
52
53
  value: Measure,
53
54
  range_transform: Optional[TimeRangeTransform] = None,
55
+ temporal_mode: Optional[TemporalMode] = None,
54
56
  ):
55
57
  if not isinstance(category, Dimension):
56
58
  raise ReportStoreValidationError(
@@ -80,12 +82,16 @@ class DonutChartReport(TypedCollectionReport):
80
82
  normalized_range_transform = normalize_range_transform(
81
83
  range_transform, granularity, (value,), "Donut"
82
84
  )
85
+ normalized_temporal_mode = normalize_temporal_mode(
86
+ temporal_mode, granularity, (value,), "Donut", range_transform
87
+ )
83
88
  definition = ReportDefinition.create(
84
89
  report_type="donut",
85
90
  contract_version=DONUT_CONTRACT_VERSION,
86
91
  fields=(category, value),
87
92
  binding={"category": category.name, "value": value.name},
88
93
  range_transform=normalized_range_transform,
94
+ temporal_mode=normalized_temporal_mode,
89
95
  )
90
96
  report = store._declare_typed_report(
91
97
  key=key,
@@ -8,6 +8,7 @@ from pam.reports.definition import ReportDefinition
8
8
  from pam.reports.errors import ReportStoreValidationError
9
9
  from pam.reports.fields import Dimension, Measure
10
10
  from pam.reports.range_transform import TimeRangeTransform, normalize_range_transform
11
+ from pam.reports.temporal import TemporalMode, normalize_temporal_mode
11
12
 
12
13
 
13
14
  LINE_RENDERER = "line"
@@ -46,6 +47,7 @@ class LineChartReport(TypedCollectionReport):
46
47
  values: Iterable[Measure],
47
48
  series: Optional[Dimension] = None,
48
49
  range_transform: Optional[TimeRangeTransform] = None,
50
+ temporal_mode: Optional[TemporalMode] = None,
49
51
  ):
50
52
  if granularity == "NONE":
51
53
  raise ReportStoreValidationError("Line granularity must be DAY or MONTH")
@@ -55,6 +57,13 @@ class LineChartReport(TypedCollectionReport):
55
57
  normalized_range_transform = normalize_range_transform(
56
58
  range_transform, granularity, normalized_values, "Line"
57
59
  )
60
+ normalized_temporal_mode = normalize_temporal_mode(
61
+ temporal_mode,
62
+ granularity,
63
+ normalized_values,
64
+ "Line",
65
+ range_transform,
66
+ )
58
67
  definition = ReportDefinition.create(
59
68
  report_type="line",
60
69
  contract_version=LINE_CONTRACT_VERSION,
@@ -65,6 +74,7 @@ class LineChartReport(TypedCollectionReport):
65
74
  "values": [value.name for value in normalized_values],
66
75
  },
67
76
  range_transform=normalized_range_transform,
77
+ temporal_mode=normalized_temporal_mode,
68
78
  )
69
79
  report = store._declare_typed_report(
70
80
  key=key,
@@ -31,9 +31,15 @@ class ReportStoreManager:
31
31
  self._sleep = sleep_fn
32
32
  self._stores = {}
33
33
 
34
- def create(self, file_name: str, shared: bool = False) -> ReportStore:
34
+ def create(
35
+ self,
36
+ file_name: str,
37
+ shared: bool = False,
38
+ tracker_name: str | None = None,
39
+ ) -> ReportStore:
35
40
  """Start a fresh local file without performing a download."""
36
41
  self._validate_file_name(file_name)
42
+ tracker_name = self._resolve_tracker_name(tracker_name)
37
43
  session_key = (file_name, shared)
38
44
  self._assert_not_open(session_key)
39
45
  path = self._new_local_path(file_name)
@@ -41,18 +47,29 @@ class ReportStoreManager:
41
47
  path,
42
48
  file_name,
43
49
  shared,
50
+ tracker_name=tracker_name,
44
51
  transport=self._transport,
45
52
  cleanup_on_close=True,
46
53
  )
47
54
  self._stores[session_key] = store
48
55
  return store
49
56
 
50
- def open(self, file_name: str, shared: bool = False) -> ReportStore:
57
+ def open(
58
+ self,
59
+ file_name: str,
60
+ shared: bool = False,
61
+ tracker_name: str | None = None,
62
+ ) -> ReportStore:
51
63
  """Resume remote state, or create only after an authoritative 404."""
52
64
  self._validate_file_name(file_name)
65
+ tracker_name = self._resolve_tracker_name(tracker_name)
53
66
  session_key = (file_name, shared)
54
67
  existing = self._stores.get(session_key)
55
68
  if existing is not None:
69
+ if existing.tracker_name != tracker_name:
70
+ raise ReportStoreLocalIOError(
71
+ "This report store is already open with a different tracker_name"
72
+ )
56
73
  return existing
57
74
 
58
75
  path = self._new_local_path(file_name)
@@ -64,6 +81,7 @@ class ReportStoreManager:
64
81
  downloaded_path,
65
82
  file_name,
66
83
  shared,
84
+ tracker_name=tracker_name,
67
85
  transport=self._transport,
68
86
  cleanup_on_close=True,
69
87
  )
@@ -75,6 +93,7 @@ class ReportStoreManager:
75
93
  path,
76
94
  file_name,
77
95
  shared,
96
+ tracker_name=tracker_name,
78
97
  transport=self._transport,
79
98
  cleanup_on_close=True,
80
99
  )
@@ -138,6 +157,15 @@ class ReportStoreManager:
138
157
  "This report store is already open for the current service run"
139
158
  )
140
159
 
160
+ def _resolve_tracker_name(self, tracker_name: str | None) -> str:
161
+ if tracker_name is None:
162
+ tracker_name = self._service.request.service_name
163
+ if not isinstance(tracker_name, str) or not tracker_name.strip():
164
+ raise ReportStoreLocalIOError(
165
+ "tracker_name must be a non-empty string"
166
+ )
167
+ return tracker_name.strip()
168
+
141
169
  @staticmethod
142
170
  def _validate_file_name(file_name: str) -> None:
143
171
  if not isinstance(file_name, str) or not file_name.strip():
@@ -7,6 +7,7 @@ from pam.reports.errors import ReportStoreValidationError
7
7
  from pam.reports.fields import Measure
8
8
  from pam.reports.record import ReportRecord, build_record_key, validate_partition_key
9
9
  from pam.reports.range_transform import TimeRangeTransform, normalize_range_transform
10
+ from pam.reports.temporal import TemporalMode, normalize_temporal_mode
10
11
 
11
12
 
12
13
  METRIC_RENDERER = "metric"
@@ -31,6 +32,7 @@ class MetricReport:
31
32
  granularity: str,
32
33
  metrics: Iterable[Measure],
33
34
  range_transform: Optional[TimeRangeTransform] = None,
35
+ temporal_mode: Optional[TemporalMode] = None,
34
36
  ):
35
37
  if isinstance(metrics, (str, bytes)):
36
38
  raise ReportStoreValidationError("metrics must contain Measure declarations")
@@ -50,6 +52,13 @@ class MetricReport:
50
52
  normalized_range_transform = normalize_range_transform(
51
53
  range_transform, granularity, normalized_metrics, "Metric"
52
54
  )
55
+ normalized_temporal_mode = normalize_temporal_mode(
56
+ temporal_mode,
57
+ granularity,
58
+ normalized_metrics,
59
+ "Metric",
60
+ range_transform,
61
+ )
53
62
 
54
63
  definition = ReportDefinition.create(
55
64
  report_type="metric",
@@ -57,6 +66,7 @@ class MetricReport:
57
66
  fields=normalized_metrics,
58
67
  binding={"metrics": [metric.name for metric in normalized_metrics]},
59
68
  range_transform=normalized_range_transform,
69
+ temporal_mode=normalized_temporal_mode,
60
70
  )
61
71
  report = store._declare_typed_report(
62
72
  key=key,
@@ -8,6 +8,7 @@ from pam.reports.definition import ReportDefinition
8
8
  from pam.reports.errors import ReportStoreValidationError
9
9
  from pam.reports.fields import Dimension, Measure
10
10
  from pam.reports.range_transform import TimeRangeTransform, normalize_range_transform
11
+ from pam.reports.temporal import TemporalMode, normalize_temporal_mode
11
12
 
12
13
 
13
14
  PIE_RENDERER = "pie"
@@ -51,6 +52,7 @@ class PieChartReport(TypedCollectionReport):
51
52
  category: Dimension,
52
53
  value: Measure,
53
54
  range_transform: Optional[TimeRangeTransform] = None,
55
+ temporal_mode: Optional[TemporalMode] = None,
54
56
  ):
55
57
  if not isinstance(category, Dimension):
56
58
  raise ReportStoreValidationError(
@@ -80,12 +82,16 @@ class PieChartReport(TypedCollectionReport):
80
82
  normalized_range_transform = normalize_range_transform(
81
83
  range_transform, granularity, (value,), "Pie"
82
84
  )
85
+ normalized_temporal_mode = normalize_temporal_mode(
86
+ temporal_mode, granularity, (value,), "Pie", range_transform
87
+ )
83
88
  definition = ReportDefinition.create(
84
89
  report_type="pie",
85
90
  contract_version=PIE_CONTRACT_VERSION,
86
91
  fields=(category, value),
87
92
  binding={"category": category.name, "value": value.name},
88
93
  range_transform=normalized_range_transform,
94
+ temporal_mode=normalized_temporal_mode,
89
95
  )
90
96
  report = store._declare_typed_report(
91
97
  key=key,
@@ -8,6 +8,7 @@ from pam.reports.definition import ReportDefinition
8
8
  from pam.reports.errors import ReportStoreValidationError
9
9
  from pam.reports.fields import Dimension, Measure
10
10
  from pam.reports.range_transform import TimeRangeTransform, normalize_range_transform
11
+ from pam.reports.temporal import TemporalMode, normalize_temporal_mode
11
12
 
12
13
 
13
14
  POLAR_AREA_RENDERER = "polar_area"
@@ -52,6 +53,7 @@ class PolarAreaChartReport(TypedCollectionReport):
52
53
  category: Dimension,
53
54
  value: Measure,
54
55
  range_transform: Optional[TimeRangeTransform] = None,
56
+ temporal_mode: Optional[TemporalMode] = None,
55
57
  ):
56
58
  if not isinstance(category, Dimension):
57
59
  raise ReportStoreValidationError(
@@ -81,12 +83,16 @@ class PolarAreaChartReport(TypedCollectionReport):
81
83
  normalized_range_transform = normalize_range_transform(
82
84
  range_transform, granularity, (value,), "Polar Area"
83
85
  )
86
+ normalized_temporal_mode = normalize_temporal_mode(
87
+ temporal_mode, granularity, (value,), "Polar Area", range_transform
88
+ )
84
89
  definition = ReportDefinition.create(
85
90
  report_type="polar_area",
86
91
  contract_version=POLAR_AREA_CONTRACT_VERSION,
87
92
  fields=(category, value),
88
93
  binding={"category": category.name, "value": value.name},
89
94
  range_transform=normalized_range_transform,
95
+ temporal_mode=normalized_temporal_mode,
90
96
  )
91
97
  report = store._declare_typed_report(
92
98
  key=key,
@@ -9,6 +9,7 @@ from pam.reports.errors import ReportStoreValidationError
9
9
  from pam.reports.fields import Measure
10
10
  from pam.reports.record import ReportRecord, build_record_key, validate_partition_key
11
11
  from pam.reports.range_transform import TimeRangeTransform, normalize_range_transform
12
+ from pam.reports.temporal import TemporalMode, normalize_temporal_mode
12
13
 
13
14
 
14
15
  RADIAL_BAR_RENDERER = "radial_bar"
@@ -32,6 +33,7 @@ class RadialBarChartReport:
32
33
  cls, store, key: str, granularity: str, metrics: Iterable[Measure],
33
34
  min_value: float, max_value: float,
34
35
  range_transform: Optional[TimeRangeTransform] = None,
36
+ temporal_mode: Optional[TemporalMode] = None,
35
37
  ):
36
38
  try:
37
39
  normalized_metrics = tuple(metrics)
@@ -66,6 +68,13 @@ class RadialBarChartReport:
66
68
  normalized_transform = normalize_range_transform(
67
69
  range_transform, granularity, normalized_metrics, "Radial Bar"
68
70
  )
71
+ normalized_temporal_mode = normalize_temporal_mode(
72
+ temporal_mode,
73
+ granularity,
74
+ normalized_metrics,
75
+ "Radial Bar",
76
+ range_transform,
77
+ )
69
78
  definition = ReportDefinition.create(
70
79
  report_type="radial_bar",
71
80
  contract_version=RADIAL_BAR_CONTRACT_VERSION,
@@ -73,6 +82,7 @@ class RadialBarChartReport:
73
82
  binding={"metrics": [metric.name for metric in normalized_metrics]},
74
83
  options={"min_value": min_value, "max_value": max_value},
75
84
  range_transform=normalized_transform,
85
+ temporal_mode=normalized_temporal_mode,
76
86
  )
77
87
  report = store._declare_typed_report(
78
88
  key, RADIAL_BAR_RENDERER, RADIAL_BAR_SCHEMA, granularity,
@@ -45,6 +45,7 @@ from pam.reports.schema import (
45
45
  STORE_VERSION,
46
46
  )
47
47
  from pam.reports.table import TableReport
48
+ from pam.reports.temporal import TemporalMode
48
49
  from pam.reports.treemap import TreemapReport
49
50
 
50
51
 
@@ -60,12 +61,14 @@ class ReportStore:
60
61
  path: Path,
61
62
  file_name: str,
62
63
  shared: bool,
64
+ tracker_name: str = "",
63
65
  transport=None,
64
66
  cleanup_on_close: bool = False,
65
67
  ):
66
68
  self.path = Path(path)
67
69
  self.file_name = file_name
68
70
  self.shared = shared
71
+ self.tracker_name = tracker_name
69
72
  self._transport = transport
70
73
  self._cleanup_on_close = cleanup_on_close
71
74
  self._lock = threading.RLock()
@@ -87,6 +90,7 @@ class ReportStore:
87
90
  path: Path,
88
91
  file_name: str,
89
92
  shared: bool,
93
+ tracker_name: str = "",
90
94
  transport=None,
91
95
  cleanup_on_close: bool = False,
92
96
  ):
@@ -97,7 +101,14 @@ class ReportStore:
97
101
  except OSError as exc:
98
102
  raise ReportStoreLocalIOError(str(exc)) from exc
99
103
 
100
- store = cls(path, file_name, shared, transport, cleanup_on_close)
104
+ store = cls(
105
+ path,
106
+ file_name,
107
+ shared,
108
+ tracker_name=tracker_name,
109
+ transport=transport,
110
+ cleanup_on_close=cleanup_on_close,
111
+ )
101
112
  try:
102
113
  with store._connection:
103
114
  store._connection.executescript(CREATE_SCHEMA_SQL)
@@ -116,10 +127,18 @@ class ReportStore:
116
127
  path: Path,
117
128
  file_name: str,
118
129
  shared: bool,
130
+ tracker_name: str = "",
119
131
  transport=None,
120
132
  cleanup_on_close: bool = False,
121
133
  ):
122
- store = cls(path, file_name, shared, transport, cleanup_on_close)
134
+ store = cls(
135
+ path,
136
+ file_name,
137
+ shared,
138
+ tracker_name=tracker_name,
139
+ transport=transport,
140
+ cleanup_on_close=cleanup_on_close,
141
+ )
123
142
  try:
124
143
  store._migrate_compatible_schema()
125
144
  store.validate()
@@ -137,9 +156,10 @@ class ReportStore:
137
156
  key: str,
138
157
  granularity: str,
139
158
  columns: Iterable[Field],
159
+ temporal_mode: Optional[TemporalMode] = None,
140
160
  ) -> TableReport:
141
161
  """Declare a typed Table report."""
142
- return TableReport.declare(self, key, granularity, columns)
162
+ return TableReport.declare(self, key, granularity, columns, temporal_mode)
143
163
 
144
164
  def metric(
145
165
  self,
@@ -147,10 +167,11 @@ class ReportStore:
147
167
  granularity: str,
148
168
  metrics: Iterable[Measure],
149
169
  range_transform: Optional[TimeRangeTransform] = None,
170
+ temporal_mode: Optional[TemporalMode] = None,
150
171
  ) -> MetricReport:
151
172
  """Declare a typed Metric report."""
152
173
  return MetricReport.declare(
153
- self, key, granularity, metrics, range_transform
174
+ self, key, granularity, metrics, range_transform, temporal_mode
154
175
  )
155
176
 
156
177
  def line_chart(
@@ -160,10 +181,11 @@ class ReportStore:
160
181
  values: Iterable[Measure],
161
182
  series: Optional[Dimension] = None,
162
183
  range_transform: Optional[TimeRangeTransform] = None,
184
+ temporal_mode: Optional[TemporalMode] = None,
163
185
  ) -> LineChartReport:
164
186
  """Declare a typed Line Chart report."""
165
187
  return LineChartReport.declare(
166
- self, key, granularity, values, series, range_transform
188
+ self, key, granularity, values, series, range_transform, temporal_mode
167
189
  )
168
190
 
169
191
  def area_chart(
@@ -174,10 +196,18 @@ class ReportStore:
174
196
  series: Optional[Dimension] = None,
175
197
  stacked: bool = False,
176
198
  range_transform: Optional[TimeRangeTransform] = None,
199
+ temporal_mode: Optional[TemporalMode] = None,
177
200
  ) -> AreaChartReport:
178
201
  """Declare a typed Area Chart report."""
179
202
  return AreaChartReport.declare(
180
- self, key, granularity, values, series, stacked, range_transform
203
+ self,
204
+ key,
205
+ granularity,
206
+ values,
207
+ series,
208
+ stacked,
209
+ range_transform,
210
+ temporal_mode,
181
211
  )
182
212
 
183
213
  def bar_chart(
@@ -190,6 +220,7 @@ class ReportStore:
190
220
  orientation: str = "vertical",
191
221
  stacked: bool = False,
192
222
  range_transform: Optional[TimeRangeTransform] = None,
223
+ temporal_mode: Optional[TemporalMode] = None,
193
224
  ) -> BarChartReport:
194
225
  """Declare a typed Bar/Column Chart report."""
195
226
  return BarChartReport.declare(
@@ -202,6 +233,7 @@ class ReportStore:
202
233
  orientation,
203
234
  stacked,
204
235
  range_transform,
236
+ temporal_mode,
205
237
  )
206
238
 
207
239
  def pie_chart(
@@ -211,10 +243,11 @@ class ReportStore:
211
243
  category: Dimension,
212
244
  value: Measure,
213
245
  range_transform: Optional[TimeRangeTransform] = None,
246
+ temporal_mode: Optional[TemporalMode] = None,
214
247
  ) -> PieChartReport:
215
248
  """Declare a typed Pie Chart report."""
216
249
  return PieChartReport.declare(
217
- self, key, granularity, category, value, range_transform
250
+ self, key, granularity, category, value, range_transform, temporal_mode
218
251
  )
219
252
 
220
253
  def donut_chart(
@@ -224,10 +257,11 @@ class ReportStore:
224
257
  category: Dimension,
225
258
  value: Measure,
226
259
  range_transform: Optional[TimeRangeTransform] = None,
260
+ temporal_mode: Optional[TemporalMode] = None,
227
261
  ) -> DonutChartReport:
228
262
  """Declare a typed Donut Chart report."""
229
263
  return DonutChartReport.declare(
230
- self, key, granularity, category, value, range_transform
264
+ self, key, granularity, category, value, range_transform, temporal_mode
231
265
  )
232
266
 
233
267
  def radial_bar_chart(
@@ -238,10 +272,18 @@ class ReportStore:
238
272
  min_value: float,
239
273
  max_value: float,
240
274
  range_transform: Optional[TimeRangeTransform] = None,
275
+ temporal_mode: Optional[TemporalMode] = None,
241
276
  ) -> RadialBarChartReport:
242
277
  """Declare a typed Radial Bar Chart report."""
243
278
  return RadialBarChartReport.declare(
244
- self, key, granularity, metrics, min_value, max_value, range_transform
279
+ self,
280
+ key,
281
+ granularity,
282
+ metrics,
283
+ min_value,
284
+ max_value,
285
+ range_transform,
286
+ temporal_mode,
245
287
  )
246
288
 
247
289
  def polar_area_chart(
@@ -251,10 +293,11 @@ class ReportStore:
251
293
  category: Dimension,
252
294
  value: Measure,
253
295
  range_transform: Optional[TimeRangeTransform] = None,
296
+ temporal_mode: Optional[TemporalMode] = None,
254
297
  ) -> PolarAreaChartReport:
255
298
  """Declare a typed Polar Area Chart report."""
256
299
  return PolarAreaChartReport.declare(
257
- self, key, granularity, category, value, range_transform
300
+ self, key, granularity, category, value, range_transform, temporal_mode
258
301
  )
259
302
 
260
303
  def scatter_chart(