fastprocesses 0.22.4__tar.gz → 0.22.6__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 (33) hide show
  1. {fastprocesses-0.22.4 → fastprocesses-0.22.6}/PKG-INFO +2 -2
  2. {fastprocesses-0.22.4 → fastprocesses-0.22.6}/README.md +1 -1
  3. {fastprocesses-0.22.4 → fastprocesses-0.22.6}/pyproject.toml +1 -1
  4. {fastprocesses-0.22.4 → fastprocesses-0.22.6}/src/fastprocesses/api/manager.py +2 -0
  5. {fastprocesses-0.22.4 → fastprocesses-0.22.6}/src/fastprocesses/core/config.py +7 -0
  6. {fastprocesses-0.22.4 → fastprocesses-0.22.6}/src/fastprocesses/worker/celery_app.py +6 -0
  7. {fastprocesses-0.22.4 → fastprocesses-0.22.6}/src/fastprocesses/worker/chord_tasks.py +72 -19
  8. {fastprocesses-0.22.4 → fastprocesses-0.22.6}/src/fastprocesses/worker/pipeline.py +21 -11
  9. {fastprocesses-0.22.4 → fastprocesses-0.22.6}/AUTHORS.md +0 -0
  10. {fastprocesses-0.22.4 → fastprocesses-0.22.6}/src/fastprocesses/__init__.py +0 -0
  11. {fastprocesses-0.22.4 → fastprocesses-0.22.6}/src/fastprocesses/api/__init__.py +0 -0
  12. {fastprocesses-0.22.4 → fastprocesses-0.22.6}/src/fastprocesses/api/router.py +0 -0
  13. {fastprocesses-0.22.4 → fastprocesses-0.22.6}/src/fastprocesses/api/server.py +0 -0
  14. {fastprocesses-0.22.4 → fastprocesses-0.22.6}/src/fastprocesses/common.py +0 -0
  15. {fastprocesses-0.22.4 → fastprocesses-0.22.6}/src/fastprocesses/core/__init__.py +0 -0
  16. {fastprocesses-0.22.4 → fastprocesses-0.22.6}/src/fastprocesses/core/base_process.py +0 -0
  17. {fastprocesses-0.22.4 → fastprocesses-0.22.6}/src/fastprocesses/core/cache.py +0 -0
  18. {fastprocesses-0.22.4 → fastprocesses-0.22.6}/src/fastprocesses/core/exceptions.py +0 -0
  19. {fastprocesses-0.22.4 → fastprocesses-0.22.6}/src/fastprocesses/core/logging.py +0 -0
  20. {fastprocesses-0.22.4 → fastprocesses-0.22.6}/src/fastprocesses/core/models.py +0 -0
  21. {fastprocesses-0.22.4 → fastprocesses-0.22.6}/src/fastprocesses/core/output_protocol.py +0 -0
  22. {fastprocesses-0.22.4 → fastprocesses-0.22.6}/src/fastprocesses/core/output_schema_resolver.py +0 -0
  23. {fastprocesses-0.22.4 → fastprocesses-0.22.6}/src/fastprocesses/core/outputs_handler.py +0 -0
  24. {fastprocesses-0.22.4 → fastprocesses-0.22.6}/src/fastprocesses/core/redis_connection.py +0 -0
  25. {fastprocesses-0.22.4 → fastprocesses-0.22.6}/src/fastprocesses/core/types.py +0 -0
  26. {fastprocesses-0.22.4 → fastprocesses-0.22.6}/src/fastprocesses/processes/__init__.py +0 -0
  27. {fastprocesses-0.22.4 → fastprocesses-0.22.6}/src/fastprocesses/processes/process_registry.py +0 -0
  28. {fastprocesses-0.22.4 → fastprocesses-0.22.6}/src/fastprocesses/py.typed +0 -0
  29. {fastprocesses-0.22.4 → fastprocesses-0.22.6}/src/fastprocesses/static/style.css +0 -0
  30. {fastprocesses-0.22.4 → fastprocesses-0.22.6}/src/fastprocesses/templates/landing.html +0 -0
  31. {fastprocesses-0.22.4 → fastprocesses-0.22.6}/src/fastprocesses/worker/__init__.py +0 -0
  32. {fastprocesses-0.22.4 → fastprocesses-0.22.6}/src/fastprocesses/worker/executors.py +0 -0
  33. {fastprocesses-0.22.4 → fastprocesses-0.22.6}/src/fastprocesses/worker/job_status.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastprocesses
3
- Version: 0.22.4
3
+ Version: 0.22.6
4
4
  Summary: A library to create a FastAPI-based OGC API Processes wrapper around existing projects.
5
5
  License-File: AUTHORS.md
6
6
  Author: Stefan Schuhart
@@ -39,7 +39,7 @@ A library to create a FastAPI-based OGC API Processes wrapper around existing pr
39
39
 
40
40
  AI helped to create this code.
41
41
 
42
- ## Version: 0.22.4
42
+ ## Version: 0.22.6
43
43
 
44
44
  ### Description
45
45
 
@@ -4,7 +4,7 @@ A library to create a FastAPI-based OGC API Processes wrapper around existing pr
4
4
 
5
5
  AI helped to create this code.
6
6
 
7
- ## Version: 0.22.4
7
+ ## Version: 0.22.6
8
8
 
9
9
  ### Description
10
10
 
@@ -79,7 +79,7 @@ exclude = [
79
79
 
80
80
  [tool.poetry]
81
81
  name = "fastprocesses"
82
- version = "0.22.4"
82
+ version = "0.22.6"
83
83
  description = "A library to create a FastAPI-based OGC API Processes wrapper around existing projects."
84
84
  authors = ["Stefan Schuhart <stefan.schuhart@gv.hamburg.de>"]
85
85
  readme = "README.md"
@@ -116,6 +116,7 @@ class AsyncExecutionStrategy(ExecutionStrategy):
116
116
  "processID": process_id,
117
117
  "created": datetime.now(timezone.utc),
118
118
  "progress": 0,
119
+ "message": "Waiting for a worker to pick up the task.",
119
120
  "links": [
120
121
  Link.model_validate(
121
122
  {
@@ -183,6 +184,7 @@ class AsyncExecutionStrategy(ExecutionStrategy):
183
184
  "processID": process_id,
184
185
  "created": datetime.now(timezone.utc),
185
186
  "progress": 0,
187
+ "message": "Waiting for a worker to pick up the task.",
186
188
  "links": [
187
189
  Link.model_validate(
188
190
  {
@@ -128,6 +128,13 @@ class OGCProcessesSettings(BaseSettings):
128
128
  default=10,
129
129
  description="Timeout in seconds for synchronous execution waiting for result."
130
130
  )
131
+ FP_SKIP_INPUT_VALIDATION: bool = Field(
132
+ default=False,
133
+ description=(
134
+ "Skip JSON-Schema input validation on the worker. "
135
+ "Intended for debugging only — never enable in production."
136
+ ),
137
+ )
131
138
  FP_LOG_LEVEL: str = Field(
132
139
  default="INFO",
133
140
  description=(
@@ -89,6 +89,12 @@ def execute_process(self, process_id: str, serialized_data: str | bytes):
89
89
  process_id,
90
90
  job_id,
91
91
  )
92
+ update_job_status(
93
+ job_id,
94
+ 0,
95
+ "Worker started, task picked up.",
96
+ process_id=process_id,
97
+ )
92
98
 
93
99
  # Check cache before running the pipeline — avoids re-execution for
94
100
  # identical inputs (especially important for parallel/scatter processes
@@ -72,10 +72,13 @@ def execute_parallel_item(
72
72
  """
73
73
  item = temp_result_cache.get(item_key)
74
74
  if item is None:
75
- raise RuntimeError(
76
- f"Claim-check data not found in cache for key '{item_key}'. "
77
- "The temp_result_cache entry may have expired or been evicted."
75
+ logger.error(
76
+ "Claim-check data not found for key '{}' (job {}). "
77
+ "Cache entry may have expired or been evicted.",
78
+ item_key,
79
+ job_id,
78
80
  )
81
+ return {"__error__": f"Claim-check data not found in cache for key '{item_key}'"}
79
82
  try:
80
83
  process = cast(
81
84
  BaseParallelProcess,
@@ -99,14 +102,14 @@ def execute_parallel_item(
99
102
  _increment_and_report_progress(job_id, total)
100
103
  return {"__claim_check__": rkey}
101
104
  except Exception as exc:
102
- update_job_status(
105
+ logger.error(
106
+ "Parallel subtask failed for process {} (job {}): {}",
107
+ process_id,
103
108
  job_id,
104
- 0,
105
- f"Parallel subtask failed: {exc}",
106
- JobStatusCode.FAILED,
107
- process_id=process_id,
109
+ exc,
110
+ exc_info=True,
108
111
  )
109
- raise
112
+ return {"__error__": str(exc)}
110
113
 
111
114
 
112
115
  def _run_parallel(
@@ -168,6 +171,29 @@ def finalize_parallel(
168
171
  actual results, caches the final output, and updates the job status.
169
172
  """
170
173
  process = cast(BaseParallelProcess, get_process_registry().get_process(process_id))
174
+
175
+ errors = [r for r in sub_results if isinstance(r, dict) and "__error__" in r]
176
+ if errors:
177
+ error_msg = errors[0]["__error__"]
178
+ update_job_status(
179
+ job_id,
180
+ 0,
181
+ f"A parallel subtask failed: {error_msg}",
182
+ JobStatusCode.FAILED,
183
+ )
184
+ _cleanup_progress_counter(job_id)
185
+ temp_result_cache.delete(meta_key)
186
+ logger.error(
187
+ "Parallel chord failed for process {} (job {}): {}/{} subtask(s) failed. "
188
+ "First error: {}",
189
+ process_id,
190
+ job_id,
191
+ len(errors),
192
+ len(sub_results),
193
+ error_msg,
194
+ )
195
+ return None
196
+
171
197
  try:
172
198
  actual_results: list[dict] = []
173
199
  for result_ref in sub_results:
@@ -242,10 +268,13 @@ def execute_scatter_step(
242
268
  """
243
269
  payload = temp_result_cache.get(payload_key)
244
270
  if payload is None:
245
- raise RuntimeError(
246
- f"Claim-check payload not found in cache for key '{payload_key}'. "
247
- "The temp_result_cache entry may have expired or been evicted."
271
+ logger.error(
272
+ "Claim-check payload not found for key '{}' (job {}). "
273
+ "Cache entry may have expired or been evicted.",
274
+ payload_key,
275
+ job_id,
248
276
  )
277
+ return {"__error__": f"Claim-check payload not found in cache for key '{payload_key}'"}
249
278
  data: dict = payload["step_input"]
250
279
  try:
251
280
  process = cast(
@@ -277,14 +306,15 @@ def execute_scatter_step(
277
306
  _record_scatter_step_done(job_id, step_name, total)
278
307
  return {"__claim_check__": rkey}
279
308
  except Exception as exc:
280
- update_job_status(
309
+ logger.error(
310
+ "Scatter step '{}' failed for process {} (job {}): {}",
311
+ step_name,
312
+ process_id,
281
313
  job_id,
282
- 0,
283
- f"Scatter step '{step_name}' failed: {exc}",
284
- JobStatusCode.FAILED,
285
- process_id=process_id,
314
+ exc,
315
+ exc_info=True,
286
316
  )
287
- raise
317
+ return {"__error__": str(exc), "__step__": step_name}
288
318
 
289
319
 
290
320
  def _run_scatter(
@@ -341,7 +371,7 @@ def finalize_scatter(
341
371
  process_id: str,
342
372
  job_id: str,
343
373
  payload_key: str,
344
- ) -> dict:
374
+ ) -> dict | None:
345
375
  """
346
376
  Chord callback for ``BaseScatterProcess``.
347
377
 
@@ -350,6 +380,29 @@ def finalize_scatter(
350
380
  and updates the job status.
351
381
  """
352
382
  process = cast(BaseScatterProcess, get_process_registry().get_process(process_id))
383
+
384
+ errors = [r for r in step_results if isinstance(r, dict) and "__error__" in r]
385
+ if errors:
386
+ first = errors[0]
387
+ error_msg = (
388
+ f"Step '{first['__step__']}' failed: {first['__error__']}"
389
+ if "__step__" in first
390
+ else first["__error__"]
391
+ )
392
+ update_job_status(job_id, 0, error_msg, JobStatusCode.FAILED)
393
+ _cleanup_scatter_roster(job_id)
394
+ temp_result_cache.delete(payload_key)
395
+ logger.error(
396
+ "Scatter chord failed for process {} (job {}): {}/{} step(s) failed. "
397
+ "First error: {}",
398
+ process_id,
399
+ job_id,
400
+ len(errors),
401
+ len(step_results),
402
+ error_msg,
403
+ )
404
+ return None
405
+
353
406
  try:
354
407
  named_results: dict[str, dict] = {}
355
408
  for step_name, result_ref in zip(step_names, step_results):
@@ -1,6 +1,7 @@
1
1
  import json
2
2
 
3
3
  from fastprocesses.core.base_process import BaseProcess
4
+ from fastprocesses.core.config import OGCProcessesSettings
4
5
  from fastprocesses.core.exceptions import (
5
6
  InputValidationError,
6
7
  ProcessClassNotFoundError,
@@ -49,17 +50,26 @@ def _run_pipeline(
49
50
  Returns the (possibly modified) data dict after remote input resolution.
50
51
  """
51
52
  # Step 1: validate wire-format inputs against the process description schema
52
- update_job_status(
53
- job_id, 0,
54
- "Pre-process step 1: Validating inputs against process description.",
55
- JobStatusCode.RUNNING
56
- )
57
- try:
58
- process.validate_inputs(data["inputs"])
59
- except ValueError as e:
60
- logger.error(f"Input validation failed for process {process_id}: {e}")
61
- update_job_status(job_id, 0, str(e), JobStatusCode.FAILED)
62
- raise InputValidationError(process_id, repr(e))
53
+ settings = OGCProcessesSettings()
54
+ if settings.FP_SKIP_INPUT_VALIDATION:
55
+ logger.warning(
56
+ "FP_SKIP_INPUT_VALIDATION is enabled — skipping input validation "
57
+ "for process {} (job {}). Do not use this in production.",
58
+ process_id,
59
+ job_id,
60
+ )
61
+ else:
62
+ update_job_status(
63
+ job_id, 0,
64
+ "Pre-process step 1: Validating inputs against process description.",
65
+ JobStatusCode.RUNNING
66
+ )
67
+ try:
68
+ process.validate_inputs(data["inputs"])
69
+ except ValueError as e:
70
+ logger.error(f"Input validation failed for process {process_id}: {e}")
71
+ update_job_status(job_id, 0, str(e), JobStatusCode.FAILED)
72
+ raise InputValidationError(process_id, repr(e))
63
73
 
64
74
  # Step 2: resolve remote inputs (URI strings → downloaded data)
65
75
  update_job_status(