pam-python 0.2.15__tar.gz → 0.2.16__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 (119) hide show
  1. {pam_python-0.2.15/pam_python.egg-info → pam_python-0.2.16}/PKG-INFO +1 -1
  2. {pam_python-0.2.15 → pam_python-0.2.16}/pam/__init__.py +1 -1
  3. {pam_python-0.2.15 → pam_python-0.2.16}/pam/task_manager.py +20 -5
  4. {pam_python-0.2.15 → pam_python-0.2.16/pam_python.egg-info}/PKG-INFO +1 -1
  5. {pam_python-0.2.15 → pam_python-0.2.16}/pam_python.egg-info/SOURCES.txt +1 -0
  6. {pam_python-0.2.15 → pam_python-0.2.16}/tests/test_data_error_v2.py +44 -0
  7. {pam_python-0.2.15 → pam_python-0.2.16}/tests/test_report_store.py +3 -1
  8. pam_python-0.2.16/tests/test_task_manager_upload_token.py +61 -0
  9. {pam_python-0.2.15 → pam_python-0.2.16}/LICENSE.txt +0 -0
  10. {pam_python-0.2.15 → pam_python-0.2.16}/README.md +0 -0
  11. {pam_python-0.2.15 → pam_python-0.2.16}/pam/api.py +0 -0
  12. {pam_python-0.2.15 → pam_python-0.2.16}/pam/cli.py +0 -0
  13. {pam_python-0.2.15 → pam_python-0.2.16}/pam/datetime_utils.py +0 -0
  14. {pam_python-0.2.15 → pam_python-0.2.16}/pam/interface_task_manager.py +0 -0
  15. {pam_python-0.2.15 → pam_python-0.2.16}/pam/logger.py +0 -0
  16. {pam_python-0.2.15 → pam_python-0.2.16}/pam/models/__init__.py +0 -0
  17. {pam_python-0.2.15 → pam_python-0.2.16}/pam/models/data_input_v2.py +0 -0
  18. {pam_python-0.2.15 → pam_python-0.2.16}/pam/models/request_command.py +0 -0
  19. {pam_python-0.2.15 → pam_python-0.2.16}/pam/models/sqlite_download.py +0 -0
  20. {pam_python-0.2.15 → pam_python-0.2.16}/pam/reports/__init__.py +0 -0
  21. {pam_python-0.2.15 → pam_python-0.2.16}/pam/reports/area.py +0 -0
  22. {pam_python-0.2.15 → pam_python-0.2.16}/pam/reports/axis.py +0 -0
  23. {pam_python-0.2.15 → pam_python-0.2.16}/pam/reports/bar.py +0 -0
  24. {pam_python-0.2.15 → pam_python-0.2.16}/pam/reports/box_plot.py +0 -0
  25. {pam_python-0.2.15 → pam_python-0.2.16}/pam/reports/bubble.py +0 -0
  26. {pam_python-0.2.15 → pam_python-0.2.16}/pam/reports/candlestick.py +0 -0
  27. {pam_python-0.2.15 → pam_python-0.2.16}/pam/reports/collection.py +0 -0
  28. {pam_python-0.2.15 → pam_python-0.2.16}/pam/reports/definition.py +0 -0
  29. {pam_python-0.2.15 → pam_python-0.2.16}/pam/reports/donut.py +0 -0
  30. {pam_python-0.2.15 → pam_python-0.2.16}/pam/reports/errors.py +0 -0
  31. {pam_python-0.2.15 → pam_python-0.2.16}/pam/reports/fields.py +0 -0
  32. {pam_python-0.2.15 → pam_python-0.2.16}/pam/reports/heatmap.py +0 -0
  33. {pam_python-0.2.15 → pam_python-0.2.16}/pam/reports/line.py +0 -0
  34. {pam_python-0.2.15 → pam_python-0.2.16}/pam/reports/manager.py +0 -0
  35. {pam_python-0.2.15 → pam_python-0.2.16}/pam/reports/metric.py +0 -0
  36. {pam_python-0.2.15 → pam_python-0.2.16}/pam/reports/pie.py +0 -0
  37. {pam_python-0.2.15 → pam_python-0.2.16}/pam/reports/polar_area.py +0 -0
  38. {pam_python-0.2.15 → pam_python-0.2.16}/pam/reports/radar.py +0 -0
  39. {pam_python-0.2.15 → pam_python-0.2.16}/pam/reports/radial_bar.py +0 -0
  40. {pam_python-0.2.15 → pam_python-0.2.16}/pam/reports/range_area.py +0 -0
  41. {pam_python-0.2.15 → pam_python-0.2.16}/pam/reports/range_bar.py +0 -0
  42. {pam_python-0.2.15 → pam_python-0.2.16}/pam/reports/range_transform.py +0 -0
  43. {pam_python-0.2.15 → pam_python-0.2.16}/pam/reports/record.py +0 -0
  44. {pam_python-0.2.15 → pam_python-0.2.16}/pam/reports/report.py +0 -0
  45. {pam_python-0.2.15 → pam_python-0.2.16}/pam/reports/scatter.py +0 -0
  46. {pam_python-0.2.15 → pam_python-0.2.16}/pam/reports/schema.py +0 -0
  47. {pam_python-0.2.15 → pam_python-0.2.16}/pam/reports/store.py +0 -0
  48. {pam_python-0.2.15 → pam_python-0.2.16}/pam/reports/table.py +0 -0
  49. {pam_python-0.2.15 → pam_python-0.2.16}/pam/reports/temporal.py +0 -0
  50. {pam_python-0.2.15 → pam_python-0.2.16}/pam/reports/transport.py +0 -0
  51. {pam_python-0.2.15 → pam_python-0.2.16}/pam/reports/treemap.py +0 -0
  52. {pam_python-0.2.15 → pam_python-0.2.16}/pam/request_file_format.py +0 -0
  53. {pam_python-0.2.15 → pam_python-0.2.16}/pam/result_batch_uploader.py +0 -0
  54. {pam_python-0.2.15 → pam_python-0.2.16}/pam/server.py +0 -0
  55. {pam_python-0.2.15 → pam_python-0.2.16}/pam/service.py +0 -0
  56. {pam_python-0.2.15 → pam_python-0.2.16}/pam/sqlite.py +0 -0
  57. {pam_python-0.2.15 → pam_python-0.2.16}/pam/temp_file_utils.py +0 -0
  58. {pam_python-0.2.15 → pam_python-0.2.16}/pam/templates/buildcmd/pamb +0 -0
  59. {pam_python-0.2.15 → pam_python-0.2.16}/pam/templates/buildcmd/pamb-base.sh +0 -0
  60. {pam_python-0.2.15 → pam_python-0.2.16}/pam/templates/init/AGENT.md +0 -0
  61. {pam_python-0.2.15 → pam_python-0.2.16}/pam/templates/init/REPORTS-th.md +0 -0
  62. {pam_python-0.2.15 → pam_python-0.2.16}/pam/templates/init/REPORTS.md +0 -0
  63. {pam_python-0.2.15 → pam_python-0.2.16}/pam/templates/init/dockerignore.tmpl +0 -0
  64. {pam_python-0.2.15 → pam_python-0.2.16}/pam/templates/init/gitignore.tmpl +0 -0
  65. {pam_python-0.2.15 → pam_python-0.2.16}/pam/templates/init/main.tmpl +0 -0
  66. {pam_python-0.2.15 → pam_python-0.2.16}/pam/templates/init/pylintrc.tmpl +0 -0
  67. {pam_python-0.2.15 → pam_python-0.2.16}/pam/templates/init/run_unit_test.bat +0 -0
  68. {pam_python-0.2.15 → pam_python-0.2.16}/pam/templates/init/run_unit_test.ps1 +0 -0
  69. {pam_python-0.2.15 → pam_python-0.2.16}/pam/templates/init/run_unit_test.sh +0 -0
  70. {pam_python-0.2.15 → pam_python-0.2.16}/pam/templates/project/pip/Dockerfile +0 -0
  71. {pam_python-0.2.15 → pam_python-0.2.16}/pam/templates/project/pip/requirements.txt +0 -0
  72. {pam_python-0.2.15 → pam_python-0.2.16}/pam/templates/project/uv/Dockerfile +0 -0
  73. {pam_python-0.2.15 → pam_python-0.2.16}/pam/templates/project/uv/pyproject.toml +0 -0
  74. {pam_python-0.2.15 → pam_python-0.2.16}/pam/templates/project/uv/python-version +0 -0
  75. {pam_python-0.2.15 → pam_python-0.2.16}/pam/templates/service/common/service.yaml +0 -0
  76. {pam_python-0.2.15 → pam_python-0.2.16}/pam/templates/service/pandas/functions.tmpl +0 -0
  77. {pam_python-0.2.15 → pam_python-0.2.16}/pam/templates/service/pandas/service.test.tmpl +0 -0
  78. {pam_python-0.2.15 → pam_python-0.2.16}/pam/templates/service/pandas/service_class.tmpl +0 -0
  79. {pam_python-0.2.15 → pam_python-0.2.16}/pam/templates/service/polars/functions.tmpl +0 -0
  80. {pam_python-0.2.15 → pam_python-0.2.16}/pam/templates/service/polars/service.test.tmpl +0 -0
  81. {pam_python-0.2.15 → pam_python-0.2.16}/pam/templates/service/polars/service_class.tmpl +0 -0
  82. {pam_python-0.2.15 → pam_python-0.2.16}/pam/tester_task.py +0 -0
  83. {pam_python-0.2.15 → pam_python-0.2.16}/pam/utils.py +0 -0
  84. {pam_python-0.2.15 → pam_python-0.2.16}/pam_python.egg-info/dependency_links.txt +0 -0
  85. {pam_python-0.2.15 → pam_python-0.2.16}/pam_python.egg-info/entry_points.txt +0 -0
  86. {pam_python-0.2.15 → pam_python-0.2.16}/pam_python.egg-info/requires.txt +0 -0
  87. {pam_python-0.2.15 → pam_python-0.2.16}/pam_python.egg-info/top_level.txt +0 -0
  88. {pam_python-0.2.15 → pam_python-0.2.16}/pyproject.toml +0 -0
  89. {pam_python-0.2.15 → pam_python-0.2.16}/setup.cfg +0 -0
  90. {pam_python-0.2.15 → pam_python-0.2.16}/tests/test_api.py +0 -0
  91. {pam_python-0.2.15 → pam_python-0.2.16}/tests/test_cli_templates.py +0 -0
  92. {pam_python-0.2.15 → pam_python-0.2.16}/tests/test_data_input_v2.py +0 -0
  93. {pam_python-0.2.15 → pam_python-0.2.16}/tests/test_data_timeout_v2.py +0 -0
  94. {pam_python-0.2.15 → pam_python-0.2.16}/tests/test_datetime_utils.py +0 -0
  95. {pam_python-0.2.15 → pam_python-0.2.16}/tests/test_range_transform.py +0 -0
  96. {pam_python-0.2.15 → pam_python-0.2.16}/tests/test_request_data_v2.py +0 -0
  97. {pam_python-0.2.15 → pam_python-0.2.16}/tests/test_result_batch_uploader.py +0 -0
  98. {pam_python-0.2.15 → pam_python-0.2.16}/tests/test_service_dataframe_output.py +0 -0
  99. {pam_python-0.2.15 → pam_python-0.2.16}/tests/test_sqlite_download_v2.py +0 -0
  100. {pam_python-0.2.15 → pam_python-0.2.16}/tests/test_temp_file_utils.py +0 -0
  101. {pam_python-0.2.15 → pam_python-0.2.16}/tests/test_temporal_mode.py +0 -0
  102. {pam_python-0.2.15 → pam_python-0.2.16}/tests/test_typed_area_report.py +0 -0
  103. {pam_python-0.2.15 → pam_python-0.2.16}/tests/test_typed_bar_report.py +0 -0
  104. {pam_python-0.2.15 → pam_python-0.2.16}/tests/test_typed_box_plot_report.py +0 -0
  105. {pam_python-0.2.15 → pam_python-0.2.16}/tests/test_typed_bubble_report.py +0 -0
  106. {pam_python-0.2.15 → pam_python-0.2.16}/tests/test_typed_candlestick_report.py +0 -0
  107. {pam_python-0.2.15 → pam_python-0.2.16}/tests/test_typed_donut_report.py +0 -0
  108. {pam_python-0.2.15 → pam_python-0.2.16}/tests/test_typed_heatmap_report.py +0 -0
  109. {pam_python-0.2.15 → pam_python-0.2.16}/tests/test_typed_line_report.py +0 -0
  110. {pam_python-0.2.15 → pam_python-0.2.16}/tests/test_typed_metric_report.py +0 -0
  111. {pam_python-0.2.15 → pam_python-0.2.16}/tests/test_typed_pie_report.py +0 -0
  112. {pam_python-0.2.15 → pam_python-0.2.16}/tests/test_typed_polar_area_report.py +0 -0
  113. {pam_python-0.2.15 → pam_python-0.2.16}/tests/test_typed_radar_report.py +0 -0
  114. {pam_python-0.2.15 → pam_python-0.2.16}/tests/test_typed_radial_bar_report.py +0 -0
  115. {pam_python-0.2.15 → pam_python-0.2.16}/tests/test_typed_range_area_report.py +0 -0
  116. {pam_python-0.2.15 → pam_python-0.2.16}/tests/test_typed_range_bar_report.py +0 -0
  117. {pam_python-0.2.15 → pam_python-0.2.16}/tests/test_typed_scatter_report.py +0 -0
  118. {pam_python-0.2.15 → pam_python-0.2.16}/tests/test_typed_table_report.py +0 -0
  119. {pam_python-0.2.15 → pam_python-0.2.16}/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.15
3
+ Version: 0.2.16
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.15"
3
+ __version__ = "0.2.16"
@@ -258,8 +258,16 @@ class TaskManager(ITaskManager):
258
258
  """
259
259
  log(f"Start Service: {service_name}, Token: {req.token}")
260
260
  service_instance = service_class(self, req)
261
- service_instance.on_start()
262
261
  self._add_service(req.token, service_instance)
262
+ try:
263
+ # Register the service before on_start. A service commonly requests
264
+ # its first page from on_start, and PAM can deliver that page before
265
+ # on_start returns. Registering afterwards loses that callback and
266
+ # also lets on_dataset_input cancel the V2 wait timer permanently.
267
+ service_instance.on_start()
268
+ except BaseException:
269
+ self._remove_service(req.token)
270
+ raise
263
271
 
264
272
  def terminate_service(self, token):
265
273
  """
@@ -419,9 +427,8 @@ class TaskManager(ITaskManager):
419
427
  from its worker, so failures must propagate for retry handling.
420
428
  """
421
429
  endpoint = service.request.response_api
422
- payload = None
430
+ payload = {"token": service.request.token}
423
431
  if options is not None:
424
- payload = {}
425
432
  if "is_realtime" in options:
426
433
  is_realtime = options.get("is_realtime")
427
434
  payload["is_realtime"] = str(is_realtime).lower() if isinstance(is_realtime, bool) else is_realtime
@@ -464,7 +471,11 @@ class TaskManager(ITaskManager):
464
471
 
465
472
  def upload_wrapper():
466
473
  """Wrapper for the report upload to handle response logging."""
467
- response = self.api.http_upload(endpoint, file_path)
474
+ response = self.api.http_upload(
475
+ endpoint,
476
+ file_path,
477
+ {"token": service.request.token},
478
+ )
468
479
  handle_report_response(response)
469
480
 
470
481
  log(f"Uploading Report to: {endpoint}")
@@ -501,7 +512,11 @@ class TaskManager(ITaskManager):
501
512
  column_name: json.dumps(pointer, ensure_ascii=False, sort_keys=True),
502
513
  })
503
514
 
504
- response = self.api.http_upload(service.request.response_api, csv_path)
515
+ response = self.api.http_upload(
516
+ service.request.response_api,
517
+ csv_path,
518
+ {"token": service.request.token},
519
+ )
505
520
  if response is None:
506
521
  return False
507
522
  success = response.ok
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pam-python
3
- Version: 0.2.15
3
+ Version: 0.2.16
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
@@ -94,6 +94,7 @@ tests/test_request_data_v2.py
94
94
  tests/test_result_batch_uploader.py
95
95
  tests/test_service_dataframe_output.py
96
96
  tests/test_sqlite_download_v2.py
97
+ tests/test_task_manager_upload_token.py
97
98
  tests/test_temp_file_utils.py
98
99
  tests/test_temporal_mode.py
99
100
  tests/test_typed_area_report.py
@@ -11,6 +11,50 @@ from pam.task_manager import TaskManager
11
11
 
12
12
 
13
13
  class DataErrorV2Test(unittest.TestCase):
14
+ def test_start_service_registers_before_immediate_data_callback(self):
15
+ manager = TaskManager.__new__(TaskManager)
16
+ manager.thread_lock = threading.Lock()
17
+ manager.services = {}
18
+ manager.data_wait_timers = {}
19
+ request = Mock(token="run-1")
20
+
21
+ class ImmediateCallbackService:
22
+ def __init__(self, task_manager, req):
23
+ self.task_manager = task_manager
24
+ self.request = req
25
+ self.received = []
26
+
27
+ def on_start(self):
28
+ self.task_manager.on_dataset_input(self.request)
29
+
30
+ def on_data_input(self, req):
31
+ self.received.append(req)
32
+
33
+ manager.start_service(ImmediateCallbackService, request, "test-service")
34
+
35
+ service = manager.services[request.token].service
36
+ self.assertEqual([request], service.received)
37
+
38
+ def test_start_service_removes_registration_when_on_start_fails(self):
39
+ manager = TaskManager.__new__(TaskManager)
40
+ manager.thread_lock = threading.Lock()
41
+ manager.services = {}
42
+ manager.data_wait_timers = {}
43
+
44
+ service = Mock()
45
+ service.on_start.side_effect = RuntimeError("start failed")
46
+ service.request = Mock(service_name="test-service", token="run-1")
47
+ service.reports = Mock()
48
+ service_class = Mock(return_value=service)
49
+ request = Mock(token="run-1")
50
+
51
+ with self.assertRaisesRegex(RuntimeError, "start failed"):
52
+ manager.start_service(service_class, request, "test-service")
53
+
54
+ self.assertNotIn(request.token, manager.services)
55
+ service.on_destroy.assert_called_once_with()
56
+ service.reports.close.assert_called_once_with()
57
+
14
58
  def test_request_command_parses_dataset_error_without_zip(self):
15
59
  app = Flask(__name__)
16
60
  with app.test_request_context(
@@ -496,8 +496,9 @@ class ReportStoreTransportTest(unittest.TestCase):
496
496
  captured = {}
497
497
  response = Mock(ok=True)
498
498
 
499
- def capture_upload(endpoint, file_path):
499
+ def capture_upload(endpoint, file_path, payload):
500
500
  captured["endpoint"] = endpoint
501
+ captured["payload"] = payload
501
502
  with open(file_path, encoding="utf-8", newline="") as input_file:
502
503
  captured["row"] = next(csv.DictReader(input_file))
503
504
  return response
@@ -535,6 +536,7 @@ class ReportStoreTransportTest(unittest.TestCase):
535
536
 
536
537
  self.assertTrue(published)
537
538
  self.assertEqual(request.response_api, captured["endpoint"])
539
+ self.assertEqual({"token": "token-1"}, captured["payload"])
538
540
  self.assertEqual("adapter-1", captured["row"]["customer"])
539
541
  self.assertEqual("Customer Report Group", captured["row"]["_tracker_name"])
540
542
  self.assertEqual(
@@ -0,0 +1,61 @@
1
+ import tempfile
2
+ import unittest
3
+ from pathlib import Path
4
+ from types import SimpleNamespace
5
+ from unittest.mock import Mock
6
+
7
+ from pam.task_manager import TaskManager
8
+
9
+
10
+ class TaskManagerUploadTokenTest(unittest.TestCase):
11
+ def setUp(self):
12
+ self.manager = TaskManager(SimpleNamespace())
13
+ self.manager.api = Mock()
14
+ self.service = SimpleNamespace(
15
+ request=SimpleNamespace(
16
+ response_api="http://pam/api/dataadapter/adapter-1/response",
17
+ token="token-1",
18
+ service_name="service-1",
19
+ ),
20
+ )
21
+ self.response = Mock(ok=True)
22
+ self.response.json.return_value = {"code": "SUCCESS"}
23
+ self.manager.api.http_upload.return_value = self.response
24
+
25
+ def test_result_upload_always_sends_token_with_options(self):
26
+ with tempfile.TemporaryDirectory() as directory:
27
+ file_path = Path(directory) / "result.csv"
28
+ file_path.write_text("id\n1\n", encoding="utf-8")
29
+
30
+ self.manager.service_upload_result(
31
+ self.service,
32
+ str(file_path),
33
+ {"is_realtime": True, "is_priority": False},
34
+ )
35
+
36
+ self.manager.api.http_upload.assert_called_once_with(
37
+ self.service.request.response_api,
38
+ str(file_path),
39
+ {
40
+ "token": "token-1",
41
+ "is_realtime": "true",
42
+ "is_priority": "false",
43
+ },
44
+ )
45
+
46
+ def test_result_upload_sends_token_without_options(self):
47
+ with tempfile.TemporaryDirectory() as directory:
48
+ file_path = Path(directory) / "result.csv"
49
+ file_path.write_text("id\n1\n", encoding="utf-8")
50
+
51
+ self.manager.service_upload_result(self.service, str(file_path))
52
+
53
+ self.manager.api.http_upload.assert_called_once_with(
54
+ self.service.request.response_api,
55
+ str(file_path),
56
+ {"token": "token-1"},
57
+ )
58
+
59
+
60
+ if __name__ == "__main__":
61
+ unittest.main()
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