pam-python 0.1.45__tar.gz → 0.2.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 (121) hide show
  1. pam_python-0.2.1/PKG-INFO +313 -0
  2. pam_python-0.2.1/README.md +293 -0
  3. pam_python-0.2.1/pam/__init__.py +3 -0
  4. pam_python-0.2.1/pam/api.py +188 -0
  5. pam_python-0.2.1/pam/cli.py +354 -0
  6. {pam_python-0.1.45 → pam_python-0.2.1}/pam/interface_task_manager.py +33 -2
  7. {pam_python-0.1.45 → pam_python-0.2.1}/pam/models/request_command.py +15 -2
  8. pam_python-0.2.1/pam/models/sqlite_download.py +26 -0
  9. pam_python-0.2.1/pam/reports/__init__.py +73 -0
  10. pam_python-0.2.1/pam/reports/area.py +97 -0
  11. pam_python-0.2.1/pam/reports/axis.py +50 -0
  12. pam_python-0.2.1/pam/reports/bar.py +154 -0
  13. pam_python-0.2.1/pam/reports/box_plot.py +119 -0
  14. pam_python-0.2.1/pam/reports/bubble.py +113 -0
  15. pam_python-0.2.1/pam/reports/candlestick.py +132 -0
  16. pam_python-0.2.1/pam/reports/collection.py +198 -0
  17. pam_python-0.2.1/pam/reports/definition.py +82 -0
  18. pam_python-0.2.1/pam/reports/donut.py +111 -0
  19. pam_python-0.2.1/pam/reports/errors.py +33 -0
  20. pam_python-0.2.1/pam/reports/fields.py +144 -0
  21. pam_python-0.2.1/pam/reports/heatmap.py +88 -0
  22. pam_python-0.2.1/pam/reports/line.py +90 -0
  23. pam_python-0.2.1/pam/reports/manager.py +148 -0
  24. pam_python-0.2.1/pam/reports/metric.py +139 -0
  25. pam_python-0.2.1/pam/reports/pie.py +111 -0
  26. pam_python-0.2.1/pam/reports/polar_area.py +112 -0
  27. pam_python-0.2.1/pam/reports/radar.py +115 -0
  28. pam_python-0.2.1/pam/reports/radial_bar.py +135 -0
  29. pam_python-0.2.1/pam/reports/range_area.py +121 -0
  30. pam_python-0.2.1/pam/reports/range_bar.py +121 -0
  31. pam_python-0.2.1/pam/reports/range_transform.py +183 -0
  32. pam_python-0.2.1/pam/reports/record.py +138 -0
  33. pam_python-0.2.1/pam/reports/report.py +35 -0
  34. pam_python-0.2.1/pam/reports/scatter.py +121 -0
  35. pam_python-0.2.1/pam/reports/schema.py +47 -0
  36. pam_python-0.2.1/pam/reports/store.py +694 -0
  37. pam_python-0.2.1/pam/reports/table.py +234 -0
  38. pam_python-0.2.1/pam/reports/transport.py +57 -0
  39. pam_python-0.2.1/pam/reports/treemap.py +121 -0
  40. pam_python-0.2.1/pam/request_file_format.py +14 -0
  41. pam_python-0.2.1/pam/result_batch_uploader.py +227 -0
  42. {pam_python-0.1.45 → pam_python-0.2.1}/pam/server.py +22 -2
  43. {pam_python-0.1.45 → pam_python-0.2.1}/pam/service.py +42 -21
  44. {pam_python-0.1.45 → pam_python-0.2.1}/pam/task_manager.py +82 -3
  45. {pam_python-0.1.45 → pam_python-0.2.1}/pam/templates/init/AGENT.md +68 -70
  46. pam_python-0.2.1/pam/templates/init/REPORTS.md +1395 -0
  47. pam_python-0.2.1/pam/templates/init/dockerignore.tmpl +6 -0
  48. {pam_python-0.1.45 → pam_python-0.2.1}/pam/templates/init/main.tmpl +3 -3
  49. pam_python-0.2.1/pam/templates/project/pip/Dockerfile +16 -0
  50. {pam_python-0.1.45/pam/templates/init → pam_python-0.2.1/pam/templates/project/pip}/requirements.txt +0 -3
  51. pam_python-0.2.1/pam/templates/project/uv/Dockerfile +20 -0
  52. pam_python-0.2.1/pam/templates/project/uv/pyproject.toml +12 -0
  53. pam_python-0.2.1/pam/templates/project/uv/python-version +1 -0
  54. pam_python-0.2.1/pam/templates/service/pandas/functions.tmpl +13 -0
  55. pam_python-0.2.1/pam/templates/service/pandas/service.test.tmpl +122 -0
  56. pam_python-0.2.1/pam/templates/service/pandas/service_class.tmpl +65 -0
  57. pam_python-0.2.1/pam/templates/service/polars/functions.tmpl +11 -0
  58. pam_python-0.2.1/pam/templates/service/polars/service.test.tmpl +122 -0
  59. pam_python-0.2.1/pam/templates/service/polars/service_class.tmpl +59 -0
  60. {pam_python-0.1.45 → pam_python-0.2.1}/pam/tester_task.py +75 -9
  61. {pam_python-0.1.45 → pam_python-0.2.1}/pam/utils.py +3 -5
  62. pam_python-0.2.1/pam_python.egg-info/PKG-INFO +313 -0
  63. pam_python-0.2.1/pam_python.egg-info/SOURCES.txt +104 -0
  64. pam_python-0.2.1/pam_python.egg-info/requires.txt +3 -0
  65. pam_python-0.2.1/pyproject.toml +54 -0
  66. pam_python-0.2.1/tests/test_cli_templates.py +133 -0
  67. pam_python-0.2.1/tests/test_range_transform.py +172 -0
  68. pam_python-0.2.1/tests/test_report_store.py +496 -0
  69. pam_python-0.2.1/tests/test_result_batch_uploader.py +137 -0
  70. pam_python-0.2.1/tests/test_service_dataframe_output.py +41 -0
  71. pam_python-0.2.1/tests/test_sqlite_download_v2.py +196 -0
  72. pam_python-0.2.1/tests/test_typed_area_report.py +101 -0
  73. pam_python-0.2.1/tests/test_typed_bar_report.py +172 -0
  74. pam_python-0.2.1/tests/test_typed_box_plot_report.py +72 -0
  75. pam_python-0.2.1/tests/test_typed_bubble_report.py +61 -0
  76. pam_python-0.2.1/tests/test_typed_candlestick_report.py +77 -0
  77. pam_python-0.2.1/tests/test_typed_donut_report.py +140 -0
  78. pam_python-0.2.1/tests/test_typed_heatmap_report.py +56 -0
  79. pam_python-0.2.1/tests/test_typed_line_report.py +128 -0
  80. pam_python-0.2.1/tests/test_typed_metric_report.py +106 -0
  81. pam_python-0.2.1/tests/test_typed_pie_report.py +149 -0
  82. pam_python-0.2.1/tests/test_typed_polar_area_report.py +139 -0
  83. pam_python-0.2.1/tests/test_typed_radar_report.py +22 -0
  84. pam_python-0.2.1/tests/test_typed_radial_bar_report.py +77 -0
  85. pam_python-0.2.1/tests/test_typed_range_area_report.py +75 -0
  86. pam_python-0.2.1/tests/test_typed_range_bar_report.py +97 -0
  87. pam_python-0.2.1/tests/test_typed_scatter_report.py +131 -0
  88. pam_python-0.2.1/tests/test_typed_table_report.py +116 -0
  89. pam_python-0.2.1/tests/test_typed_treemap_report.py +19 -0
  90. pam_python-0.1.45/PKG-INFO +0 -259
  91. pam_python-0.1.45/README.md +0 -220
  92. pam_python-0.1.45/pam/api.py +0 -90
  93. pam_python-0.1.45/pam/cli.py +0 -208
  94. pam_python-0.1.45/pam/models/__init__.py +0 -0
  95. pam_python-0.1.45/pam/request_file_format.py +0 -5
  96. pam_python-0.1.45/pam/templates/docker/Dockerfile +0 -21
  97. pam_python-0.1.45/pam/templates/init/dockerignore.tmpl +0 -4
  98. pam_python-0.1.45/pam/templates/service/functions.tmpl +0 -38
  99. pam_python-0.1.45/pam/templates/service/service.test.tmpl +0 -116
  100. pam_python-0.1.45/pam/templates/service/service_class.tmpl +0 -77
  101. pam_python-0.1.45/pam_python.egg-info/PKG-INFO +0 -259
  102. pam_python-0.1.45/pam_python.egg-info/SOURCES.txt +0 -40
  103. pam_python-0.1.45/pam_python.egg-info/requires.txt +0 -10
  104. pam_python-0.1.45/setup.py +0 -44
  105. {pam_python-0.1.45 → pam_python-0.2.1}/LICENSE.txt +0 -0
  106. {pam_python-0.1.45 → pam_python-0.2.1}/pam/logger.py +0 -0
  107. {pam_python-0.1.45/pam → pam_python-0.2.1/pam/models}/__init__.py +0 -0
  108. {pam_python-0.1.45 → pam_python-0.2.1}/pam/sqlite.py +0 -0
  109. {pam_python-0.1.45 → pam_python-0.2.1}/pam/temp_file_utils.py +0 -0
  110. {pam_python-0.1.45 → pam_python-0.2.1}/pam/templates/buildcmd/pamb +0 -0
  111. {pam_python-0.1.45 → pam_python-0.2.1}/pam/templates/buildcmd/pamb-base.sh +0 -0
  112. {pam_python-0.1.45 → pam_python-0.2.1}/pam/templates/init/gitignore.tmpl +0 -0
  113. {pam_python-0.1.45 → pam_python-0.2.1}/pam/templates/init/pylintrc.tmpl +0 -0
  114. {pam_python-0.1.45 → pam_python-0.2.1}/pam/templates/init/run_unit_test.bat +0 -0
  115. {pam_python-0.1.45 → pam_python-0.2.1}/pam/templates/init/run_unit_test.ps1 +0 -0
  116. {pam_python-0.1.45 → pam_python-0.2.1}/pam/templates/init/run_unit_test.sh +0 -0
  117. {pam_python-0.1.45/pam/templates/service → pam_python-0.2.1/pam/templates/service/common}/service.yaml +1 -1
  118. {pam_python-0.1.45 → pam_python-0.2.1}/pam_python.egg-info/dependency_links.txt +0 -0
  119. {pam_python-0.1.45 → pam_python-0.2.1}/pam_python.egg-info/entry_points.txt +0 -0
  120. {pam_python-0.1.45 → pam_python-0.2.1}/pam_python.egg-info/top_level.txt +0 -0
  121. {pam_python-0.1.45 → pam_python-0.2.1}/setup.cfg +0 -0
@@ -0,0 +1,313 @@
1
+ Metadata-Version: 2.4
2
+ Name: pam-python
3
+ Version: 0.2.1
4
+ Summary: Pam Python Library
5
+ Author-email: Narongrit Kanhanoi <narongrit@pams.ai>
6
+ Project-URL: Homepage, https://github.com/heart/pam-python
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Programming Language :: Python :: 3.12
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Intended Audience :: Customer Service
12
+ Classifier: Topic :: Software Development :: Libraries
13
+ Requires-Python: <3.13,>=3.12
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE.txt
16
+ Requires-Dist: Flask>=3.0.2
17
+ Requires-Dist: requests>=2.32.3
18
+ Requires-Dist: PyYAML>=6.0.2
19
+ Dynamic: license-file
20
+
21
+ # pam-python-data-plugin-framework
22
+
23
+ This repository provides the `pam` CLI and runtime framework to build Data Plugin services for PAM Real CDP. It generates a ready-to-run project, standardizes service lifecycle, and handles common tasks like input handling, temp storage, uploads, and service monitoring.
24
+
25
+ This README is a practical, step-by-step guide you can follow to create and run a real service.
26
+
27
+ **What you get**
28
+
29
+ - CLI to initialize a project and scaffold services
30
+ - Service lifecycle contract (start, data input, upload, exit)
31
+ - Temp file and SQLite helpers
32
+ - Managed Report Store V2 with typed Table, Metric, and ApexCharts-compatible builders
33
+ - Named `ResultBatchUploader` streams for bounded DataFrame uploads
34
+ - A monitoring loop for service timeouts and periodic cleanup
35
+
36
+ ---
37
+
38
+ **Table of Contents**
39
+
40
+ 1. Prerequisites
41
+ 2. Install
42
+ 3. Initialize a Project
43
+ 4. Create a Service
44
+ 5. Understand the Lifecycle
45
+ 6. Using Temp Files Correctly
46
+ 7. Running the Server
47
+ 8. Testing a Service
48
+ 9. Configuration
49
+ 10. Project Structure
50
+ 11. Troubleshooting
51
+
52
+ ---
53
+
54
+ **Prerequisites**
55
+
56
+ - Python 3.12
57
+ - `uv` (recommended) or `pip`
58
+
59
+ ---
60
+
61
+ **Install**
62
+ Recommended installation:
63
+
64
+ ```bash
65
+ mkdir my_data_plugin
66
+ cd my_data_plugin
67
+ uv tool install pam-python
68
+ ```
69
+
70
+ Pip remains supported:
71
+
72
+ ```bash
73
+ python3 -m venv venv
74
+ source venv/bin/activate
75
+ python -m pip install pam-python
76
+ ```
77
+
78
+ ---
79
+
80
+ **Initialize a Project**
81
+ This creates a runnable project with templates, including `AGENT.md` for runtime
82
+ rules and `REPORTS.md` for the managed-report public API and copyable examples.
83
+
84
+ ```bash
85
+ pam init
86
+ ```
87
+
88
+ `pam init` asks you to select `uv` (default) or `pip`. A uv project contains
89
+ `pyproject.toml`, `.python-version`, and `uv.lock`; a pip project contains
90
+ `requirements.txt`. The two templates are independent and are never mixed.
91
+
92
+ Initialization only supports a new project directory and will not migrate or
93
+ overwrite an existing project.
94
+
95
+ ---
96
+
97
+ **Create a Service**
98
+
99
+ ```bash
100
+ pam new service rfm_segment
101
+ ```
102
+
103
+ The generator asks you to select Polars (default) or Pandas. Polars services use
104
+ lazy Parquet processing. Pandas services also receive Parquet and install
105
+ `pandas` plus `pyarrow`. The selected direct dependencies are added to the
106
+ project automatically.
107
+
108
+ This creates a new folder (e.g. `rfm_segment/`) with:
109
+
110
+ - a service class (`RfmSegmentSvc.py`)
111
+ - `functions.py` for your logic
112
+ - `service.yaml` for registration
113
+ - a test file
114
+
115
+ ---
116
+
117
+ **Understand the Lifecycle**
118
+ The runtime calls your service in two main phases.
119
+
120
+ 1. `on_start`
121
+
122
+ - Called once at the beginning
123
+ - Read parameters from `self.request.runtime_parameters`
124
+ - Should return quickly (start a thread for long work)
125
+
126
+ 2. `on_data_input`
127
+
128
+ - Called when CDP sends input files
129
+ - `req.input_files` contains ordered Parquet files
130
+ - `req.file_format` is the typed format sent by PAM
131
+ - Should also return quickly (use a thread if needed)
132
+
133
+ When your service is done:
134
+
135
+ - Upload ordinary CDP result rows through `ResultBatchUploader` or `_upload_result(...)`
136
+ - Publish managed reports through `self.reports`; do not build report JSON manually
137
+ - Call `self._exit()` to signal completion
138
+
139
+ ---
140
+
141
+ **Using Temp Files Correctly**
142
+ Temp storage is managed by the framework. Do not delete temp files manually.
143
+
144
+ Standard helpers:
145
+
146
+ - `TempfileUtils.get_temp_path_for_service(self, self.service_name)`
147
+ - `TempfileUtils.get_temp_file_name_for_service(self, self.service_name, prefix, extension)`
148
+
149
+ Notes:
150
+
151
+ - `get_temp_path_for_service(...)` returns a directory path without a trailing slash.
152
+ - The temp path includes date/service/token in this structure:
153
+ `TEMP_DATASOURCE_PATH/YYYY_MM_DD/<service>/<token>`
154
+
155
+ ---
156
+
157
+ **Uploading Results in Batches**
158
+ Polars templates pass `LazyFrame` results directly to `_upload_result(...)`, which
159
+ streams them to the CSV upload boundary. Do not collect or convert to Pandas first.
160
+
161
+ For eager Polars or Pandas DataFrames, use the batch uploader to handle chunking
162
+ and flushing automatically.
163
+
164
+ Recommended usage:
165
+
166
+ ```python
167
+ from pam.result_batch_uploader import ResultBatchUploader
168
+
169
+ batch_uploader = ResultBatchUploader(self, batch_size=50000)
170
+ batch_uploader.upload(df, name="main")
171
+ batch_uploader.flush()
172
+ status = batch_uploader.get_status()
173
+ ```
174
+
175
+ Notes:
176
+
177
+ - `request.runtime_parameters["batch_size"]` overrides the constructor default when valid.
178
+ - `name` separates result streams and enforces one stable column schema per stream.
179
+ - `options` may be passed to `upload(...)` and are forwarded to `_upload_result`.
180
+ - Complete batches upload immediately; `flush()` sends the remaining rows.
181
+ - `get_status()` returns buffered rows, uploaded rows, and uploaded batch counts per stream.
182
+ - `ResultBatchUploader` intentionally rejects Polars `LazyFrame`; upload it directly.
183
+
184
+ ---
185
+
186
+ **Managed Reports (Report Store V2)**
187
+
188
+ Managed reports are static facts stored in framework-owned SQLite. Plugin code
189
+ selects a typed builder, writes source-derived `DAY`, `MONTH`, or `NONE` facts, and
190
+ publishes only after the full calculation succeeds.
191
+
192
+ ```python
193
+ fresh = self.request.get_runtime_bool("fresh", default=False)
194
+ store = (
195
+ self.reports.create("reports.sqlite")
196
+ if fresh
197
+ else self.reports.open("reports.sqlite")
198
+ )
199
+
200
+ # Choose the builder agreed in the customer requirement.
201
+ # See generated REPORTS.md for every supported declaration and write API.
202
+ ```
203
+
204
+ Important rules:
205
+
206
+ - Use only typed builders documented in `REPORTS.md`; there is no raw-report API.
207
+ - `open()` resumes the remote file or creates it only after an authoritative 404.
208
+ - Network/5xx download failures never create a replacement store.
209
+ - Use `create()` only for a human-requested fresh restart.
210
+ - Delete a partition, write its recalculated facts, then call `store.publish()` once.
211
+ - Low-level `_request_sqlite`/`_upload_sqlite` remain for custom plugin state, not reports.
212
+
213
+ ---
214
+
215
+ **Running the Server**
216
+ The generated `main.py` runs the Flask server.
217
+
218
+ ```bash
219
+ uv run python main.py # uv project
220
+ python main.py # pip project
221
+ ```
222
+
223
+ By default it binds to `0.0.0.0:8000`. You can override with:
224
+
225
+ ```bash
226
+ export SERVER_HOST=0.0.0.0
227
+ export SERVER_PORT=8000
228
+ ```
229
+
230
+ ---
231
+
232
+ **Testing a Service**
233
+ For uv projects, use one cross-platform command:
234
+
235
+ ```bash
236
+ uv run python -m unittest discover -s rfm_segment -p "test_*.py"
237
+ ```
238
+
239
+ Pip projects include shell-specific scripts:
240
+
241
+ macOS/Linux:
242
+
243
+ ```bash
244
+ ./run_unit_test.sh rfm_segment
245
+ ```
246
+
247
+ Windows PowerShell:
248
+
249
+ ```powershell
250
+ .\run_unit_test.ps1 rfm_segment
251
+ ```
252
+
253
+ Windows Command Prompt:
254
+
255
+ ```bat
256
+ run_unit_test.bat rfm_segment
257
+ ```
258
+
259
+ Place custom tests in the service folder and name them `test_<service>.py`.
260
+
261
+ ---
262
+
263
+ **Configuration**
264
+ Environment variables you can set:
265
+
266
+ - `SERVER_HOST`
267
+ - `SERVER_PORT`
268
+ - `TEMP_BASE_PATH` (default `/app/data`)
269
+ - `TEMP_DATASOURCE_PATH` (default `/app/data/data_sources`)
270
+ - `TEMP_CLEAN_DAYS` (default `10`)
271
+ - `TEMP_CLEAN_INTERVAL_HOURS` (default `6`, set empty to disable periodic cleanup)
272
+
273
+ ---
274
+
275
+ **Project Structure**
276
+ After `pam init` and one service:
277
+
278
+ ```
279
+ .
280
+ ├── main.py
281
+ ├── AGENT.md
282
+ ├── REPORTS.md
283
+ ├── Dockerfile
284
+ ├── pyproject.toml
285
+ ├── uv.lock
286
+ ├── .python-version
287
+ ├── rfm_segment/
288
+ │ ├── RfmSegmentSvc.py
289
+ │ ├── functions.py
290
+ │ ├── service.yaml
291
+ │ └── test_rfm_segment.py
292
+ └── .pam-project
293
+ ```
294
+
295
+ ---
296
+
297
+ **Troubleshooting**
298
+
299
+ - If `pam` is missing, verify the uv tool installation or activate the pip virtualenv.
300
+ - If uv initialization fails, verify `uv` is on `PATH` and package indexes are reachable.
301
+ - If `pam new service` fails, confirm the service name is provided.
302
+ - If temp cleanup is too frequent or too slow, adjust `TEMP_CLEAN_INTERVAL_HOURS` and `TEMP_CLEAN_DAYS`.
303
+
304
+ ---
305
+
306
+ **Next Steps**
307
+
308
+ - Implement your logic in `functions.py`.
309
+ - Wire it into `on_start` and `on_data_input` in your service class.
310
+ - Use the temp utilities to write intermediate files.
311
+ - Keep Polars transformations lazy and upload the resulting `LazyFrame` directly.
312
+ - Use `ResultBatchUploader` only for eager Polars/Pandas frames that need row batching.
313
+ - Read `REPORTS.md` before implementing a managed report.
@@ -0,0 +1,293 @@
1
+ # pam-python-data-plugin-framework
2
+
3
+ This repository provides the `pam` CLI and runtime framework to build Data Plugin services for PAM Real CDP. It generates a ready-to-run project, standardizes service lifecycle, and handles common tasks like input handling, temp storage, uploads, and service monitoring.
4
+
5
+ This README is a practical, step-by-step guide you can follow to create and run a real service.
6
+
7
+ **What you get**
8
+
9
+ - CLI to initialize a project and scaffold services
10
+ - Service lifecycle contract (start, data input, upload, exit)
11
+ - Temp file and SQLite helpers
12
+ - Managed Report Store V2 with typed Table, Metric, and ApexCharts-compatible builders
13
+ - Named `ResultBatchUploader` streams for bounded DataFrame uploads
14
+ - A monitoring loop for service timeouts and periodic cleanup
15
+
16
+ ---
17
+
18
+ **Table of Contents**
19
+
20
+ 1. Prerequisites
21
+ 2. Install
22
+ 3. Initialize a Project
23
+ 4. Create a Service
24
+ 5. Understand the Lifecycle
25
+ 6. Using Temp Files Correctly
26
+ 7. Running the Server
27
+ 8. Testing a Service
28
+ 9. Configuration
29
+ 10. Project Structure
30
+ 11. Troubleshooting
31
+
32
+ ---
33
+
34
+ **Prerequisites**
35
+
36
+ - Python 3.12
37
+ - `uv` (recommended) or `pip`
38
+
39
+ ---
40
+
41
+ **Install**
42
+ Recommended installation:
43
+
44
+ ```bash
45
+ mkdir my_data_plugin
46
+ cd my_data_plugin
47
+ uv tool install pam-python
48
+ ```
49
+
50
+ Pip remains supported:
51
+
52
+ ```bash
53
+ python3 -m venv venv
54
+ source venv/bin/activate
55
+ python -m pip install pam-python
56
+ ```
57
+
58
+ ---
59
+
60
+ **Initialize a Project**
61
+ This creates a runnable project with templates, including `AGENT.md` for runtime
62
+ rules and `REPORTS.md` for the managed-report public API and copyable examples.
63
+
64
+ ```bash
65
+ pam init
66
+ ```
67
+
68
+ `pam init` asks you to select `uv` (default) or `pip`. A uv project contains
69
+ `pyproject.toml`, `.python-version`, and `uv.lock`; a pip project contains
70
+ `requirements.txt`. The two templates are independent and are never mixed.
71
+
72
+ Initialization only supports a new project directory and will not migrate or
73
+ overwrite an existing project.
74
+
75
+ ---
76
+
77
+ **Create a Service**
78
+
79
+ ```bash
80
+ pam new service rfm_segment
81
+ ```
82
+
83
+ The generator asks you to select Polars (default) or Pandas. Polars services use
84
+ lazy Parquet processing. Pandas services also receive Parquet and install
85
+ `pandas` plus `pyarrow`. The selected direct dependencies are added to the
86
+ project automatically.
87
+
88
+ This creates a new folder (e.g. `rfm_segment/`) with:
89
+
90
+ - a service class (`RfmSegmentSvc.py`)
91
+ - `functions.py` for your logic
92
+ - `service.yaml` for registration
93
+ - a test file
94
+
95
+ ---
96
+
97
+ **Understand the Lifecycle**
98
+ The runtime calls your service in two main phases.
99
+
100
+ 1. `on_start`
101
+
102
+ - Called once at the beginning
103
+ - Read parameters from `self.request.runtime_parameters`
104
+ - Should return quickly (start a thread for long work)
105
+
106
+ 2. `on_data_input`
107
+
108
+ - Called when CDP sends input files
109
+ - `req.input_files` contains ordered Parquet files
110
+ - `req.file_format` is the typed format sent by PAM
111
+ - Should also return quickly (use a thread if needed)
112
+
113
+ When your service is done:
114
+
115
+ - Upload ordinary CDP result rows through `ResultBatchUploader` or `_upload_result(...)`
116
+ - Publish managed reports through `self.reports`; do not build report JSON manually
117
+ - Call `self._exit()` to signal completion
118
+
119
+ ---
120
+
121
+ **Using Temp Files Correctly**
122
+ Temp storage is managed by the framework. Do not delete temp files manually.
123
+
124
+ Standard helpers:
125
+
126
+ - `TempfileUtils.get_temp_path_for_service(self, self.service_name)`
127
+ - `TempfileUtils.get_temp_file_name_for_service(self, self.service_name, prefix, extension)`
128
+
129
+ Notes:
130
+
131
+ - `get_temp_path_for_service(...)` returns a directory path without a trailing slash.
132
+ - The temp path includes date/service/token in this structure:
133
+ `TEMP_DATASOURCE_PATH/YYYY_MM_DD/<service>/<token>`
134
+
135
+ ---
136
+
137
+ **Uploading Results in Batches**
138
+ Polars templates pass `LazyFrame` results directly to `_upload_result(...)`, which
139
+ streams them to the CSV upload boundary. Do not collect or convert to Pandas first.
140
+
141
+ For eager Polars or Pandas DataFrames, use the batch uploader to handle chunking
142
+ and flushing automatically.
143
+
144
+ Recommended usage:
145
+
146
+ ```python
147
+ from pam.result_batch_uploader import ResultBatchUploader
148
+
149
+ batch_uploader = ResultBatchUploader(self, batch_size=50000)
150
+ batch_uploader.upload(df, name="main")
151
+ batch_uploader.flush()
152
+ status = batch_uploader.get_status()
153
+ ```
154
+
155
+ Notes:
156
+
157
+ - `request.runtime_parameters["batch_size"]` overrides the constructor default when valid.
158
+ - `name` separates result streams and enforces one stable column schema per stream.
159
+ - `options` may be passed to `upload(...)` and are forwarded to `_upload_result`.
160
+ - Complete batches upload immediately; `flush()` sends the remaining rows.
161
+ - `get_status()` returns buffered rows, uploaded rows, and uploaded batch counts per stream.
162
+ - `ResultBatchUploader` intentionally rejects Polars `LazyFrame`; upload it directly.
163
+
164
+ ---
165
+
166
+ **Managed Reports (Report Store V2)**
167
+
168
+ Managed reports are static facts stored in framework-owned SQLite. Plugin code
169
+ selects a typed builder, writes source-derived `DAY`, `MONTH`, or `NONE` facts, and
170
+ publishes only after the full calculation succeeds.
171
+
172
+ ```python
173
+ fresh = self.request.get_runtime_bool("fresh", default=False)
174
+ store = (
175
+ self.reports.create("reports.sqlite")
176
+ if fresh
177
+ else self.reports.open("reports.sqlite")
178
+ )
179
+
180
+ # Choose the builder agreed in the customer requirement.
181
+ # See generated REPORTS.md for every supported declaration and write API.
182
+ ```
183
+
184
+ Important rules:
185
+
186
+ - Use only typed builders documented in `REPORTS.md`; there is no raw-report API.
187
+ - `open()` resumes the remote file or creates it only after an authoritative 404.
188
+ - Network/5xx download failures never create a replacement store.
189
+ - Use `create()` only for a human-requested fresh restart.
190
+ - Delete a partition, write its recalculated facts, then call `store.publish()` once.
191
+ - Low-level `_request_sqlite`/`_upload_sqlite` remain for custom plugin state, not reports.
192
+
193
+ ---
194
+
195
+ **Running the Server**
196
+ The generated `main.py` runs the Flask server.
197
+
198
+ ```bash
199
+ uv run python main.py # uv project
200
+ python main.py # pip project
201
+ ```
202
+
203
+ By default it binds to `0.0.0.0:8000`. You can override with:
204
+
205
+ ```bash
206
+ export SERVER_HOST=0.0.0.0
207
+ export SERVER_PORT=8000
208
+ ```
209
+
210
+ ---
211
+
212
+ **Testing a Service**
213
+ For uv projects, use one cross-platform command:
214
+
215
+ ```bash
216
+ uv run python -m unittest discover -s rfm_segment -p "test_*.py"
217
+ ```
218
+
219
+ Pip projects include shell-specific scripts:
220
+
221
+ macOS/Linux:
222
+
223
+ ```bash
224
+ ./run_unit_test.sh rfm_segment
225
+ ```
226
+
227
+ Windows PowerShell:
228
+
229
+ ```powershell
230
+ .\run_unit_test.ps1 rfm_segment
231
+ ```
232
+
233
+ Windows Command Prompt:
234
+
235
+ ```bat
236
+ run_unit_test.bat rfm_segment
237
+ ```
238
+
239
+ Place custom tests in the service folder and name them `test_<service>.py`.
240
+
241
+ ---
242
+
243
+ **Configuration**
244
+ Environment variables you can set:
245
+
246
+ - `SERVER_HOST`
247
+ - `SERVER_PORT`
248
+ - `TEMP_BASE_PATH` (default `/app/data`)
249
+ - `TEMP_DATASOURCE_PATH` (default `/app/data/data_sources`)
250
+ - `TEMP_CLEAN_DAYS` (default `10`)
251
+ - `TEMP_CLEAN_INTERVAL_HOURS` (default `6`, set empty to disable periodic cleanup)
252
+
253
+ ---
254
+
255
+ **Project Structure**
256
+ After `pam init` and one service:
257
+
258
+ ```
259
+ .
260
+ ├── main.py
261
+ ├── AGENT.md
262
+ ├── REPORTS.md
263
+ ├── Dockerfile
264
+ ├── pyproject.toml
265
+ ├── uv.lock
266
+ ├── .python-version
267
+ ├── rfm_segment/
268
+ │ ├── RfmSegmentSvc.py
269
+ │ ├── functions.py
270
+ │ ├── service.yaml
271
+ │ └── test_rfm_segment.py
272
+ └── .pam-project
273
+ ```
274
+
275
+ ---
276
+
277
+ **Troubleshooting**
278
+
279
+ - If `pam` is missing, verify the uv tool installation or activate the pip virtualenv.
280
+ - If uv initialization fails, verify `uv` is on `PATH` and package indexes are reachable.
281
+ - If `pam new service` fails, confirm the service name is provided.
282
+ - If temp cleanup is too frequent or too slow, adjust `TEMP_CLEAN_INTERVAL_HOURS` and `TEMP_CLEAN_DAYS`.
283
+
284
+ ---
285
+
286
+ **Next Steps**
287
+
288
+ - Implement your logic in `functions.py`.
289
+ - Wire it into `on_start` and `on_data_input` in your service class.
290
+ - Use the temp utilities to write intermediate files.
291
+ - Keep Polars transformations lazy and upload the resulting `LazyFrame` directly.
292
+ - Use `ResultBatchUploader` only for eager Polars/Pandas frames that need row batching.
293
+ - Read `REPORTS.md` before implementing a managed report.
@@ -0,0 +1,3 @@
1
+ """PAM Data Plugin framework."""
2
+
3
+ __version__ = "0.2.1"