psr-factory 5.0.0b21__py3-none-win_amd64.whl → 5.0.0b69__py3-none-win_amd64.whl

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.
psr/execqueue/client.py CHANGED
@@ -28,12 +28,23 @@ def upload_case_file(zip_path, server_url):
28
28
  print("Upload failed:", response.text)
29
29
  return None
30
30
 
31
+ def run_module(case_id: str, module_name: str, server_url: str) -> Optional[str]:
32
+ """Add a module to the execution queue. Returns the execution id."""
33
+ data = {"case_id": case_id, "module_name": module_name}
34
+ response = requests.post(f"{server_url}/run_module",data=data)
35
+
36
+ if response.status_code == 200:
37
+ print("Added to execution queue successfully!")
38
+ print("Execution ID:", response.json().get('execution_id'))
39
+ return response.json().get('execution_id')
40
+ else:
41
+ print("Module enqueue failed:", response.status_code, response.text)
42
+ return None
31
43
 
32
44
  def run_case(case_id: str, server_url: str, cloud_execution: bool = False) -> Optional[str]:
33
45
  """Add a case to the execution queue. For server-local run,
34
46
  returns the execution id. For cloud run, returns the cloud upload id."""
35
- data = {"case_id": case_id,'cloud_execution': cloud_execution}
36
- response = requests.post(f"{server_url}/run",data=data)
47
+ response = requests.post(f"{server_url}/run" , params={'cloud_execution': cloud_execution})
37
48
 
38
49
  if response.status_code == 200:
39
50
  if not cloud_execution:
@@ -45,7 +56,20 @@ def run_case(case_id: str, server_url: str, cloud_execution: bool = False) -> Op
45
56
  print("Cloud upload ID:", response.json().get('cloud_upload_id'))
46
57
  return response.json().get('cloud_upload_id')
47
58
  else:
48
- print("Upload failed:", response.text)
59
+ print("Run case failed:", response.status_code, response.text)
60
+ return None
61
+
62
+
63
+ def get_module_log(case_id: str, server_url: str, module_name: Optional[str] = None) -> Optional[str]:
64
+ """Fetch the content of a module's fixed log file. If module_name is None, returns last module run log."""
65
+ params = {}
66
+ if module_name:
67
+ params['module'] = module_name
68
+ response = requests.get(f"{server_url}/module_log/{case_id}", params=params)
69
+ if response.status_code == 200:
70
+ return response.text
71
+ else:
72
+ print("Fetch module log failed:", response.text)
49
73
  return None
50
74
 
51
75
 
@@ -53,8 +77,7 @@ def upload_and_run_file(zip_path: str, server_url: str, cloud_execution: bool =
53
77
  """Upload a zip file to the server."""
54
78
  with open(zip_path, 'rb') as f:
55
79
  files = {'file': (os.path.basename(zip_path), f)}
56
- data = {'cloud_execution': cloud_execution}
57
- response = requests.post(f"{server_url}/upload_and_run", files=files, data=data)
80
+ response = requests.post(f"{server_url}/upload_and_run", files=files , params={'cloud_execution': cloud_execution} )
58
81
 
59
82
  if response.status_code == 200:
60
83
  print("Upload successful! Waiting for execution.")
@@ -73,14 +96,13 @@ def upload_and_run_file(zip_path: str, server_url: str, cloud_execution: bool =
73
96
  def get_execution_status(execution_id: str, server_url: str, cloud_execution: bool = False) -> Optional[tuple[int, str]]:
74
97
  """Get the status of an execution."""
75
98
  print("Getting status for execution ID:", execution_id)
76
- data = {'cloud_execution': cloud_execution}
77
- response = requests.get(f"{server_url}/status/{execution_id}", data=data)
99
+ response = requests.get(f"{server_url}/status/{execution_id}", params={'cloud_execution': cloud_execution})
78
100
  result = response.status_code == 200
79
101
  return response.json().get('status_id'), response.json().get('status_msg') if result else None
80
102
 
81
103
  def get_results(execution_id, server_url, cloud_execution=False) -> Optional[List[str]]:
82
104
  """Download the results of an execution."""
83
- response = requests.get(f"{server_url}/results/{execution_id}", data={'cloud_execution': cloud_execution})
105
+ response = requests.get(f"{server_url}/results/{execution_id}", params={'cloud_execution': cloud_execution})
84
106
 
85
107
  if response.status_code == 200:
86
108
  print("Results downloaded successfully!")
@@ -94,8 +116,7 @@ def get_results(execution_id, server_url, cloud_execution=False) -> Optional[Lis
94
116
 
95
117
  def download_execution_file(execution_id: str, server_url: str, file: str, download_path: str, cloud_execution: bool = False):
96
118
  """Download the results of an execution."""
97
- data = {'cloud_execution': cloud_execution}
98
- response = requests.get(f"{server_url}/results/{execution_id}/{file}", data=data)
119
+ response = requests.get(f"{server_url}/results/{execution_id}/{file}", params={'cloud_execution': cloud_execution})
99
120
 
100
121
  # TODO: add validation for download_path existence.
101
122
  if response.status_code == 200:
psr/execqueue/config.py CHANGED
@@ -4,6 +4,7 @@ import tomllib
4
4
  __version__ = "0.3.0"
5
5
  _app_name = "PSR Factory ExecQueue"
6
6
  DEFAULT_PORT = 5000
7
+ DEFAULT_HOST = "127.0.0.1"
7
8
  FLASK_DEBUG = False
8
9
  _SETTINGS_FILE_PATH = "server_settings.toml"
9
10
 
@@ -42,3 +43,10 @@ CLOUD_RESULTS_FOLDER = os.path.join(STORAGE_PATH, 'cloud_results')
42
43
  # Where temporary extracted case files will be stored
43
44
  TEMPORARY_UPLOAD_FOLDER = os.path.join(STORAGE_PATH, 'tmp')
44
45
 
46
+ # Optional: modules configuration
47
+ # Expected format in server_settings.toml:
48
+ # [modules.<name>]
49
+ # command = "python some_script.py --case \"{case_path}\""
50
+ # log_file = "<optional fixed log file name>"
51
+ MODULES = settings.get("modules", {})
52
+
psr/execqueue/db.py CHANGED
@@ -62,7 +62,10 @@ class LocalExecution(Base):
62
62
  case_id = Column(String(26), ForeignKey('cases.case_id'))
63
63
  start_time = Column(TIMESTAMP, default=datetime.datetime.utcnow)
64
64
  finish_time = Column(TIMESTAMP)
65
- status = Column(Integer, default=CloudStatus.RUNNING.value)
65
+ status = Column(Integer, default=LOCAL_EXECUTION_RUNNING)
66
+ # Module-related fields
67
+ is_module = Column(Integer, default=0) # 0 = no, 1 = yes
68
+ module = Column(String(128)) # optional module name
66
69
 
67
70
  case = relationship("Case", back_populates="local_executions")
68
71
 
@@ -103,6 +106,23 @@ def initialize():
103
106
  if _create_db:
104
107
  Base.metadata.create_all(engine)
105
108
  _first_time_setup(session)
109
+ else:
110
+ # Basic migration: add columns if missing
111
+ # Note: SQLite supports limited ALTERs; use simple try/except for idempotency
112
+ from sqlalchemy import inspect
113
+ inspector = inspect(engine)
114
+ cols = {c['name'] for c in inspector.get_columns('local_executions')}
115
+ with engine.connect() as conn:
116
+ if 'is_module' not in cols:
117
+ try:
118
+ conn.execute("ALTER TABLE local_executions ADD COLUMN is_module INTEGER DEFAULT 0")
119
+ except Exception:
120
+ pass
121
+ if 'module' not in cols:
122
+ try:
123
+ conn.execute("ALTER TABLE local_executions ADD COLUMN module VARCHAR(128)")
124
+ except Exception:
125
+ pass
106
126
 
107
127
  return session, engine
108
128
 
@@ -127,12 +147,15 @@ def register_case(session, case_id, checksum):
127
147
  return case
128
148
 
129
149
 
130
- def register_local_execution(session, case_id: str, execution_id: str):
150
+ def register_local_execution(session, case_id: str, execution_id: str, *, is_module: int = 0, module: Optional[str] = None):
131
151
  case = session.query(Case).filter(Case.case_id == case_id).first()
132
- local_execution = LocalExecution(execution_id=execution_id,
133
- case_id=case_id,
134
- start_time=datetime.datetime.utcnow()
135
- )
152
+ local_execution = LocalExecution(
153
+ execution_id=execution_id,
154
+ case_id=case_id,
155
+ start_time=datetime.datetime.utcnow(),
156
+ is_module=is_module,
157
+ module=module,
158
+ )
136
159
  case.local_executions.append(local_execution)
137
160
  session.commit()
138
161
  return local_execution
@@ -143,7 +166,7 @@ def get_case_id_from_execution_id(session, execution_id: str) -> Optional[str]:
143
166
  return local_execution.case_id if local_execution else None
144
167
 
145
168
 
146
- def update_local_execution_status(session, execution_id: str, status: int):
169
+ def update_local_execution_status(session, execution_id: str, status: int) -> bool:
147
170
  local_execution = session.query(LocalExecution).filter(LocalExecution.execution_id == execution_id).first()
148
171
  if local_execution:
149
172
  if status not in [LOCAL_EXECUTION_FINISHED, LOCAL_EXECUTION_ERROR,
@@ -154,8 +177,10 @@ def update_local_execution_status(session, execution_id: str, status: int):
154
177
  local_execution.finish_time = datetime.datetime.utcnow()
155
178
  session.commit()
156
179
  return True
180
+ return True
157
181
 
158
- def update_cloud_execution_status(session, repository_id: int, status: int):
182
+
183
+ def update_cloud_execution_status(session, repository_id: int, status: int) -> bool:
159
184
  cloud_execution = session.query(CloudExecution).filter(CloudExecution.repository_id == repository_id).first()
160
185
  if cloud_execution:
161
186
  if CloudStatus(status) not in CloudStatus:
@@ -163,12 +188,49 @@ def update_cloud_execution_status(session, repository_id: int, status: int):
163
188
  cloud_execution.status = status
164
189
  session.commit()
165
190
  return True
166
-
191
+ return False
192
+
193
+
167
194
  def get_local_execution_status(session, execution_id: str) -> Optional[int]:
168
195
  local_execution = session.query(LocalExecution).filter(LocalExecution.execution_id == execution_id).first()
169
196
  return local_execution.status if local_execution else None
170
197
 
171
198
 
199
+ def any_running_modules_for_case(session, case_id: str) -> bool:
200
+ return session.query(LocalExecution).filter(
201
+ LocalExecution.case_id == case_id,
202
+ LocalExecution.is_module == 1,
203
+ LocalExecution.status == LOCAL_EXECUTION_RUNNING
204
+ ).count() > 0
205
+
206
+
207
+ def any_failed_modules_for_case(session, case_id: str) -> bool:
208
+ return session.query(LocalExecution).filter(
209
+ LocalExecution.case_id == case_id,
210
+ LocalExecution.is_module == 1,
211
+ LocalExecution.status == LOCAL_EXECUTION_ERROR
212
+ ).count() > 0
213
+
214
+
215
+ def last_module_execution_for_case(session, case_id: str, module: Optional[str] = None) -> Optional[LocalExecution]:
216
+ q = session.query(LocalExecution).filter(
217
+ LocalExecution.case_id == case_id,
218
+ LocalExecution.is_module == 1
219
+ )
220
+ if module:
221
+ q = q.filter(LocalExecution.module == module)
222
+ return q.order_by(LocalExecution.start_time.desc()).first()
223
+
224
+ def get_distinct_module_names_for_case(session, case_id: str) -> List[str]:
225
+ rows = session.query(LocalExecution.module).filter(
226
+ LocalExecution.case_id == case_id,
227
+ LocalExecution.is_module == 1,
228
+ LocalExecution.module.isnot(None)
229
+ ).distinct().all()
230
+ # rows is a list of tuples [(module,), ...]
231
+ return [r[0] for r in rows if r and r[0]]
232
+
233
+
172
234
  def register_cloud_upload(session, case_id: str, cloud_upload_id: str):
173
235
  case = session.query(Case).filter(Case.case_id == case_id).first()
174
236
  cloud_upload = CloudUpload(cloud_upload_id=cloud_upload_id,