fastprocesses 0.19.3__tar.gz → 0.19.4__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 (26) hide show
  1. {fastprocesses-0.19.3 → fastprocesses-0.19.4}/PKG-INFO +2 -2
  2. {fastprocesses-0.19.3 → fastprocesses-0.19.4}/README.md +1 -1
  3. {fastprocesses-0.19.3 → fastprocesses-0.19.4}/pyproject.toml +1 -1
  4. {fastprocesses-0.19.3 → fastprocesses-0.19.4}/src/fastprocesses/api/manager.py +20 -0
  5. {fastprocesses-0.19.3 → fastprocesses-0.19.4}/src/fastprocesses/common.py +1 -0
  6. {fastprocesses-0.19.3 → fastprocesses-0.19.4}/src/fastprocesses/core/base_process.py +1 -1
  7. {fastprocesses-0.19.3 → fastprocesses-0.19.4}/src/fastprocesses/core/config.py +4 -0
  8. {fastprocesses-0.19.3 → fastprocesses-0.19.4}/src/fastprocesses/processes/process_registry.py +1 -1
  9. {fastprocesses-0.19.3 → fastprocesses-0.19.4}/src/fastprocesses/worker/celery_app.py +30 -11
  10. {fastprocesses-0.19.3 → fastprocesses-0.19.4}/AUTHORS.md +0 -0
  11. {fastprocesses-0.19.3 → fastprocesses-0.19.4}/src/fastprocesses/__init__.py +0 -0
  12. {fastprocesses-0.19.3 → fastprocesses-0.19.4}/src/fastprocesses/api/__init__.py +0 -0
  13. {fastprocesses-0.19.3 → fastprocesses-0.19.4}/src/fastprocesses/api/router.py +0 -0
  14. {fastprocesses-0.19.3 → fastprocesses-0.19.4}/src/fastprocesses/api/server.py +0 -0
  15. {fastprocesses-0.19.3 → fastprocesses-0.19.4}/src/fastprocesses/core/__init__.py +0 -0
  16. {fastprocesses-0.19.3 → fastprocesses-0.19.4}/src/fastprocesses/core/cache.py +0 -0
  17. {fastprocesses-0.19.3 → fastprocesses-0.19.4}/src/fastprocesses/core/exceptions.py +0 -0
  18. {fastprocesses-0.19.3 → fastprocesses-0.19.4}/src/fastprocesses/core/logging.py +0 -0
  19. {fastprocesses-0.19.3 → fastprocesses-0.19.4}/src/fastprocesses/core/models.py +0 -0
  20. {fastprocesses-0.19.3 → fastprocesses-0.19.4}/src/fastprocesses/core/redis_connection.py +0 -0
  21. {fastprocesses-0.19.3 → fastprocesses-0.19.4}/src/fastprocesses/core/types.py +0 -0
  22. {fastprocesses-0.19.3 → fastprocesses-0.19.4}/src/fastprocesses/processes/__init__.py +0 -0
  23. {fastprocesses-0.19.3 → fastprocesses-0.19.4}/src/fastprocesses/py.typed +0 -0
  24. {fastprocesses-0.19.3 → fastprocesses-0.19.4}/src/fastprocesses/static/style.css +0 -0
  25. {fastprocesses-0.19.3 → fastprocesses-0.19.4}/src/fastprocesses/templates/landing.html +0 -0
  26. {fastprocesses-0.19.3 → fastprocesses-0.19.4}/src/fastprocesses/worker/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: fastprocesses
3
- Version: 0.19.3
3
+ Version: 0.19.4
4
4
  Summary: A library to create a FastAPI-based OGC API Processes wrapper around existing projects.
5
5
  Author: Stefan Schuhart
6
6
  Author-email: stefan.schuhart@gv.hamburg.de
@@ -38,7 +38,7 @@ A library to create a FastAPI-based OGC API Processes wrapper around existing pr
38
38
 
39
39
  AI helped to create this code.
40
40
 
41
- ## Version: 0.19.3
41
+ ## Version: 0.19.4
42
42
 
43
43
  ### Description
44
44
 
@@ -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.19.3
7
+ ## Version: 0.19.4
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.19.3"
82
+ version = "0.19.4"
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"
@@ -94,6 +94,12 @@ class AsyncExecutionStrategy(ExecutionStrategy):
94
94
  )
95
95
  raise BrokerUnavailableError(str(exc)) from exc
96
96
  send_elapsed = time.monotonic() - send_start
97
+ logger.info(
98
+ "Task queued: process_id={}, job_id={}, mode=async, send_time={:.3f}s",
99
+ process_id,
100
+ task.id,
101
+ send_elapsed,
102
+ )
97
103
  if send_elapsed > 1.0:
98
104
  logger.warning(
99
105
  "Celery async send_task for process_id={} took {:.2f}s",
@@ -155,6 +161,13 @@ class AsyncExecutionStrategy(ExecutionStrategy):
155
161
  )
156
162
  raise BrokerUnavailableError(str(exc)) from exc
157
163
  send_elapsed = time.monotonic() - send_start
164
+ logger.info(
165
+ "Task queued: process_id={}, job_id={},"
166
+ " mode=async (raw), send_time={:.3f}s",
167
+ process_id,
168
+ task.id,
169
+ send_elapsed,
170
+ )
158
171
  if send_elapsed > 1.0:
159
172
  logger.warning(
160
173
  "Celery async send_task for process_id={} took {:.2f}s",
@@ -218,6 +231,13 @@ class SyncExecutionStrategy(ExecutionStrategy):
218
231
  exc,
219
232
  )
220
233
  raise BrokerUnavailableError(str(exc)) from exc
234
+ send_elapsed_sync = time.monotonic() - send_start
235
+ logger.info(
236
+ "Task queued: process_id={}, job_id={}, mode=sync, send_time={:.3f}s",
237
+ process_id,
238
+ task.id,
239
+ send_elapsed_sync,
240
+ )
221
241
  send_elapsed = time.monotonic() - send_start
222
242
  if send_elapsed > 1.0:
223
243
  logger.warning(
@@ -65,6 +65,7 @@ celery_app = Celery(
65
65
  )
66
66
 
67
67
  celery_app.conf.update(
68
+ task_default_queue=settings.FP_CELERY_QUEUE,
68
69
  task_serializer="custom_json",
69
70
  result_serializer="custom_json",
70
71
  accept_content=["custom_json", "json"], # Accept only the custom serializer
@@ -255,7 +255,7 @@ class BaseProcess(ABC):
255
255
 
256
256
  # First, check all provided inputs
257
257
  for input_name, input_value in inputs.items():
258
- logger.info("Validating input '%s'", input_name)
258
+ logger.info("Validating input '{}'", input_name)
259
259
 
260
260
  if input_name not in required_inputs:
261
261
  raise ValueError(
@@ -74,6 +74,10 @@ class OGCProcessesSettings(BaseSettings):
74
74
  FP_CELERY_RESULTS_TTL_DAYS: int = 365
75
75
  FP_CELERY_TASK_TLIMIT_HARD: int = 900 # seconds
76
76
  FP_CELERY_TASK_TLIMIT_SOFT: int = 600 # seconds
77
+ FP_CELERY_QUEUE: str = Field(
78
+ default="celery",
79
+ description="Celery queue name for task routing. Must match the worker -Q flag.",
80
+ )
77
81
  FP_CELERY_JOB_MODE: bool = Field(
78
82
  default=False,
79
83
  description="Enable job mode for graceful shutdown after task completion"
@@ -15,7 +15,7 @@ class ProcessRegistry:
15
15
  """Manages the registration and retrieval of available processs (processes)."""
16
16
 
17
17
  def __init__(self, redis_connection: RedisConnection | None = None):
18
- self.registry_key = "process_registry"
18
+ self.registry_key = f"process_registry:{settings.FP_CELERY_QUEUE}"
19
19
  if redis_connection is None:
20
20
  redis_connection = RedisConnection(str(settings.results_cache.connection))
21
21
  self.redis_connection = redis_connection
@@ -3,6 +3,8 @@ import asyncio
3
3
  import inspect
4
4
  import json
5
5
  import signal
6
+ import time
7
+ import traceback
6
8
  from datetime import datetime, timezone
7
9
  from typing import Any, Dict, cast
8
10
 
@@ -581,12 +583,16 @@ def execute_process(self, process_id: str, serialized_data: str | bytes):
581
583
  serialized_data_str = bytes(serialized_data).decode("utf-8")
582
584
  data: dict = json.loads(serialized_data_str)
583
585
 
584
- logger.info(f"Executing process {process_id} with data {serialized_data[:80]}")
585
586
  job_id = self.request.id # Get the task/job ID
587
+ task_start = time.monotonic()
588
+ logger.info(
589
+ "Worker picked up task: process_id={}, job_id={}",
590
+ process_id,
591
+ job_id,
592
+ )
586
593
 
587
594
  # First: Get the process
588
595
  try:
589
- logger.info(f"Worker retrieving process {process_id} from registry")
590
596
  process = get_process_registry().get_process(process_id)
591
597
  except ValueError as e:
592
598
  job_status = JobStatusCode.FAILED
@@ -602,7 +608,6 @@ def execute_process(self, process_id: str, serialized_data: str | bytes):
602
608
 
603
609
  # Second: validate wire-format inputs against the process description schema
604
610
  try:
605
- logger.info(f"Worker validating inputs for process {process_id}")
606
611
  update_job_status(
607
612
  job_id,
608
613
  0,
@@ -651,7 +656,6 @@ def execute_process(self, process_id: str, serialized_data: str | bytes):
651
656
 
652
657
  # Fourth: Execute the process
653
658
  try:
654
- logger.info("Worker executing process {}", process_id)
655
659
  job_status = JobStatusCode.RUNNING
656
660
  # BUG: if redis returns no job_status, this fails and creates a ValueError too
657
661
  update_job_status(
@@ -701,21 +705,33 @@ def execute_process(self, process_id: str, serialized_data: str | bytes):
701
705
  except Exception as e:
702
706
  # Update job with error status
703
707
  job_status = JobStatusCode.FAILED
708
+ task_elapsed = time.monotonic() - task_start
704
709
 
705
710
  # decide if its a validation error or a general error
706
711
  if isinstance(e, ValueError) or isinstance(e, ValidationError):
707
- logger.error(f"Validation error in process {process_id}: {e}")
712
+ logger.error(
713
+ "Task failed: process_id={}, job_id={}, duration={:.2f}s, reason={}",
714
+ process_id,
715
+ job_id,
716
+ task_elapsed,
717
+ e,
718
+ )
708
719
  job_message = e
709
720
  raise e
710
721
 
711
722
  # Log the full traceback server-side only — never expose internal
712
723
  # class names, source lines, or line numbers to the client.
713
- logger.exception(
714
- "Unhandled exception in process '{}' (job {}): {}",
724
+ logger.error(
725
+ "Task failed: process_id={}, job_id={}, duration={:.2f}s, reason={}",
715
726
  process_id,
716
727
  job_id,
728
+ task_elapsed,
717
729
  e,
718
- exc_info=True,
730
+ )
731
+ logger.debug(
732
+ "Full traceback for job {}:\n{}",
733
+ job_id,
734
+ traceback.format_exc(),
719
735
  )
720
736
 
721
737
  job_message = (
@@ -731,10 +747,13 @@ def execute_process(self, process_id: str, serialized_data: str | bytes):
731
747
  return None
732
748
 
733
749
  if result:
734
- result_dump = jsonable_encoder(result)
750
+ task_elapsed = time.monotonic() - task_start
735
751
  logger.info(
736
- f"Process {process_id} executed "
737
- f"successfully with result {json.dumps(result_dump)[:80]}"
752
+ "Task completed: process_id={}, job_id={},"
753
+ " duration={:.2f}s",
754
+ process_id,
755
+ job_id,
756
+ task_elapsed,
738
757
  )
739
758
  job_status = JobStatusCode.SUCCESSFUL
740
759