fairchild 0.0.5__py3-none-any.whl → 0.0.6__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.
fairchild/__init__.py CHANGED
@@ -1,11 +1,9 @@
1
1
  from fairchild.task import task
2
2
  from fairchild.job import Job
3
- from fairchild.record import Record
4
3
  from fairchild.fairchild import Fairchild
5
4
 
6
5
  __all__ = [
7
6
  "task",
8
7
  "Job",
9
- "Record",
10
8
  "Fairchild",
11
9
  ]
@@ -0,0 +1,2 @@
1
+ -- Rename recorded column to result
2
+ ALTER TABLE fairchild_jobs RENAME COLUMN recorded TO result;
fairchild/task.py CHANGED
@@ -74,7 +74,7 @@ def task(
74
74
  Usage:
75
75
  @task(queue="default")
76
76
  def my_task(item_id: int):
77
- return Record({"result": item_id * 2})
77
+ return {"result": item_id * 2}
78
78
 
79
79
  # Enqueue
80
80
  my_task.enqueue(item_id=42)
@@ -197,13 +197,7 @@ class Task:
197
197
  return Future(job_id=job_id)
198
198
 
199
199
  # Not inside a task - execute directly
200
- from fairchild.record import Record
201
-
202
- result = self.fn(*args, **kwargs)
203
- # Unwrap Record so local runs behave like resolved futures
204
- if isinstance(result, Record):
205
- return result.value
206
- return result
200
+ return self.fn(*args, **kwargs)
207
201
 
208
202
  def _serialize_args(self, kwargs: dict[str, Any]) -> dict[str, Any]:
209
203
  """Serialize arguments, converting Futures to their job IDs."""
@@ -255,6 +255,16 @@
255
255
  .job-link:hover {
256
256
  text-decoration: underline;
257
257
  }
258
+ .task-args {
259
+ font-family: "SF Mono", Monaco, monospace;
260
+ font-size: 0.75rem;
261
+ color: var(--text-dim);
262
+ margin-top: 0.25rem;
263
+ max-width: 300px;
264
+ overflow: hidden;
265
+ text-overflow: ellipsis;
266
+ white-space: nowrap;
267
+ }
258
268
 
259
269
  .queues {
260
270
  display: grid;
@@ -1207,6 +1217,12 @@
1207
1217
  }
1208
1218
  }
1209
1219
 
1220
+ function formatArgs(args) {
1221
+ if (!args || Object.keys(args).length === 0) return "";
1222
+ const str = typeof args === "string" ? args : JSON.stringify(args);
1223
+ return str.length > 50 ? str.slice(0, 50) + "..." : str;
1224
+ }
1225
+
1210
1226
  async function fetchJobs(state = "") {
1211
1227
  const url = state ? `/api/jobs?state=${state}` : "/api/jobs";
1212
1228
  const res = await fetch(url);
@@ -1214,18 +1230,26 @@
1214
1230
 
1215
1231
  const tbody = document.querySelector("#jobs-table tbody");
1216
1232
  tbody.innerHTML = jobs
1217
- .map(
1218
- (job) => `
1233
+ .map((job) => {
1234
+ const argsDisplay = formatArgs(job.args);
1235
+ const argsTitle =
1236
+ typeof job.args === "string"
1237
+ ? job.args
1238
+ : JSON.stringify(job.args);
1239
+ const argsHtml = argsDisplay
1240
+ ? `<div class="task-args" title="${argsTitle.replace(/"/g, "&quot;")}">${argsDisplay}</div>`
1241
+ : "";
1242
+ return `
1219
1243
  <tr>
1220
1244
  <td class="mono"><a href="/jobs/${job.id}" class="job-link">${job.id}</a></td>
1221
- <td class="truncate">${job.task_name}</td>
1245
+ <td>${job.task_name}${argsHtml}</td>
1222
1246
  <td>${job.queue}</td>
1223
1247
  <td><span class="badge ${job.state}">${job.state}</span></td>
1224
1248
  <td>${job.attempt}/${job.max_attempts}</td>
1225
1249
  <td class="mono">${formatTime(job.inserted_at)}</td>
1226
1250
  </tr>
1227
- `,
1228
- )
1251
+ `;
1252
+ })
1229
1253
  .join("");
1230
1254
  }
1231
1255
 
@@ -392,6 +392,64 @@
392
392
  border-radius: 4px;
393
393
  }
394
394
 
395
+ /* Task Summary Stats */
396
+ .task-summary {
397
+ margin-bottom: 1rem;
398
+ overflow-x: auto;
399
+ }
400
+ .task-summary table {
401
+ width: 100%;
402
+ border-collapse: collapse;
403
+ font-size: 0.8125rem;
404
+ }
405
+ .task-summary th,
406
+ .task-summary td {
407
+ padding: 0.5rem 0.75rem;
408
+ text-align: left;
409
+ border-bottom: 1px solid var(--border-color);
410
+ }
411
+ .task-summary th {
412
+ background: var(--bg-secondary);
413
+ color: var(--text-muted);
414
+ font-weight: 500;
415
+ text-transform: uppercase;
416
+ font-size: 0.6875rem;
417
+ }
418
+ .task-summary td {
419
+ color: var(--text-secondary);
420
+ }
421
+ .task-summary .task-name {
422
+ font-family: "SF Mono", Monaco, monospace;
423
+ font-weight: 500;
424
+ color: var(--text-primary);
425
+ }
426
+ .task-summary .stat-cell {
427
+ text-align: center;
428
+ min-width: 80px;
429
+ }
430
+ .task-summary .stat-cell .count {
431
+ font-weight: 600;
432
+ }
433
+ .task-summary .stat-cell .pct {
434
+ color: var(--text-dim);
435
+ font-size: 0.75rem;
436
+ margin-left: 0.25rem;
437
+ }
438
+ .task-summary .total-row {
439
+ font-weight: 600;
440
+ background: var(--bg-secondary);
441
+ }
442
+ .task-summary .total-row td {
443
+ border-bottom: none;
444
+ }
445
+ .task-summary .timing-cell {
446
+ white-space: nowrap;
447
+ }
448
+ .task-summary .timing-range {
449
+ color: var(--text-dim);
450
+ font-size: 0.7rem;
451
+ }
452
+
395
453
  #family-table {
396
454
  width: 100%;
397
455
  border-collapse: collapse;
@@ -522,6 +580,9 @@
522
580
  </div>
523
581
  <div id="dag-flow"></div>
524
582
  </div>
583
+ <div class="task-summary" id="task-summary" style="margin-top: 16px">
584
+ <!-- Populated by JS -->
585
+ </div>
525
586
  <table id="family-table" style="margin-top: 16px">
526
587
  <thead>
527
588
  <tr>
@@ -755,10 +816,10 @@
755
816
  );
756
817
 
757
818
  // Result
758
- if (job.recorded !== null && job.recorded !== undefined) {
819
+ if (job.result !== null && job.result !== undefined) {
759
820
  document.getElementById("result-card").style.display = "block";
760
821
  document.getElementById("result-block").textContent = formatJson(
761
- job.recorded,
822
+ job.result,
762
823
  );
763
824
  }
764
825
 
@@ -815,10 +876,21 @@
815
876
 
816
877
  familyData = await res.json();
817
878
 
818
- // Only show DAG if there's more than one job in the family
819
- if (familyData.jobs && familyData.jobs.length > 1) {
879
+ // Find the current job in the family
880
+ const currentJob = familyData.jobs.find((j) => j.id === jobId);
881
+
882
+ // Only show family view if:
883
+ // 1. Current job is the root (no parent_id) AND has children
884
+ // 2. Don't show for child jobs - they should just see their own info
885
+ const isRoot = currentJob && !currentJob.parent_id;
886
+ const hasChildren = familyData.jobs.some(
887
+ (j) => j.parent_id === jobId,
888
+ );
889
+
890
+ if (isRoot && familyData.jobs.length > 1) {
820
891
  document.getElementById("family-card").style.display = "block";
821
892
  renderDAG(familyData.jobs);
893
+ renderTaskSummary(familyData.jobs);
822
894
  renderFamilyTable(familyData.jobs);
823
895
  }
824
896
  } catch (err) {
@@ -1190,6 +1262,149 @@
1190
1262
  );
1191
1263
  }
1192
1264
 
1265
+ function renderTaskSummary(jobs) {
1266
+ const container = document.getElementById("task-summary");
1267
+ const allStates = [
1268
+ "completed",
1269
+ "running",
1270
+ "available",
1271
+ "scheduled",
1272
+ "failed",
1273
+ "discarded",
1274
+ ];
1275
+ const stateLabels = {
1276
+ completed: "Done",
1277
+ running: "Run",
1278
+ available: "Avail",
1279
+ scheduled: "Sched",
1280
+ failed: "Fail",
1281
+ discarded: "Disc",
1282
+ };
1283
+
1284
+ function fmtDur(ms) {
1285
+ if (ms < 1000) return Math.round(ms) + "ms";
1286
+ if (ms < 60000) return (ms / 1000).toFixed(1) + "s";
1287
+ if (ms < 3600000) return (ms / 60000).toFixed(1) + "m";
1288
+ return (ms / 3600000).toFixed(1) + "h";
1289
+ }
1290
+
1291
+ const taskStats = {};
1292
+ let totals = { total: 0, durations: [] };
1293
+ allStates.forEach((s) => (totals[s] = 0));
1294
+
1295
+ jobs.forEach((job) => {
1296
+ const taskName = job.task_name;
1297
+ if (!taskStats[taskName]) {
1298
+ taskStats[taskName] = { total: 0, durations: [] };
1299
+ allStates.forEach((s) => (taskStats[taskName][s] = 0));
1300
+ }
1301
+ taskStats[taskName].total++;
1302
+ taskStats[taskName][job.state] =
1303
+ (taskStats[taskName][job.state] || 0) + 1;
1304
+ totals.total++;
1305
+ totals[job.state] = (totals[job.state] || 0) + 1;
1306
+
1307
+ // Include timing for completed, failed, and discarded jobs
1308
+ if (
1309
+ ["completed", "failed", "discarded"].includes(job.state) &&
1310
+ job.attempted_at &&
1311
+ job.completed_at
1312
+ ) {
1313
+ const duration =
1314
+ new Date(job.completed_at) - new Date(job.attempted_at);
1315
+ taskStats[taskName].durations.push(duration);
1316
+ totals.durations.push(duration);
1317
+ }
1318
+ });
1319
+
1320
+ // Show all state columns for consistency
1321
+ const states = allStates;
1322
+
1323
+ function calcDurationStats(durations) {
1324
+ if (durations.length === 0) return null;
1325
+ const sorted = durations.slice().sort((a, b) => a - b);
1326
+ const sum = sorted.reduce((a, b) => a + b, 0);
1327
+ const p95Index = Math.floor(sorted.length * 0.95);
1328
+ return {
1329
+ avg: sum / sorted.length,
1330
+ p95: sorted[Math.min(p95Index, sorted.length - 1)],
1331
+ };
1332
+ }
1333
+
1334
+ const taskNames = Object.keys(taskStats).sort();
1335
+ const headerCells = states
1336
+ .map((s) => `<th class="stat-cell">${stateLabels[s]}</th>`)
1337
+ .join("");
1338
+
1339
+ const rows = taskNames
1340
+ .map((taskName) => {
1341
+ const stats = taskStats[taskName];
1342
+ const shortName = taskName.split(".").pop();
1343
+ const ds = calcDurationStats(stats.durations);
1344
+
1345
+ const statCells = states
1346
+ .map((s) => {
1347
+ const count = stats[s] || 0;
1348
+ if (count === 0) return `<td class="stat-cell">-</td>`;
1349
+ const pct =
1350
+ stats.total > 0 ? Math.round((count / stats.total) * 100) : 0;
1351
+ return `<td class="stat-cell"><span class="count">${count}</span><span class="pct">(${pct}%)</span></td>`;
1352
+ })
1353
+ .join("");
1354
+
1355
+ const timing = ds
1356
+ ? `${fmtDur(ds.avg)}<span class="timing-range"> (${fmtDur(ds.p95)})</span>`
1357
+ : "-";
1358
+
1359
+ return `<tr>
1360
+ <td class="task-name" title="${taskName}">${shortName}</td>
1361
+ <td class="stat-cell">${stats.total}</td>
1362
+ ${statCells}
1363
+ <td class="stat-cell timing-cell">${timing}</td>
1364
+ </tr>`;
1365
+ })
1366
+ .join("");
1367
+
1368
+ const tds = calcDurationStats(totals.durations);
1369
+ const totalCells = states
1370
+ .map((s) => {
1371
+ const count = totals[s] || 0;
1372
+ if (count === 0) return `<td class="stat-cell">-</td>`;
1373
+ const pct =
1374
+ totals.total > 0 ? Math.round((count / totals.total) * 100) : 0;
1375
+ return `<td class="stat-cell"><span class="count">${count}</span><span class="pct">(${pct}%)</span></td>`;
1376
+ })
1377
+ .join("");
1378
+
1379
+ const totalTiming = tds
1380
+ ? `${fmtDur(tds.avg)}<span class="timing-range"> (${fmtDur(tds.p95)})</span>`
1381
+ : "-";
1382
+
1383
+ const totalRow = `<tr class="total-row">
1384
+ <td>Total</td>
1385
+ <td class="stat-cell">${totals.total}</td>
1386
+ ${totalCells}
1387
+ <td class="stat-cell timing-cell">${totalTiming}</td>
1388
+ </tr>`;
1389
+
1390
+ container.innerHTML = `
1391
+ <table>
1392
+ <thead>
1393
+ <tr>
1394
+ <th>Task</th>
1395
+ <th class="stat-cell">#</th>
1396
+ ${headerCells}
1397
+ <th class="stat-cell">Avg (p95)</th>
1398
+ </tr>
1399
+ </thead>
1400
+ <tbody>
1401
+ ${rows}
1402
+ ${totalRow}
1403
+ </tbody>
1404
+ </table>
1405
+ `;
1406
+ }
1407
+
1193
1408
  function renderFamilyTable(jobs) {
1194
1409
  const tbody = document.querySelector("#family-table tbody");
1195
1410
 
@@ -1214,13 +1429,15 @@
1214
1429
  }
1215
1430
 
1216
1431
  let result = "-";
1217
- if (job.recorded !== null && job.recorded !== undefined) {
1218
- const recorded =
1219
- typeof job.recorded === "string"
1220
- ? job.recorded
1221
- : JSON.stringify(job.recorded);
1432
+ if (job.result !== null && job.result !== undefined) {
1433
+ const resultStr =
1434
+ typeof job.result === "string"
1435
+ ? job.result
1436
+ : JSON.stringify(job.result);
1222
1437
  result =
1223
- recorded.length > 30 ? recorded.slice(0, 30) + "..." : recorded;
1438
+ resultStr.length > 30
1439
+ ? resultStr.slice(0, 30) + "..."
1440
+ : resultStr;
1224
1441
  }
1225
1442
 
1226
1443
  return `
fairchild/ui.py CHANGED
@@ -159,7 +159,7 @@ async def api_jobs(request: web.Request) -> web.Response:
159
159
  parent_id, deps,
160
160
  state, priority, scheduled_at,
161
161
  attempted_at, completed_at, attempt, max_attempts,
162
- recorded, errors, tags,
162
+ result, errors, tags,
163
163
  inserted_at, updated_at
164
164
  FROM fairchild_jobs
165
165
  {where}
@@ -316,7 +316,7 @@ async def api_job_detail(request: web.Request) -> web.Response:
316
316
  parent_id, deps,
317
317
  state, priority, scheduled_at,
318
318
  attempted_at, completed_at, attempt, max_attempts,
319
- recorded, errors, tags,
319
+ result, errors, tags,
320
320
  inserted_at, updated_at
321
321
  FROM fairchild_jobs
322
322
  WHERE id = $1
@@ -382,15 +382,15 @@ async def api_job_family(request: web.Request) -> web.Response:
382
382
  # Get all descendants from the root
383
383
  family_query = """
384
384
  WITH RECURSIVE family AS (
385
- SELECT id, task_name, parent_id, state, deps, recorded,
386
- attempted_at, completed_at, attempt, max_attempts
385
+ SELECT id, task_name, parent_id, state, deps, result,
386
+ attempted_at, completed_at, updated_at, attempt, max_attempts
387
387
  FROM fairchild_jobs
388
388
  WHERE id = $1
389
389
 
390
390
  UNION ALL
391
391
 
392
- SELECT j.id, j.task_name, j.parent_id, j.state, j.deps, j.recorded,
393
- j.attempted_at, j.completed_at, j.attempt, j.max_attempts
392
+ SELECT j.id, j.task_name, j.parent_id, j.state, j.deps, j.result,
393
+ j.attempted_at, j.completed_at, j.updated_at, j.attempt, j.max_attempts
394
394
  FROM fairchild_jobs j
395
395
  INNER JOIN family f ON j.parent_id = f.id
396
396
  )
@@ -409,6 +409,7 @@ async def api_job_family(request: web.Request) -> web.Response:
409
409
  job["completed_at"] = (
410
410
  job["completed_at"].isoformat() if job["completed_at"] else None
411
411
  )
412
+ job["updated_at"] = job["updated_at"].isoformat() if job["updated_at"] else None
412
413
  jobs.append(job)
413
414
 
414
415
  return web.json_response({"root_id": str(root_id), "jobs": jobs})
fairchild/worker.py CHANGED
@@ -10,7 +10,6 @@ from uuid import uuid4
10
10
  from fairchild.context import set_current_job, get_pending_children
11
11
  from fairchild.fairchild import Fairchild
12
12
  from fairchild.job import Job, JobState
13
- from fairchild.record import Record
14
13
  from fairchild.task import get_task, get_task_schemas
15
14
 
16
15
 
@@ -120,9 +119,9 @@ class Worker:
120
119
  # Check if this is a future marker
121
120
  if "__future__" in obj and len(obj) == 1:
122
121
  job_id = obj["__future__"]
123
- # Fetch the recorded result from the completed job
122
+ # Fetch the result from the completed job
124
123
  query = """
125
- SELECT recorded FROM fairchild_jobs
124
+ SELECT result FROM fairchild_jobs
126
125
  WHERE id = $1 AND state = 'completed'
127
126
  """
128
127
  from uuid import UUID
@@ -180,19 +179,23 @@ class Worker:
180
179
  for child_job in pending_children:
181
180
  await self.fairchild._insert_job(child_job)
182
181
 
183
- # Handle Record() return values
184
- recorded_value = None
185
- if isinstance(result, Record):
186
- recorded_value = result.value
182
+ # Try to capture the return value as JSON
183
+ result_value = None
184
+ if result is not None:
185
+ try:
186
+ json.dumps(result) # Test if serializable
187
+ result_value = result
188
+ except (TypeError, ValueError):
189
+ pass # Not JSON-serializable, skip
187
190
 
188
191
  # Check if this job spawned children - if so, wait for them
189
192
  has_children = len(pending_children) > 0
190
193
  if has_children:
191
194
  # Keep job in running state, it will be completed when children finish
192
- await self._mark_waiting_for_children(job, recorded_value)
195
+ await self._mark_waiting_for_children(job, result_value)
193
196
  print(f"[{self.name}] Job {job.id} waiting for children to complete")
194
197
  else:
195
- await self._complete_job(job, recorded_value)
198
+ await self._complete_job(job, result_value)
196
199
  print(f"[{self.name}] Completed job {job.id}")
197
200
 
198
201
  except Exception as e:
@@ -214,19 +217,19 @@ class Worker:
214
217
  f"[{self.name}] Job {job.id} failed (attempt {job.attempt}/{job.max_attempts}): {e}"
215
218
  )
216
219
 
217
- async def _complete_job(self, job: Job, recorded: Any | None):
220
+ async def _complete_job(self, job: Job, result: Any | None):
218
221
  """Mark a job as completed."""
219
222
  query = """
220
223
  UPDATE fairchild_jobs
221
224
  SET state = 'completed',
222
225
  completed_at = now(),
223
- recorded = $2,
226
+ result = $2,
224
227
  updated_at = now()
225
228
  WHERE id = $1
226
229
  """
227
230
 
228
- recorded_json = json.dumps(recorded) if recorded is not None else None
229
- await self.fairchild._pool.execute(query, job.id, recorded_json)
231
+ result_json = json.dumps(result) if result is not None else None
232
+ await self.fairchild._pool.execute(query, job.id, result_json)
230
233
 
231
234
  # Check if this completion unblocks jobs waiting on this one
232
235
  await self._check_child_deps(job)
@@ -246,17 +249,17 @@ class Worker:
246
249
  """
247
250
  return await self.fairchild._pool.fetchval(query, job.id)
248
251
 
249
- async def _mark_waiting_for_children(self, job: Job, recorded: Any | None):
252
+ async def _mark_waiting_for_children(self, job: Job, result: Any | None):
250
253
  """Mark a job as waiting for children (stays in running state but stores result)."""
251
- # Store the recorded value so we can use it when completing later
254
+ # Store the result value so we can use it when completing later
252
255
  query = """
253
256
  UPDATE fairchild_jobs
254
- SET recorded = $2,
257
+ SET result = $2,
255
258
  updated_at = now()
256
259
  WHERE id = $1
257
260
  """
258
- recorded_json = json.dumps(recorded) if recorded is not None else None
259
- await self.fairchild._pool.execute(query, job.id, recorded_json)
261
+ result_json = json.dumps(result) if result is not None else None
262
+ await self.fairchild._pool.execute(query, job.id, result_json)
260
263
 
261
264
  async def _check_parent_completion(self, parent_id):
262
265
  """Check if a parent job can be completed (all children done)."""
@@ -326,6 +329,7 @@ class Worker:
326
329
  UPDATE fairchild_jobs
327
330
  SET state = $2,
328
331
  errors = errors || $3::jsonb,
332
+ completed_at = now(),
329
333
  updated_at = now()
330
334
  WHERE id = $1
331
335
  """
@@ -338,7 +342,7 @@ class Worker:
338
342
  else:
339
343
  query = """
340
344
  UPDATE fairchild_jobs
341
- SET state = $2, updated_at = now()
345
+ SET state = $2, completed_at = now(), updated_at = now()
342
346
  WHERE id = $1
343
347
  """
344
348
  await self.fairchild._pool.execute(query, job.id, new_state)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fairchild
3
- Version: 0.0.5
3
+ Version: 0.0.6
4
4
  Summary: Workflow scheduling with PostgreSQL
5
5
  Requires-Python: >=3.13
6
6
  Description-Content-Type: text/markdown
@@ -1,13 +1,12 @@
1
- fairchild/__init__.py,sha256=Q7FvQFBPDM12tf2hAg_nlROSdg7dYIXWnDwyqX-6mLU,209
1
+ fairchild/__init__.py,sha256=80mYyoeUFY-85M5lipyZE-4QPTdJ2fqM6KsFUqAyytw,159
2
2
  fairchild/cli.py,sha256=LOrF4e6LgMasWGmeRpciD6iDptC3IiiVF53Ll-TzN8M,9895
3
3
  fairchild/context.py,sha256=64yEo5Cj4LeBT6LQd2UdN00tM7SXSd2Vw_dM4RV1u68,1757
4
4
  fairchild/fairchild.py,sha256=QxGPvkhedyaEtjtdavIONgfX3O4H5BKo1hG1T2WFNJ4,4451
5
5
  fairchild/future.py,sha256=SeVm_6Ds4k3qdwpKnFl71Qknx9tRM9v3UWZ_tJCp7cw,2302
6
6
  fairchild/job.py,sha256=_ilXLHg1aQOM4xOUZArT-UDGzPQnKVDYKvru4Qz1MXs,3767
7
- fairchild/record.py,sha256=DjTQgeE2YjjsU2nB3pegB0Njq1_sNWgJialPLZQ1k7c,575
8
- fairchild/task.py,sha256=RZJbpN49dJz4zaFo9b4PxBI6TpXA4RLzvRi95JtS8_k,8350
9
- fairchild/ui.py,sha256=Ib4vXlPGZvTTlvIsYFoIEYPJATXrYEppCv8R-1gtHs4,18106
10
- fairchild/worker.py,sha256=5jDJAaryv-uC1cVAks68G-Fz20qt7JmHavrLHFXGDwc,17250
7
+ fairchild/task.py,sha256=TsOjs2UYt8qN8WV3YxOP_4ppzBARh4ySz4eM1O9z4II,8135
8
+ fairchild/ui.py,sha256=apOUmn7HZYg06F4J-Sf4EteV97U6YVmcFvE8fiHX7GI,18213
9
+ fairchild/worker.py,sha256=OIv_NnKHU3fZuIQvDZqvcJ7-2EOvmfIctksxAuEzu_8,17421
11
10
  fairchild/db/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
11
  fairchild/db/migrations.py,sha256=tL5UPqllCipYC-XcEH_4ZfaRZLtJKaPACp70ZusvEvg,2174
13
12
  fairchild/db/migrations/001_initial.sql,sha256=FFObm3bIHdASd3d7lXzuAzWsmkCm3OAO2F63pw6HcMs,1711
@@ -15,11 +14,12 @@ fairchild/db/migrations/002_add_parent_id.sql,sha256=ftDT053BaMnXX42aDxTtHnRnAgN
15
14
  fairchild/db/migrations/003_remove_workflows.sql,sha256=6svXO2nM7JiO-QHXfzIJCd6DeOSZ0AFQDsuQgPPv4IY,368
16
15
  fairchild/db/migrations/004_add_workers.sql,sha256=SOvHykAinr_O9gDf7kllEUZ57pw-USoHUsYLCfQumMI,714
17
16
  fairchild/db/migrations/005_add_worker_tasks.sql,sha256=0VJA5HBE2Aidjmk72nqLzreWA8A2jRDZsg1-6rcqTzo,186
18
- fairchild/templates/dashboard.html,sha256=3prb9Li1lDF1pRxPnLq6VQRvPQgx0RD8qraG-QzuIJk,49171
19
- fairchild/templates/job.html,sha256=Pg7hd5p8dU5XJeCW9SryvDbvcErY1AMrxYm0z8hldnA,38042
20
- fairchild-0.0.5.dist-info/licenses/LICENSE,sha256=ad6qehkQLI1ax2pV6ocs7YePX06CPLBs3SThRbNC0q0,1068
21
- fairchild-0.0.5.dist-info/METADATA,sha256=BKFhgEsktIplI1bjcvU7k8-TCP1b_7M02lo4W4_rDGE,11006
22
- fairchild-0.0.5.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
23
- fairchild-0.0.5.dist-info/entry_points.txt,sha256=urOgjfuYex5__jBX91srCX8T5GgHvYZDPpWYkuA7z90,49
24
- fairchild-0.0.5.dist-info/top_level.txt,sha256=_2zkPnqS4i3JjLMpxRPDrBS0a04KBXZiD1NHFv7CO4U,10
25
- fairchild-0.0.5.dist-info/RECORD,,
17
+ fairchild/db/migrations/006_rename_recorded_to_result.sql,sha256=LAKa_VnK5hXIeUY9rJNWlOYGz5hBV7ttHuKHlevWCjY,97
18
+ fairchild/templates/dashboard.html,sha256=2kF3RLUMuZqQWbdlV-GPvYsBD8JnA_nWf4lBqRt60Sc,50071
19
+ fairchild/templates/job.html,sha256=ptliW9AGguv0EaouSH5HFWfz-JqpuTj17ih8LbR2eQw,45059
20
+ fairchild-0.0.6.dist-info/licenses/LICENSE,sha256=ad6qehkQLI1ax2pV6ocs7YePX06CPLBs3SThRbNC0q0,1068
21
+ fairchild-0.0.6.dist-info/METADATA,sha256=mBRHxvdGvnjGhRoFvAT_FnnfrAwbqqINfV5M4RwvIfk,11006
22
+ fairchild-0.0.6.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
23
+ fairchild-0.0.6.dist-info/entry_points.txt,sha256=urOgjfuYex5__jBX91srCX8T5GgHvYZDPpWYkuA7z90,49
24
+ fairchild-0.0.6.dist-info/top_level.txt,sha256=_2zkPnqS4i3JjLMpxRPDrBS0a04KBXZiD1NHFv7CO4U,10
25
+ fairchild-0.0.6.dist-info/RECORD,,
fairchild/record.py DELETED
@@ -1,22 +0,0 @@
1
- from dataclasses import dataclass
2
- from typing import Any
3
-
4
-
5
- @dataclass(frozen=True)
6
- class Record:
7
- """Wrapper to indicate a task's return value should be persisted.
8
-
9
- Usage:
10
- @task(queue="default")
11
- def my_task(item_id: int):
12
- result = process(item_id)
13
- return Record({"item_id": item_id, "result": result})
14
-
15
- The recorded value will be stored in the job's `recorded` column
16
- and can be retrieved by downstream jobs in a workflow.
17
- """
18
-
19
- value: Any
20
-
21
- def __repr__(self) -> str:
22
- return f"Record({self.value!r})"