marimo-export 0.0.3__tar.gz → 0.0.4__tar.gz

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 (183) hide show
  1. {marimo_export-0.0.3 → marimo_export-0.0.4}/.agent-plugin/skills/notebook-to-static-app/scripts/scaffold_app.py +1 -1
  2. {marimo_export-0.0.3 → marimo_export-0.0.4}/PKG-INFO +1 -1
  3. {marimo_export-0.0.3 → marimo_export-0.0.4}/pyproject.toml +1 -1
  4. {marimo_export-0.0.3 → marimo_export-0.0.4}/pyproject.toml.orig +1 -1
  5. marimo_export-0.0.3/src/marimo_export/_services/identity.py → marimo_export-0.0.4/src/marimo_export/_environment.py +19 -200
  6. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_marimo/compat/cache/attempts.py +59 -2
  7. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_marimo/compat/cache/lifecycle.py +3 -0
  8. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_marimo/compat/cache/patch.py +11 -2
  9. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_marimo/compat/cache/probe.py +2 -0
  10. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_marimo/compat/execution.py +10 -0
  11. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_marimo/compat/managed_kernel.py +6 -4
  12. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_marimo/compat/release.json +6 -5
  13. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_publication.py +13 -6
  14. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_repository/observations.py +29 -19
  15. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_repository/preparation.py +33 -24
  16. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_services/export_artifacts.py +9 -13
  17. marimo_export-0.0.4/src/marimo_export/_services/identity.py +206 -0
  18. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/prepared.py +18 -2
  19. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/repository.py +11 -2
  20. {marimo_export-0.0.3 → marimo_export-0.0.4}/.agent-plugin/plugin.json +0 -0
  21. {marimo_export-0.0.3 → marimo_export-0.0.4}/.agent-plugin/skills/notebook-to-static-app/SKILL.md +0 -0
  22. {marimo_export-0.0.3 → marimo_export-0.0.4}/.agent-plugin/skills/notebook-to-static-app/agents/openai.yaml +0 -0
  23. {marimo_export-0.0.3 → marimo_export-0.0.4}/.agent-plugin/skills/notebook-to-static-app/assets/app/.gitignore +0 -0
  24. {marimo_export-0.0.3 → marimo_export-0.0.4}/.agent-plugin/skills/notebook-to-static-app/assets/app/index.html +0 -0
  25. {marimo_export-0.0.3 → marimo_export-0.0.4}/.agent-plugin/skills/notebook-to-static-app/assets/app/pnpm-workspace.yaml +0 -0
  26. {marimo_export-0.0.3 → marimo_export-0.0.4}/.agent-plugin/skills/notebook-to-static-app/assets/app/src/main.ts +0 -0
  27. {marimo_export-0.0.3 → marimo_export-0.0.4}/.agent-plugin/skills/notebook-to-static-app/assets/app/src/style.css +0 -0
  28. {marimo_export-0.0.3 → marimo_export-0.0.4}/.agent-plugin/skills/notebook-to-static-app/assets/app/src/vite-env.d.ts +0 -0
  29. {marimo_export-0.0.3 → marimo_export-0.0.4}/.agent-plugin/skills/notebook-to-static-app/assets/app/tsconfig.json +0 -0
  30. {marimo_export-0.0.3 → marimo_export-0.0.4}/.agent-plugin/skills/notebook-to-static-app/assets/app/vite.config.ts +0 -0
  31. {marimo_export-0.0.3 → marimo_export-0.0.4}/.agent-plugin/skills/notebook-to-static-app/references/contracts.md +0 -0
  32. {marimo_export-0.0.3 → marimo_export-0.0.4}/.agent-plugin/skills/notebook-to-static-app/references/workflow.md +0 -0
  33. {marimo_export-0.0.3 → marimo_export-0.0.4}/LICENSE +0 -0
  34. {marimo_export-0.0.3 → marimo_export-0.0.4}/README.md +0 -0
  35. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/__init__.py +0 -0
  36. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_blob_asset.py +0 -0
  37. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_build.py +0 -0
  38. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_cell_ids.py +0 -0
  39. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_cli/__init__.py +0 -0
  40. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_cli/arguments.py +0 -0
  41. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_cli/commands.py +0 -0
  42. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_cli/errors.py +0 -0
  43. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_cli/render.py +0 -0
  44. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_client_protocol.py +0 -0
  45. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_control_roots.py +0 -0
  46. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_delivery.py +0 -0
  47. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_delivery_validation.py +0 -0
  48. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_diagnostics.py +0 -0
  49. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_directory.py +0 -0
  50. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_directory_native.py +0 -0
  51. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_directory_security.py +0 -0
  52. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_directory_target.py +0 -0
  53. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_execution/__init__.py +0 -0
  54. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_execution/plan.py +0 -0
  55. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_format.py +0 -0
  56. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_http_url.py +0 -0
  57. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_identity.py +0 -0
  58. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_json.py +0 -0
  59. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_limits.py +0 -0
  60. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_marimo/__init__.py +0 -0
  61. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_marimo/anywidget.py +0 -0
  62. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_marimo/blob.py +0 -0
  63. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_marimo/bridge.py +0 -0
  64. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_marimo/capabilities.py +0 -0
  65. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_marimo/compat/__init__.py +0 -0
  66. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_marimo/compat/anywidget.py +0 -0
  67. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_marimo/compat/anywidget_assets.py +0 -0
  68. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_marimo/compat/blob.py +0 -0
  69. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_marimo/compat/cache/__init__.py +0 -0
  70. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_marimo/compat/cache/barrier.py +0 -0
  71. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_marimo/compat/cache/host.py +0 -0
  72. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_marimo/compat/cache/loader.py +0 -0
  73. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_marimo/compat/cache/receipts.py +0 -0
  74. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_marimo/compat/child_run.py +0 -0
  75. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_marimo/compat/exporter_identity.py +0 -0
  76. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_marimo/compat/exporters.py +0 -0
  77. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_marimo/compat/file_closure.py +0 -0
  78. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_marimo/compat/inspection.py +0 -0
  79. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_marimo/compat/kernel.py +0 -0
  80. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_marimo/compat/managed_server.py +0 -0
  81. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_marimo/compat/observations.py +0 -0
  82. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_marimo/compat/output_data.py +0 -0
  83. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_marimo/compat/projections.py +0 -0
  84. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_marimo/compat/receipts.py +0 -0
  85. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_marimo/compat/replay.py +0 -0
  86. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_marimo/compat/transfer.py +0 -0
  87. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_marimo/composition.py +0 -0
  88. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_marimo/entrypoints.py +0 -0
  89. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_marimo/managed_server.py +0 -0
  90. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_marimo/transfer.py +0 -0
  91. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_media_type.py +0 -0
  92. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_notebook.py +0 -0
  93. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_observations/__init__.py +0 -0
  94. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_observations/candidates.py +0 -0
  95. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_observations/ledger.py +0 -0
  96. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_observations/queue.py +0 -0
  97. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_observations/source.py +0 -0
  98. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_portable.py +0 -0
  99. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_prepared_integrity.py +0 -0
  100. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_remote/__init__.py +0 -0
  101. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_remote/auth.py +0 -0
  102. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_remote/client.py +0 -0
  103. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_remote/managed.py +0 -0
  104. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_remote/sse.py +0 -0
  105. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_remote/windows_job.py +0 -0
  106. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_repository/__init__.py +0 -0
  107. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_repository/artifact_access.py +0 -0
  108. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_repository/artifact_commit.py +0 -0
  109. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_repository/artifact_context.py +0 -0
  110. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_repository/artifact_lifecycle.py +0 -0
  111. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_repository/artifacts.py +0 -0
  112. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_repository/capabilities.py +0 -0
  113. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_repository/files.py +0 -0
  114. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_repository/handles.py +0 -0
  115. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_repository/leases.py +0 -0
  116. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_repository/models.py +0 -0
  117. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_repository/paths.py +0 -0
  118. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_repository/sqlite/__init__.py +0 -0
  119. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_repository/sqlite/artifacts.py +0 -0
  120. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_repository/sqlite/catalog.py +0 -0
  121. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_repository/sqlite/generations.py +0 -0
  122. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_repository/sqlite/leases.py +0 -0
  123. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_repository/sqlite/maintenance.py +0 -0
  124. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_repository/sqlite/observations.py +0 -0
  125. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_repository/sqlite/open.py +0 -0
  126. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_repository/sqlite/prepared_states.py +0 -0
  127. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_repository/sqlite/records.py +0 -0
  128. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_repository/sqlite/recovery.py +0 -0
  129. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_repository/sqlite/retention.py +0 -0
  130. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_repository/sqlite/retired.py +0 -0
  131. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_repository/sqlite/schema.py +0 -0
  132. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_repository/sqlite/status.py +0 -0
  133. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_secure_io.py +0 -0
  134. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_selector.py +0 -0
  135. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_services/__init__.py +0 -0
  136. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_services/capture_export.py +0 -0
  137. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_services/plan_export.py +0 -0
  138. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_services/plan_wire.py +0 -0
  139. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_services/preparation_support.py +0 -0
  140. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_services/prepare_export.py +0 -0
  141. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_services/state_preparation.py +0 -0
  142. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_services/write_export.py +0 -0
  143. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_snapshot.py +0 -0
  144. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/_writer.py +0 -0
  145. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/agent.py +0 -0
  146. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/cli.py +0 -0
  147. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/client.py +0 -0
  148. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/delivery.py +0 -0
  149. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/descriptors.py +0 -0
  150. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/diagnostics.py +0 -0
  151. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/errors.py +0 -0
  152. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/exporters/__init__.py +0 -0
  153. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/exporters/_anywidget_payload.py +0 -0
  154. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/exporters/_definitions.py +0 -0
  155. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/exporters/_optional.py +0 -0
  156. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/exporters/_runtime/__init__.py +0 -0
  157. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/exporters/_runtime/altair.py +0 -0
  158. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/exporters/_runtime/anywidget.py +0 -0
  159. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/exporters/_runtime/blob.py +0 -0
  160. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/exporters/_runtime/parquet.py +0 -0
  161. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/exporters/_spec.py +0 -0
  162. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/exporters/altair.py +0 -0
  163. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/exporters/anywidget.py +0 -0
  164. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/exporters/blob.py +0 -0
  165. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/exporters/parquet.py +0 -0
  166. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/index.py +0 -0
  167. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/inspection.py +0 -0
  168. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/integration.py +0 -0
  169. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/limits.py +0 -0
  170. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/manifest.py +0 -0
  171. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/observations.py +0 -0
  172. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/outputs.py +0 -0
  173. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/planning.py +0 -0
  174. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/producer.py +0 -0
  175. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/progress.py +0 -0
  176. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/publication.py +0 -0
  177. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/py.typed +0 -0
  178. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/reader.py +0 -0
  179. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/result.py +0 -0
  180. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/sessions.py +0 -0
  181. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/spec.py +0 -0
  182. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/verification.py +0 -0
  183. {marimo_export-0.0.3 → marimo_export-0.0.4}/src/marimo_export/wire.py +0 -0
@@ -380,7 +380,7 @@ def _write_package_json(
380
380
  "vite-plus": "0.3.0",
381
381
  },
382
382
  "engines": {"node": ">=22.18.0"},
383
- "packageManager": "pnpm@11.25.0",
383
+ "packageManager": "pnpm@12.3.4",
384
384
  }
385
385
  path.write_text(json.dumps(value, indent=2) + "\n", encoding="utf-8")
386
386
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: marimo-export
3
- Version: 0.0.3
3
+ Version: 0.0.4
4
4
  Summary: Prepare and read verified exports of selected marimo notebook outputs.
5
5
  Keywords: cache,export,marimo,notebook
6
6
  Author: marimo team
@@ -7,7 +7,7 @@ build-backend = "agent_plugins.build.uv_build"
7
7
 
8
8
  [project]
9
9
  name = "marimo-export"
10
- version = "0.0.3"
10
+ version = "0.0.4"
11
11
  description = "Prepare and read verified exports of selected marimo notebook outputs."
12
12
  readme = "README.md"
13
13
  license = "Apache-2.0"
@@ -4,7 +4,7 @@ build-backend = "agent_plugins.build.uv_build"
4
4
 
5
5
  [project]
6
6
  name = "marimo-export"
7
- version = "0.0.3"
7
+ version = "0.0.4"
8
8
  description = "Prepare and read verified exports of selected marimo notebook outputs."
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -1,4 +1,4 @@
1
- """Compute export producer identity without starting a notebook process."""
1
+ """Fingerprint installed distributions and local runtime dependencies."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
@@ -7,185 +7,18 @@ import hashlib
7
7
  import importlib.metadata
8
8
  import importlib.util
9
9
  import os
10
- import platform
11
- import stat
12
10
  import sys
13
- from dataclasses import dataclass
14
11
  from pathlib import Path
15
12
  from typing import Any
16
13
 
17
- from marimo_export._format import digest
18
- from marimo_export._identity import require_implementation_stable
19
14
  from marimo_export._json import JsonObject, canonical_bytes, sha256_bytes
20
- from marimo_export._notebook import _notebook_path, _read_stable_source, document_sha256
21
- from marimo_export._portable import validate_portable_basename
22
- from marimo_export.errors import CompatibilityError, ExecutionError
23
- from marimo_export.index import ProducerProvenance
24
- from marimo_export.spec import StrPath
25
15
 
26
16
 
27
- @dataclass(frozen=True, slots=True)
28
- class ProducerIdentity:
29
- """Stable producer facts used for repository reuse and export provenance."""
17
+ def environment_identity(source: Path | None, *, exclude_source: bool = False) -> str:
18
+ """Hash stable runtime dependencies, optionally leaving notebook code to Marimo."""
30
19
 
31
- source: Path | None
32
- filename: str | None
33
- source_sha256: str
34
- document_sha256: str
35
- producer_sha256: str
36
- marimo_version: str
37
- marimo_export_version: str
38
- implementation_sha256: str
39
- environment_sha256: str
40
-
41
- def __post_init__(self) -> None:
42
- if self.source is not None:
43
- if not isinstance(self.source, Path) or not self.source.is_absolute():
44
- raise ValueError("producer source must be an absolute pathlib.Path or None")
45
- try:
46
- metadata = self.source.lstat()
47
- except OSError as error:
48
- raise ValueError("producer source must be available") from error
49
- if not stat.S_ISREG(metadata.st_mode):
50
- raise ValueError("producer source must be a regular file")
51
- if self.filename != self.source.name:
52
- raise ValueError("producer filename must match its source basename")
53
- if self.filename is not None:
54
- validate_portable_basename(self.filename, "producer filename")
55
- for name in (
56
- "source_sha256",
57
- "document_sha256",
58
- "producer_sha256",
59
- "implementation_sha256",
60
- "environment_sha256",
61
- ):
62
- digest(getattr(self, name), f"producer {name}")
63
- for name in ("marimo_version", "marimo_export_version"):
64
- value = getattr(self, name)
65
- if (
66
- not isinstance(value, str)
67
- or not value
68
- or len(value.encode("utf-8")) > 255
69
- or any(ord(character) < 32 or ord(character) == 127 for character in value)
70
- ):
71
- raise ValueError(f"producer {name} must be a bounded nonempty string")
72
-
73
- @property
74
- def provenance(self) -> ProducerProvenance:
75
- return ProducerProvenance(
76
- marimo=self.marimo_version,
77
- marimo_export=self.marimo_export_version,
78
- implementation_sha256=self.implementation_sha256,
79
- )
80
-
81
-
82
- def producer_identity(source: StrPath) -> ProducerIdentity:
83
- """Return exact source and runtime identity without executing notebook code."""
84
-
85
- notebook = _notebook_path(source)
86
- _, source_sha256, source_revision = _read_stable_source(notebook)
87
- document = document_sha256(notebook)
88
- _, after_sha256, after_revision = _read_stable_source(notebook)
89
- if after_sha256 != source_sha256 or after_revision != source_revision:
90
- raise RuntimeError("notebook source changed while producer identity was computed")
91
-
92
- return _identity_from_facts(
93
- document_sha256=document,
94
- source_sha256=source_sha256,
95
- source=notebook,
96
- filename=notebook.name,
97
- )
98
-
99
-
100
- def runtime_producer_identity(
101
- *,
102
- document_sha256: str,
103
- source: StrPath | None = None,
104
- source_sha256: str | None = None,
105
- filename: str | None = None,
106
- ) -> ProducerIdentity:
107
- """Return producer identity from runtime facts available inside a kernel."""
108
-
109
- if source is not None:
110
- stable = producer_identity(source)
111
- if stable.document_sha256 != document_sha256:
112
- raise ExecutionError(
113
- "the runtime source does not match the inspected notebook document",
114
- code="parent_document_changed",
115
- details={
116
- "expected": document_sha256,
117
- "actual": stable.document_sha256,
118
- },
119
- )
120
- return stable
121
- return _identity_from_facts(
122
- document_sha256=document_sha256,
123
- source_sha256=document_sha256 if source_sha256 is None else source_sha256,
124
- source=None,
125
- filename=filename,
126
- )
127
-
128
-
129
- def _identity_from_facts(
130
- *,
131
- document_sha256: str,
132
- source_sha256: str,
133
- source: Path | None,
134
- filename: str | None,
135
- ) -> ProducerIdentity:
136
- marimo_version = _required_distribution_version("marimo")
137
- export_version = _distribution_version("marimo-export", fallback="0.0.0")
138
- implementation = require_implementation_stable()
139
- environment = _environment_identity(source)
140
- producer = sha256_bytes(
141
- canonical_bytes(
142
- {
143
- "document_sha256": document_sha256,
144
- "environment_sha256": environment,
145
- "implementation_sha256": implementation,
146
- "marimo": marimo_version,
147
- "marimo_export": export_version,
148
- "platform": {
149
- "machine": platform.machine(),
150
- "system": platform.system(),
151
- },
152
- "python": {
153
- "abi": sys.implementation.cache_tag or "unknown",
154
- "implementation": sys.implementation.name,
155
- "version": platform.python_version(),
156
- },
157
- "source_sha256": source_sha256,
158
- }
159
- )
160
- )
161
- return ProducerIdentity(
162
- source=source,
163
- filename=filename,
164
- source_sha256=source_sha256,
165
- document_sha256=document_sha256,
166
- producer_sha256=producer,
167
- marimo_version=marimo_version,
168
- marimo_export_version=export_version,
169
- implementation_sha256=implementation,
170
- environment_sha256=environment,
171
- )
172
-
173
-
174
- def producer_sha256(source: StrPath) -> str:
175
- """Return the spec-independent producer identity for one notebook source."""
176
-
177
- return producer_identity(source).producer_sha256
178
-
179
-
180
- def managed_runtime_source(runtime_path: str | None) -> str | None:
181
- """Return the authored source path selected for an attached runtime."""
182
-
183
- return os.environ.get("MARIMO_EXPORT_MANAGED_SOURCE") or runtime_path
184
-
185
-
186
- def _environment_identity(source: Path | None) -> str:
187
20
  distributions = _installed_distributions()
188
- sources = {} if source is None else _local_source_record(source)
21
+ sources = {} if source is None else _local_source_record(source, exclude_source=exclude_source)
189
22
  if _installed_distributions() != distributions:
190
23
  raise RuntimeError("installed distributions changed while identity was computed")
191
24
  return sha256_bytes(
@@ -214,16 +47,20 @@ def _installed_distributions() -> list[JsonObject]:
214
47
  return distributions
215
48
 
216
49
 
217
- def _local_source_record(source: Path) -> JsonObject:
50
+ def _local_source_record(source: Path, *, exclude_source: bool = False) -> JsonObject:
218
51
  try:
219
52
  resolved = source.resolve(strict=True)
220
53
  root, search_roots = _local_source_roots(resolved)
221
54
  before = _local_source_manifest(root, search_roots)
55
+ if exclude_source:
56
+ before = tuple(row for row in before if root / row[0] != resolved)
222
57
  records: JsonObject = {}
223
58
  for relative, *_revision_parts in before:
224
59
  records[relative] = _stable_file_sha256(root / relative)
225
60
  after_root, after_search_roots = _local_source_roots(resolved)
226
61
  after = _local_source_manifest(root, search_roots)
62
+ if exclude_source:
63
+ after = tuple(row for row in after if root / row[0] != resolved)
227
64
  after_records = {
228
65
  relative: _stable_file_sha256(root / relative) for relative, *_revision_parts in after
229
66
  }
@@ -249,7 +86,7 @@ def _local_source_manifest(
249
86
  paths: set[Path] = set()
250
87
  for search_root in selected:
251
88
  if search_root.is_file():
252
- if _source_file(search_root) and not _excluded_source(search_root, root):
89
+ if _source_file(search_root) and not _excluded_source(search_root, search_root.parent):
253
90
  paths.add(search_root)
254
91
  continue
255
92
  if not search_root.is_dir() or search_root.is_symlink():
@@ -259,12 +96,12 @@ def _local_source_manifest(
259
96
  directories[:] = [
260
97
  name
261
98
  for name in sorted(directories)
262
- if not _excluded_source(current_path / name, root)
99
+ if not _excluded_source(current_path / name, search_root)
263
100
  and not (current_path / name).is_symlink()
264
101
  ]
265
102
  for name in sorted(filenames):
266
103
  path = current_path / name
267
- if _source_file(path) and not _excluded_source(path, root):
104
+ if _source_file(path) and not _excluded_source(path, search_root):
268
105
  paths.add(path)
269
106
  return tuple(
270
107
  (
@@ -301,7 +138,12 @@ def _local_source_roots(source: Path) -> tuple[Path, tuple[Path, ...]]:
301
138
  if not base.is_dir() or base.is_symlink():
302
139
  continue
303
140
  for child in base.iterdir():
304
- if child.is_dir() and not child.is_symlink() and (child / "__init__.py").is_file():
141
+ if (
142
+ child.is_dir()
143
+ and not child.is_symlink()
144
+ and not _excluded_source(child, base)
145
+ and (child / "__init__.py").is_file()
146
+ ):
305
147
  roots.add(child)
306
148
  for name in names:
307
149
  module = base / f"{name}.py"
@@ -400,27 +242,4 @@ def _revision(value: Any) -> tuple[int, int, int, int, int]:
400
242
  )
401
243
 
402
244
 
403
- def _required_distribution_version(name: str) -> str:
404
- try:
405
- return importlib.metadata.version(name)
406
- except importlib.metadata.PackageNotFoundError as error:
407
- raise CompatibilityError(
408
- f"required runtime distribution {name!r} is unavailable",
409
- code="runtime_distribution_unavailable",
410
- ) from error
411
-
412
-
413
- def _distribution_version(name: str, *, fallback: str) -> str:
414
- try:
415
- return importlib.metadata.version(name)
416
- except importlib.metadata.PackageNotFoundError:
417
- return fallback
418
-
419
-
420
- __all__ = [
421
- "ProducerIdentity",
422
- "managed_runtime_source",
423
- "producer_identity",
424
- "producer_sha256",
425
- "runtime_producer_identity",
426
- ]
245
+ __all__ = ["environment_identity"]
@@ -2,6 +2,8 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import ast
6
+ import copy
5
7
  import threading
6
8
  from collections.abc import Callable, Iterator
7
9
  from contextlib import contextmanager
@@ -54,6 +56,7 @@ class _GraphScope:
54
56
  graph: Any
55
57
  output_cells: frozenset[Any]
56
58
  activity: CacheAttemptLog
59
+ environment: str | None = None
57
60
  forced_cells: frozenset[Any] = frozenset()
58
61
 
59
62
 
@@ -65,6 +68,8 @@ _SCOPES: dict[int, _GraphScope] = {}
65
68
  def track_notebook_cache(
66
69
  child_graph: Any,
67
70
  output_cell_ids: frozenset[Any],
71
+ *,
72
+ environment: str | None = None,
68
73
  ) -> Iterator[CacheAttemptLog]:
69
74
  """Record effective cache decisions for one exact child graph."""
70
75
 
@@ -72,6 +77,7 @@ def track_notebook_cache(
72
77
  graph=child_graph,
73
78
  output_cells=output_cell_ids,
74
79
  activity=CacheAttemptLog(),
80
+ environment=environment,
75
81
  )
76
82
  key = id(child_graph)
77
83
  with _SCOPES_LOCK:
@@ -88,10 +94,10 @@ def track_notebook_cache(
88
94
 
89
95
 
90
96
  @contextmanager
91
- def track_managed_parent_cache(graph: Any) -> Iterator[None]:
97
+ def track_managed_parent_cache(graph: Any, *, environment: str | None = None) -> Iterator[None]:
92
98
  """Activate export cache policy for one owned parent graph."""
93
99
 
94
- with track_notebook_cache(graph, frozenset()):
100
+ with track_notebook_cache(graph, frozenset(), environment=environment):
95
101
  yield
96
102
 
97
103
 
@@ -124,6 +130,11 @@ def cache_attempt_wrapper(native: Callable[..., Cache]) -> Callable[..., Cache]:
124
130
  *args: Any,
125
131
  **kwargs: Any,
126
132
  ) -> Cache:
133
+ with _SCOPES_LOCK:
134
+ scope = _SCOPES.get(id(graph))
135
+ environment = scope.environment if scope is not None and scope.graph is graph else None
136
+ if environment is not None:
137
+ module, scope_values = _with_environment(module, scope_values, environment)
127
138
  attempt = native(
128
139
  module,
129
140
  graph,
@@ -160,6 +171,42 @@ def cache_attempt_wrapper(native: Callable[..., Cache]) -> Callable[..., Cache]:
160
171
  return tracked
161
172
 
162
173
 
174
+ def _with_environment(
175
+ module: ast.Module,
176
+ scope: dict[str, Any],
177
+ environment: str,
178
+ ) -> tuple[ast.Module, dict[str, Any]]:
179
+ from marimo._ast.transformers import DeprivateVisitor
180
+ from marimo._ast.variables import unmangle_local
181
+ from marimo._ast.visitor import ScopedVisitor
182
+
183
+ lookup = DeprivateVisitor().visit(copy.deepcopy(module))
184
+ visitor = ScopedVisitor("", ignore_local=True)
185
+ visitor.visit(lookup)
186
+ names = visitor.defs | visitor.refs | {unmangle_local(name).name for name in scope}
187
+ name = "__marimo_export_cache_environment"
188
+ while name in scope or name in names:
189
+ name += "_"
190
+ reference = ast.Expr(value=ast.Name(id=name, ctx=ast.Load()))
191
+ # The native hasher receives this reference. Authored execution keeps its
192
+ # original AST and scope, so the dependency input creates no notebook name.
193
+ lookup.body.append(reference)
194
+ return ast.fix_missing_locations(lookup), {**scope, name: environment}
195
+
196
+
197
+ def record_cache_miss(graph: Any, cell_id: Any) -> None:
198
+ """Record a live run chosen after native restoration."""
199
+
200
+ with _SCOPES_LOCK:
201
+ scope = _scope_for(graph)
202
+ target = (
203
+ scope.activity.output_cells
204
+ if cell_id in scope.output_cells
205
+ else scope.activity.authored_cells
206
+ )
207
+ target[cell_id] = "miss"
208
+
209
+
163
210
  def _scope_for(graph: Any) -> _GraphScope:
164
211
  scope = _SCOPES.get(id(graph))
165
212
  if scope is None or scope.graph is not graph:
@@ -175,6 +222,14 @@ def has_cache_scope(graph: Any) -> bool:
175
222
  return scope is not None and scope.graph is graph
176
223
 
177
224
 
225
+ def tracked_environment(graph: Any) -> str | None:
226
+ """Return the dependency identity frozen by this graph's owner."""
227
+
228
+ with _SCOPES_LOCK:
229
+ scope = _SCOPES.get(id(graph))
230
+ return scope.environment if scope is not None and scope.graph is graph else None
231
+
232
+
178
233
  def _rerun_unavailable_attempt(attempt: Cache) -> Cache:
179
234
  if not attempt.hit or not (
180
235
  any(_contains_unavailable(value) for value in attempt.defs.values())
@@ -213,6 +268,8 @@ __all__ = [
213
268
  "cache_attempt_wrapper",
214
269
  "force_cache_misses",
215
270
  "has_cache_scope",
271
+ "record_cache_miss",
216
272
  "track_managed_parent_cache",
217
273
  "track_notebook_cache",
274
+ "tracked_environment",
218
275
  ]
@@ -12,6 +12,7 @@ from marimo_export._marimo.compat.cache.attempts import (
12
12
  _empty_attempt,
13
13
  _rerun_unavailable_attempt,
14
14
  has_cache_scope,
15
+ record_cache_miss,
15
16
  )
16
17
 
17
18
 
@@ -23,6 +24,7 @@ class CompleteCachedLifecycle(CachedLifecycle):
23
24
  if not has_cache_scope(self._graph):
24
25
  return decision
25
26
  if not isinstance(decision, Skip):
27
+ record_cache_miss(self._graph, cell.cell_id)
26
28
  return decision
27
29
  attempt = self._attempts.get(cell.cell_id)
28
30
  if attempt is None:
@@ -36,6 +38,7 @@ class CompleteCachedLifecycle(CachedLifecycle):
36
38
  return decision
37
39
  self._attempts[cell.cell_id] = retry
38
40
  self._exec_starts[cell.cell_id] = time.time()
41
+ record_cache_miss(self._graph, cell.cell_id)
39
42
  return None
40
43
 
41
44
 
@@ -179,13 +179,22 @@ async def sequential_cache_loader() -> AsyncIterator[None]:
179
179
 
180
180
 
181
181
  @contextmanager
182
- def managed_cache_compat(hooks: Any, parent_graph: Any | None = None) -> Iterator[None]:
182
+ def managed_cache_compat(
183
+ hooks: Any,
184
+ parent_graph: Any | None = None,
185
+ *,
186
+ environment: str | None = None,
187
+ ) -> Iterator[None]:
183
188
  """Install cache behavior for one owned kernel lifespan."""
184
189
 
185
190
  from marimo_export._marimo.compat.cache.attempts import track_managed_parent_cache
186
191
  from marimo_export._marimo.compat.cache.barrier import add_cache_write_barrier
187
192
 
188
- scope = nullcontext() if parent_graph is None else track_managed_parent_cache(parent_graph)
193
+ scope = (
194
+ nullcontext()
195
+ if parent_graph is None
196
+ else track_managed_parent_cache(parent_graph, environment=environment)
197
+ )
189
198
  with scope:
190
199
  handle = _PATCHES.open()
191
200
  try:
@@ -49,6 +49,7 @@ def require_cache_capabilities() -> None:
49
49
 
50
50
  try:
51
51
  import marimo._runtime.context as context_module
52
+ import marimo._save.hash as hash_module
52
53
  import marimo._save.loaders as loaders_module
53
54
  import marimo._save.loaders.lazy as lazy_module
54
55
  import marimo._save.stubs as stubs_module
@@ -71,6 +72,7 @@ def require_cache_capabilities() -> None:
71
72
  blob_schema = getattr(stubs_module, "BlobAsset", None)
72
73
 
73
74
  symbols = {
75
+ "BlockHasher": getattr(hash_module, "BlockHasher", None),
74
76
  "CachedLifecycle.__init__": getattr(lifecycle, "__init__", None),
75
77
  "CachedLifecycle._restored_ui_defs": restored_ui_defs,
76
78
  "CachedLifecycle.setup": getattr(lifecycle, "setup", None),
@@ -7,9 +7,11 @@ import time
7
7
  from collections.abc import Iterator, Mapping
8
8
  from contextlib import contextmanager
9
9
  from dataclasses import dataclass
10
+ from pathlib import Path
10
11
  from types import MappingProxyType
11
12
  from typing import Any
12
13
 
14
+ from marimo_export._environment import environment_identity
13
15
  from marimo_export._execution.plan import ExecutionPlan, NormalizedState, PlannedOutput
14
16
  from marimo_export._json import (
15
17
  JsonObject,
@@ -21,6 +23,7 @@ from marimo_export._marimo.compat.cache.attempts import (
21
23
  CacheAttemptLog,
22
24
  force_cache_misses,
23
25
  track_notebook_cache,
26
+ tracked_environment,
24
27
  )
25
28
  from marimo_export._marimo.compat.child_run import (
26
29
  StateChild,
@@ -94,9 +97,16 @@ async def execute_state(
94
97
  state=state,
95
98
  plan=plan,
96
99
  )
100
+ environment = tracked_environment(state_child.context.parent.graph)
101
+ if environment is None:
102
+ source = state_child.context.filename
103
+ environment = environment_identity(
104
+ None if source is None else Path(source), exclude_source=True
105
+ )
97
106
  with track_notebook_cache(
98
107
  state_child.runner._kernel.graph,
99
108
  run_plan.transient_cache_cells,
109
+ environment=environment,
100
110
  ) as notebook_cache:
101
111
  input_phase = await _execute_inputs(state_child, state, run_plan)
102
112
  output_phase = await _execute_outputs(
@@ -5,6 +5,9 @@ from __future__ import annotations
5
5
  import os
6
6
  from collections.abc import AsyncIterator
7
7
  from contextlib import asynccontextmanager
8
+ from pathlib import Path
9
+
10
+ from marimo_export._environment import environment_identity
8
11
 
9
12
  _MANAGED_CACHE_COMPAT_ENV = "MARIMO_EXPORT_MANAGED_CACHE_COMPAT"
10
13
  _MANAGED_CACHE_ACTIVATION_ENV = "MARIMO_EXPORT_MANAGED_CACHE_ACTIVATION"
@@ -39,14 +42,13 @@ async def kernel_lifespan(_: None) -> AsyncIterator[None]:
39
42
  from marimo_export._marimo.compat.inspection import install_parent_stop_provenance
40
43
 
41
44
  context = get_context()
42
- if not isinstance(context, KernelRuntimeContext):
45
+ if not isinstance(context, KernelRuntimeContext) or context.filename is None:
43
46
  raise RuntimeError("managed cache integration requires a file-backed marimo kernel")
44
47
  require_cache_capabilities()
48
+ environment = environment_identity(Path(context.filename), exclude_source=True)
45
49
  release_stop_provenance = install_parent_stop_provenance(context)
46
50
  try:
47
- with managed_cache_compat(context._kernel._hooks, context.graph):
48
- from pathlib import Path
49
-
51
+ with managed_cache_compat(context._kernel._hooks, context.graph, environment=environment):
50
52
  Path(activation_path).write_text(activation_token, encoding="utf-8")
51
53
  yield
52
54
  finally:
@@ -2,12 +2,13 @@
2
2
  "version": "0.24.0",
3
3
  "commit": "854f7f2910b4bb4b6aebe650efc1f83ad40d9bef",
4
4
  "cache_source_sha256": {
5
- "CachedLifecycle.__init__": "b68aab07eac87ce3e1d1dfb4aaf586cc4903e7e962b86aa6f05f369b487d5650",
6
- "CachedLifecycle._restored_ui_defs": "4f1d2220a6d7e78d208d76fd4e08b4ab4ddb8da724e9725260e0264bf7cd77fc",
7
- "CachedLifecycle.setup": "90d8e2a30661c728df5c4e40370e1224c61dc26de08f2e72bb273179e85ebfcc",
8
5
  "BlobAsset.schema_source": "ade24b0b25f4816aa296c3eda6623960f4a77c514c9124b5b6394f0c1ef3818a",
6
+ "BlockHasher": "c0b088d5c51322419fc3dbc8a0ed0bf715348300c4568b0eecaf25b5eb2e6ce1",
9
7
  "Cache.schema_source": "c587eb7298afd67887d9cb0c75d9f81e16d54c9f0c6b819d673ecbba35df8119",
10
8
  "CacheSignatureError": "ee0427f2e16982e20dad8ee34ea25b7ddb53ff545b2478ba36ab087c27e7280a",
9
+ "CachedLifecycle.__init__": "b68aab07eac87ce3e1d1dfb4aaf586cc4903e7e962b86aa6f05f369b487d5650",
10
+ "CachedLifecycle._restored_ui_defs": "4f1d2220a6d7e78d208d76fd4e08b4ab4ddb8da724e9725260e0264bf7cd77fc",
11
+ "CachedLifecycle.setup": "90d8e2a30661c728df5c4e40370e1224c61dc26de08f2e72bb273179e85ebfcc",
11
12
  "Item.schema_source": "4b7f1722b2d1583d1c905540a3be691d6bca1e5014d8227a48cf245e733f7062",
12
13
  "LazyLoader._effective_mode": "0e5efc063f8cf83a3b324f539fe3ed0eead25294cea12e5f1f11734b199bd3bb",
13
14
  "LazyLoader._read_blobs": "c0c69d24de338c5ab61669fa8b570e623944c60a014176d98c62df2c0af2f97f",
@@ -16,8 +17,8 @@
16
17
  "_verify_signed_blob": "8d650a60e10b9b36b3e312843fc1642e8b67c0b020c30f1afef6fa097473dd50",
17
18
  "cache_attempt_from_hash": "4a1b9421f70703337b2b0874997de0a3022dec99c3a7ed15b140c0e4ccb4f576",
18
19
  "encode._contiguous_tensor_bytes": "b5af49d65687b2ee6b1469ebe46299cb0f975d2f3135962108cbe1b7255f4642",
19
- "from_item": "7a14447ebb9f4a8957580fe3eb16ec2108aefb7f086ae739d020de8a4f3ae7c8",
20
- "flush_active_caches": "43a9a8956d21ab9adea51be5daaae66db07e17572dd7d138a53ef9695abe3a7c"
20
+ "flush_active_caches": "43a9a8956d21ab9adea51be5daaae66db07e17572dd7d138a53ef9695abe3a7c",
21
+ "from_item": "7a14447ebb9f4a8957580fe3eb16ec2108aefb7f086ae739d020de8a4f3ae7c8"
21
22
  },
22
23
  "receipt_schema_fields": {
23
24
  "BlobAsset": ["data", "media_type", "filename", "metadata"],
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
+ import math
4
5
  import threading
5
6
  from collections.abc import Callable, Hashable
6
7
  from dataclasses import dataclass, field
@@ -54,8 +55,12 @@ class PublicationControllerState(Generic[KeyT, MetadataT]):
54
55
  route_key: Callable[[KeyT], Hashable] | None,
55
56
  route_grace_seconds: float,
56
57
  ) -> None:
57
- if route_grace_seconds < 0:
58
- raise ValueError("route_grace_seconds must be nonnegative")
58
+ if isinstance(route_grace_seconds, bool) or not isinstance(
59
+ route_grace_seconds, (int, float)
60
+ ):
61
+ raise TypeError("route_grace_seconds must be a number")
62
+ if not math.isfinite(route_grace_seconds) or route_grace_seconds < 0:
63
+ raise ValueError("route_grace_seconds must be a finite nonnegative number")
59
64
  self._repository_value = repository
60
65
  self._owns_repository = repository is None
61
66
  self._repository_lock = threading.Lock()
@@ -118,7 +123,9 @@ class PublicationControllerState(Generic[KeyT, MetadataT]):
118
123
  return None
119
124
  pending = any(work.group == owned.group for work in self._work.values())
120
125
  if key not in self._refresh_tasks and not pending:
121
- task = asyncio.create_task(self._refresh_if_stale(owned))
126
+ task = asyncio.create_task(
127
+ self._refresh_if_stale(owned, self._desired.get(owned.group))
128
+ )
122
129
  self._refresh_tasks[key] = task
123
130
  task.add_done_callback(
124
131
  lambda completed, selected_key=key: self._refresh_finished(selected_key, completed)
@@ -202,9 +209,7 @@ class PublicationControllerState(Generic[KeyT, MetadataT]):
202
209
  work: _Work[KeyT, MetadataT],
203
210
  ) -> PreparedPublication[KeyT, MetadataT]:
204
211
  candidate = await asyncio.to_thread(
205
- work.prepare,
206
- self._repository(),
207
- work.cancelled.is_set,
212
+ lambda: work.prepare(self._repository(), work.cancelled.is_set)
208
213
  )
209
214
  if not isinstance(candidate, PreparedPublicationCandidate):
210
215
  raise TypeError("prepare must return a PreparedPublicationCandidate")
@@ -245,6 +250,7 @@ class PublicationControllerState(Generic[KeyT, MetadataT]):
245
250
  async def _refresh_if_stale(
246
251
  self,
247
252
  selected: _OwnedPublication[KeyT, MetadataT],
253
+ desired: int | None,
248
254
  ) -> None:
249
255
  try:
250
256
  revision = await asyncio.to_thread(
@@ -254,6 +260,7 @@ class PublicationControllerState(Generic[KeyT, MetadataT]):
254
260
  if (
255
261
  self._closed
256
262
  or self._current.get(selected.key) is not selected
263
+ or self._desired.get(selected.group) != desired
257
264
  or revision <= selected.publication.plan.observation_revision
258
265
  ):
259
266
  return