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.
- pam_python-0.2.1/PKG-INFO +313 -0
- pam_python-0.2.1/README.md +293 -0
- pam_python-0.2.1/pam/__init__.py +3 -0
- pam_python-0.2.1/pam/api.py +188 -0
- pam_python-0.2.1/pam/cli.py +354 -0
- {pam_python-0.1.45 → pam_python-0.2.1}/pam/interface_task_manager.py +33 -2
- {pam_python-0.1.45 → pam_python-0.2.1}/pam/models/request_command.py +15 -2
- pam_python-0.2.1/pam/models/sqlite_download.py +26 -0
- pam_python-0.2.1/pam/reports/__init__.py +73 -0
- pam_python-0.2.1/pam/reports/area.py +97 -0
- pam_python-0.2.1/pam/reports/axis.py +50 -0
- pam_python-0.2.1/pam/reports/bar.py +154 -0
- pam_python-0.2.1/pam/reports/box_plot.py +119 -0
- pam_python-0.2.1/pam/reports/bubble.py +113 -0
- pam_python-0.2.1/pam/reports/candlestick.py +132 -0
- pam_python-0.2.1/pam/reports/collection.py +198 -0
- pam_python-0.2.1/pam/reports/definition.py +82 -0
- pam_python-0.2.1/pam/reports/donut.py +111 -0
- pam_python-0.2.1/pam/reports/errors.py +33 -0
- pam_python-0.2.1/pam/reports/fields.py +144 -0
- pam_python-0.2.1/pam/reports/heatmap.py +88 -0
- pam_python-0.2.1/pam/reports/line.py +90 -0
- pam_python-0.2.1/pam/reports/manager.py +148 -0
- pam_python-0.2.1/pam/reports/metric.py +139 -0
- pam_python-0.2.1/pam/reports/pie.py +111 -0
- pam_python-0.2.1/pam/reports/polar_area.py +112 -0
- pam_python-0.2.1/pam/reports/radar.py +115 -0
- pam_python-0.2.1/pam/reports/radial_bar.py +135 -0
- pam_python-0.2.1/pam/reports/range_area.py +121 -0
- pam_python-0.2.1/pam/reports/range_bar.py +121 -0
- pam_python-0.2.1/pam/reports/range_transform.py +183 -0
- pam_python-0.2.1/pam/reports/record.py +138 -0
- pam_python-0.2.1/pam/reports/report.py +35 -0
- pam_python-0.2.1/pam/reports/scatter.py +121 -0
- pam_python-0.2.1/pam/reports/schema.py +47 -0
- pam_python-0.2.1/pam/reports/store.py +694 -0
- pam_python-0.2.1/pam/reports/table.py +234 -0
- pam_python-0.2.1/pam/reports/transport.py +57 -0
- pam_python-0.2.1/pam/reports/treemap.py +121 -0
- pam_python-0.2.1/pam/request_file_format.py +14 -0
- pam_python-0.2.1/pam/result_batch_uploader.py +227 -0
- {pam_python-0.1.45 → pam_python-0.2.1}/pam/server.py +22 -2
- {pam_python-0.1.45 → pam_python-0.2.1}/pam/service.py +42 -21
- {pam_python-0.1.45 → pam_python-0.2.1}/pam/task_manager.py +82 -3
- {pam_python-0.1.45 → pam_python-0.2.1}/pam/templates/init/AGENT.md +68 -70
- pam_python-0.2.1/pam/templates/init/REPORTS.md +1395 -0
- pam_python-0.2.1/pam/templates/init/dockerignore.tmpl +6 -0
- {pam_python-0.1.45 → pam_python-0.2.1}/pam/templates/init/main.tmpl +3 -3
- pam_python-0.2.1/pam/templates/project/pip/Dockerfile +16 -0
- {pam_python-0.1.45/pam/templates/init → pam_python-0.2.1/pam/templates/project/pip}/requirements.txt +0 -3
- pam_python-0.2.1/pam/templates/project/uv/Dockerfile +20 -0
- pam_python-0.2.1/pam/templates/project/uv/pyproject.toml +12 -0
- pam_python-0.2.1/pam/templates/project/uv/python-version +1 -0
- pam_python-0.2.1/pam/templates/service/pandas/functions.tmpl +13 -0
- pam_python-0.2.1/pam/templates/service/pandas/service.test.tmpl +122 -0
- pam_python-0.2.1/pam/templates/service/pandas/service_class.tmpl +65 -0
- pam_python-0.2.1/pam/templates/service/polars/functions.tmpl +11 -0
- pam_python-0.2.1/pam/templates/service/polars/service.test.tmpl +122 -0
- pam_python-0.2.1/pam/templates/service/polars/service_class.tmpl +59 -0
- {pam_python-0.1.45 → pam_python-0.2.1}/pam/tester_task.py +75 -9
- {pam_python-0.1.45 → pam_python-0.2.1}/pam/utils.py +3 -5
- pam_python-0.2.1/pam_python.egg-info/PKG-INFO +313 -0
- pam_python-0.2.1/pam_python.egg-info/SOURCES.txt +104 -0
- pam_python-0.2.1/pam_python.egg-info/requires.txt +3 -0
- pam_python-0.2.1/pyproject.toml +54 -0
- pam_python-0.2.1/tests/test_cli_templates.py +133 -0
- pam_python-0.2.1/tests/test_range_transform.py +172 -0
- pam_python-0.2.1/tests/test_report_store.py +496 -0
- pam_python-0.2.1/tests/test_result_batch_uploader.py +137 -0
- pam_python-0.2.1/tests/test_service_dataframe_output.py +41 -0
- pam_python-0.2.1/tests/test_sqlite_download_v2.py +196 -0
- pam_python-0.2.1/tests/test_typed_area_report.py +101 -0
- pam_python-0.2.1/tests/test_typed_bar_report.py +172 -0
- pam_python-0.2.1/tests/test_typed_box_plot_report.py +72 -0
- pam_python-0.2.1/tests/test_typed_bubble_report.py +61 -0
- pam_python-0.2.1/tests/test_typed_candlestick_report.py +77 -0
- pam_python-0.2.1/tests/test_typed_donut_report.py +140 -0
- pam_python-0.2.1/tests/test_typed_heatmap_report.py +56 -0
- pam_python-0.2.1/tests/test_typed_line_report.py +128 -0
- pam_python-0.2.1/tests/test_typed_metric_report.py +106 -0
- pam_python-0.2.1/tests/test_typed_pie_report.py +149 -0
- pam_python-0.2.1/tests/test_typed_polar_area_report.py +139 -0
- pam_python-0.2.1/tests/test_typed_radar_report.py +22 -0
- pam_python-0.2.1/tests/test_typed_radial_bar_report.py +77 -0
- pam_python-0.2.1/tests/test_typed_range_area_report.py +75 -0
- pam_python-0.2.1/tests/test_typed_range_bar_report.py +97 -0
- pam_python-0.2.1/tests/test_typed_scatter_report.py +131 -0
- pam_python-0.2.1/tests/test_typed_table_report.py +116 -0
- pam_python-0.2.1/tests/test_typed_treemap_report.py +19 -0
- pam_python-0.1.45/PKG-INFO +0 -259
- pam_python-0.1.45/README.md +0 -220
- pam_python-0.1.45/pam/api.py +0 -90
- pam_python-0.1.45/pam/cli.py +0 -208
- pam_python-0.1.45/pam/models/__init__.py +0 -0
- pam_python-0.1.45/pam/request_file_format.py +0 -5
- pam_python-0.1.45/pam/templates/docker/Dockerfile +0 -21
- pam_python-0.1.45/pam/templates/init/dockerignore.tmpl +0 -4
- pam_python-0.1.45/pam/templates/service/functions.tmpl +0 -38
- pam_python-0.1.45/pam/templates/service/service.test.tmpl +0 -116
- pam_python-0.1.45/pam/templates/service/service_class.tmpl +0 -77
- pam_python-0.1.45/pam_python.egg-info/PKG-INFO +0 -259
- pam_python-0.1.45/pam_python.egg-info/SOURCES.txt +0 -40
- pam_python-0.1.45/pam_python.egg-info/requires.txt +0 -10
- pam_python-0.1.45/setup.py +0 -44
- {pam_python-0.1.45 → pam_python-0.2.1}/LICENSE.txt +0 -0
- {pam_python-0.1.45 → pam_python-0.2.1}/pam/logger.py +0 -0
- {pam_python-0.1.45/pam → pam_python-0.2.1/pam/models}/__init__.py +0 -0
- {pam_python-0.1.45 → pam_python-0.2.1}/pam/sqlite.py +0 -0
- {pam_python-0.1.45 → pam_python-0.2.1}/pam/temp_file_utils.py +0 -0
- {pam_python-0.1.45 → pam_python-0.2.1}/pam/templates/buildcmd/pamb +0 -0
- {pam_python-0.1.45 → pam_python-0.2.1}/pam/templates/buildcmd/pamb-base.sh +0 -0
- {pam_python-0.1.45 → pam_python-0.2.1}/pam/templates/init/gitignore.tmpl +0 -0
- {pam_python-0.1.45 → pam_python-0.2.1}/pam/templates/init/pylintrc.tmpl +0 -0
- {pam_python-0.1.45 → pam_python-0.2.1}/pam/templates/init/run_unit_test.bat +0 -0
- {pam_python-0.1.45 → pam_python-0.2.1}/pam/templates/init/run_unit_test.ps1 +0 -0
- {pam_python-0.1.45 → pam_python-0.2.1}/pam/templates/init/run_unit_test.sh +0 -0
- {pam_python-0.1.45/pam/templates/service → pam_python-0.2.1/pam/templates/service/common}/service.yaml +1 -1
- {pam_python-0.1.45 → pam_python-0.2.1}/pam_python.egg-info/dependency_links.txt +0 -0
- {pam_python-0.1.45 → pam_python-0.2.1}/pam_python.egg-info/entry_points.txt +0 -0
- {pam_python-0.1.45 → pam_python-0.2.1}/pam_python.egg-info/top_level.txt +0 -0
- {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.
|