pam-python 0.2.8__tar.gz → 0.2.9__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 (113) hide show
  1. {pam_python-0.2.8/pam_python.egg-info → pam_python-0.2.9}/PKG-INFO +1 -1
  2. {pam_python-0.2.8 → pam_python-0.2.9}/pam/__init__.py +1 -1
  3. {pam_python-0.2.8 → pam_python-0.2.9}/pam/models/data_input_v2.py +10 -4
  4. {pam_python-0.2.8 → pam_python-0.2.9}/pam/templates/init/AGENT.md +8 -0
  5. {pam_python-0.2.8 → pam_python-0.2.9/pam_python.egg-info}/PKG-INFO +1 -1
  6. {pam_python-0.2.8 → pam_python-0.2.9}/tests/test_data_input_v2.py +24 -7
  7. {pam_python-0.2.8 → pam_python-0.2.9}/LICENSE.txt +0 -0
  8. {pam_python-0.2.8 → pam_python-0.2.9}/README.md +0 -0
  9. {pam_python-0.2.8 → pam_python-0.2.9}/pam/api.py +0 -0
  10. {pam_python-0.2.8 → pam_python-0.2.9}/pam/cli.py +0 -0
  11. {pam_python-0.2.8 → pam_python-0.2.9}/pam/interface_task_manager.py +0 -0
  12. {pam_python-0.2.8 → pam_python-0.2.9}/pam/logger.py +0 -0
  13. {pam_python-0.2.8 → pam_python-0.2.9}/pam/models/__init__.py +0 -0
  14. {pam_python-0.2.8 → pam_python-0.2.9}/pam/models/request_command.py +0 -0
  15. {pam_python-0.2.8 → pam_python-0.2.9}/pam/models/sqlite_download.py +0 -0
  16. {pam_python-0.2.8 → pam_python-0.2.9}/pam/reports/__init__.py +0 -0
  17. {pam_python-0.2.8 → pam_python-0.2.9}/pam/reports/area.py +0 -0
  18. {pam_python-0.2.8 → pam_python-0.2.9}/pam/reports/axis.py +0 -0
  19. {pam_python-0.2.8 → pam_python-0.2.9}/pam/reports/bar.py +0 -0
  20. {pam_python-0.2.8 → pam_python-0.2.9}/pam/reports/box_plot.py +0 -0
  21. {pam_python-0.2.8 → pam_python-0.2.9}/pam/reports/bubble.py +0 -0
  22. {pam_python-0.2.8 → pam_python-0.2.9}/pam/reports/candlestick.py +0 -0
  23. {pam_python-0.2.8 → pam_python-0.2.9}/pam/reports/collection.py +0 -0
  24. {pam_python-0.2.8 → pam_python-0.2.9}/pam/reports/definition.py +0 -0
  25. {pam_python-0.2.8 → pam_python-0.2.9}/pam/reports/donut.py +0 -0
  26. {pam_python-0.2.8 → pam_python-0.2.9}/pam/reports/errors.py +0 -0
  27. {pam_python-0.2.8 → pam_python-0.2.9}/pam/reports/fields.py +0 -0
  28. {pam_python-0.2.8 → pam_python-0.2.9}/pam/reports/heatmap.py +0 -0
  29. {pam_python-0.2.8 → pam_python-0.2.9}/pam/reports/line.py +0 -0
  30. {pam_python-0.2.8 → pam_python-0.2.9}/pam/reports/manager.py +0 -0
  31. {pam_python-0.2.8 → pam_python-0.2.9}/pam/reports/metric.py +0 -0
  32. {pam_python-0.2.8 → pam_python-0.2.9}/pam/reports/pie.py +0 -0
  33. {pam_python-0.2.8 → pam_python-0.2.9}/pam/reports/polar_area.py +0 -0
  34. {pam_python-0.2.8 → pam_python-0.2.9}/pam/reports/radar.py +0 -0
  35. {pam_python-0.2.8 → pam_python-0.2.9}/pam/reports/radial_bar.py +0 -0
  36. {pam_python-0.2.8 → pam_python-0.2.9}/pam/reports/range_area.py +0 -0
  37. {pam_python-0.2.8 → pam_python-0.2.9}/pam/reports/range_bar.py +0 -0
  38. {pam_python-0.2.8 → pam_python-0.2.9}/pam/reports/range_transform.py +0 -0
  39. {pam_python-0.2.8 → pam_python-0.2.9}/pam/reports/record.py +0 -0
  40. {pam_python-0.2.8 → pam_python-0.2.9}/pam/reports/report.py +0 -0
  41. {pam_python-0.2.8 → pam_python-0.2.9}/pam/reports/scatter.py +0 -0
  42. {pam_python-0.2.8 → pam_python-0.2.9}/pam/reports/schema.py +0 -0
  43. {pam_python-0.2.8 → pam_python-0.2.9}/pam/reports/store.py +0 -0
  44. {pam_python-0.2.8 → pam_python-0.2.9}/pam/reports/table.py +0 -0
  45. {pam_python-0.2.8 → pam_python-0.2.9}/pam/reports/transport.py +0 -0
  46. {pam_python-0.2.8 → pam_python-0.2.9}/pam/reports/treemap.py +0 -0
  47. {pam_python-0.2.8 → pam_python-0.2.9}/pam/request_file_format.py +0 -0
  48. {pam_python-0.2.8 → pam_python-0.2.9}/pam/result_batch_uploader.py +0 -0
  49. {pam_python-0.2.8 → pam_python-0.2.9}/pam/server.py +0 -0
  50. {pam_python-0.2.8 → pam_python-0.2.9}/pam/service.py +0 -0
  51. {pam_python-0.2.8 → pam_python-0.2.9}/pam/sqlite.py +0 -0
  52. {pam_python-0.2.8 → pam_python-0.2.9}/pam/task_manager.py +0 -0
  53. {pam_python-0.2.8 → pam_python-0.2.9}/pam/temp_file_utils.py +0 -0
  54. {pam_python-0.2.8 → pam_python-0.2.9}/pam/templates/buildcmd/pamb +0 -0
  55. {pam_python-0.2.8 → pam_python-0.2.9}/pam/templates/buildcmd/pamb-base.sh +0 -0
  56. {pam_python-0.2.8 → pam_python-0.2.9}/pam/templates/init/REPORTS.md +0 -0
  57. {pam_python-0.2.8 → pam_python-0.2.9}/pam/templates/init/dockerignore.tmpl +0 -0
  58. {pam_python-0.2.8 → pam_python-0.2.9}/pam/templates/init/gitignore.tmpl +0 -0
  59. {pam_python-0.2.8 → pam_python-0.2.9}/pam/templates/init/main.tmpl +0 -0
  60. {pam_python-0.2.8 → pam_python-0.2.9}/pam/templates/init/pylintrc.tmpl +0 -0
  61. {pam_python-0.2.8 → pam_python-0.2.9}/pam/templates/init/run_unit_test.bat +0 -0
  62. {pam_python-0.2.8 → pam_python-0.2.9}/pam/templates/init/run_unit_test.ps1 +0 -0
  63. {pam_python-0.2.8 → pam_python-0.2.9}/pam/templates/init/run_unit_test.sh +0 -0
  64. {pam_python-0.2.8 → pam_python-0.2.9}/pam/templates/project/pip/Dockerfile +0 -0
  65. {pam_python-0.2.8 → pam_python-0.2.9}/pam/templates/project/pip/requirements.txt +0 -0
  66. {pam_python-0.2.8 → pam_python-0.2.9}/pam/templates/project/uv/Dockerfile +0 -0
  67. {pam_python-0.2.8 → pam_python-0.2.9}/pam/templates/project/uv/pyproject.toml +0 -0
  68. {pam_python-0.2.8 → pam_python-0.2.9}/pam/templates/project/uv/python-version +0 -0
  69. {pam_python-0.2.8 → pam_python-0.2.9}/pam/templates/service/common/service.yaml +0 -0
  70. {pam_python-0.2.8 → pam_python-0.2.9}/pam/templates/service/pandas/functions.tmpl +0 -0
  71. {pam_python-0.2.8 → pam_python-0.2.9}/pam/templates/service/pandas/service.test.tmpl +0 -0
  72. {pam_python-0.2.8 → pam_python-0.2.9}/pam/templates/service/pandas/service_class.tmpl +0 -0
  73. {pam_python-0.2.8 → pam_python-0.2.9}/pam/templates/service/polars/functions.tmpl +0 -0
  74. {pam_python-0.2.8 → pam_python-0.2.9}/pam/templates/service/polars/service.test.tmpl +0 -0
  75. {pam_python-0.2.8 → pam_python-0.2.9}/pam/templates/service/polars/service_class.tmpl +0 -0
  76. {pam_python-0.2.8 → pam_python-0.2.9}/pam/tester_task.py +0 -0
  77. {pam_python-0.2.8 → pam_python-0.2.9}/pam/utils.py +0 -0
  78. {pam_python-0.2.8 → pam_python-0.2.9}/pam_python.egg-info/SOURCES.txt +0 -0
  79. {pam_python-0.2.8 → pam_python-0.2.9}/pam_python.egg-info/dependency_links.txt +0 -0
  80. {pam_python-0.2.8 → pam_python-0.2.9}/pam_python.egg-info/entry_points.txt +0 -0
  81. {pam_python-0.2.8 → pam_python-0.2.9}/pam_python.egg-info/requires.txt +0 -0
  82. {pam_python-0.2.8 → pam_python-0.2.9}/pam_python.egg-info/top_level.txt +0 -0
  83. {pam_python-0.2.8 → pam_python-0.2.9}/pyproject.toml +0 -0
  84. {pam_python-0.2.8 → pam_python-0.2.9}/setup.cfg +0 -0
  85. {pam_python-0.2.8 → pam_python-0.2.9}/tests/test_api.py +0 -0
  86. {pam_python-0.2.8 → pam_python-0.2.9}/tests/test_cli_templates.py +0 -0
  87. {pam_python-0.2.8 → pam_python-0.2.9}/tests/test_data_error_v2.py +0 -0
  88. {pam_python-0.2.8 → pam_python-0.2.9}/tests/test_data_timeout_v2.py +0 -0
  89. {pam_python-0.2.8 → pam_python-0.2.9}/tests/test_range_transform.py +0 -0
  90. {pam_python-0.2.8 → pam_python-0.2.9}/tests/test_report_store.py +0 -0
  91. {pam_python-0.2.8 → pam_python-0.2.9}/tests/test_request_data_v2.py +0 -0
  92. {pam_python-0.2.8 → pam_python-0.2.9}/tests/test_result_batch_uploader.py +0 -0
  93. {pam_python-0.2.8 → pam_python-0.2.9}/tests/test_service_dataframe_output.py +0 -0
  94. {pam_python-0.2.8 → pam_python-0.2.9}/tests/test_sqlite_download_v2.py +0 -0
  95. {pam_python-0.2.8 → pam_python-0.2.9}/tests/test_temp_file_utils.py +0 -0
  96. {pam_python-0.2.8 → pam_python-0.2.9}/tests/test_typed_area_report.py +0 -0
  97. {pam_python-0.2.8 → pam_python-0.2.9}/tests/test_typed_bar_report.py +0 -0
  98. {pam_python-0.2.8 → pam_python-0.2.9}/tests/test_typed_box_plot_report.py +0 -0
  99. {pam_python-0.2.8 → pam_python-0.2.9}/tests/test_typed_bubble_report.py +0 -0
  100. {pam_python-0.2.8 → pam_python-0.2.9}/tests/test_typed_candlestick_report.py +0 -0
  101. {pam_python-0.2.8 → pam_python-0.2.9}/tests/test_typed_donut_report.py +0 -0
  102. {pam_python-0.2.8 → pam_python-0.2.9}/tests/test_typed_heatmap_report.py +0 -0
  103. {pam_python-0.2.8 → pam_python-0.2.9}/tests/test_typed_line_report.py +0 -0
  104. {pam_python-0.2.8 → pam_python-0.2.9}/tests/test_typed_metric_report.py +0 -0
  105. {pam_python-0.2.8 → pam_python-0.2.9}/tests/test_typed_pie_report.py +0 -0
  106. {pam_python-0.2.8 → pam_python-0.2.9}/tests/test_typed_polar_area_report.py +0 -0
  107. {pam_python-0.2.8 → pam_python-0.2.9}/tests/test_typed_radar_report.py +0 -0
  108. {pam_python-0.2.8 → pam_python-0.2.9}/tests/test_typed_radial_bar_report.py +0 -0
  109. {pam_python-0.2.8 → pam_python-0.2.9}/tests/test_typed_range_area_report.py +0 -0
  110. {pam_python-0.2.8 → pam_python-0.2.9}/tests/test_typed_range_bar_report.py +0 -0
  111. {pam_python-0.2.8 → pam_python-0.2.9}/tests/test_typed_scatter_report.py +0 -0
  112. {pam_python-0.2.8 → pam_python-0.2.9}/tests/test_typed_table_report.py +0 -0
  113. {pam_python-0.2.8 → pam_python-0.2.9}/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.8
3
+ Version: 0.2.9
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.8"
3
+ __version__ = "0.2.9"
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import csv
4
4
  from dataclasses import dataclass
5
- from datetime import datetime
5
+ from datetime import datetime, timezone
6
6
  from hashlib import sha256
7
7
  import json
8
8
  from pathlib import Path
@@ -15,6 +15,7 @@ V2_PROTOCOL_VERSION = "data-plugin-input/v2"
15
15
  V2_MANIFEST_FILE_NAME = "manifest.json"
16
16
  V2_MAX_DATASETS = 5
17
17
  V2_POPULATION_MODES = {"contacts", "event_any", "event_all"}
18
+ PAM_TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S"
18
19
 
19
20
 
20
21
  @dataclass(frozen=True)
@@ -364,6 +365,11 @@ def _required_optional_string(source: dict[str, Any], key: str) -> str:
364
365
  return value
365
366
 
366
367
 
368
+ def _parse_pam_timestamp(value: str) -> datetime:
369
+ """Parse PAM's suffix-free timestamp as UTC+0 without shifting it."""
370
+ return datetime.strptime(value, PAM_TIMESTAMP_FORMAT).replace(tzinfo=timezone.utc)
371
+
372
+
367
373
  def _parse_time_window(raw_time_window: Any) -> DataInputV2TimeWindow | None:
368
374
  if raw_time_window is None:
369
375
  return None
@@ -374,11 +380,11 @@ def _parse_time_window(raw_time_window: Any) -> DataInputV2TimeWindow | None:
374
380
  start_inclusive = _required_bool(raw_time_window, "start_inclusive")
375
381
  end_inclusive = _required_bool(raw_time_window, "end_inclusive")
376
382
  try:
377
- start_time = datetime.fromisoformat(start.replace("Z", "+00:00"))
378
- end_time = datetime.fromisoformat(end.replace("Z", "+00:00"))
383
+ start_time = _parse_pam_timestamp(start)
384
+ end_time = _parse_pam_timestamp(end)
379
385
  except ValueError as error:
380
386
  raise ValueError("V2 manifest time_window timestamps are invalid") from error
381
- if start_time.tzinfo is None or end_time.tzinfo is None or start_time >= end_time:
387
+ if start_time >= end_time:
382
388
  raise ValueError("V2 manifest time_window order is invalid")
383
389
  if not start_inclusive or end_inclusive:
384
390
  raise ValueError("V2 manifest time_window must be half-open")
@@ -1,5 +1,13 @@
1
1
  # AGENT
2
2
 
3
+ ## PAM date and time contract
4
+
5
+ PAM timestamps are UTC+0 strings in the exact format `%Y-%m-%d %H:%M:%S`, for
6
+ example `2026-03-31 02:37:44`. The timezone suffix is intentionally omitted.
7
+ Interpret these values as UTC without shifting the clock value. Do not emit or
8
+ expect RFC3339, ISO-8601, `T`, `Z`, or explicit timezone suffixes in internal PAM
9
+ contracts.
10
+
3
11
  This document defines the **data plugin runtime specification** and execution contract so developers and agents can create services and understand runtime behavior inside the CDP environment.
4
12
 
5
13
  This is not a tutorial.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pam-python
3
- Version: 0.2.8
3
+ Version: 0.2.9
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
@@ -23,7 +23,7 @@ class DataInputV2Test(unittest.TestCase):
23
23
  TempfileUtils.temp_datasource_path = str(self.temp_dir / "data_sources")
24
24
  self.population_path = self._write_file(
25
25
  "population.csv",
26
- b"id,contact_created_at\ncontact-1,2025-01-01T00:00:00Z\n",
26
+ b"id,contact_created_at\ncontact-1,2025-01-01 00:00:00\n",
27
27
  )
28
28
  self.dataset_path = self._write_file(
29
29
  "dataset_001_demographic_part_0001.csv",
@@ -255,8 +255,8 @@ class DataInputV2Test(unittest.TestCase):
255
255
  manifest["datasets"][0]["type"] = "event"
256
256
  manifest["datasets"][0]["event_names"] = ["purchase"]
257
257
  manifest["time_window"] = {
258
- "start": "2025-08-14T07:00:00Z",
259
- "end": "2026-08-14T07:00:00Z",
258
+ "start": "2025-08-14 07:00:00",
259
+ "end": "2026-08-14 07:00:00",
260
260
  "start_inclusive": True,
261
261
  "end_inclusive": False,
262
262
  }
@@ -268,8 +268,8 @@ class DataInputV2Test(unittest.TestCase):
268
268
 
269
269
  self.assertEqual("event_all", data_input.population_mode)
270
270
  self.assertIsNotNone(data_input.time_window)
271
- self.assertEqual("2025-08-14T07:00:00Z", data_input.time_window.start)
272
- self.assertEqual("2026-08-14T07:00:00Z", data_input.time_window.end)
271
+ self.assertEqual("2025-08-14 07:00:00", data_input.time_window.start)
272
+ self.assertEqual("2026-08-14 07:00:00", data_input.time_window.end)
273
273
  self.assertTrue(data_input.time_window.start_inclusive)
274
274
  self.assertFalse(data_input.time_window.end_inclusive)
275
275
 
@@ -299,8 +299,8 @@ class DataInputV2Test(unittest.TestCase):
299
299
  manifest["datasets"][0]["type"] = "event"
300
300
  manifest["datasets"][0]["event_names"] = ["purchase"]
301
301
  manifest["time_window"] = {
302
- "start": "2025-08-14T07:00:00Z",
303
- "end": "2026-08-14T07:00:00Z",
302
+ "start": "2025-08-14 07:00:00",
303
+ "end": "2026-08-14 07:00:00",
304
304
  "start_inclusive": True,
305
305
  "end_inclusive": True,
306
306
  }
@@ -311,6 +311,23 @@ class DataInputV2Test(unittest.TestCase):
311
311
  [str(manifest_path), str(self.population_path), str(self.dataset_path)]
312
312
  )
313
313
 
314
+ def test_parse_rejects_rfc3339_event_time_window(self):
315
+ manifest = self._valid_manifest()
316
+ manifest["datasets"][0]["type"] = "event"
317
+ manifest["datasets"][0]["event_names"] = ["purchase"]
318
+ manifest["time_window"] = {
319
+ "start": "2025-08-14T07:00:00Z",
320
+ "end": "2026-08-14T07:00:00Z",
321
+ "start_inclusive": True,
322
+ "end_inclusive": False,
323
+ }
324
+ manifest_path = self._write_manifest(manifest)
325
+
326
+ with self.assertRaisesRegex(ValueError, "timestamps are invalid"):
327
+ parse_data_input_v2(
328
+ [str(manifest_path), str(self.population_path), str(self.dataset_path)]
329
+ )
330
+
314
331
  def test_parse_rejects_event_population_without_event_dataset(self):
315
332
  manifest = self._valid_manifest()
316
333
  manifest["population"]["mode"] = "event_any"
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