pulp-engine 0.85.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.
- pulp_engine/__init__.py +48 -0
- pulp_engine/_http.py +230 -0
- pulp_engine/client.py +96 -0
- pulp_engine/errors.py +142 -0
- pulp_engine/pagination.py +51 -0
- pulp_engine/py.typed +0 -0
- pulp_engine/resources/__init__.py +25 -0
- pulp_engine/resources/admin.py +71 -0
- pulp_engine/resources/assets.py +68 -0
- pulp_engine/resources/audit_events.py +62 -0
- pulp_engine/resources/auth.py +26 -0
- pulp_engine/resources/batch.py +146 -0
- pulp_engine/resources/health.py +23 -0
- pulp_engine/resources/pdf_transform.py +89 -0
- pulp_engine/resources/render.py +309 -0
- pulp_engine/resources/schedules.py +95 -0
- pulp_engine/resources/templates.py +161 -0
- pulp_engine/types.py +394 -0
- pulp_engine-0.85.0.dist-info/METADATA +217 -0
- pulp_engine-0.85.0.dist-info/RECORD +21 -0
- pulp_engine-0.85.0.dist-info/WHEEL +4 -0
pulp_engine/types.py
ADDED
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
"""Pydantic models mirroring the TS SDK's response types."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Literal
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class _Base(BaseModel):
|
|
11
|
+
"""Base model that ignores unknown fields for forward compatibility."""
|
|
12
|
+
|
|
13
|
+
model_config = ConfigDict(extra="ignore")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# ── Templates ─────────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TemplateSummary(_Base):
|
|
20
|
+
"""Template summary returned by list and create operations."""
|
|
21
|
+
|
|
22
|
+
key: str
|
|
23
|
+
name: str
|
|
24
|
+
current_version: str = Field(alias="currentVersion")
|
|
25
|
+
description: str | None = None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class TemplateWithDefinition(_Base):
|
|
29
|
+
"""Full template with definition returned by get operations."""
|
|
30
|
+
|
|
31
|
+
key: str
|
|
32
|
+
name: str
|
|
33
|
+
current_version: str = Field(alias="currentVersion")
|
|
34
|
+
description: str | None = None
|
|
35
|
+
definition: dict[str, Any]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class VersionSummary(_Base):
|
|
39
|
+
"""Version summary returned by version history listing."""
|
|
40
|
+
|
|
41
|
+
version: str
|
|
42
|
+
created_at: str = Field(alias="createdAt")
|
|
43
|
+
created_by: str | None = Field(default=None, alias="createdBy")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ValidationIssue(_Base):
|
|
47
|
+
message: str
|
|
48
|
+
path: str | None = None
|
|
49
|
+
code: str | None = None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ValidationResult(_Base):
|
|
53
|
+
"""Validation result returned by the validate endpoint."""
|
|
54
|
+
|
|
55
|
+
valid: bool
|
|
56
|
+
issues: list[ValidationIssue]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# ── Template diff ─────────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class TemplateDiffPropertyChange(_Base):
|
|
63
|
+
"""Single property-level change in a TemplateDiff."""
|
|
64
|
+
|
|
65
|
+
path: str
|
|
66
|
+
before: Any = None
|
|
67
|
+
after: Any = None
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class TemplateDiffNodeChange(_Base):
|
|
71
|
+
"""Structural change to a single AST node in a TemplateDiff."""
|
|
72
|
+
|
|
73
|
+
node_id: str = Field(alias="nodeId")
|
|
74
|
+
node_type: str = Field(alias="nodeType")
|
|
75
|
+
kind: Literal["added", "removed", "modified", "moved"]
|
|
76
|
+
from_path: str | None = Field(default=None, alias="fromPath")
|
|
77
|
+
to_path: str | None = Field(default=None, alias="toPath")
|
|
78
|
+
properties: list[TemplateDiffPropertyChange] | None = None
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class TemplateDiffDiagnostic(_Base):
|
|
82
|
+
"""Non-fatal diagnostic surfaced by the diff engine."""
|
|
83
|
+
|
|
84
|
+
level: Literal["warn"]
|
|
85
|
+
message: str
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class TemplateDiffSummary(_Base):
|
|
89
|
+
"""Aggregate counts for a TemplateDiff."""
|
|
90
|
+
|
|
91
|
+
nodes_added: int = Field(alias="nodesAdded")
|
|
92
|
+
nodes_removed: int = Field(alias="nodesRemoved")
|
|
93
|
+
nodes_modified: int = Field(alias="nodesModified")
|
|
94
|
+
nodes_moved: int = Field(alias="nodesMoved")
|
|
95
|
+
properties_changed: int = Field(alias="propertiesChanged")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class TemplateDiff(_Base):
|
|
99
|
+
"""Structural diff between two stored versions of a template.
|
|
100
|
+
|
|
101
|
+
Mirrors the ``TemplateDiff`` envelope from ``@pulp-engine/template-diff``.
|
|
102
|
+
Returned by :meth:`TemplatesResource.diff`.
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
template_key: str = Field(alias="templateKey")
|
|
106
|
+
metadata_changes: list[TemplateDiffPropertyChange] = Field(alias="metadataChanges")
|
|
107
|
+
render_config_changes: list[TemplateDiffPropertyChange] = Field(
|
|
108
|
+
alias="renderConfigChanges"
|
|
109
|
+
)
|
|
110
|
+
input_schema_changes: list[TemplateDiffPropertyChange] = Field(
|
|
111
|
+
alias="inputSchemaChanges"
|
|
112
|
+
)
|
|
113
|
+
field_mapping_changes: list[TemplateDiffPropertyChange] = Field(
|
|
114
|
+
alias="fieldMappingChanges"
|
|
115
|
+
)
|
|
116
|
+
document_root_changes: list[TemplateDiffPropertyChange] = Field(
|
|
117
|
+
alias="documentRootChanges"
|
|
118
|
+
)
|
|
119
|
+
document_changes: list[TemplateDiffNodeChange] = Field(alias="documentChanges")
|
|
120
|
+
diagnostics: list[TemplateDiffDiagnostic]
|
|
121
|
+
summary: TemplateDiffSummary
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
# ── Render ────────────────────────────────────────────────────────────────
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class RenderOptions(_Base):
|
|
128
|
+
paper_size: Literal["A4", "A3", "Letter", "Legal", "Tabloid"] | None = Field(
|
|
129
|
+
default=None, alias="paperSize"
|
|
130
|
+
)
|
|
131
|
+
orientation: Literal["portrait", "landscape"] | None = None
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# ── Dry run (Stage 3) ─────────────────────────────────────────────────────
|
|
135
|
+
#
|
|
136
|
+
# Mirrors the TS SDK's DryRunResult / DryRunExpressionLocation /
|
|
137
|
+
# DryRunExpressionSuggestion / DryRunExpressionError types. Returned by
|
|
138
|
+
# RenderResource.dry_run() — never raises on template author errors,
|
|
139
|
+
# surfaces them in the result so callers can display them to users.
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class DryRunValidationError(_Base):
|
|
143
|
+
"""Single schema validation failure from a dry-run result."""
|
|
144
|
+
|
|
145
|
+
path: str
|
|
146
|
+
message: str
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class DryRunExpressionLocation(_Base):
|
|
150
|
+
"""Structured location of a template expression failure (mirrors API response)."""
|
|
151
|
+
|
|
152
|
+
node_id: str = Field(alias="nodeId")
|
|
153
|
+
node_type: str = Field(alias="nodeType")
|
|
154
|
+
node_path: str = Field(alias="nodePath")
|
|
155
|
+
line: int | None = None
|
|
156
|
+
column: int | None = None
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class DryRunExpressionSuggestion(_Base):
|
|
160
|
+
'''"Did you mean?" suggestion for an undefined Handlebars variable.'''
|
|
161
|
+
|
|
162
|
+
variable: str
|
|
163
|
+
suggestions: list[str]
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class DryRunExpressionError(_Base):
|
|
167
|
+
"""A single template expression failure from a dry-run result."""
|
|
168
|
+
|
|
169
|
+
message: str
|
|
170
|
+
location: DryRunExpressionLocation | None = None
|
|
171
|
+
suggestion: DryRunExpressionSuggestion | None = None
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class DryRunValidationSection(_Base):
|
|
175
|
+
valid: bool
|
|
176
|
+
errors: list[DryRunValidationError]
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class DryRunExpressionsSection(_Base):
|
|
180
|
+
valid: bool
|
|
181
|
+
errors: list[DryRunExpressionError]
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class DryRunResult(_Base):
|
|
185
|
+
"""Result of a dry-run render — JSON envelope, never raises on template errors.
|
|
186
|
+
|
|
187
|
+
Returned by :meth:`RenderResource.dry_run`. Same shape as the TS SDK's
|
|
188
|
+
``DryRunResult``. ``valid`` is true iff both ``validation.valid`` and
|
|
189
|
+
``expressions.valid`` are true.
|
|
190
|
+
"""
|
|
191
|
+
|
|
192
|
+
dry_run: Literal[True] = Field(alias="dryRun")
|
|
193
|
+
valid: bool
|
|
194
|
+
validation: DryRunValidationSection
|
|
195
|
+
expressions: DryRunExpressionsSection
|
|
196
|
+
field_mappings_applied: int = Field(alias="fieldMappingsApplied")
|
|
197
|
+
data_sources_resolved: int = Field(alias="dataSourcesResolved")
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
# ── Batch ─────────────────────────────────────────────────────────────────
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class BatchItemError(_Base):
|
|
204
|
+
"""A failed batch item's error envelope (API-4), mirroring the
|
|
205
|
+
single-render error contract. Present only when ``success`` is False."""
|
|
206
|
+
|
|
207
|
+
message: str
|
|
208
|
+
code: str | None = None
|
|
209
|
+
details: object | None = None
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
class BatchResultItem(_Base):
|
|
213
|
+
index: int
|
|
214
|
+
template: str
|
|
215
|
+
success: bool
|
|
216
|
+
pdf: str | None = None
|
|
217
|
+
docx: str | None = None
|
|
218
|
+
error: BatchItemError | None = None
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class BatchResult(_Base):
|
|
222
|
+
total: int
|
|
223
|
+
succeeded: int
|
|
224
|
+
failed: int
|
|
225
|
+
results: list[BatchResultItem]
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
class AsyncJobAccepted(_Base):
|
|
229
|
+
job_id: str = Field(alias="jobId")
|
|
230
|
+
status: Literal["pending"]
|
|
231
|
+
created_at: str = Field(alias="createdAt")
|
|
232
|
+
poll_url: str = Field(alias="pollUrl")
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
class AsyncJobStatus(_Base):
|
|
236
|
+
job_id: str = Field(alias="jobId")
|
|
237
|
+
status: Literal["pending", "processing", "completed", "failed"]
|
|
238
|
+
created_at: str = Field(alias="createdAt")
|
|
239
|
+
completed_at: str | None = Field(default=None, alias="completedAt")
|
|
240
|
+
format: Literal["pdf", "docx"]
|
|
241
|
+
item_count: int = Field(alias="itemCount")
|
|
242
|
+
webhook_delivered: bool = Field(alias="webhookDelivered")
|
|
243
|
+
results: BatchResult | None = None
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
# ── Assets ────────────────────────────────────────────────────────────────
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
class AssetReference(_Base):
|
|
250
|
+
key: str
|
|
251
|
+
name: str
|
|
252
|
+
current_version: str = Field(alias="currentVersion")
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
class AssetRecord(_Base):
|
|
256
|
+
id: str
|
|
257
|
+
filename: str
|
|
258
|
+
original_name: str = Field(alias="originalName")
|
|
259
|
+
mime_type: str = Field(alias="mimeType")
|
|
260
|
+
size_bytes: int = Field(alias="sizeBytes")
|
|
261
|
+
url: str
|
|
262
|
+
created_at: str = Field(alias="createdAt")
|
|
263
|
+
created_by: str | None = Field(default=None, alias="createdBy")
|
|
264
|
+
referenced_by: list[AssetReference] | None = Field(default=None, alias="referencedBy")
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
# ── Audit events ──────────────────────────────────────────────────────────
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
class AuditEvent(_Base):
|
|
271
|
+
id: str
|
|
272
|
+
timestamp: str
|
|
273
|
+
event: str
|
|
274
|
+
operation: str
|
|
275
|
+
resource_type: str | None = Field(default=None, alias="resourceType")
|
|
276
|
+
resource_id: str | None = Field(default=None, alias="resourceId")
|
|
277
|
+
actor: str | None = None
|
|
278
|
+
credential_scope: str | None = Field(default=None, alias="credentialScope")
|
|
279
|
+
details: dict[str, Any] | None = None
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
# ── Admin ─────────────────────────────────────────────────────────────────
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
class UserRecord(_Base):
|
|
286
|
+
id: str
|
|
287
|
+
display_name: str = Field(alias="displayName")
|
|
288
|
+
role: Literal["editor", "admin"]
|
|
289
|
+
key_hint: str = Field(alias="keyHint")
|
|
290
|
+
token_issued_after: str | None = Field(default=None, alias="tokenIssuedAfter")
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
# ── Auth ──────────────────────────────────────────────────────────────────
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
class AuthStatus(_Base):
|
|
297
|
+
auth_required: bool = Field(alias="authRequired")
|
|
298
|
+
editor_login_available: bool = Field(alias="editorLoginAvailable")
|
|
299
|
+
identity_mode: Literal["named-users", "shared-key"] = Field(alias="identityMode")
|
|
300
|
+
login_unavailable_reason: str | None = Field(default=None, alias="loginUnavailableReason")
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
class EditorTokenResponse(_Base):
|
|
304
|
+
token: str
|
|
305
|
+
expires_at: str = Field(alias="expiresAt")
|
|
306
|
+
actor: str | None = None
|
|
307
|
+
display_name: str | None = Field(default=None, alias="displayName")
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
# ── Health ────────────────────────────────────────────────────────────────
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
class HealthResponse(_Base):
|
|
314
|
+
status: Literal["ok"]
|
|
315
|
+
version: str
|
|
316
|
+
timestamp: str
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
class ReadinessResponse(_Base):
|
|
320
|
+
status: Literal["ok", "degraded"]
|
|
321
|
+
version: str
|
|
322
|
+
timestamp: str
|
|
323
|
+
checks: dict[str, str]
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
# ── Schedules ─────────────────────────────────────────────────────────────
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
class DataSource(_Base):
|
|
330
|
+
type: Literal["static", "url"]
|
|
331
|
+
data: dict[str, Any] | None = None
|
|
332
|
+
url: str | None = None
|
|
333
|
+
headers: dict[str, str] | None = None
|
|
334
|
+
timeout_ms: int | None = Field(default=None, alias="timeoutMs")
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
class DeliveryTarget(_Base):
|
|
338
|
+
type: Literal["email", "s3", "webhook"]
|
|
339
|
+
# email fields
|
|
340
|
+
to: list[str] | None = None
|
|
341
|
+
cc: list[str] | None = None
|
|
342
|
+
subject: str | None = None
|
|
343
|
+
body: str | None = None
|
|
344
|
+
attachment_name: str | None = Field(default=None, alias="attachmentName")
|
|
345
|
+
# s3 fields
|
|
346
|
+
key: str | None = None
|
|
347
|
+
bucket: str | None = None
|
|
348
|
+
region: str | None = None
|
|
349
|
+
# webhook fields
|
|
350
|
+
url: str | None = None
|
|
351
|
+
secret: str | None = None
|
|
352
|
+
include_body: bool | None = Field(default=None, alias="includeBody")
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
class Schedule(_Base):
|
|
356
|
+
id: str
|
|
357
|
+
name: str
|
|
358
|
+
enabled: bool
|
|
359
|
+
cron_expression: str = Field(alias="cronExpression")
|
|
360
|
+
timezone: str
|
|
361
|
+
template_key: str = Field(alias="templateKey")
|
|
362
|
+
template_version: str | None = Field(default=None, alias="templateVersion")
|
|
363
|
+
format: Literal["pdf", "docx"]
|
|
364
|
+
data_source: DataSource = Field(alias="dataSource")
|
|
365
|
+
delivery_targets: list[DeliveryTarget] = Field(alias="deliveryTargets")
|
|
366
|
+
render_options: dict[str, Any] | None = Field(default=None, alias="renderOptions")
|
|
367
|
+
next_fire_at: str | None = Field(default=None, alias="nextFireAt")
|
|
368
|
+
last_fired_at: str | None = Field(default=None, alias="lastFiredAt")
|
|
369
|
+
created_at: str = Field(alias="createdAt")
|
|
370
|
+
updated_at: str = Field(alias="updatedAt")
|
|
371
|
+
created_by: str | None = Field(default=None, alias="createdBy")
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
class DeliveryTargetResult(_Base):
|
|
375
|
+
target_type: str = Field(alias="targetType")
|
|
376
|
+
delivered: bool
|
|
377
|
+
attempts: int
|
|
378
|
+
error: str | None = None
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
class ScheduleExecution(_Base):
|
|
382
|
+
id: str
|
|
383
|
+
schedule_id: str = Field(alias="scheduleId")
|
|
384
|
+
fire_time: str = Field(alias="fireTime")
|
|
385
|
+
status: str
|
|
386
|
+
claimed_by: str | None = Field(default=None, alias="claimedBy")
|
|
387
|
+
claimed_at: str | None = Field(default=None, alias="claimedAt")
|
|
388
|
+
render_duration_ms: int | None = Field(default=None, alias="renderDurationMs")
|
|
389
|
+
delivery_results: list[DeliveryTargetResult] | None = Field(
|
|
390
|
+
default=None, alias="deliveryResults"
|
|
391
|
+
)
|
|
392
|
+
last_error: str | None = Field(default=None, alias="lastError")
|
|
393
|
+
created_at: str = Field(alias="createdAt")
|
|
394
|
+
updated_at: str = Field(alias="updatedAt")
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pulp-engine
|
|
3
|
+
Version: 0.85.0
|
|
4
|
+
Summary: Python client for the Pulp Engine document generation API
|
|
5
|
+
Project-URL: Homepage, https://github.com/TroyCoderBoy/pulp-engine
|
|
6
|
+
Project-URL: Documentation, https://github.com/TroyCoderBoy/pulp-engine/tree/main/packages/sdk-python
|
|
7
|
+
Project-URL: Issues, https://github.com/TroyCoderBoy/pulp-engine/issues
|
|
8
|
+
Project-URL: Source, https://github.com/TroyCoderBoy/pulp-engine
|
|
9
|
+
Project-URL: Changelog, https://github.com/TroyCoderBoy/pulp-engine/blob/main/CHANGELOG.md
|
|
10
|
+
Author: Pulp Engine
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
Keywords: document-generation,pdf,pulp-engine,templates
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: >=3.11
|
|
23
|
+
Requires-Dist: httpx>=0.27.0
|
|
24
|
+
Requires-Dist: pydantic>=2.0.0
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: build>=1.2.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: mypy>=1.10.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest-httpx>=0.30.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: ruff>=0.6.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: twine>=5.0.0; extra == 'dev'
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# Pulp Engine Python SDK
|
|
35
|
+
|
|
36
|
+
Typed Python client for the [PulpEngine](https://github.com/TroyCoderBoy/pulp-engine) document generation API.
|
|
37
|
+
|
|
38
|
+
Mirrors the [TypeScript SDK](../sdk-typescript) 1:1 with snake_case method names and Pythonic conventions (context manager, type hints, Pydantic response models).
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
> **Not yet published to PyPI.** This SDK currently ships in-repo only; registry
|
|
43
|
+
> publication is pending trusted-publisher setup. Until then, install from the
|
|
44
|
+
> workspace path (`pip install -e packages/sdk-python`). The command below will
|
|
45
|
+
> work once the package is live on PyPI.
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install pulp-engine
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Requires Python 3.11+.
|
|
52
|
+
|
|
53
|
+
## Quickstart
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from pulp_engine import PulpEngineClient
|
|
57
|
+
|
|
58
|
+
with PulpEngineClient(
|
|
59
|
+
base_url="https://pulp-engine.example.com",
|
|
60
|
+
api_key="dk_admin_...",
|
|
61
|
+
) as client:
|
|
62
|
+
# List templates
|
|
63
|
+
templates = client.templates.list()
|
|
64
|
+
for t in templates.items:
|
|
65
|
+
print(t.key, t.current_version)
|
|
66
|
+
|
|
67
|
+
# Render a PDF
|
|
68
|
+
result = client.render.pdf("invoice", {"amount": 100, "customer": "Acme"})
|
|
69
|
+
result.save("invoice.pdf")
|
|
70
|
+
|
|
71
|
+
# Render to HTML (returns string)
|
|
72
|
+
html = client.render.html("invoice", {"amount": 100, "customer": "Acme"})
|
|
73
|
+
print(html)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Preview routes in production
|
|
77
|
+
|
|
78
|
+
The Pulp Engine OpenAPI spec this SDK is generated from includes `/render/preview/html` and `/render/preview/pdf` routes. In production (`NODE_ENV=production`), those routes return `404` unless the API operator has explicitly enabled them with `PREVIEW_ROUTES_ENABLED=true`. They are intended for the live editor, not for production rendering pipelines.
|
|
79
|
+
|
|
80
|
+
Before calling a preview method against an unknown deployment, query `GET /capabilities` at runtime and check the advertised preview capability. Use the production render endpoints (`POST /render/pdf` — `POST /render` is a deprecated alias — and the per-format routes `POST /render/html|csv|xlsx|docx|pptx`, plus `POST /render/batch` for bulk jobs) for all production document generation — they are always registered and are not affected by this flag.
|
|
81
|
+
|
|
82
|
+
## Authentication
|
|
83
|
+
|
|
84
|
+
Pass either an API key (`X-Api-Key` header) or an editor session token (`X-Editor-Token` header):
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
# API key
|
|
88
|
+
client = PulpEngineClient(base_url="...", api_key="dk_admin_...")
|
|
89
|
+
|
|
90
|
+
# Editor session token
|
|
91
|
+
client = PulpEngineClient(base_url="...", editor_token="...")
|
|
92
|
+
|
|
93
|
+
# Switch at runtime
|
|
94
|
+
client.set_api_key("new-key")
|
|
95
|
+
client.set_editor_token("new-token")
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Available resources
|
|
99
|
+
|
|
100
|
+
| Resource | Methods |
|
|
101
|
+
|---|---|
|
|
102
|
+
| `client.templates` | `list`, `get`, `create`, `update`, `delete`, `schema`, `sample`, `validate`, `versions`, `get_version`, `restore` |
|
|
103
|
+
| `client.render` | `pdf`, `html`, `csv`, `xlsx`, `docx`, `pptx`, `dry_run`, `validate` |
|
|
104
|
+
| `client.batch` | `pdf`, `docx`, `submit_async`, `submit_async_docx`, `poll_job`, `wait_for_job` |
|
|
105
|
+
| `client.pdf_transform` | `merge`, `watermark`, `insert` |
|
|
106
|
+
| `client.assets` | `list`, `upload`, `delete` |
|
|
107
|
+
| `client.audit_events` | `list`, `purge` |
|
|
108
|
+
| `client.admin` | `list_users`, `create_user`, `update_user`, `delete_user`, `reload_users` |
|
|
109
|
+
| `client.auth` | `status`, `editor_token` |
|
|
110
|
+
| `client.health` | `liveness`, `readiness` |
|
|
111
|
+
| `client.schedules` | `list`, `get`, `create`, `update`, `patch`, `delete`, `trigger`, `list_executions`, `get_execution` |
|
|
112
|
+
|
|
113
|
+
## Error handling
|
|
114
|
+
|
|
115
|
+
All methods raise `PulpEngineError` on non-2xx responses:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
from pulp_engine import PulpEngineClient, PulpEngineError
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
client.render.pdf("missing-template", {})
|
|
122
|
+
except PulpEngineError as e:
|
|
123
|
+
print(e.status) # 404
|
|
124
|
+
print(e.error) # "NotFound"
|
|
125
|
+
print(e.code) # None (or e.g. "template_expression_error" for render errors)
|
|
126
|
+
print(e.issues) # None (or list[ValidationIssue] for 400/422)
|
|
127
|
+
print(str(e)) # Human-readable message
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Dry-run mode (CI/CD pre-flight checks)
|
|
131
|
+
|
|
132
|
+
`client.render.dry_run()` validates input data and exercises all template
|
|
133
|
+
expressions via a trial HTML render, then returns a structured result without
|
|
134
|
+
producing any binary output. Skips Chromium / DOCX / PPTX entirely — typical
|
|
135
|
+
latency is 5–50 ms vs 1–10 s for a full PDF render. Useful for CI/CD
|
|
136
|
+
pre-flight checks and integration tests.
|
|
137
|
+
|
|
138
|
+
Unlike the normal render methods, `dry_run()` does **not** raise on template
|
|
139
|
+
author errors (validation failures, undefined variables, etc.) — those are
|
|
140
|
+
surfaced in the result so callers can display them to users. The only way it
|
|
141
|
+
raises is if the request itself is malformed (network error, 4xx auth, etc.).
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
result = client.render.dry_run("invoice", {"amount": 100})
|
|
145
|
+
|
|
146
|
+
if result.valid:
|
|
147
|
+
print(f"Template OK ({result.field_mappings_applied} field mappings applied)")
|
|
148
|
+
else:
|
|
149
|
+
# Validation errors (schema mismatches)
|
|
150
|
+
for err in result.validation.errors:
|
|
151
|
+
print(f"Validation: {err.path}: {err.message}")
|
|
152
|
+
|
|
153
|
+
# Expression errors (Handlebars failures, undefined variables)
|
|
154
|
+
for err in result.expressions.errors:
|
|
155
|
+
location = err.location.node_path if err.location else "(no location)"
|
|
156
|
+
print(f"Expression at {location}: {err.message}")
|
|
157
|
+
if err.suggestion:
|
|
158
|
+
print(f" Did you mean: {', '.join(err.suggestion.suggestions)}?")
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
The `format` parameter selects which per-format route to hit (defaults to
|
|
162
|
+
`"pdf"`). The result shape is identical regardless of format — the selector
|
|
163
|
+
only matters when different formats have different rate limits or feature
|
|
164
|
+
gates server-side:
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
client.render.dry_run("invoice", data, format="html")
|
|
168
|
+
client.render.dry_run("invoice", data, format="docx")
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Binary results
|
|
172
|
+
|
|
173
|
+
PDF, CSV, XLSX, DOCX, and PPTX renders return a `BinaryResult` (or `PptxResult`) with:
|
|
174
|
+
|
|
175
|
+
- `.data` — raw bytes
|
|
176
|
+
- `.content_type` — response Content-Type header
|
|
177
|
+
- `.content_disposition` — response Content-Disposition header
|
|
178
|
+
- `.save(path)` — convenience method to write bytes to a file (creates parent dirs)
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
result = client.render.pdf("invoice", {"amount": 100})
|
|
182
|
+
result.save("output/invoice.pdf") # creates output/ if needed
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
`PptxResult` adds:
|
|
186
|
+
|
|
187
|
+
- `.warning_count` — number of structured warnings parsed from the `X-Render-Warnings` header
|
|
188
|
+
|
|
189
|
+
## Pagination
|
|
190
|
+
|
|
191
|
+
List endpoints return a `PaginatedResult[T]` envelope:
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
page = client.templates.list(limit=20, offset=0)
|
|
195
|
+
print(page.total) # total count across all pages
|
|
196
|
+
print(len(page.items)) # items on this page
|
|
197
|
+
|
|
198
|
+
# Auto-paginate with the helper
|
|
199
|
+
from pulp_engine import paginate
|
|
200
|
+
|
|
201
|
+
for template in paginate(lambda offset, limit: client.templates.list(limit=limit, offset=offset)):
|
|
202
|
+
print(template.key)
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Development
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
cd packages/sdk-python
|
|
209
|
+
pip install -e ".[dev]"
|
|
210
|
+
pytest
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Releasing
|
|
214
|
+
|
|
215
|
+
See [RELEASING.md](./RELEASING.md) for the publish runbook. Releases are
|
|
216
|
+
automated via the [`publish-sdk-python.yml`](../../.github/workflows/publish-sdk-python.yml)
|
|
217
|
+
GitHub Actions workflow using PyPI Trusted Publishing (no API tokens).
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
pulp_engine/__init__.py,sha256=eCrLNPlY7SrDE4sUgmrD5CTFw92M9Fwq5tfh0IERJM8,1279
|
|
2
|
+
pulp_engine/_http.py,sha256=nez7KS1NXr6_ccL-ESSYv2iXdfRk2hHlAqHWcDfyUm4,7831
|
|
3
|
+
pulp_engine/client.py,sha256=uVKAHxr2C8u9VEypzJ7j9A5EtJfmcgqWfH4bqgtgM5s,3049
|
|
4
|
+
pulp_engine/errors.py,sha256=Uko4DnkBIm3joaNTXwE-E2rItKdmrU0e1rzWXiBOWRc,5308
|
|
5
|
+
pulp_engine/pagination.py,sha256=KXClcGCCO-QGRbH-CQoftOGVhxL7nyM54wk0SmeTA4Y,1314
|
|
6
|
+
pulp_engine/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
pulp_engine/types.py,sha256=ztUUIgqi01VYReCyQHZ9KOrbNHsMCay19yOqCZZ3jfw,13322
|
|
8
|
+
pulp_engine/resources/__init__.py,sha256=WfFCv5xa3YuWpLxRLH4HQBs7pgY5pcG0ZUVlx9VclYU,714
|
|
9
|
+
pulp_engine/resources/admin.py,sha256=xnXBS0RqeFJgvdJYRoPP-edOG4_yYJL3LOtwGO0i8Gg,2271
|
|
10
|
+
pulp_engine/resources/assets.py,sha256=oKiD8hAsKwjOnsNv-Z-klZ0qdAVqF9sNjlKYNs6OyoY,2026
|
|
11
|
+
pulp_engine/resources/audit_events.py,sha256=1mKk7MXPs7KYnRBFduYNJYmrxrSxOHQsJFuwJDUqcVw,1899
|
|
12
|
+
pulp_engine/resources/auth.py,sha256=00Ulu37_UErWAIyINZmuG8tj9_In6KbsETwnHsPu_so,1025
|
|
13
|
+
pulp_engine/resources/batch.py,sha256=cnxjfe2jkty1OZLJi9UWnUFe6pyRnN7RRXOXLsbgh_I,5730
|
|
14
|
+
pulp_engine/resources/health.py,sha256=FdccWhFk_fowiuLw5ffwSXmTXiSTYee6v9UMqJ4SioQ,767
|
|
15
|
+
pulp_engine/resources/pdf_transform.py,sha256=LOWx8IdGVQi-D1sPwL0NXktkCKyNpPZNdIwheZb7Y-k,2920
|
|
16
|
+
pulp_engine/resources/render.py,sha256=A1s-OCOOMfImsPuIBwqi5HlHOQJ-mzI0X0jwYdux2D8,11219
|
|
17
|
+
pulp_engine/resources/schedules.py,sha256=Isa6Iskm0tDWdkaEjYdeiwL6q5fEGvZl9RyLWO6LEc4,3485
|
|
18
|
+
pulp_engine/resources/templates.py,sha256=JD6M9PZqIty1ncfygHKkdyMJ3gFZxlF6NLn9DsfWKww,5376
|
|
19
|
+
pulp_engine-0.85.0.dist-info/METADATA,sha256=GeK-gLW-gFKAqG8ZHy0M8XPmbWqdXvbWqH5BhQlbY7U,8384
|
|
20
|
+
pulp_engine-0.85.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
21
|
+
pulp_engine-0.85.0.dist-info/RECORD,,
|