pam-python 0.1.22__tar.gz → 0.1.24__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 (37) hide show
  1. {pam_python-0.1.22/pam_python.egg-info → pam_python-0.1.24}/PKG-INFO +1 -1
  2. {pam_python-0.1.22 → pam_python-0.1.24}/pam/interface_task_manager.py +8 -0
  3. {pam_python-0.1.22 → pam_python-0.1.24}/pam/service.py +2 -58
  4. {pam_python-0.1.22 → pam_python-0.1.24}/pam/task_manager.py +73 -1
  5. {pam_python-0.1.22 → pam_python-0.1.24}/pam/templates/docker/Dockerfile +2 -2
  6. pam_python-0.1.24/pam/templates/init/run_test.sh +7 -0
  7. {pam_python-0.1.22 → pam_python-0.1.24}/pam/templates/service/service.test.tmpl +12 -1
  8. {pam_python-0.1.22 → pam_python-0.1.24}/pam/templates/service/service_class.tmpl +10 -1
  9. {pam_python-0.1.22 → pam_python-0.1.24}/pam/tester_task.py +34 -1
  10. {pam_python-0.1.22 → pam_python-0.1.24/pam_python.egg-info}/PKG-INFO +1 -1
  11. {pam_python-0.1.22 → pam_python-0.1.24}/setup.py +8 -8
  12. pam_python-0.1.22/pam/templates/init/run_test.sh +0 -1
  13. {pam_python-0.1.22 → pam_python-0.1.24}/LICENSE.txt +0 -0
  14. {pam_python-0.1.22 → pam_python-0.1.24}/README.md +0 -0
  15. {pam_python-0.1.22 → pam_python-0.1.24}/pam/__init__.py +0 -0
  16. {pam_python-0.1.22 → pam_python-0.1.24}/pam/api.py +0 -0
  17. {pam_python-0.1.22 → pam_python-0.1.24}/pam/cli.py +0 -0
  18. {pam_python-0.1.22 → pam_python-0.1.24}/pam/models/__init__.py +0 -0
  19. {pam_python-0.1.22 → pam_python-0.1.24}/pam/models/request_command.py +0 -0
  20. {pam_python-0.1.22 → pam_python-0.1.24}/pam/server.py +0 -0
  21. {pam_python-0.1.22 → pam_python-0.1.24}/pam/sqlite.py +0 -0
  22. {pam_python-0.1.22 → pam_python-0.1.24}/pam/temp_file_utils.py +0 -0
  23. {pam_python-0.1.22 → pam_python-0.1.24}/pam/templates/buildcmd/pamb +0 -0
  24. {pam_python-0.1.22 → pam_python-0.1.24}/pam/templates/buildcmd/pamb-base.sh +0 -0
  25. {pam_python-0.1.22 → pam_python-0.1.24}/pam/templates/init/dockerignore.tmpl +0 -0
  26. {pam_python-0.1.22 → pam_python-0.1.24}/pam/templates/init/gitignore.tmpl +0 -0
  27. {pam_python-0.1.22 → pam_python-0.1.24}/pam/templates/init/main.tmpl +0 -0
  28. {pam_python-0.1.22 → pam_python-0.1.24}/pam/templates/init/pylintrc.tmpl +0 -0
  29. {pam_python-0.1.22 → pam_python-0.1.24}/pam/templates/service/functions.tmpl +0 -0
  30. {pam_python-0.1.22 → pam_python-0.1.24}/pam/templates/service/service.yaml +0 -0
  31. {pam_python-0.1.22 → pam_python-0.1.24}/pam/utils.py +0 -0
  32. {pam_python-0.1.22 → pam_python-0.1.24}/pam_python.egg-info/SOURCES.txt +0 -0
  33. {pam_python-0.1.22 → pam_python-0.1.24}/pam_python.egg-info/dependency_links.txt +0 -0
  34. {pam_python-0.1.22 → pam_python-0.1.24}/pam_python.egg-info/entry_points.txt +0 -0
  35. {pam_python-0.1.22 → pam_python-0.1.24}/pam_python.egg-info/requires.txt +0 -0
  36. {pam_python-0.1.22 → pam_python-0.1.24}/pam_python.egg-info/top_level.txt +0 -0
  37. {pam_python-0.1.22 → pam_python-0.1.24}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pam-python
3
- Version: 0.1.22
3
+ Version: 0.1.24
4
4
  Summary: Pam Python Library
5
5
  Home-page: https://github.com/heart/pam-python
6
6
  Author: Narongrit Kanhanoi
@@ -21,6 +21,14 @@ class ITaskManager(ABC):
21
21
  def service_request_data(self, service: Service, page):
22
22
  pass
23
23
 
24
+ @abstractmethod
25
+ def service_request_sqlite(self, service: Service, file_name: str = "", is_shared: bool = False) -> str:
26
+ pass
27
+
28
+ @abstractmethod
29
+ def service_upload_sqlite(self, service: Service, file_name: str = "", is_shared: bool = False, sqlite_file: str = "") -> str:
30
+ pass
31
+
24
32
  @abstractmethod
25
33
  def service_upload_result(self, service: Service, file_path):
26
34
  pass
@@ -109,41 +109,12 @@ class Service:
109
109
 
110
110
  return report_csv_path
111
111
 
112
-
113
112
  def _request_sqlite(self, file_name: str = "", is_shared: bool = False) -> str:
114
113
  """Requests last sqlite file that this plugin has uploaded from the last time.
115
-
116
114
  Returns:
117
115
  The file path to the downloaded SQLite file, or None if the download failed.
118
116
  """
119
- log(f"{self.request.service_name}: Requesting sqlite for file_name={file_name}")
120
-
121
- endpoint = self.request.sqlite_download
122
- token = self.request.token
123
- json_data = {"file_name": file_name, "is_shared": is_shared, "token": token}
124
-
125
- # Create a unique suffix using current datetime
126
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
127
- unique_file_name = f"{file_name or 'latest'}_{timestamp}"
128
-
129
- # Create a temp file path for the downloaded .sqlite file
130
- output_path = TempfileUtils.get_temp_file_name(
131
- self.request.service_name,
132
- token,
133
- f"sqlite_{unique_file_name}_",
134
- ".sqlite"
135
- )
136
-
137
- # Download SQLite file
138
- success = self.task_manager.api.download_sqlite_from_post(endpoint, json_data, output_path)
139
-
140
- if success:
141
- log(f"{self.request.service_name}: Successfully downloaded SQLite file to {output_path}")
142
- return output_path
143
- else:
144
- log(f"{self.request.service_name}: Failed to download SQLite file.")
145
- return output_path
146
-
117
+ return self.task_manager.service_request_sqlite(self, file_name, is_shared)
147
118
 
148
119
  def _upload_sqlite(self, file_name: str = "", is_shared: bool = False, sqlite_file: str = "") -> str:
149
120
  """
@@ -154,34 +125,7 @@ class Service:
154
125
  :param sqlite_file: Path to the SQLite file to upload.
155
126
  :return: The uploaded file name if successful, or empty string if failed.
156
127
  """
157
- if not sqlite_file:
158
- log(f"{self.request.service_name}: No sqlite file provided for upload.")
159
- return ""
160
-
161
- if not Path(sqlite_file).is_file():
162
- log(f"{self.request.service_name}: SQLite file does not exist: {sqlite_file}")
163
- return ""
164
-
165
- endpoint = self.request.sqlite_upload
166
- payload = {
167
- "file_name": file_name,
168
- "is_shared": str(is_shared).lower(), # form data values must be string
169
- "token": self.request.token
170
- }
171
-
172
- try:
173
- log(f"{self.request.service_name}: Uploading sqlite file: {sqlite_file} with payload: {payload}")
174
- response = self.task_manager.api.http_upload(endpoint, sqlite_file, payload)
175
-
176
- if response and response.ok:
177
- log(f"{self.request.service_name}: SQLite file uploaded successfully.")
178
- return file_name or Path(sqlite_file).name
179
- else:
180
- log(f"{self.request.service_name}: Failed to upload sqlite. Status: {response.status_code if response else 'N/A'}")
181
- return ""
182
- except Exception as e:
183
- log(f"{self.request.service_name}: Exception while uploading sqlite: {e}")
184
- return ""
128
+ return self.task_manager.service_upload_sqlite(self, file_name, is_shared, sqlite_file)
185
129
 
186
130
  def _exit(self) -> None:
187
131
  """Signals the task manager to exit the service."""
@@ -1,4 +1,5 @@
1
1
  from datetime import datetime, timedelta
2
+ from pathlib import Path
2
3
  import time
3
4
  import threading
4
5
  from typing import Dict
@@ -7,7 +8,7 @@ from pam.api import API
7
8
  from pam.service import Service
8
9
  from pam.models.request_command import RequestCommand
9
10
  from pam.interface_task_manager import ITaskManager
10
-
11
+ from pam.temp_file_utils import TempfileUtils
11
12
 
12
13
  class ServiceHolder:
13
14
  def __init__(self, service: Service):
@@ -152,6 +153,77 @@ class TaskManager(ITaskManager):
152
153
  http_thread = threading.Thread(target=api_call_wrapper)
153
154
  http_thread.start()
154
155
 
156
+ def service_request_sqlite(self, service: Service, file_name="", is_shared=False):
157
+ """Requests last sqlite file that this plugin has uploaded from the last time.
158
+ Returns:
159
+ The file path to the downloaded SQLite file, or None if the download failed.
160
+ """
161
+
162
+ log(f"{service.request.service_name}: Requesting sqlite for file_name={file_name}")
163
+
164
+ endpoint = service.request.sqlite_download
165
+ token = service.request.token
166
+ json_data = {"file_name": file_name, "is_shared": is_shared, "token": token}
167
+
168
+ # Create a unique suffix using current datetime
169
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
170
+ unique_file_name = f"{file_name or 'latest'}_{timestamp}"
171
+
172
+ # Create a temp file path for the downloaded .sqlite file
173
+ output_path = TempfileUtils.get_temp_file_name(
174
+ service.request.service_name,
175
+ token,
176
+ f"sqlite_{unique_file_name}_",
177
+ ".sqlite"
178
+ )
179
+
180
+ # Download SQLite file
181
+ success = self.api.download_sqlite_from_post(endpoint, json_data, output_path)
182
+
183
+ if success:
184
+ log(f"{service.request.service_name}: Successfully downloaded SQLite file to {output_path}")
185
+ return output_path
186
+ else:
187
+ log(f"{service.request.service_name}: Failed to download SQLite file.")
188
+ return output_path
189
+
190
+ def service_upload_sqlite(self, service: Service, file_name: str = "", is_shared: bool = False, sqlite_file: str = "") -> str:
191
+ """
192
+ Uploads the result SQLite file to CDP.
193
+
194
+ :param file_name: The logical file name being uploaded.
195
+ :param is_shared: Whether this file should be treated as shared.
196
+ :param sqlite_file: Path to the SQLite file to upload.
197
+ :return: The uploaded file name if successful, or empty string if failed.
198
+ """
199
+ if not sqlite_file:
200
+ log(f"{service.request.service_name}: No sqlite file provided for upload.")
201
+ return ""
202
+
203
+ if not Path(sqlite_file).is_file():
204
+ log(f"{service.request.service_name}: SQLite file does not exist: {sqlite_file}")
205
+ return ""
206
+
207
+ endpoint = service.request.sqlite_upload
208
+ payload = {
209
+ "file_name": file_name,
210
+ "is_shared": str(is_shared).lower(), # form data values must be string
211
+ "token": service.request.token
212
+ }
213
+
214
+ try:
215
+ log(f"{service.request.service_name}: Uploading sqlite file: {sqlite_file} with payload: {payload}")
216
+ response = self.api.http_upload(endpoint, sqlite_file, payload)
217
+
218
+ if response and response.ok:
219
+ log(f"{service.request.service_name}: SQLite file uploaded successfully.")
220
+ return file_name or Path(sqlite_file).name
221
+ else:
222
+ log(f"{service.request.service_name}: Failed to upload sqlite. Status: {response.status_code if response else 'N/A'}")
223
+ return ""
224
+ except Exception as e:
225
+ log(f"{service.request.service_name}: Exception while uploading sqlite: {e}")
226
+ return ""
155
227
 
156
228
  def service_upload_result(self, service: Service, file_path):
157
229
  """
@@ -1,4 +1,4 @@
1
- FROM python:3.12-slim
1
+ FROM python:3.13-alpine
2
2
 
3
3
  COPY . /app
4
4
 
@@ -15,4 +15,4 @@ EXPOSE 8000
15
15
 
16
16
  ENV PYTHONUNBUFFERED=1
17
17
 
18
- CMD ["gunicorn", "-w", "1", "-b", "0.0.0.0:8000", "main:app"]
18
+ CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:8000", "main:app"]
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+
3
+ if [ -n "$1" ]; then
4
+ python -m unittest discover -s "$1" -p "test_*.py"
5
+ else
6
+ python -m unittest discover
7
+ fi
@@ -9,7 +9,7 @@ from pam.tester_task import TesterTask
9
9
  from pam.models.request_command import RequestCommand
10
10
  from pam.temp_file_utils import TempfileUtils
11
11
  #pylint: disable=import-error
12
- from .#CLASS_NAME# import #CLASS_NAME#
12
+ from #MODULE_NAME#.#CLASS_NAME# import #CLASS_NAME#
13
13
 
14
14
 
15
15
  # ==================== GLOBAL VARIABLES ====================
@@ -50,6 +50,13 @@ def on_request_data(page) -> Tuple[List[str], bool, str]:
50
50
  mock_csv = f"{MOCK_DIR}/mock_data_{page}.csv"
51
51
  return [mock_csv], is_end, next_page
52
52
 
53
+ def on_request_sqlite():
54
+ sqlite_path = f"{MOCK_DIR}/sqlite_path.sqlite"
55
+ return sqlite_path
56
+
57
+
58
+ def on_upload_sqlite(sqlite_path):
59
+ print(f"Upload SQLite {sqlite_path}")
53
60
 
54
61
  def on_upload_report(report_file):
55
62
  print(f"Uploaded Report {report_file}")
@@ -77,6 +84,8 @@ class Test#CLASS_NAME#(unittest.TestCase):
77
84
  self.testerTask.set_on_upload_result(on_upload_result)
78
85
  self.testerTask.set_on_request_data(on_request_data)
79
86
  self.testerTask.set_on_upload_report(on_upload_report)
87
+ self.testerTask.set_on_upload_sqlite(on_upload_sqlite)
88
+ self.testerTask.set_on_request_sqlite(on_request_sqlite)
80
89
 
81
90
  def test_run_service(self):
82
91
  #---- Create Request Command
@@ -88,6 +97,8 @@ class Test#CLASS_NAME#(unittest.TestCase):
88
97
  RESPONSE_API="https://localhost.test/api/dataadapter/test"
89
98
 
90
99
  req = RequestCommand(
100
+ sqlite_download="",
101
+ sqlite_upload="",
91
102
  runtime_parameters=runtime_parameters,
92
103
  token=token,
93
104
  cmd="start",
@@ -1,8 +1,9 @@
1
+ import threading
1
2
  from pam.service import Service
2
3
  from pam.models.request_command import RequestCommand
3
4
  from pam.utils import log
4
5
  #pylint: disable=import-error
5
- from .functions import create_report, load_csv_file
6
+ from #MODULE_NAME#.functions import create_report, load_csv_file
6
7
 
7
8
  class #CLASS_NAME#(Service):
8
9
 
@@ -22,6 +23,14 @@ class #CLASS_NAME#(Service):
22
23
  def on_data_input(self, req: RequestCommand):
23
24
  log(f"on_data_input req.is_end = {req.is_end}")
24
25
 
26
+ #RUN IN A SEPERATED THREAD TO PREVENT HTTP REQUEST BLOCKING
27
+ thread = threading.Thread(
28
+ target=lambda: self.__run_process_data_in_thread(req),
29
+ daemon=True,
30
+ )
31
+ thread.start()
32
+
33
+ def __run_process_data_in_thread(self, req: RequestCommand):
25
34
  dataframe = self.__process_data(req.input_files)
26
35
  self._upload_result(dataframe)
27
36
 
@@ -5,10 +5,11 @@ from pam.service import Service
5
5
 
6
6
 
7
7
  RequestDataCallbackType = Callable[[str], Tuple[List[str], bool, str]]
8
+ RequestSqliteCallbackType = Callable[[str, bool], Tuple[str]]
9
+ UploadSqliteCallbackType = Callable[[str, bool, str], Tuple[str]]
8
10
  UploadResultCallbackType = Callable[[str], None]
9
11
  UploadReportCallbackType = Callable[[str], None]
10
12
 
11
-
12
13
  class TesterTask(ITaskManager):
13
14
  """
14
15
  Mock implementation of ITaskManager for testing services.
@@ -18,6 +19,8 @@ class TesterTask(ITaskManager):
18
19
 
19
20
  def __init__(self):
20
21
  self.request_data_callback: Optional[RequestDataCallbackType] = None
22
+ self.request_sqlite_callback: Optional[RequestSqliteCallbackType] = None
23
+ self.upload_sqlite_callback: Optional[UploadSqliteCallbackType] = None
21
24
  self.upload_result_callback: Optional[UploadResultCallbackType] = None
22
25
  self.upload_report_callback: Optional[UploadReportCallbackType] = None
23
26
 
@@ -27,6 +30,18 @@ class TesterTask(ITaskManager):
27
30
  """
28
31
  self.request_data_callback = request_data_callback
29
32
 
33
+ def set_on_request_sqlite(self, request_sqlite_callback: RequestSqliteCallbackType):
34
+ """
35
+ Set the callback for simulating data requests.
36
+ """
37
+ self.request_sqlite_callback = request_sqlite_callback
38
+
39
+ def set_on_upload_sqlite(self, upload_sqlite_callback: UploadSqliteCallbackType):
40
+ """
41
+ Set the callback for simulating SQLite uploads.
42
+ """
43
+ self.upload_sqlite_callback = upload_sqlite_callback
44
+
30
45
  def set_on_upload_result(self, upload_result_callback: UploadResultCallbackType):
31
46
  """
32
47
  Set the callback for simulating result uploads.
@@ -89,6 +104,24 @@ class TesterTask(ITaskManager):
89
104
  else:
90
105
  print("Request data callback is not set.")
91
106
 
107
+ def service_request_sqlite(self, service: Service, file_name: str = "", is_shared: bool = False) -> str:
108
+ if self.request_sqlite_callback is not None:
109
+ try:
110
+ return self.request_sqlite_callback(file_name, is_shared)
111
+ except Exception as e:
112
+ print(f"Error in service_request_sqlite: {e}")
113
+ else:
114
+ print("Request sqlite callback is not set.")
115
+
116
+ def service_upload_sqlite(self, service: Service, file_name: str = "", is_shared: bool = False, sqlite_file: str = "") -> str:
117
+ if self.upload_sqlite_callback is not None:
118
+ try:
119
+ return self.upload_sqlite_callback(file_name, is_shared, sqlite_file)
120
+ except Exception as e:
121
+ print(f"Error in service_upload_sqlite: {e}")
122
+ else:
123
+ print("Upload sqlite callback is not set.")
124
+
92
125
  def service_upload_result(self, service: Service, file_path: str):
93
126
  """
94
127
  Simulate a service result upload.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pam-python
3
- Version: 0.1.22
3
+ Version: 0.1.24
4
4
  Summary: Pam Python Library
5
5
  Home-page: https://github.com/heart/pam-python
6
6
  Author: Narongrit Kanhanoi
@@ -1,27 +1,27 @@
1
1
  from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
- name="pam-python",
5
- version="0.1.22",
4
+ name="pam-python",
5
+ version="0.1.24",
6
6
  author="Narongrit Kanhanoi",
7
7
  author_email="narongrit@pams.ai",
8
8
  description="Pam Python Library",
9
9
  long_description=open("README.md", encoding='utf-8').read(),
10
10
  long_description_content_type="text/markdown",
11
- url="https://github.com/heart/pam-python",
12
- packages=find_packages(),
11
+ url="https://github.com/heart/pam-python",
12
+ packages=find_packages(),
13
13
  classifiers=[
14
14
  "Programming Language :: Python :: 3",
15
15
  "Programming Language :: Python :: 3.8",
16
16
  "Programming Language :: Python :: 3.9",
17
- "License :: Other/Proprietary License",
17
+ "License :: Other/Proprietary License",
18
18
  "Operating System :: OS Independent",
19
19
  "Intended Audience :: Developers",
20
20
  "Intended Audience :: Customer Service",
21
21
  "Topic :: Software Development :: Libraries",
22
22
  ],
23
23
  package_data={
24
- "pam": ["templates/**/*"],
24
+ "pam": ["templates/**/*"],
25
25
  },
26
26
  install_requires=[
27
27
  "setuptools>=70.0.0",
@@ -37,8 +37,8 @@ setup(
37
37
  ],
38
38
  entry_points={
39
39
  "console_scripts": [
40
- "pam=pam.cli:main",
40
+ "pam=pam.cli:main",
41
41
  ],
42
42
  },
43
- python_requires=">=3.8",
43
+ python_requires=">=3.8",
44
44
  )
@@ -1 +0,0 @@
1
- python -m unittest discover
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