queueflow 0.1.0__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.
Files changed (62) hide show
  1. queueflow/__init__.py +80 -0
  2. queueflow/api/__init__.py +11 -0
  3. queueflow/api/cron_api.py +1808 -0
  4. queueflow/api/dlq_api.py +996 -0
  5. queueflow/api/health_api.py +527 -0
  6. queueflow/api/jobs_api.py +1830 -0
  7. queueflow/api/system_api.py +523 -0
  8. queueflow/api/worker_api.py +1229 -0
  9. queueflow/api/workflows_api.py +1804 -0
  10. queueflow/api_client.py +797 -0
  11. queueflow/api_response.py +21 -0
  12. queueflow/configuration.py +579 -0
  13. queueflow/exceptions.py +199 -0
  14. queueflow/facade.py +255 -0
  15. queueflow/models/__init__.py +57 -0
  16. queueflow/models/backoff_strategy.py +38 -0
  17. queueflow/models/complete_job_request.py +89 -0
  18. queueflow/models/create_batch_jobs_request.py +95 -0
  19. queueflow/models/create_batch_jobs_response.py +90 -0
  20. queueflow/models/create_cron_request.py +111 -0
  21. queueflow/models/create_cron_response.py +87 -0
  22. queueflow/models/create_job_request.py +108 -0
  23. queueflow/models/create_job_response.py +87 -0
  24. queueflow/models/create_workflow_request.py +101 -0
  25. queueflow/models/create_workflow_response.py +87 -0
  26. queueflow/models/cron_schedule.py +134 -0
  27. queueflow/models/dead_letter.py +136 -0
  28. queueflow/models/error_body.py +90 -0
  29. queueflow/models/fail_job_request.py +91 -0
  30. queueflow/models/health_status.py +92 -0
  31. queueflow/models/heartbeat_request.py +90 -0
  32. queueflow/models/heartbeat_response.py +88 -0
  33. queueflow/models/job.py +172 -0
  34. queueflow/models/job_config.py +106 -0
  35. queueflow/models/job_config_request.py +143 -0
  36. queueflow/models/job_status.py +41 -0
  37. queueflow/models/lease_jobs_request.py +107 -0
  38. queueflow/models/lease_jobs_response.py +95 -0
  39. queueflow/models/leased_job.py +93 -0
  40. queueflow/models/list_crons_response.py +115 -0
  41. queueflow/models/list_dead_letters_response.py +115 -0
  42. queueflow/models/list_jobs_response.py +115 -0
  43. queueflow/models/list_workflows_response.py +115 -0
  44. queueflow/models/on_failure.py +38 -0
  45. queueflow/models/on_success.py +36 -0
  46. queueflow/models/ready_status.py +87 -0
  47. queueflow/models/replay_dead_letter_response.py +87 -0
  48. queueflow/models/stats_snapshot.py +102 -0
  49. queueflow/models/step_status.py +41 -0
  50. queueflow/models/tasks_response.py +87 -0
  51. queueflow/models/workflow.py +130 -0
  52. queueflow/models/workflow_diagram_response.py +89 -0
  53. queueflow/models/workflow_status.py +41 -0
  54. queueflow/models/workflow_step.py +112 -0
  55. queueflow/models/workflow_step_state.py +97 -0
  56. queueflow/models/workflow_step_states_response.py +95 -0
  57. queueflow/py.typed +0 -0
  58. queueflow/rest.py +257 -0
  59. queueflow-0.1.0.dist-info/METADATA +26 -0
  60. queueflow-0.1.0.dist-info/RECORD +62 -0
  61. queueflow-0.1.0.dist-info/WHEEL +5 -0
  62. queueflow-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,199 @@
1
+ # coding: utf-8
2
+
3
+ """
4
+ QueueFlow API
5
+
6
+ REST API for QueueFlow, a PostgreSQL-native distributed job queue and workflow engine.
7
+
8
+ The version of the OpenAPI document: 1.0.0
9
+ Generated by OpenAPI Generator (https://openapi-generator.tech)
10
+
11
+ Do not edit the class manually.
12
+ """ # noqa: E501
13
+
14
+ from typing import Any, Optional
15
+ from typing_extensions import Self
16
+
17
+ class OpenApiException(Exception):
18
+ """The base exception class for all OpenAPIExceptions"""
19
+
20
+
21
+ class ApiTypeError(OpenApiException, TypeError):
22
+ def __init__(self, msg, path_to_item=None, valid_classes=None,
23
+ key_type=None) -> None:
24
+ """ Raises an exception for TypeErrors
25
+
26
+ Args:
27
+ msg (str): the exception message
28
+
29
+ Keyword Args:
30
+ path_to_item (list): a list of keys an indices to get to the
31
+ current_item
32
+ None if unset
33
+ valid_classes (tuple): the primitive classes that current item
34
+ should be an instance of
35
+ None if unset
36
+ key_type (bool): False if our value is a value in a dict
37
+ True if it is a key in a dict
38
+ False if our item is an item in a list
39
+ None if unset
40
+ """
41
+ self.path_to_item = path_to_item
42
+ self.valid_classes = valid_classes
43
+ self.key_type = key_type
44
+ full_msg = msg
45
+ if path_to_item:
46
+ full_msg = "{0} at {1}".format(msg, render_path(path_to_item))
47
+ super(ApiTypeError, self).__init__(full_msg)
48
+
49
+
50
+ class ApiValueError(OpenApiException, ValueError):
51
+ def __init__(self, msg, path_to_item=None) -> None:
52
+ """
53
+ Args:
54
+ msg (str): the exception message
55
+
56
+ Keyword Args:
57
+ path_to_item (list) the path to the exception in the
58
+ received_data dict. None if unset
59
+ """
60
+
61
+ self.path_to_item = path_to_item
62
+ full_msg = msg
63
+ if path_to_item:
64
+ full_msg = "{0} at {1}".format(msg, render_path(path_to_item))
65
+ super(ApiValueError, self).__init__(full_msg)
66
+
67
+
68
+ class ApiAttributeError(OpenApiException, AttributeError):
69
+ def __init__(self, msg, path_to_item=None) -> None:
70
+ """
71
+ Raised when an attribute reference or assignment fails.
72
+
73
+ Args:
74
+ msg (str): the exception message
75
+
76
+ Keyword Args:
77
+ path_to_item (None/list) the path to the exception in the
78
+ received_data dict
79
+ """
80
+ self.path_to_item = path_to_item
81
+ full_msg = msg
82
+ if path_to_item:
83
+ full_msg = "{0} at {1}".format(msg, render_path(path_to_item))
84
+ super(ApiAttributeError, self).__init__(full_msg)
85
+
86
+
87
+ class ApiKeyError(OpenApiException, KeyError):
88
+ def __init__(self, msg, path_to_item=None) -> None:
89
+ """
90
+ Args:
91
+ msg (str): the exception message
92
+
93
+ Keyword Args:
94
+ path_to_item (None/list) the path to the exception in the
95
+ received_data dict
96
+ """
97
+ self.path_to_item = path_to_item
98
+ full_msg = msg
99
+ if path_to_item:
100
+ full_msg = "{0} at {1}".format(msg, render_path(path_to_item))
101
+ super(ApiKeyError, self).__init__(full_msg)
102
+
103
+
104
+ class ApiException(OpenApiException):
105
+
106
+ def __init__(
107
+ self,
108
+ status=None,
109
+ reason=None,
110
+ http_resp=None,
111
+ *,
112
+ body: Optional[str] = None,
113
+ data: Optional[Any] = None,
114
+ ) -> None:
115
+ self.status = status
116
+ self.reason = reason
117
+ self.body = body
118
+ self.data = data
119
+ self.headers = None
120
+
121
+ if http_resp:
122
+ if self.status is None:
123
+ self.status = http_resp.status
124
+ if self.reason is None:
125
+ self.reason = http_resp.reason
126
+ if self.body is None:
127
+ try:
128
+ self.body = http_resp.data.decode('utf-8')
129
+ except Exception:
130
+ pass
131
+ self.headers = http_resp.getheaders()
132
+
133
+ @classmethod
134
+ def from_response(
135
+ cls,
136
+ *,
137
+ http_resp,
138
+ body: Optional[str],
139
+ data: Optional[Any],
140
+ ) -> Self:
141
+ if http_resp.status == 400:
142
+ raise BadRequestException(http_resp=http_resp, body=body, data=data)
143
+
144
+ if http_resp.status == 401:
145
+ raise UnauthorizedException(http_resp=http_resp, body=body, data=data)
146
+
147
+ if http_resp.status == 403:
148
+ raise ForbiddenException(http_resp=http_resp, body=body, data=data)
149
+
150
+ if http_resp.status == 404:
151
+ raise NotFoundException(http_resp=http_resp, body=body, data=data)
152
+
153
+ if 500 <= http_resp.status <= 599:
154
+ raise ServiceException(http_resp=http_resp, body=body, data=data)
155
+ raise ApiException(http_resp=http_resp, body=body, data=data)
156
+
157
+ def __str__(self):
158
+ """Custom error messages for exception"""
159
+ error_message = "({0})\n"\
160
+ "Reason: {1}\n".format(self.status, self.reason)
161
+ if self.headers:
162
+ error_message += "HTTP response headers: {0}\n".format(
163
+ self.headers)
164
+
165
+ if self.data or self.body:
166
+ error_message += "HTTP response body: {0}\n".format(self.data or self.body)
167
+
168
+ return error_message
169
+
170
+
171
+ class BadRequestException(ApiException):
172
+ pass
173
+
174
+
175
+ class NotFoundException(ApiException):
176
+ pass
177
+
178
+
179
+ class UnauthorizedException(ApiException):
180
+ pass
181
+
182
+
183
+ class ForbiddenException(ApiException):
184
+ pass
185
+
186
+
187
+ class ServiceException(ApiException):
188
+ pass
189
+
190
+
191
+ def render_path(path_to_item):
192
+ """Returns a string representation of a path"""
193
+ result = ""
194
+ for pth in path_to_item:
195
+ if isinstance(pth, int):
196
+ result += "[{0}]".format(pth)
197
+ else:
198
+ result += "['{0}']".format(pth)
199
+ return result
queueflow/facade.py ADDED
@@ -0,0 +1,255 @@
1
+ """Ergonomic facade for the QueueFlow Python SDK.
2
+
3
+ Hand-written ergonomics layered on the GENERATED core (api clients + models).
4
+ Injected at generation time as a supporting file, so it ships with the
5
+ generated package but is never produced by the raw codegen. The wire types and
6
+ the per-tag api clients come from the generated modules and are never edited.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import time
12
+ from typing import Any, Dict, List, Optional
13
+
14
+ from .api_client import ApiClient
15
+ from .configuration import Configuration
16
+ from .api.jobs_api import JobsApi
17
+ from .api.workflows_api import WorkflowsApi
18
+ from .api.worker_api import WorkerApi
19
+ from .api.system_api import SystemApi
20
+ from .api.health_api import HealthApi
21
+ from .api.cron_api import CronApi
22
+ from .api.dlq_api import DlqApi
23
+ from .models.create_job_request import CreateJobRequest
24
+ from .models.job_config_request import JobConfigRequest
25
+ from .models.create_workflow_request import CreateWorkflowRequest
26
+ from .models.create_cron_request import CreateCronRequest
27
+ from .models.workflow_step import WorkflowStep
28
+ from .models.job import Job
29
+ from .models.workflow import Workflow
30
+
31
+ _TERMINAL_JOB = {"completed", "failed", "cancelled"}
32
+ _TERMINAL_WORKFLOW = {"completed", "failed", "partially_failed", "cancelled"}
33
+
34
+
35
+ class WaitTimeout(TimeoutError):
36
+ """Raised when wait_for_* exceeds its deadline."""
37
+
38
+
39
+ class WorkflowValidationError(ValueError):
40
+ """Raised by WorkflowBuilder.build() for a structurally invalid DAG."""
41
+
42
+
43
+ class QueueFlow:
44
+ """Ergonomic client wrapping the generated api clients.
45
+
46
+ qf = QueueFlow("http://localhost:8000", "dev")
47
+ job = qf.create_job("echo", payload={"hi": 1})
48
+ done = qf.wait_for_job(job.id)
49
+
50
+ The worker-protocol routes (``qf.worker``: lease, heartbeat, complete,
51
+ fail) authenticate with the server's *worker token*, not a tenant token.
52
+ Pass it as ``worker_token=``; without it, ``qf.worker`` reuses the tenant
53
+ token, which only works in the server's development mode.
54
+ """
55
+
56
+ def __init__(self, base_url: str, token: str, worker_token: Optional[str] = None) -> None:
57
+ config = Configuration(host=base_url.rstrip("/"))
58
+ config.access_token = token
59
+ self._client = ApiClient(config)
60
+ self.jobs = JobsApi(self._client)
61
+ self.workflows = WorkflowsApi(self._client)
62
+ if worker_token is not None:
63
+ worker_config = Configuration(host=base_url.rstrip("/"))
64
+ worker_config.access_token = worker_token
65
+ self._worker_client: Optional[ApiClient] = ApiClient(worker_config)
66
+ self.worker = WorkerApi(self._worker_client)
67
+ else:
68
+ self._worker_client = None
69
+ self.worker = WorkerApi(self._client)
70
+ self.cron = CronApi(self._client)
71
+ self.dlq = DlqApi(self._client)
72
+ self.system = SystemApi(self._client)
73
+ self.health_api = HealthApi(self._client)
74
+
75
+ # --- jobs ---------------------------------------------------------------
76
+
77
+ def create_job(
78
+ self,
79
+ task: str,
80
+ payload: Optional[Dict[str, Any]] = None,
81
+ priority: Optional[int] = None,
82
+ max_retries: Optional[int] = None,
83
+ timeout: Optional[int] = None,
84
+ queue: Optional[str] = None,
85
+ retry_backoff: Optional[str] = None,
86
+ retry_delay_secs: Optional[int] = None,
87
+ retry_max_delay_secs: Optional[int] = None,
88
+ jitter_factor: Optional[float] = None,
89
+ idempotency_key: Optional[str] = None,
90
+ run_at: Optional[Any] = None,
91
+ ) -> Job:
92
+ """Enqueue a job and return its freshly-created record.
93
+
94
+ run_at (a datetime) delays the first run: the job is created
95
+ immediately but stays invisible to workers until then.
96
+ """
97
+ config = None
98
+ overrides = (
99
+ priority, max_retries, timeout, queue,
100
+ retry_backoff, retry_delay_secs, retry_max_delay_secs, jitter_factor,
101
+ )
102
+ if any(v is not None for v in overrides):
103
+ config = JobConfigRequest(
104
+ priority=priority,
105
+ max_retries=max_retries,
106
+ timeout=timeout,
107
+ queue=queue,
108
+ retry_backoff=retry_backoff,
109
+ retry_delay_secs=retry_delay_secs,
110
+ retry_max_delay_secs=retry_max_delay_secs,
111
+ jitter_factor=jitter_factor,
112
+ )
113
+ req = CreateJobRequest(task_name=task, payload=payload or {}, config=config, run_at=run_at)
114
+ created = self.jobs.create_job(req, idempotency_key=idempotency_key)
115
+ return self.jobs.get_job(created.job_id)
116
+
117
+ def wait_for_job(self, job_id: str, timeout: float = 60.0, interval: float = 0.5) -> Job:
118
+ """Poll until the job reaches a terminal state (completed/failed/cancelled)."""
119
+ return self._poll(lambda: self.jobs.get_job(job_id), _TERMINAL_JOB, job_id, "job", timeout, interval)
120
+
121
+ # --- workflows ----------------------------------------------------------
122
+
123
+ def create_workflow(self, workflow: "WorkflowBuilder | CreateWorkflowRequest") -> Workflow:
124
+ body = workflow.build() if isinstance(workflow, WorkflowBuilder) else workflow
125
+ created = self.workflows.create_workflow(body)
126
+ return self.workflows.get_workflow(created.workflow_id)
127
+
128
+ def wait_for_workflow(self, workflow_id: str, timeout: float = 60.0, interval: float = 0.5) -> Workflow:
129
+ return self._poll(
130
+ lambda: self.workflows.get_workflow(workflow_id),
131
+ _TERMINAL_WORKFLOW,
132
+ workflow_id,
133
+ "workflow",
134
+ timeout,
135
+ interval,
136
+ )
137
+
138
+ # --- cron & dead letters ------------------------------------------------
139
+
140
+ def create_cron(
141
+ self,
142
+ name: str,
143
+ schedule: str,
144
+ task: str,
145
+ payload: Optional[Dict[str, Any]] = None,
146
+ queue: Optional[str] = None,
147
+ ) -> str:
148
+ """Register a recurring enqueue (5-field crontab, UTC); returns the schedule id."""
149
+ req = CreateCronRequest(
150
+ name=name, cron_expr=schedule, task_name=task, payload=payload or {}, queue=queue
151
+ )
152
+ return self.cron.create_cron(req).cron_id
153
+
154
+ def replay_dead_letter(self, dead_letter_id: int) -> Job:
155
+ """Re-run a dead-lettered job as a fresh job; returns the new job record.
156
+
157
+ Each entry replays at most once (a second replay is a 409).
158
+ """
159
+ replayed = self.dlq.replay_dead_letter(dead_letter_id)
160
+ return self.jobs.get_job(replayed.job_id)
161
+
162
+ # --- internal -----------------------------------------------------------
163
+
164
+ @staticmethod
165
+ def _poll(fetch, terminal, rid, kind, timeout, interval):
166
+ deadline = time.monotonic() + timeout
167
+ while True:
168
+ value = fetch()
169
+ if value.status in terminal:
170
+ return value
171
+ if time.monotonic() + interval > deadline:
172
+ raise WaitTimeout(kind + " " + rid + " did not finish within " + str(timeout) + "s")
173
+ time.sleep(interval)
174
+
175
+
176
+ class WorkflowBuilder:
177
+ """Typed builder for a workflow DAG, with local validation before the round-trip."""
178
+
179
+ def __init__(self, name: str) -> None:
180
+ if not name:
181
+ raise WorkflowValidationError("workflow name is required")
182
+ self.name = name
183
+ self._steps: List[WorkflowStep] = []
184
+ self._context: Dict[str, Any] = {}
185
+ self._metadata: Dict[str, Any] = {}
186
+
187
+ def step(
188
+ self,
189
+ name: str,
190
+ task: str,
191
+ after: Optional[List[str]] = None,
192
+ payload: Optional[Dict[str, Any]] = None,
193
+ on_failure: Optional[str] = None,
194
+ on_success: Optional[str] = None,
195
+ config: Optional[Any] = None,
196
+ ) -> "WorkflowBuilder":
197
+ """Add a step. config is a models.JobConfig override for this step;
198
+ the server fills defaults for any field left unset."""
199
+ self._steps.append(
200
+ WorkflowStep(
201
+ name=name,
202
+ task_name=task,
203
+ depends_on=after or [],
204
+ payload=payload or {},
205
+ on_failure=on_failure,
206
+ on_success=on_success,
207
+ config=config,
208
+ )
209
+ )
210
+ return self
211
+
212
+ def context(self, **kwargs: Any) -> "WorkflowBuilder":
213
+ self._context.update(kwargs)
214
+ return self
215
+
216
+ def build(self) -> CreateWorkflowRequest:
217
+ if not self._steps:
218
+ raise WorkflowValidationError('workflow "' + self.name + '" has no steps')
219
+ names = set()
220
+ for s in self._steps:
221
+ if s.name in names:
222
+ raise WorkflowValidationError('duplicate step name "' + s.name + '"')
223
+ names.add(s.name)
224
+ for s in self._steps:
225
+ for dep in (s.depends_on or []):
226
+ if dep not in names:
227
+ raise WorkflowValidationError('step "' + s.name + '" depends on unknown step "' + dep + '"')
228
+ self._assert_acyclic()
229
+ return CreateWorkflowRequest(
230
+ name=self.name, steps=self._steps, context=self._context, metadata=self._metadata
231
+ )
232
+
233
+ def _assert_acyclic(self) -> None:
234
+ by_name = {s.name: s for s in self._steps}
235
+ visiting, done = set(), set()
236
+
237
+ def visit(node: str, stack: List[str]) -> None:
238
+ if node in done:
239
+ return
240
+ if node in visiting:
241
+ cycle = " -> ".join(stack[stack.index(node):] + [node])
242
+ raise WorkflowValidationError("dependency cycle: " + cycle)
243
+ visiting.add(node)
244
+ for dep in (by_name[node].depends_on or []):
245
+ visit(dep, stack + [node])
246
+ visiting.discard(node)
247
+ done.add(node)
248
+
249
+ for s in self._steps:
250
+ visit(s.name, [])
251
+
252
+
253
+ def wf(name: str) -> WorkflowBuilder:
254
+ """Shorthand for WorkflowBuilder(name)."""
255
+ return WorkflowBuilder(name)
@@ -0,0 +1,57 @@
1
+ # coding: utf-8
2
+
3
+ # flake8: noqa
4
+ """
5
+ QueueFlow API
6
+
7
+ REST API for QueueFlow, a PostgreSQL-native distributed job queue and workflow engine.
8
+
9
+ The version of the OpenAPI document: 1.0.0
10
+ Generated by OpenAPI Generator (https://openapi-generator.tech)
11
+
12
+ Do not edit the class manually.
13
+ """ # noqa: E501
14
+
15
+
16
+ # import models into model package
17
+ from queueflow.models.backoff_strategy import BackoffStrategy
18
+ from queueflow.models.complete_job_request import CompleteJobRequest
19
+ from queueflow.models.create_batch_jobs_request import CreateBatchJobsRequest
20
+ from queueflow.models.create_batch_jobs_response import CreateBatchJobsResponse
21
+ from queueflow.models.create_cron_request import CreateCronRequest
22
+ from queueflow.models.create_cron_response import CreateCronResponse
23
+ from queueflow.models.create_job_request import CreateJobRequest
24
+ from queueflow.models.create_job_response import CreateJobResponse
25
+ from queueflow.models.create_workflow_request import CreateWorkflowRequest
26
+ from queueflow.models.create_workflow_response import CreateWorkflowResponse
27
+ from queueflow.models.cron_schedule import CronSchedule
28
+ from queueflow.models.dead_letter import DeadLetter
29
+ from queueflow.models.error_body import ErrorBody
30
+ from queueflow.models.fail_job_request import FailJobRequest
31
+ from queueflow.models.health_status import HealthStatus
32
+ from queueflow.models.heartbeat_request import HeartbeatRequest
33
+ from queueflow.models.heartbeat_response import HeartbeatResponse
34
+ from queueflow.models.job import Job
35
+ from queueflow.models.job_config import JobConfig
36
+ from queueflow.models.job_config_request import JobConfigRequest
37
+ from queueflow.models.job_status import JobStatus
38
+ from queueflow.models.lease_jobs_request import LeaseJobsRequest
39
+ from queueflow.models.lease_jobs_response import LeaseJobsResponse
40
+ from queueflow.models.leased_job import LeasedJob
41
+ from queueflow.models.list_crons_response import ListCronsResponse
42
+ from queueflow.models.list_dead_letters_response import ListDeadLettersResponse
43
+ from queueflow.models.list_jobs_response import ListJobsResponse
44
+ from queueflow.models.list_workflows_response import ListWorkflowsResponse
45
+ from queueflow.models.on_failure import OnFailure
46
+ from queueflow.models.on_success import OnSuccess
47
+ from queueflow.models.ready_status import ReadyStatus
48
+ from queueflow.models.replay_dead_letter_response import ReplayDeadLetterResponse
49
+ from queueflow.models.stats_snapshot import StatsSnapshot
50
+ from queueflow.models.step_status import StepStatus
51
+ from queueflow.models.tasks_response import TasksResponse
52
+ from queueflow.models.workflow import Workflow
53
+ from queueflow.models.workflow_diagram_response import WorkflowDiagramResponse
54
+ from queueflow.models.workflow_status import WorkflowStatus
55
+ from queueflow.models.workflow_step import WorkflowStep
56
+ from queueflow.models.workflow_step_state import WorkflowStepState
57
+ from queueflow.models.workflow_step_states_response import WorkflowStepStatesResponse
@@ -0,0 +1,38 @@
1
+ # coding: utf-8
2
+
3
+ """
4
+ QueueFlow API
5
+
6
+ REST API for QueueFlow, a PostgreSQL-native distributed job queue and workflow engine.
7
+
8
+ The version of the OpenAPI document: 1.0.0
9
+ Generated by OpenAPI Generator (https://openapi-generator.tech)
10
+
11
+ Do not edit the class manually.
12
+ """ # noqa: E501
13
+
14
+
15
+ from __future__ import annotations
16
+ import json
17
+ from enum import Enum
18
+ from typing_extensions import Self
19
+
20
+
21
+ class BackoffStrategy(str, Enum):
22
+ """
23
+ How retry delays grow between attempts.
24
+ """
25
+
26
+ """
27
+ allowed enum values
28
+ """
29
+ FIXED = 'fixed'
30
+ LINEAR = 'linear'
31
+ EXPONENTIAL = 'exponential'
32
+
33
+ @classmethod
34
+ def from_json(cls, json_str: str) -> Self:
35
+ """Create an instance of BackoffStrategy from a JSON string"""
36
+ return cls(json.loads(json_str))
37
+
38
+
@@ -0,0 +1,89 @@
1
+ # coding: utf-8
2
+
3
+ """
4
+ QueueFlow API
5
+
6
+ REST API for QueueFlow, a PostgreSQL-native distributed job queue and workflow engine.
7
+
8
+ The version of the OpenAPI document: 1.0.0
9
+ Generated by OpenAPI Generator (https://openapi-generator.tech)
10
+
11
+ Do not edit the class manually.
12
+ """ # noqa: E501
13
+
14
+
15
+ from __future__ import annotations
16
+ import pprint
17
+ import re # noqa: F401
18
+ import json
19
+
20
+ from pydantic import BaseModel, ConfigDict, Field, StrictStr
21
+ from typing import Any, ClassVar, Dict, List, Optional
22
+ from typing import Optional, Set
23
+ from typing_extensions import Self
24
+
25
+ class CompleteJobRequest(BaseModel):
26
+ """
27
+ Body for `POST /api/v1/jobs/{id}/complete`.
28
+ """ # noqa: E501
29
+ lease_token: StrictStr = Field(description="The lease token returned by the lease call.")
30
+ result: Optional[Dict[str, Any]] = Field(default=None, description="Handler result, recorded on the job and merged into workflow context.")
31
+ __properties: ClassVar[List[str]] = ["lease_token", "result"]
32
+
33
+ model_config = ConfigDict(
34
+ populate_by_name=True,
35
+ validate_assignment=True,
36
+ protected_namespaces=(),
37
+ )
38
+
39
+
40
+ def to_str(self) -> str:
41
+ """Returns the string representation of the model using alias"""
42
+ return pprint.pformat(self.model_dump(by_alias=True))
43
+
44
+ def to_json(self) -> str:
45
+ """Returns the JSON representation of the model using alias"""
46
+ # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
47
+ return json.dumps(self.to_dict())
48
+
49
+ @classmethod
50
+ def from_json(cls, json_str: str) -> Optional[Self]:
51
+ """Create an instance of CompleteJobRequest from a JSON string"""
52
+ return cls.from_dict(json.loads(json_str))
53
+
54
+ def to_dict(self) -> Dict[str, Any]:
55
+ """Return the dictionary representation of the model using alias.
56
+
57
+ This has the following differences from calling pydantic's
58
+ `self.model_dump(by_alias=True)`:
59
+
60
+ * `None` is only added to the output dict for nullable fields that
61
+ were set at model initialization. Other fields with value `None`
62
+ are ignored.
63
+ """
64
+ excluded_fields: Set[str] = set([
65
+ ])
66
+
67
+ _dict = self.model_dump(
68
+ by_alias=True,
69
+ exclude=excluded_fields,
70
+ exclude_none=True,
71
+ )
72
+ return _dict
73
+
74
+ @classmethod
75
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
76
+ """Create an instance of CompleteJobRequest from a dict"""
77
+ if obj is None:
78
+ return None
79
+
80
+ if not isinstance(obj, dict):
81
+ return cls.model_validate(obj)
82
+
83
+ _obj = cls.model_validate({
84
+ "lease_token": obj.get("lease_token"),
85
+ "result": obj.get("result")
86
+ })
87
+ return _obj
88
+
89
+