calkit-python 0.41.17__py3-none-any.whl → 0.41.18__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 (22) hide show
  1. calkit/cli/scheduler.py +61 -30
  2. calkit/tests/cli/test_scheduler.py +49 -18
  3. {calkit_python-0.41.17.dist-info → calkit_python-0.41.18.dist-info}/METADATA +1 -1
  4. {calkit_python-0.41.17.dist-info → calkit_python-0.41.18.dist-info}/RECORD +22 -22
  5. {calkit_python-0.41.17.data → calkit_python-0.41.18.data}/data/etc/jupyter/jupyter_server_config.d/calkit.json +0 -0
  6. {calkit_python-0.41.17.data → calkit_python-0.41.18.data}/data/share/jupyter/labextensions/calkit/install.json +0 -0
  7. {calkit_python-0.41.17.data → calkit_python-0.41.18.data}/data/share/jupyter/labextensions/calkit/package.json +0 -0
  8. {calkit_python-0.41.17.data → calkit_python-0.41.18.data}/data/share/jupyter/labextensions/calkit/schemas/calkit/package.json.orig +0 -0
  9. {calkit_python-0.41.17.data → calkit_python-0.41.18.data}/data/share/jupyter/labextensions/calkit/schemas/calkit/plugin.json +0 -0
  10. {calkit_python-0.41.17.data → calkit_python-0.41.18.data}/data/share/jupyter/labextensions/calkit/static/502.9a2c5772a15466e923ef.js +0 -0
  11. {calkit_python-0.41.17.data → calkit_python-0.41.18.data}/data/share/jupyter/labextensions/calkit/static/695.2c41003a452d43d2b358.js +0 -0
  12. {calkit_python-0.41.17.data → calkit_python-0.41.18.data}/data/share/jupyter/labextensions/calkit/static/867.a42a046aa5108f54f8fb.js +0 -0
  13. {calkit_python-0.41.17.data → calkit_python-0.41.18.data}/data/share/jupyter/labextensions/calkit/static/909.e3f9cc3408834a7fdcc3.js +0 -0
  14. {calkit_python-0.41.17.data → calkit_python-0.41.18.data}/data/share/jupyter/labextensions/calkit/static/946.050af2abf7845cfbdbd2.js +0 -0
  15. {calkit_python-0.41.17.data → calkit_python-0.41.18.data}/data/share/jupyter/labextensions/calkit/static/946.050af2abf7845cfbdbd2.js.LICENSE.txt +0 -0
  16. {calkit_python-0.41.17.data → calkit_python-0.41.18.data}/data/share/jupyter/labextensions/calkit/static/b2f1c3efe70cb539d121.png +0 -0
  17. {calkit_python-0.41.17.data → calkit_python-0.41.18.data}/data/share/jupyter/labextensions/calkit/static/remoteEntry.ac9035764d5b2adbb542.js +0 -0
  18. {calkit_python-0.41.17.data → calkit_python-0.41.18.data}/data/share/jupyter/labextensions/calkit/static/style.js +0 -0
  19. {calkit_python-0.41.17.data → calkit_python-0.41.18.data}/data/share/jupyter/labextensions/calkit/static/third-party-licenses.json +0 -0
  20. {calkit_python-0.41.17.dist-info → calkit_python-0.41.18.dist-info}/WHEEL +0 -0
  21. {calkit_python-0.41.17.dist-info → calkit_python-0.41.18.dist-info}/entry_points.txt +0 -0
  22. {calkit_python-0.41.17.dist-info → calkit_python-0.41.18.dist-info}/licenses/LICENSE +0 -0
calkit/cli/scheduler.py CHANGED
@@ -304,6 +304,25 @@ def _slurm_exit_code(job_id: str) -> int | None:
304
304
  return None
305
305
 
306
306
 
307
+ def _parse_pbs_state(stdout: str) -> tuple[str | None, int | None]:
308
+ # `qstat -f` output is a flat list of `key = value` lines; pull out the
309
+ # job's state and, for a finished job, its exit status. The status field is
310
+ # spelled `Exit_status` on PBS Pro and `exit_status` on some Torque builds,
311
+ # so match it case-insensitively.
312
+ state = None
313
+ exit_code = None
314
+ for line in stdout.splitlines():
315
+ stripped = line.strip()
316
+ if stripped.startswith("job_state"):
317
+ state = stripped.split("=", 1)[-1].strip()
318
+ elif stripped.lower().startswith("exit_status"):
319
+ try:
320
+ exit_code = int(stripped.split("=", 1)[-1].strip())
321
+ except ValueError:
322
+ exit_code = None
323
+ return state, exit_code
324
+
325
+
307
326
  def _poll_job(kind: str, job_id: str) -> tuple[bool, int | None]:
308
327
  """Return ``(active, exit_code)`` for a scheduler job.
309
328
 
@@ -339,39 +358,51 @@ def _poll_job(kind: str, job_id: str) -> tuple[bool, int | None]:
339
358
  if "invalid job id" in stderr:
340
359
  return False, _slurm_exit_code(job_id)
341
360
  return True, None
342
- # Use `qstat -f` and parse job_state: on Torque/OpenPBS, plain `qstat
343
- # <id>` returns exit 0 even for completed (C) jobs, so checking the
344
- # return code alone would cause `calkit sched batch` to hang forever
345
- # after a PBS job finishes. States C and F mean the job is done, and a
346
- # finished job's record carries its `Exit_status`.
361
+ # PBS reports a finished job differently across variants, so poll in two
362
+ # steps that mirror the SLURM path above. First consult the active-queue
363
+ # view with `qstat -f`: every live state (R, Q, H, E) shows here, and on
364
+ # Torque a completed job lingers here in state `C` carrying its
365
+ # `Exit_status`. `qstat -f` is used rather than plain `qstat <id>` because
366
+ # the latter exits 0 even for completed jobs, so the return code alone
367
+ # could not tell running from done.
347
368
  p = subprocess.run(
348
369
  ["qstat", "-f", job_id], capture_output=True, text=True, check=False
349
370
  )
350
- if p.returncode != 0:
351
- # A non-zero exit is ambiguous: either the job is gone (completed and
352
- # purged from history) or `qstat` itself failed transiently---a busy
353
- # PBS server periodically refuses connections or times out. Treating a
354
- # transient failure as completion would stop the wait while the job is
355
- # still running and writing its log, so only conclude the job is done
356
- # when qstat positively reports it is unknown; otherwise keep waiting.
357
- # A purged job (unknown to qstat) is done but with an unknowable exit
358
- # status; any other error is transient, so report the job as active.
359
- stderr = (p.stderr or "").lower()
360
- return ("unknown job" not in stderr), None
361
- state = None
362
- exit_code = None
363
- for line in p.stdout.splitlines():
364
- stripped = line.strip()
365
- if stripped.startswith("job_state"):
366
- state = stripped.split("=", 1)[-1].strip()
367
- elif stripped.lower().startswith("exit_status"):
368
- try:
369
- exit_code = int(stripped.split("=", 1)[-1].strip())
370
- except ValueError:
371
- exit_code = None
372
- if state in ("C", "F"):
373
- return False, exit_code
374
- return True, None
371
+ if p.returncode == 0:
372
+ state, exit_code = _parse_pbs_state(p.stdout)
373
+ if state in ("C", "F"):
374
+ return False, exit_code
375
+ if state is not None:
376
+ return True, None
377
+ # The job is no longer in the active queue---on PBS Pro a finished job is
378
+ # hidden from plain `qstat` and shows (as state `F` with its `Exit_status`)
379
+ # only under `qstat -x`---or qstat failed. Ask the history view for the
380
+ # finished record. Without this, a PBS Pro job that has finished is never
381
+ # seen as done and the wait hangs forever. `-x` means "include finished
382
+ # jobs" on PBS Pro; on Torque it switches qstat to XML output, which simply
383
+ # won't parse here, leaving the stderr check below to settle it (Torque
384
+ # already reported completion above via state `C`).
385
+ hist = subprocess.run(
386
+ ["qstat", "-x", "-f", job_id],
387
+ capture_output=True,
388
+ text=True,
389
+ check=False,
390
+ )
391
+ if hist.returncode == 0:
392
+ state, exit_code = _parse_pbs_state(hist.stdout)
393
+ if state in ("C", "F"):
394
+ return False, exit_code
395
+ if state is not None:
396
+ return True, None
397
+ # Neither view resolved a state. A non-zero exit is ambiguous: either the
398
+ # job is gone (finished and purged from history) or qstat failed
399
+ # transiently---a busy PBS server periodically refuses connections or times
400
+ # out. Treating a transient failure as completion would stop the wait while
401
+ # the job is still running and writing its log, so only conclude the job is
402
+ # done when qstat positively reports it is unknown; otherwise keep waiting.
403
+ # A purged job is done but with an unknowable exit status.
404
+ stderr = (p.stderr or "").lower()
405
+ return ("unknown job" not in stderr), None
375
406
 
376
407
 
377
408
  def _is_active(kind: str, job_id: str) -> bool:
@@ -215,44 +215,75 @@ def test_poll_job_pbs(monkeypatch):
215
215
 
216
216
  import calkit.cli.scheduler as sched
217
217
 
218
- outcomes: dict = {}
218
+ # PBS is polled in two steps: plain `qstat -f` for the active queue, then
219
+ # `qstat -x -f` for the finished-job history view. The fake dispatches on
220
+ # whether `-x` is present so each step can be simulated independently, the
221
+ # way a real PBS Pro server answers them.
222
+ active: dict = {}
223
+ history: dict = {}
219
224
 
220
- def _fake_run(*args, **kwargs):
225
+ def _fake_run(cmd, *args, **kwargs):
226
+ outcomes = history if "-x" in cmd else active
221
227
  return subprocess.CompletedProcess(
222
- args=args[0],
228
+ cmd,
223
229
  returncode=outcomes["returncode"],
224
230
  stdout=outcomes.get("stdout", ""),
225
231
  stderr=outcomes.get("stderr", ""),
226
232
  )
227
233
 
228
234
  monkeypatch.setattr(sched.subprocess, "run", _fake_run)
229
- # A running job (state R) is active with no exit code yet.
230
- outcomes.update(returncode=0, stdout=" job_state = R\n", stderr="")
235
+ # A running job (state R) is active with no exit code yet, and resolves
236
+ # from the active queue alone---the history view is never consulted.
237
+ active.update(returncode=0, stdout=" job_state = R\n", stderr="")
238
+ history.clear()
231
239
  assert _poll_job("pbs", "1.pbs") == (True, None)
232
- # Terminal states C (Torque) and F (PBS Pro) mean the job is done; qstat
233
- # still exits 0 while the record lingers in history and carries the exit
234
- # status, which is reported back so the caller can fail a bad job.
235
- outcomes.update(
240
+ # The reported bug: on PBS Pro a finished job leaves the active queue and
241
+ # is shown only under `qstat -x`, where it appears as state F with its
242
+ # Exit_status. Plain `qstat -f` errors with a message that is NOT "unknown
243
+ # job", which used to be read as "still active" and hang the wait forever.
244
+ active.update(
245
+ returncode=1,
246
+ stdout="",
247
+ stderr="qstat: 1.pbs Job has finished, use -x to obtain historical "
248
+ "job information\n",
249
+ )
250
+ history.update(
236
251
  returncode=0, stdout=" job_state = F\n Exit_status = 0\n"
237
252
  )
238
253
  assert _poll_job("pbs", "1.pbs") == (False, 0)
239
- outcomes.update(
254
+ # A non-zero exit code from the finished job is reported back so the caller
255
+ # can fail the stage.
256
+ history.update(
257
+ returncode=0, stdout=" job_state = F\n Exit_status = 137\n"
258
+ )
259
+ assert _poll_job("pbs", "1.pbs") == (False, 137)
260
+ # On Torque a completed job lingers in the active queue as state C with its
261
+ # exit status, so it is done without ever needing the history view.
262
+ active.update(
240
263
  returncode=0, stdout=" job_state = C\n exit_status = 137\n"
241
264
  )
265
+ history.update(returncode=1, stdout="", stderr="should not be reached\n")
242
266
  assert _poll_job("pbs", "1.pbs") == (False, 137)
243
267
  # A finished job whose record lacks an exit status yields an unknown code.
244
- outcomes.update(returncode=0, stdout=" job_state = F\n")
268
+ active.update(returncode=1, stdout="", stderr="qstat: Unknown Job Id\n")
269
+ history.update(returncode=0, stdout=" job_state = F\n")
245
270
  assert _poll_job("pbs", "1.pbs") == (False, None)
246
- # Once purged, qstat exits non-zero and reports the job unknown: done, but
247
- # with no way to recover the exit status.
248
- outcomes.update(
271
+ # Once purged from history too, both views error with "unknown job": done,
272
+ # but with no way to recover the exit status.
273
+ active.update(
274
+ returncode=1, stdout="", stderr="qstat: Unknown Job Id 1.pbs\n"
275
+ )
276
+ history.update(
249
277
  returncode=1, stdout="", stderr="qstat: Unknown Job Id 1.pbs\n"
250
278
  )
251
279
  assert _poll_job("pbs", "1.pbs") == (False, None)
252
- # A transient qstat failure (busy/unreachable server) is NOT completion:
253
- # treating it as done would stop the wait while the job still runs, so the
254
- # job is reported active and the caller keeps polling.
255
- outcomes.update(
280
+ # A transient qstat failure (busy/unreachable server) on both views is NOT
281
+ # completion: treating it as done would stop the wait while the job still
282
+ # runs, so the job is reported active and the caller keeps polling.
283
+ active.update(
284
+ returncode=1, stdout="", stderr="qstat: cannot connect to server\n"
285
+ )
286
+ history.update(
256
287
  returncode=1, stdout="", stderr="qstat: cannot connect to server\n"
257
288
  )
258
289
  assert _poll_job("pbs", "1.pbs") == (True, None)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: calkit-python
3
- Version: 0.41.17
3
+ Version: 0.41.18
4
4
  Summary: Reproducibility simplified.
5
5
  Project-URL: Homepage, https://calkit.org
6
6
  Project-URL: Issues, https://github.com/calkit/calkit/issues
@@ -44,7 +44,7 @@ calkit/cli/new.py,sha256=OtXcXmhtywXM54dgyjxQgtYEfCYBAi_UVKvMlPjSI8k,129111
44
44
  calkit/cli/notebooks.py,sha256=mMNHE8yT0BDN0iJE07GCBFAseyDF0jSRshIm1CQjgrY,24645
45
45
  calkit/cli/office.py,sha256=jVSTvbl91TCzlglST5O2b8hdApZA_QHw_UiEQFJqxdc,1754
46
46
  calkit/cli/overleaf.py,sha256=bS-SkYsSqqhC9YsP-NCzpYJCGS3820w18w_s8JOFihg,25216
47
- calkit/cli/scheduler.py,sha256=Mn_G2tx78A_wJ3XYE0hRtTkPj376sKTWpSxMZKirHuA,38660
47
+ calkit/cli/scheduler.py,sha256=Nt1wQMJzuY3ZNkq1KVEV0dZk4T8Gr0XPLQ6mJpXPrZE,40125
48
48
  calkit/cli/update.py,sha256=KaO-VbUxMpR_a9FV42HG8d9p-iaA4-2-L-fAgb2LiYI,43499
49
49
  calkit/cli/main/__init__.py,sha256=qu1POZPyqs33ZKfasOxv_Wc-EzVcEgK3Dt_vwFL8Bi8,65
50
50
  calkit/cli/main/core.py,sha256=MMKEAI4bAJm1YJcS4E5MTQAR-XluwtGn3wlsNYjK-NE,123878
@@ -103,7 +103,7 @@ calkit/tests/cli/test_list.py,sha256=ejMg6q8vtjCbKxvS9zZ32rv_oeKH94JbvpEiVjnw5QA
103
103
  calkit/tests/cli/test_new.py,sha256=_-lQH53OkKC-nyLPrdnwndTDyzXv8-aGnXOaDQ_bu-s,46568
104
104
  calkit/tests/cli/test_notebooks.py,sha256=2t1KiGhEz9H9LcIdWy4jGM05REUxJpWiIg6fSpX12ME,10678
105
105
  calkit/tests/cli/test_overleaf.py,sha256=Y-ud4kZHh3nhyoZlXrtMfx61z-GPNytUpfDHMad3tuA,16474
106
- calkit/tests/cli/test_scheduler.py,sha256=MY2tmhcDLPypNS2yFONUeLjvQog37iw2XGHc9c2wtCY,15715
106
+ calkit/tests/cli/test_scheduler.py,sha256=UBJCcZzx9lvPwymFi3SOt5bdwuNZ8wzDy2obMZ-Qn5E,17205
107
107
  calkit/tests/cli/test_update.py,sha256=bwRF0kpqbCVxNvGH777LHFX3oUP2DyN3XXUBDCEKNoc,6356
108
108
  calkit/tests/cli/main/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
109
109
  calkit/tests/cli/main/test_core.py,sha256=lowrbL1WCUPAEQjzMWFLPTbrARyFGEQKtVWGp2Lqrpg,63418
@@ -123,23 +123,23 @@ calkit/tests/models/test_pipeline.py,sha256=AJsMAX4928U3_Omt0_8K96U5LU_KoNErgpHe
123
123
  calkit/agent_skills/add-pipeline-stage/SKILL.md,sha256=kpYYb-rJsS4B9p1Ub3gL21irCG5ZZ4DvWZpdJeEwPZk,3772
124
124
  calkit/agent_skills/conventions/SKILL.md,sha256=nAM2FjSMk6ED56Dr5zh2bL_dhfzDra-h2ZgzHU7ruS4,9524
125
125
  calkit/agent_skills/create-pipeline/SKILL.md,sha256=2FGw1iDQVRk5FUq79GP3lPdgvgof2Wi1O9O-abGGtgs,5422
126
- calkit_python-0.41.17.data/data/etc/jupyter/jupyter_server_config.d/calkit.json,sha256=CWrZP--JDGz8fsvbAlKr_duTL1vDriGT48SL1YCtkY0,81
127
- calkit_python-0.41.17.data/data/share/jupyter/labextensions/calkit/package.json,sha256=39PQEWOzw0qtqD7Kep3OPbUR113vI7CG5TeD84v7clM,6224
128
- calkit_python-0.41.17.data/data/share/jupyter/labextensions/calkit/schemas/calkit/package.json.orig,sha256=NBG3t7gFYb6_2J-yPU29qS7Ck9pYgm8-wC5Q1LrzQeI,6082
129
- calkit_python-0.41.17.data/data/share/jupyter/labextensions/calkit/schemas/calkit/plugin.json,sha256=YSpIrwpwB-lQBk9Mwv-npedWTOOZreO6nlWZhqlWyGI,840
130
- calkit_python-0.41.17.data/data/share/jupyter/labextensions/calkit/static/502.9a2c5772a15466e923ef.js,sha256=mixXcqFUZukj7_jQVxHTavsYl7cdZv83sEe4phJgkh0,59893
131
- calkit_python-0.41.17.data/data/share/jupyter/labextensions/calkit/static/695.2c41003a452d43d2b358.js,sha256=LEEAOkUtQ9KzWEHn-QFv5yGi70B-sBOnrvztzHr0Shw,223
132
- calkit_python-0.41.17.data/data/share/jupyter/labextensions/calkit/static/867.a42a046aa5108f54f8fb.js,sha256=pCoEaqUQj1T4-zAc9SUd__9ZGm7uL2wHQve2xwL89NI,8156
133
- calkit_python-0.41.17.data/data/share/jupyter/labextensions/calkit/static/909.e3f9cc3408834a7fdcc3.js,sha256=4_nMNAiDSn_cw7UjIHEwone4fizrvqrqSgbwUWvjmoU,114571
134
- calkit_python-0.41.17.data/data/share/jupyter/labextensions/calkit/static/946.050af2abf7845cfbdbd2.js,sha256=n_a0yu_gE7l6fauyoXXv9BZ7ZEYt2387mTfs7CbLcs4,51939
135
- calkit_python-0.41.17.data/data/share/jupyter/labextensions/calkit/static/946.050af2abf7845cfbdbd2.js.LICENSE.txt,sha256=eNJ8gc9n9IF8nW1d9sI9niuHstYzjNz5vqXx9UgWSPc,249
136
- calkit_python-0.41.17.data/data/share/jupyter/labextensions/calkit/static/b2f1c3efe70cb539d121.png,sha256=svHD7-cMtTnRITFwugwsVaB9nZ-h8A61ose8z32HiRE,24850
137
- calkit_python-0.41.17.data/data/share/jupyter/labextensions/calkit/static/remoteEntry.ac9035764d5b2adbb542.js,sha256=rJA1dk1bKtu1QkURrp5HEC2WCxzZppPV1SCYz-35GTc,8737
138
- calkit_python-0.41.17.data/data/share/jupyter/labextensions/calkit/static/style.js,sha256=r89Jlk5v1drcwhCpv9FnC3Jig_JH4k24kZNIvxh6Htk,149
139
- calkit_python-0.41.17.data/data/share/jupyter/labextensions/calkit/static/third-party-licenses.json,sha256=2BGdItLJwO3fAopLl4eJvp26TEwuscYC0-yFaF-LaLU,13683
140
- calkit_python-0.41.17.data/data/share/jupyter/labextensions/calkit/install.json,sha256=DK9d8G-q-rMVlcT3rAeGIyo3REWKw6FySBZceLU9yaw,187
141
- calkit_python-0.41.17.dist-info/METADATA,sha256=wqrw0yWOq-fJqDiURfAMqy0zMP4MSuBHuZAMlsC3ddo,12437
142
- calkit_python-0.41.17.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
143
- calkit_python-0.41.17.dist-info/entry_points.txt,sha256=2iQBBzjTAOdk66CwS-f3ecXXW5DjUqkG1KBRj8mHI1I,133
144
- calkit_python-0.41.17.dist-info/licenses/LICENSE,sha256=9ZamCaSUTZk9rcrnf-sWFKLOHr3ws-S_dgKMegW4nw8,1056
145
- calkit_python-0.41.17.dist-info/RECORD,,
126
+ calkit_python-0.41.18.data/data/etc/jupyter/jupyter_server_config.d/calkit.json,sha256=CWrZP--JDGz8fsvbAlKr_duTL1vDriGT48SL1YCtkY0,81
127
+ calkit_python-0.41.18.data/data/share/jupyter/labextensions/calkit/package.json,sha256=39PQEWOzw0qtqD7Kep3OPbUR113vI7CG5TeD84v7clM,6224
128
+ calkit_python-0.41.18.data/data/share/jupyter/labextensions/calkit/schemas/calkit/package.json.orig,sha256=NBG3t7gFYb6_2J-yPU29qS7Ck9pYgm8-wC5Q1LrzQeI,6082
129
+ calkit_python-0.41.18.data/data/share/jupyter/labextensions/calkit/schemas/calkit/plugin.json,sha256=YSpIrwpwB-lQBk9Mwv-npedWTOOZreO6nlWZhqlWyGI,840
130
+ calkit_python-0.41.18.data/data/share/jupyter/labextensions/calkit/static/502.9a2c5772a15466e923ef.js,sha256=mixXcqFUZukj7_jQVxHTavsYl7cdZv83sEe4phJgkh0,59893
131
+ calkit_python-0.41.18.data/data/share/jupyter/labextensions/calkit/static/695.2c41003a452d43d2b358.js,sha256=LEEAOkUtQ9KzWEHn-QFv5yGi70B-sBOnrvztzHr0Shw,223
132
+ calkit_python-0.41.18.data/data/share/jupyter/labextensions/calkit/static/867.a42a046aa5108f54f8fb.js,sha256=pCoEaqUQj1T4-zAc9SUd__9ZGm7uL2wHQve2xwL89NI,8156
133
+ calkit_python-0.41.18.data/data/share/jupyter/labextensions/calkit/static/909.e3f9cc3408834a7fdcc3.js,sha256=4_nMNAiDSn_cw7UjIHEwone4fizrvqrqSgbwUWvjmoU,114571
134
+ calkit_python-0.41.18.data/data/share/jupyter/labextensions/calkit/static/946.050af2abf7845cfbdbd2.js,sha256=n_a0yu_gE7l6fauyoXXv9BZ7ZEYt2387mTfs7CbLcs4,51939
135
+ calkit_python-0.41.18.data/data/share/jupyter/labextensions/calkit/static/946.050af2abf7845cfbdbd2.js.LICENSE.txt,sha256=eNJ8gc9n9IF8nW1d9sI9niuHstYzjNz5vqXx9UgWSPc,249
136
+ calkit_python-0.41.18.data/data/share/jupyter/labextensions/calkit/static/b2f1c3efe70cb539d121.png,sha256=svHD7-cMtTnRITFwugwsVaB9nZ-h8A61ose8z32HiRE,24850
137
+ calkit_python-0.41.18.data/data/share/jupyter/labextensions/calkit/static/remoteEntry.ac9035764d5b2adbb542.js,sha256=rJA1dk1bKtu1QkURrp5HEC2WCxzZppPV1SCYz-35GTc,8737
138
+ calkit_python-0.41.18.data/data/share/jupyter/labextensions/calkit/static/style.js,sha256=r89Jlk5v1drcwhCpv9FnC3Jig_JH4k24kZNIvxh6Htk,149
139
+ calkit_python-0.41.18.data/data/share/jupyter/labextensions/calkit/static/third-party-licenses.json,sha256=2BGdItLJwO3fAopLl4eJvp26TEwuscYC0-yFaF-LaLU,13683
140
+ calkit_python-0.41.18.data/data/share/jupyter/labextensions/calkit/install.json,sha256=DK9d8G-q-rMVlcT3rAeGIyo3REWKw6FySBZceLU9yaw,187
141
+ calkit_python-0.41.18.dist-info/METADATA,sha256=zexxBw6ZYpD61wV5bcv8i25G0lGpGxrhJ0HKM3CAh1s,12437
142
+ calkit_python-0.41.18.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
143
+ calkit_python-0.41.18.dist-info/entry_points.txt,sha256=2iQBBzjTAOdk66CwS-f3ecXXW5DjUqkG1KBRj8mHI1I,133
144
+ calkit_python-0.41.18.dist-info/licenses/LICENSE,sha256=9ZamCaSUTZk9rcrnf-sWFKLOHr3ws-S_dgKMegW4nw8,1056
145
+ calkit_python-0.41.18.dist-info/RECORD,,