fastprocesses 0.7.4__py3-none-any.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.
@@ -0,0 +1,154 @@
1
+ # worker/celery_app.py
2
+ import asyncio
3
+ from datetime import datetime, timezone
4
+ import json
5
+ from typing import Any, Dict
6
+
7
+ from celery import Task
8
+ from pydantic import BaseModel
9
+
10
+ from fastprocesses.common import celery_app, redis_cache
11
+ from fastprocesses.core.logging import logger
12
+ from fastprocesses.core.models import CalculationTask
13
+ from fastprocesses.processes.process_registry import get_process_registry
14
+
15
+ from celery.exceptions import SoftTimeLimitExceeded
16
+
17
+ # NOTE: Cache hash key is based on original unprocessed inputs always
18
+ # this ensures consistent caching and cache retrieval
19
+ # which does not depend on arbitrary processed data, which can change
20
+ # when the process is updated or changed!
21
+
22
+ class CacheResultTask(Task):
23
+ def on_success(
24
+ self, retval: dict | BaseModel,
25
+ task_id, args, kwargs
26
+ ):
27
+ try:
28
+ # Deserialize the original data
29
+ original_data = json.loads(args[1])
30
+ calculation_task = CalculationTask(**original_data)
31
+
32
+ # Get the the hash key for the task
33
+ key = calculation_task.celery_key
34
+
35
+ # Store the result in cache
36
+ # Use the task ID as the key
37
+ redis_cache.put(key=key, value=retval)
38
+
39
+ logger.info(f"Saved result with key {key} to cache: {retval}")
40
+ except Exception as e:
41
+ logger.error(f"Error caching results: {e}")
42
+
43
+
44
+ @celery_app.task(bind=True, name="execute_process", base=CacheResultTask)
45
+ def execute_process(self, process_id: str, serialized_data: Dict[str, Any]):
46
+
47
+ data = json.loads(serialized_data)
48
+
49
+ logger.info(f"Executing process {process_id} with data {data}")
50
+ job_id = self.request.id # Get the task/job ID
51
+
52
+ # Create a progress update function that captures the job_id
53
+ def update_progress(progress: int, message: str = None):
54
+ job_key = f"job:{job_id}"
55
+ job_info = redis_cache.get(job_key) or {}
56
+
57
+ job_info.update(
58
+ {
59
+ "status": "running",
60
+ "progress": progress,
61
+ "updated": datetime.now(timezone.utc),
62
+ "process_id": process_id,
63
+ }
64
+ )
65
+
66
+ if message:
67
+ job_info["message"] = message
68
+
69
+ redis_cache.put(job_key, job_info)
70
+ logger.debug(f"Updated progress for job {job_id}: {progress}%, {message}")
71
+
72
+ try:
73
+ # Initialize progress
74
+ update_progress(0, "Starting process")
75
+
76
+ service = get_process_registry().get_service(process_id)
77
+
78
+ if asyncio.iscoroutinefunction(service.execute):
79
+ result = asyncio.run(
80
+ service.execute(
81
+ data,
82
+ progress_callback=update_progress,
83
+ )
84
+ )
85
+ else:
86
+ result = service.execute(data)
87
+
88
+ logger.info(f"Process {process_id} executed successfully with result {result}")
89
+
90
+ # Mark job as complete
91
+ update_progress(100, "Process completed")
92
+
93
+ return result
94
+ except SoftTimeLimitExceeded as e:
95
+ logger.warning(f"Task {job_id} hit the soft time limit: {e}")
96
+ # Attempt to resume the process
97
+ try:
98
+ if asyncio.iscoroutinefunction(service.execute):
99
+ result = asyncio.run(service.execute(data))
100
+ else:
101
+ result = service.execute(data)
102
+
103
+ logger.info(f"Process {process_id} completed after soft time limit")
104
+ update_progress(100, "Process completed after soft time limit")
105
+ return result
106
+ except Exception as inner_exception:
107
+ logger.error(
108
+ f"Error while completing task after soft time limit: {inner_exception}"
109
+ )
110
+ raise inner_exception
111
+
112
+ except Exception as e:
113
+ # Update job with error status
114
+ job_key = f"job:{job_id}"
115
+ job_info = redis_cache.get(job_key) or {}
116
+ job_info.update(
117
+ {
118
+ "status": "failed",
119
+ "message": str(e),
120
+ "updated": datetime.now(timezone.utc),
121
+ "process_id": process_id,
122
+ }
123
+ )
124
+ redis_cache.put(job_key, job_info)
125
+
126
+ logger.error(f"Error executing process {process_id}: {e}")
127
+ raise
128
+
129
+
130
+ @celery_app.task(name="check_cache")
131
+ def check_cache(calculation_task: Dict[str, Any]) -> Dict[str, Any]:
132
+ """
133
+ Check if results exist in cache and return status
134
+ """
135
+ task = CalculationTask(**calculation_task)
136
+ cached_result = redis_cache.get(key=task.celery_key)
137
+
138
+ if cached_result:
139
+ logger.info(f"Cache hit for key {task.celery_key}")
140
+ return {"status": "HIT", "result": cached_result}
141
+
142
+ logger.info(f"Cache miss for key {task.celery_key}")
143
+ return {"status": "MISS"}
144
+
145
+
146
+ @celery_app.task(name="find_result_in_cache")
147
+ def find_result_in_cache(celery_key: str) -> dict | None:
148
+ """
149
+ Retrieve result from cache
150
+ """
151
+ result = redis_cache.get(key=celery_key)
152
+ if result:
153
+ logger.info(f"Retrieved result from cache for key {celery_key}")
154
+ return result
@@ -0,0 +1 @@
1
+ # Authors
@@ -0,0 +1,274 @@
1
+ Metadata-Version: 2.3
2
+ Name: fastprocesses
3
+ Version: 0.7.4
4
+ Summary: A library to create a FastAPI-based OGC API Processes wrapper around existing projects.
5
+ Author: Stefan Schuhart
6
+ Author-email: stefan.schuhart@gv.hamburg.de
7
+ Requires-Python: >=3.10
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Programming Language :: Python
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3 :: Only
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Software Development
17
+ Classifier: Topic :: Utilities
18
+ Classifier: Typing :: Typed
19
+ Requires-Dist: celery (>=5.4.0,<6.0.0)
20
+ Requires-Dist: fastapi (>=0.115.8,<0.116.0)
21
+ Requires-Dist: loguru (>=0.7.3,<0.8.0)
22
+ Requires-Dist: pydantic (>=2.10.6,<3.0.0)
23
+ Requires-Dist: pydantic-settings (>=2.7.1,<3.0.0)
24
+ Requires-Dist: redis (>=5.2.1,<6.0.0)
25
+ Requires-Dist: uvicorn (>=0.34.0,<0.35.0)
26
+ Project-URL: Changelog, https://github.com/geowerkstatt-hamburg/fastProcesses/CHANGELOG.md
27
+ Project-URL: Documentation, https://github.com/geowerkstatt-hamburg/fastProcesses/README.md
28
+ Project-URL: Homepage, https://geowerkstatt-hamburg.github.io/fastProcesses
29
+ Project-URL: Repository, https://github.com/geowerkstatt-hamburg/fastProcesses
30
+ Description-Content-Type: text/markdown
31
+
32
+ # fastprocesses
33
+
34
+ A library to create a FastAPI-based OGC API Processes wrapper around existing projects. This library simplifies the process of defining and registering processes, making it easy to build and deploy OGC API Processes.
35
+
36
+ AI was used to create this code.
37
+
38
+ ## Version: 0.7.4
39
+
40
+ ### Description
41
+
42
+ fastprocesses is a Python library that provides a simple and efficient way to create OGC API Processes using FastAPI. It allows you to define processes, register them, and expose them through a FastAPI application with minimal effort, following the OGC API Processes 1.0.0 specification.
43
+
44
+ ### Features
45
+
46
+ - **OGC API Processes Compliance**: Fully implements the OGC API Processes 1.0.0 Core specification
47
+ - **FastAPI Integration**: Leverages FastAPI for building high-performance APIs
48
+ - **Process Management**: Supports both synchronous and asynchronous process execution
49
+ - **Job Control**: Implements job control options (sync-execute, async-execute)
50
+ - **Output Handling**: Supports various output transmission modes (value, reference)
51
+ - **Result Caching**: Built-in Redis-based caching for process results
52
+ - **Celery Integration**: Asynchronous task processing using Celery
53
+ - **Pydantic Models**: Strong type validation for process inputs and outputs
54
+ - **Logging**: Uses `loguru` for modern logging with rotation support
55
+
56
+ ### Architecture
57
+
58
+ ```mermaid
59
+ graph TB
60
+ subgraph Client
61
+ CLI[Client Request]
62
+ end
63
+
64
+ subgraph FastAPI Application
65
+ API[OGCProcessesAPI]
66
+ Router[API Router]
67
+ PM[ProcessManager]
68
+ PR[ProcessRegistry]
69
+ end
70
+
71
+ subgraph Redis
72
+ RC[Redis Cache]
73
+ RR[Redis Registry]
74
+ end
75
+
76
+ subgraph Process
77
+ BP[BaseProcess]
78
+ SP[SimpleProcess]
79
+ end
80
+
81
+ subgraph Worker
82
+ CW[Celery Worker]
83
+ CT[CacheResultTask]
84
+ end
85
+
86
+ %% Client interactions
87
+ CLI -->|HTTP Request| API
88
+ API -->|Route Request| Router
89
+ Router -->|Execute Process| PM
90
+
91
+ %% Process Manager flow
92
+ PM -->|Get Process| PR
93
+ PM -->|Check Cache| RC
94
+ PM -->|Submit Task| CW
95
+ PM -->|Get Result| RC
96
+
97
+ %% Process Registry
98
+ PR -->|Store/Retrieve| RR
99
+ PR -.->|Registers| SP
100
+ SP -->|Inherits| BP
101
+
102
+ %% Worker flow
103
+ CW -->|Execute| SP
104
+ CW -->|Cache Result| CT
105
+ CT -->|Store| RC
106
+
107
+ %% Styling
108
+ classDef api fill:#f9f,stroke:#333,stroke-width:2px
109
+ classDef cache fill:#bbf,stroke:#333,stroke-width:2px
110
+ classDef process fill:#bfb,stroke:#333,stroke-width:2px
111
+ classDef worker fill:#fbb,stroke:#333,stroke-width:2px
112
+
113
+ class API,Router api
114
+ class RC,RR cache
115
+ class BP,SP process
116
+ class CW,CT worker
117
+ ```
118
+
119
+ ### Usage
120
+
121
+ 1. **Define a Process**: Create a new process by subclassing `BaseProcess` and using the `@register_process` decorator.
122
+
123
+ ```python
124
+ from fastprocesses.core.base_process import BaseProcess
125
+ from fastprocesses.core.models import (
126
+ ProcessDescription,
127
+ ProcessInput,
128
+ ProcessJobControlOptions,
129
+ ProcessOutput,
130
+ ProcessOutputTransmission,
131
+ Schema,
132
+ )
133
+ from fastprocesses.processes.process_registry import register_process
134
+
135
+ @register_process("simple_process")
136
+ class SimpleProcess(BaseProcess):
137
+ # Define process description as a class variable
138
+ process_description = ProcessDescription(
139
+ id="simple_process",
140
+ title="Simple Process",
141
+ version="1.0.0",
142
+ description="A simple example process",
143
+ jobControlOptions=[
144
+ ProcessJobControlOptions.SYNC_EXECUTE,
145
+ ProcessJobControlOptions.ASYNC_EXECUTE
146
+ ],
147
+ outputTransmission=[
148
+ ProcessOutputTransmission.VALUE
149
+ ],
150
+ inputs={
151
+ "input_text": ProcessInput(
152
+ title="Input Text",
153
+ description="Text to process",
154
+ schema=Schema(
155
+ type="string",
156
+ minLength=1,
157
+ maxLength=1000
158
+ )
159
+ )
160
+ },
161
+ outputs={
162
+ "output_text": ProcessOutput(
163
+ title="Output Text",
164
+ description="Processed text",
165
+ schema=Schema(
166
+ type="string"
167
+ )
168
+ )
169
+ },
170
+ keywords=["text", "processing"],
171
+ metadata={
172
+ "created": "2024-02-19",
173
+ "provider": "Example Organization"
174
+ }
175
+ )
176
+
177
+ async def execute(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
178
+ input_text = inputs["inputs"]["input_text"]
179
+ output_text = input_text.upper()
180
+ return {"output_text": output_text}
181
+ ```
182
+
183
+ 2. **Create the FastAPI Application**:
184
+
185
+ ```python
186
+ import uvicorn
187
+ from fastprocesses.api.server import OGCProcessesAPI
188
+
189
+ app = OGCProcessesAPI(
190
+ title="Simple Process API",
191
+ version="1.0.0",
192
+ description="A simple API for running processes"
193
+ ).get_app()
194
+
195
+ if __name__ == "__main__":
196
+ uvicorn.run(app, host="0.0.0.0", port=8000)
197
+ ```
198
+
199
+ 3. **Start the Services**:
200
+
201
+ Start Redis (required for caching and Celery):
202
+ ```bash
203
+ docker run -d -p 6379:6379 redis
204
+ ```
205
+
206
+ Start the Celery worker:
207
+ ```bash
208
+ celery -A fastprocesses.worker.celery_app worker
209
+ ```
210
+
211
+ Start the FastAPI application:
212
+ ```bash
213
+ poetry run python examples/run_example.py
214
+ ```
215
+
216
+ 4. **Use the API**:
217
+
218
+ Execute a process (async):
219
+ ```bash
220
+ curl -X POST "http://localhost:8000/processes/simple_process/execution" \
221
+ -H "Content-Type: application/json" \
222
+ -d '{
223
+ "inputs": {
224
+ "input_text": "hello world"
225
+ },
226
+ "mode": "async"
227
+ }'
228
+ ```
229
+
230
+ Execute a process (sync):
231
+ ```bash
232
+ curl -X POST "http://localhost:8000/processes/simple_process/execution" \
233
+ -H "Content-Type: application/json" \
234
+ -d '{
235
+ "inputs": {
236
+ "input_text": "hello world"
237
+ },
238
+ "mode": "sync"
239
+ }'
240
+ ```
241
+
242
+ ### API Endpoints
243
+
244
+ - `GET /`: Landing page
245
+ - `GET /conformance`: OGC API conformance declaration
246
+ - `GET /processes`: List available processes
247
+ - `GET /processes/{process_id}`: Get process description
248
+ - `POST /processes/{process_id}/execution`: Execute a process
249
+ - `GET /jobs`: List all jobs
250
+ - `GET /jobs/{job_id}`: Get job status
251
+ - `GET /jobs/{job_id}/results`: Get job results
252
+
253
+ ### Configuration
254
+
255
+ The library can be configured using environment variables:
256
+
257
+ ```bash
258
+ REDIS_CACHE_URL=redis://localhost:6379/0
259
+ CELERY_BROKER_URL=redis://localhost:6379/1
260
+ CELERY_RESULT_BACKEND=redis://localhost:6379/2
261
+ ```
262
+
263
+ ### Notes:
264
+ How to serialize pydantic models within celery? -> https://benninger.ca/posts/celery-serializer-pydantic/
265
+
266
+ !IMPORTANT!: Cache hash key is based on original unprocessed inputs always this ensures consistent caching and cache retrieval which does not depend on arbitrary processed data, which can change when the process is updated or changed!
267
+
268
+ ### Version Notes
269
+ - **Version: 0.7.4**: added paging to processes and jobs, including limit and offset query params
270
+ - **0.5.0**: Extended Schema model
271
+ - **0.4.0**: Added full OGC API Processes 1.0.0 Core compliance
272
+ - **0.3.0**: Added job control and output transmission options
273
+ - **0.2.0**: Added Redis caching and Celery integration
274
+ - **0.1.0**: Initial release with basic process support
@@ -0,0 +1,23 @@
1
+ fastprocesses/__init__.py,sha256=OG7vLPTeVifB2d4TZn55ZGUwSnPvzGIjjgwXtOh69KA,181
2
+ fastprocesses/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ fastprocesses/api/manager.py,sha256=wDxWd8nwu4xvOf4tJzyqFP6wiFrK4JbC3CjIydoQsm0,13850
4
+ fastprocesses/api/router.py,sha256=uymoKUoqCRtvPhK-qI1VJJZIVdyJEMhc0T8zDtSa5ks,6265
5
+ fastprocesses/api/server.py,sha256=9egolkONiMYDGlRsAFCPB3Zf2ayCjeS-XVdLAll77w8,671
6
+ fastprocesses/celery_worker.py,sha256=2BF7pey9nVrRffEqZJD2jCqNMxP8qsCLQejFng537wc,1211
7
+ fastprocesses/common.py,sha256=sZn8B6RfsvG7xwgMnsq8_svyGBOrndHgG6pW9Ozfmq0,1777
8
+ fastprocesses/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ fastprocesses/core/base_process.py,sha256=sAj_RfG3q9Pv2c7hhKu66NH4oIMlwhHhA_Hu8FH0MJI,5099
10
+ fastprocesses/core/cache.py,sha256=hZzJcpCYqP94hlllv5cyYTIZS0-aXq5kxHrXBDasoRc,1932
11
+ fastprocesses/core/config.py,sha256=I9hIKbGXUWEkSgDSS9KuGAZ4pEu9fwTE3AczRmdNct4,569
12
+ fastprocesses/core/logging.py,sha256=GpCQn5bvq-hVxtYMzVCYoHvZF37hiI2q4Kpj7vys9Io,1816
13
+ fastprocesses/core/models.py,sha256=67SVAQvh-K-tjlt_t79PcPc7ZJ4YYUGzRnoliqcHi2M,4796
14
+ fastprocesses/processes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ fastprocesses/processes/process_registry.py,sha256=ugsXD0Rj5-Snj--e1FeIzg9CVKhRFlllJVXupBcURr0,4267
16
+ fastprocesses/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
+ fastprocesses/worker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
+ fastprocesses/worker/celery_app.py,sha256=KEuulMqUp29zgD_XB5fsD49eI4qcU96YfWVpqxQ8Z5c,5058
19
+ fastprocesses-0.7.4.dist-info/AUTHORS.md,sha256=F8zDIs87H_lW-w4iCvDHMfX84oMW6x4AGtK1Rd4hGBY,9
20
+ fastprocesses-0.7.4.dist-info/METADATA,sha256=cf7MfKSgPNWk-OlkspYE3Wiu0ShEjkAF42A-oHpch_s,8478
21
+ fastprocesses-0.7.4.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
22
+ fastprocesses-0.7.4.dist-info/entry_points.txt,sha256=MeSG-KBsVp1wDIvyaatauFal78lgb-0VJUFRsPZ5pkw,72
23
+ fastprocesses-0.7.4.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 2.1.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ start-celery-worker=fastprocesses.celery_worker:main
3
+