pam-python 0.1.30__tar.gz → 0.1.31__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 (39) hide show
  1. {pam_python-0.1.30 → pam_python-0.1.31}/PKG-INFO +1 -1
  2. {pam_python-0.1.30 → pam_python-0.1.31}/pam/cli.py +2 -0
  3. pam_python-0.1.31/pam/templates/init/AGENT.md +191 -0
  4. {pam_python-0.1.30 → pam_python-0.1.31}/pam_python.egg-info/PKG-INFO +1 -1
  5. {pam_python-0.1.30 → pam_python-0.1.31}/pam_python.egg-info/SOURCES.txt +1 -0
  6. {pam_python-0.1.30 → pam_python-0.1.31}/setup.py +1 -1
  7. {pam_python-0.1.30 → pam_python-0.1.31}/LICENSE.txt +0 -0
  8. {pam_python-0.1.30 → pam_python-0.1.31}/README.md +0 -0
  9. {pam_python-0.1.30 → pam_python-0.1.31}/pam/__init__.py +0 -0
  10. {pam_python-0.1.30 → pam_python-0.1.31}/pam/api.py +0 -0
  11. {pam_python-0.1.30 → pam_python-0.1.31}/pam/interface_task_manager.py +0 -0
  12. {pam_python-0.1.30 → pam_python-0.1.31}/pam/models/__init__.py +0 -0
  13. {pam_python-0.1.30 → pam_python-0.1.31}/pam/models/request_command.py +0 -0
  14. {pam_python-0.1.30 → pam_python-0.1.31}/pam/server.py +0 -0
  15. {pam_python-0.1.30 → pam_python-0.1.31}/pam/service.py +0 -0
  16. {pam_python-0.1.30 → pam_python-0.1.31}/pam/sqlite.py +0 -0
  17. {pam_python-0.1.30 → pam_python-0.1.31}/pam/task_manager.py +0 -0
  18. {pam_python-0.1.30 → pam_python-0.1.31}/pam/temp_file_utils.py +0 -0
  19. {pam_python-0.1.30 → pam_python-0.1.31}/pam/templates/buildcmd/pamb +0 -0
  20. {pam_python-0.1.30 → pam_python-0.1.31}/pam/templates/buildcmd/pamb-base.sh +0 -0
  21. {pam_python-0.1.30 → pam_python-0.1.31}/pam/templates/docker/Dockerfile +0 -0
  22. {pam_python-0.1.30 → pam_python-0.1.31}/pam/templates/init/dockerignore.tmpl +0 -0
  23. {pam_python-0.1.30 → pam_python-0.1.31}/pam/templates/init/gitignore.tmpl +0 -0
  24. {pam_python-0.1.30 → pam_python-0.1.31}/pam/templates/init/main.tmpl +0 -0
  25. {pam_python-0.1.30 → pam_python-0.1.31}/pam/templates/init/pylintrc.tmpl +0 -0
  26. {pam_python-0.1.30 → pam_python-0.1.31}/pam/templates/init/run_unit_test.bat +0 -0
  27. {pam_python-0.1.30 → pam_python-0.1.31}/pam/templates/init/run_unit_test.ps1 +0 -0
  28. {pam_python-0.1.30 → pam_python-0.1.31}/pam/templates/init/run_unit_test.sh +0 -0
  29. {pam_python-0.1.30 → pam_python-0.1.31}/pam/templates/service/functions.tmpl +0 -0
  30. {pam_python-0.1.30 → pam_python-0.1.31}/pam/templates/service/service.test.tmpl +0 -0
  31. {pam_python-0.1.30 → pam_python-0.1.31}/pam/templates/service/service.yaml +0 -0
  32. {pam_python-0.1.30 → pam_python-0.1.31}/pam/templates/service/service_class.tmpl +0 -0
  33. {pam_python-0.1.30 → pam_python-0.1.31}/pam/tester_task.py +0 -0
  34. {pam_python-0.1.30 → pam_python-0.1.31}/pam/utils.py +0 -0
  35. {pam_python-0.1.30 → pam_python-0.1.31}/pam_python.egg-info/dependency_links.txt +0 -0
  36. {pam_python-0.1.30 → pam_python-0.1.31}/pam_python.egg-info/entry_points.txt +0 -0
  37. {pam_python-0.1.30 → pam_python-0.1.31}/pam_python.egg-info/requires.txt +0 -0
  38. {pam_python-0.1.30 → pam_python-0.1.31}/pam_python.egg-info/top_level.txt +0 -0
  39. {pam_python-0.1.30 → pam_python-0.1.31}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pam-python
3
- Version: 0.1.30
3
+ Version: 0.1.31
4
4
  Summary: Pam Python Library
5
5
  Home-page: https://github.com/heart/pam-python
6
6
  Author: Narongrit Kanhanoi
@@ -74,6 +74,7 @@ def create_service(name):
74
74
  cpy("service/service.yaml", os.path.join(name, "service.yaml"))
75
75
  cpy("service/functions.tmpl", os.path.join(name, "functions.py"))
76
76
  cpy("service/service.test.tmpl", os.path.join(name, f"test_{name}.py"))
77
+
77
78
 
78
79
  replace_template_content(name, to_pascal_case(
79
80
  name)+"Svc", to_pascal_case(name)+"Svc.py")
@@ -96,6 +97,7 @@ def init_project():
96
97
  cpy("init/run_unit_test.sh", "run_unit_test.sh")
97
98
  cpy("init/run_unit_test.bat", "run_unit_test.bat")
98
99
  cpy("init/run_unit_test.ps1", "run_unit_test.ps1")
100
+ cpy("init/AGENT.md", "AGENT.md")
99
101
 
100
102
  if not os.path.exists("requirements.txt"):
101
103
  open("requirements.txt", 'a', encoding='utf-8').close()
@@ -0,0 +1,191 @@
1
+ # AGENT
2
+
3
+ This document explains the data plugin service lifecycle so developers can create new services and understand runtime behavior inside the CDP environment.
4
+
5
+ ## Project Overview
6
+
7
+ 1. This project is a data plugin that will be connected to the main system in production.
8
+ 2. The data plugin processes data from CDP and is created as separate services by domain. For example, if we process RFM, we create a new service dedicated to that domain.
9
+
10
+ ## Create a New Service
11
+
12
+ 1. Create a service with:
13
+ `pam new service [service name]`
14
+ 2. The system creates a new folder named `[service name]` with sample code and basic templates.
15
+ 3. Example:
16
+ `pam new service rfm_segment`
17
+ This generates `rfm_segment/Rfm_segmentSvc.py`, which is the starting point of the service.
18
+ 4. `rfm_segment/service.yaml` registers the service automatically.
19
+ The value `endpoint: /Rfm_segmentSvc` means CDP will call:
20
+ `http://ip-of-this-k8s-pod/services/Rfm_segmentSvc`
21
+
22
+ ## Service Lifecycle
23
+
24
+ CDP spawns a service instance when a job starts and terminates it after the job completes. This design supports parallel execution. For example, two runs will spawn two separate instances. The only risk is shared local files (e.g., sqlite) which can cause conflicts.
25
+
26
+ The lifecycle is wired in `rfm_segment/Rfm_segmentSvc.py` (example service name).
27
+
28
+ ### on_start
29
+
30
+ `on_start` runs at startup. Use it to read runtime parameters from CDP and initialize work.
31
+
32
+ Example:
33
+
34
+ ```python
35
+ def on_start(self):
36
+ log("on_start")
37
+
38
+ some_param = self.request.runtime_parameters.get("some_param", "")
39
+ self._request_data()
40
+ ```
41
+
42
+ `runtime_parameters` are configured by CDP to control service behavior without code changes (e.g., `period_day="7"`).
43
+
44
+ Notes
45
+
46
+ - All values are strings. Cast with defaults to avoid errors.
47
+ - `dry_run` is the only built-in parameter. If missing, it runs for real. If `dry_run="true"`, the service runs fully but does not upload results to CDP.
48
+ - You do not need to check `dry_run` manually. Always call `_upload_result`; the system handles it.
49
+
50
+ **Important:** `on_start` must return quickly because CDP calls via HTTP. If it hangs, the client may time out and the job will be terminated. If work is long-running, move it to a thread and return from `on_start` first. This applies to all lifecycle functions.
51
+
52
+ ### Request Data from CDP
53
+
54
+ Call `self._request_data()` to ask CDP for input data. CDP returns up to 5 CSV files (each file is a separate event).
55
+
56
+ - File order is guaranteed per agreement, so you can index by position.
57
+ - Column names and record counts are defined by CDP configuration.
58
+
59
+ CSV example:
60
+
61
+ ```csv
62
+ _id,id,data1,data2,data3
63
+ xx,yy,zz,nn,mm
64
+ ```
65
+
66
+ Column meaning
67
+
68
+ - `_id` = event id for dedup/processing checks. Often unused and can be dropped.
69
+ - `id` = customer id used by CDP as a key.
70
+ - Other columns depend on the agreement.
71
+
72
+ Date format is always:
73
+ `"02/08/2026, 18:23:54"`
74
+ Timezone may vary by customer data (usually Bangkok time).
75
+ You may sometimes be asked for the exact time zone if it is logically important for the program.
76
+
77
+ ### on_data_input
78
+
79
+ When CDP finishes collecting data, it calls `on_data_input`.
80
+
81
+ Example:
82
+
83
+ ```python
84
+ def on_data_input(self, req: RequestCommand):
85
+ log(f"on_data_input req.is_end = {req.is_end}")
86
+
87
+ # RUN IN A SEPARATED THREAD TO PREVENT HTTP REQUEST BLOCKING
88
+ thread = threading.Thread(
89
+ target=lambda: self.__run_process_data_in_thread(req),
90
+ daemon=True,
91
+ )
92
+ thread.start()
93
+
94
+
95
+ def __run_process_data_in_thread(self, req: RequestCommand):
96
+ dataframe = self.__process_data(req.input_files)
97
+ self._upload_result(dataframe)
98
+
99
+ if not req.is_end:
100
+ self._request_data(req.next)
101
+ else:
102
+ self._exit()
103
+ ```
104
+
105
+ Why a thread: to end the HTTP request quickly and avoid timeouts.
106
+
107
+ `req.input_files` is the ordered list of CSV paths.
108
+
109
+ ### Upload Results to CDP
110
+
111
+ After processing, call `_upload_result` with a dataframe. Example output:
112
+
113
+ ```csv
114
+ id,data_x,rfm
115
+ xxx,20,hero
116
+ ```
117
+
118
+ - `id` is required for CDP matching.
119
+ - Other columns are defined by agreement.
120
+
121
+ Sometimes you may include an `event` column to create per-user events:
122
+
123
+ ```csv
124
+ id,data_x,event
125
+ xxx,20,purchase
126
+ yyy,50,refund
127
+ ```
128
+
129
+ If `event` is missing, CDP uses the default event configured in the system.
130
+
131
+ ### Pagination
132
+
133
+ In the example:
134
+
135
+ ```python
136
+ if not req.is_end:
137
+ self._request_data(req.next)
138
+ else:
139
+ self._exit()
140
+ ```
141
+
142
+ - `req.is_end` indicates whether this is the last page.
143
+ - If not end, call `self._request_data(req.next)` for the next page.
144
+ - If end, call `self._exit()` to terminate the instance.
145
+
146
+ CDP will not send the next page until you call `_request_data`, so the flow is serialized and no two threads will handle the same data unless you explicitly design it that way.
147
+
148
+ ### on_terminate
149
+
150
+ Before termination, the system calls:
151
+
152
+ ```python
153
+ def on_terminate(self):
154
+ log("on_terminate")
155
+ ```
156
+
157
+ Use it for cleanup.
158
+
159
+ ## Persist State Between Runs (sqlite/duckdb)
160
+
161
+ Services can be killed and restarted at any time. If you need state across runs, use sqlite or duckdb via the provided API.
162
+
163
+ Example (sqlite):
164
+
165
+ ```python
166
+ SQLITE_FILE_NAME = "mydata.sqlite"
167
+
168
+ sqlite_file = self._request_sqlite(file_name=SQLITE_FILE_NAME, is_shared=False)
169
+ if sqlite_file is not None:
170
+ pass
171
+
172
+ self._upload_sqlite(
173
+ file_name=SQLITE_FILE_NAME,
174
+ is_shared=False,
175
+ sqlite_file="/local/path/to/upload/sqlite.sqlite",
176
+ )
177
+ ```
178
+
179
+ Notes
180
+
181
+ - `_request_sqlite` downloads from the server and is automatically scoped to this plugin. It will not mix with other plugins even if the name matches.
182
+ - The call is synchronous and returns the local path.
183
+ - If the return value is `None`, no file exists on the server; you can create a new one.
184
+ - `_upload_sqlite` always overwrites the previous file.
185
+
186
+ `is_shared` behavior
187
+
188
+ - `False` (recommended): isolate per service. Multiple instances get their own copies.
189
+ - `True`: shared storage. If another plugin updates the file, this plugin can download the latest version.
190
+
191
+ Note: `file_name` acts like a server key, while `sqlite_file` is the local file path.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pam-python
3
- Version: 0.1.30
3
+ Version: 0.1.31
4
4
  Summary: Pam Python Library
5
5
  Home-page: https://github.com/heart/pam-python
6
6
  Author: Narongrit Kanhanoi
@@ -17,6 +17,7 @@ pam/models/request_command.py
17
17
  pam/templates/buildcmd/pamb
18
18
  pam/templates/buildcmd/pamb-base.sh
19
19
  pam/templates/docker/Dockerfile
20
+ pam/templates/init/AGENT.md
20
21
  pam/templates/init/dockerignore.tmpl
21
22
  pam/templates/init/gitignore.tmpl
22
23
  pam/templates/init/main.tmpl
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="pam-python",
5
- version="0.1.30",
5
+ version="0.1.31",
6
6
  author="Narongrit Kanhanoi",
7
7
  author_email="narongrit@pams.ai",
8
8
  description="Pam Python Library",
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