pywire 0.1.4__tar.gz → 0.1.5__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 (205) hide show
  1. {pywire-0.1.4 → pywire-0.1.5}/.github/workflows/publish.yml +0 -6
  2. {pywire-0.1.4 → pywire-0.1.5}/PKG-INFO +1 -1
  3. pywire-0.1.5/hatch_build.py +140 -0
  4. {pywire-0.1.4 → pywire-0.1.5}/pyproject.toml +10 -2
  5. pywire-0.1.5/src/pywire/__init__.py +12 -0
  6. pywire-0.1.5/src/pywire/_version.py +34 -0
  7. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/cli/main.py +13 -10
  8. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/client/build.mjs +4 -1
  9. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/client/package.json +1 -1
  10. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/client/src/core/app.ts +5 -0
  11. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/client/src/core/transports/base.ts +7 -1
  12. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/client/src/core/transports/http.ts +5 -1
  13. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/client/tsconfig.json +2 -1
  14. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/runtime/app.py +2 -1
  15. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/runtime/http_transport.py +3 -1
  16. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/runtime/websocket.py +6 -0
  17. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/static/pywire.core.min.js +3 -3
  18. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/static/pywire.dev.min.js +4 -4
  19. {pywire-0.1.4 → pywire-0.1.5}/tests/test_app_runtime.py +2 -1
  20. {pywire-0.1.4 → pywire-0.1.5}/tests/test_pjax_error.py +3 -0
  21. {pywire-0.1.4 → pywire-0.1.5}/tests/test_relocation_error.py +3 -0
  22. pywire-0.1.4/src/pywire/__init__.py +0 -2
  23. {pywire-0.1.4 → pywire-0.1.5}/.github/CODEOWNERS +0 -0
  24. {pywire-0.1.4 → pywire-0.1.5}/.github/workflows/ci.yml +0 -0
  25. {pywire-0.1.4 → pywire-0.1.5}/.github/workflows/deploy-docs.yml +0 -0
  26. {pywire-0.1.4 → pywire-0.1.5}/.gitignore +0 -0
  27. {pywire-0.1.4 → pywire-0.1.5}/LICENSE +0 -0
  28. {pywire-0.1.4 → pywire-0.1.5}/README.md +0 -0
  29. {pywire-0.1.4 → pywire-0.1.5}/docs/.gitignore +0 -0
  30. {pywire-0.1.4 → pywire-0.1.5}/docs/.prettierignore +0 -0
  31. {pywire-0.1.4 → pywire-0.1.5}/docs/.prettierrc +0 -0
  32. {pywire-0.1.4 → pywire-0.1.5}/docs/.vscode/extensions.json +0 -0
  33. {pywire-0.1.4 → pywire-0.1.5}/docs/.vscode/launch.json +0 -0
  34. {pywire-0.1.4 → pywire-0.1.5}/docs/README.md +0 -0
  35. {pywire-0.1.4 → pywire-0.1.5}/docs/astro.config.mjs +0 -0
  36. {pywire-0.1.4 → pywire-0.1.5}/docs/eslint.config.js +0 -0
  37. {pywire-0.1.4 → pywire-0.1.5}/docs/package.json +0 -0
  38. {pywire-0.1.4 → pywire-0.1.5}/docs/pnpm-lock.yaml +0 -0
  39. {pywire-0.1.4 → pywire-0.1.5}/docs/public/favicon.svg +0 -0
  40. {pywire-0.1.4 → pywire-0.1.5}/docs/scripts/check +0 -0
  41. {pywire-0.1.4 → pywire-0.1.5}/docs/scripts/lint +0 -0
  42. {pywire-0.1.4 → pywire-0.1.5}/docs/src/assets/houston.webp +0 -0
  43. {pywire-0.1.4 → pywire-0.1.5}/docs/src/content/docs/guides/example.md +0 -0
  44. {pywire-0.1.4 → pywire-0.1.5}/docs/src/content/docs/guides/getting-started.md +0 -0
  45. {pywire-0.1.4 → pywire-0.1.5}/docs/src/content/docs/guides/walkthrough.md +0 -0
  46. {pywire-0.1.4 → pywire-0.1.5}/docs/src/content/docs/index.mdx +0 -0
  47. {pywire-0.1.4 → pywire-0.1.5}/docs/src/content/docs/reference/api.md +0 -0
  48. {pywire-0.1.4 → pywire-0.1.5}/docs/src/content/docs/reference/example.md +0 -0
  49. {pywire-0.1.4 → pywire-0.1.5}/docs/src/content.config.ts +0 -0
  50. {pywire-0.1.4 → pywire-0.1.5}/docs/src/styles/custom.css +0 -0
  51. {pywire-0.1.4 → pywire-0.1.5}/docs/tsconfig.json +0 -0
  52. {pywire-0.1.4 → pywire-0.1.5}/scripts/README.md +0 -0
  53. {pywire-0.1.4 → pywire-0.1.5}/scripts/build +0 -0
  54. {pywire-0.1.4 → pywire-0.1.5}/scripts/check +0 -0
  55. {pywire-0.1.4 → pywire-0.1.5}/scripts/coverage +0 -0
  56. {pywire-0.1.4 → pywire-0.1.5}/scripts/docs +0 -0
  57. {pywire-0.1.4 → pywire-0.1.5}/scripts/install +0 -0
  58. {pywire-0.1.4 → pywire-0.1.5}/scripts/lint +0 -0
  59. {pywire-0.1.4 → pywire-0.1.5}/scripts/test +0 -0
  60. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/cli/__init__.py +0 -0
  61. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/cli/generators.py +0 -0
  62. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/cli/tui.py +0 -0
  63. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/cli/validate.py +0 -0
  64. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/client/.prettierignore +0 -0
  65. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/client/.prettierrc +0 -0
  66. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/client/eslint.config.js +0 -0
  67. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/client/pnpm-lock.yaml +0 -0
  68. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/client/src/core/dom-updater.test.ts +0 -0
  69. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/client/src/core/dom-updater.ts +0 -0
  70. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/client/src/core/index.ts +0 -0
  71. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/client/src/core/transport-manager.test.ts +0 -0
  72. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/client/src/core/transport-manager.ts +0 -0
  73. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/client/src/core/transports/index.ts +0 -0
  74. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/client/src/core/transports/websocket.ts +0 -0
  75. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/client/src/core/transports/webtransport.ts +0 -0
  76. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/client/src/dev/dev-app.ts +0 -0
  77. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/client/src/dev/error-trace.test.ts +0 -0
  78. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/client/src/dev/error-trace.ts +0 -0
  79. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/client/src/dev/index.ts +0 -0
  80. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/client/src/dev/status-overlay.ts +0 -0
  81. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/client/src/events/handler.test.ts +0 -0
  82. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/client/src/events/handler.ts +0 -0
  83. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/client/src/pywire.core.ts +0 -0
  84. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/client/src/pywire.dev.ts +0 -0
  85. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/client/vitest.config.ts +0 -0
  86. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/compiler/__init__.py +0 -0
  87. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/compiler/ast_nodes.py +0 -0
  88. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/compiler/attributes/__init__.py +0 -0
  89. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/compiler/attributes/base.py +0 -0
  90. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/compiler/attributes/conditional.py +0 -0
  91. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/compiler/attributes/events.py +0 -0
  92. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/compiler/attributes/form.py +0 -0
  93. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/compiler/attributes/loop.py +0 -0
  94. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/compiler/attributes/reactive.py +0 -0
  95. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/compiler/build.py +0 -0
  96. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/compiler/build_artifacts.py +0 -0
  97. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/compiler/codegen/__init__.py +0 -0
  98. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/compiler/codegen/attributes/__init__.py +0 -0
  99. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/compiler/codegen/attributes/base.py +0 -0
  100. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/compiler/codegen/attributes/events.py +0 -0
  101. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/compiler/codegen/directives/__init__.py +0 -0
  102. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/compiler/codegen/directives/base.py +0 -0
  103. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/compiler/codegen/directives/path.py +0 -0
  104. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/compiler/codegen/generator.py +0 -0
  105. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/compiler/codegen/template.py +0 -0
  106. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/compiler/directives/__init__.py +0 -0
  107. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/compiler/directives/base.py +0 -0
  108. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/compiler/directives/component.py +0 -0
  109. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/compiler/directives/context.py +0 -0
  110. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/compiler/directives/layout.py +0 -0
  111. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/compiler/directives/no_spa.py +0 -0
  112. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/compiler/directives/path.py +0 -0
  113. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/compiler/directives/props.py +0 -0
  114. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/compiler/exceptions.py +0 -0
  115. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/compiler/interpolation/__init__.py +0 -0
  116. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/compiler/interpolation/base.py +0 -0
  117. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/compiler/interpolation/jinja.py +0 -0
  118. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/compiler/parser.py +0 -0
  119. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/compiler/paths.py +0 -0
  120. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/compiler/preprocessor.py +0 -0
  121. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/core/wire.py +0 -0
  122. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/py.typed +0 -0
  123. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/runtime/__init__.py +0 -0
  124. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/runtime/aioquic_server.py +0 -0
  125. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/runtime/compile_error_page.py +0 -0
  126. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/runtime/debug.py +0 -0
  127. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/runtime/dev_server.py +0 -0
  128. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/runtime/dev_server.py.broken +0 -0
  129. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/runtime/error_page.py +0 -0
  130. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/runtime/error_renderer.py +0 -0
  131. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/runtime/escape.py +0 -0
  132. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/runtime/files.py +0 -0
  133. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/runtime/helpers.py +0 -0
  134. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/runtime/loader.py +0 -0
  135. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/runtime/logging.py +0 -0
  136. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/runtime/page.py +0 -0
  137. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/runtime/pydantic_integration.py +0 -0
  138. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/runtime/router.py +0 -0
  139. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/runtime/server.py +0 -0
  140. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/runtime/style_collector.py +0 -0
  141. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/runtime/upload_manager.py +0 -0
  142. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/runtime/validation.py +0 -0
  143. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/runtime/webtransport_handler.py +0 -0
  144. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/templates/error/404.html +0 -0
  145. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/templates/error/500.html +0 -0
  146. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/templates/error/base.html +0 -0
  147. {pywire-0.1.4 → pywire-0.1.5}/src/pywire/templates/error/compile_error.html +0 -0
  148. {pywire-0.1.4 → pywire-0.1.5}/tests/debug_router.py +0 -0
  149. {pywire-0.1.4 → pywire-0.1.5}/tests/test_app_advanced.py +0 -0
  150. {pywire-0.1.4 → pywire-0.1.5}/tests/test_app_exhaustive.py +0 -0
  151. {pywire-0.1.4 → pywire-0.1.5}/tests/test_app_mode.py +0 -0
  152. {pywire-0.1.4 → pywire-0.1.5}/tests/test_async_features.py +0 -0
  153. {pywire-0.1.4 → pywire-0.1.5}/tests/test_codegen_generator.py +0 -0
  154. {pywire-0.1.4 → pywire-0.1.5}/tests/test_codegen_template.py +0 -0
  155. {pywire-0.1.4 → pywire-0.1.5}/tests/test_codegen_template_exhaustive.py +0 -0
  156. {pywire-0.1.4 → pywire-0.1.5}/tests/test_compile_error.py +0 -0
  157. {pywire-0.1.4 → pywire-0.1.5}/tests/test_compiler_sourcemap.py +0 -0
  158. {pywire-0.1.4 → pywire-0.1.5}/tests/test_comprehensive_python_syntax.py +0 -0
  159. {pywire-0.1.4 → pywire-0.1.5}/tests/test_config.py +0 -0
  160. {pywire-0.1.4 → pywire-0.1.5}/tests/test_custom_errors.py +0 -0
  161. {pywire-0.1.4 → pywire-0.1.5}/tests/test_debug_middleware.py +0 -0
  162. {pywire-0.1.4 → pywire-0.1.5}/tests/test_debug_mock.py +0 -0
  163. {pywire-0.1.4 → pywire-0.1.5}/tests/test_error_handling_debug.py +0 -0
  164. {pywire-0.1.4 → pywire-0.1.5}/tests/test_file_routing.py +0 -0
  165. {pywire-0.1.4 → pywire-0.1.5}/tests/test_fixes.py +0 -0
  166. {pywire-0.1.4 → pywire-0.1.5}/tests/test_form_validation.py +0 -0
  167. {pywire-0.1.4 → pywire-0.1.5}/tests/test_generator_advanced.py +0 -0
  168. {pywire-0.1.4 → pywire-0.1.5}/tests/test_generator_exhaustive.py +0 -0
  169. {pywire-0.1.4 → pywire-0.1.5}/tests/test_hooks_removal.py +0 -0
  170. {pywire-0.1.4 → pywire-0.1.5}/tests/test_http_transport.py +0 -0
  171. {pywire-0.1.4 → pywire-0.1.5}/tests/test_inline_compilation.py +0 -0
  172. {pywire-0.1.4 → pywire-0.1.5}/tests/test_interactivity_codegen_complex.py +0 -0
  173. {pywire-0.1.4 → pywire-0.1.5}/tests/test_interactivity_compiler.py +0 -0
  174. {pywire-0.1.4 → pywire-0.1.5}/tests/test_jinja_interpolation.py +0 -0
  175. {pywire-0.1.4 → pywire-0.1.5}/tests/test_layouts.py +0 -0
  176. {pywire-0.1.4 → pywire-0.1.5}/tests/test_lifecycle_hooks.py +0 -0
  177. {pywire-0.1.4 → pywire-0.1.5}/tests/test_logging_stream.py +0 -0
  178. {pywire-0.1.4 → pywire-0.1.5}/tests/test_method_binding.py +0 -0
  179. {pywire-0.1.4 → pywire-0.1.5}/tests/test_middleware_error.py +0 -0
  180. {pywire-0.1.4 → pywire-0.1.5}/tests/test_mode_gating.py +0 -0
  181. {pywire-0.1.4 → pywire-0.1.5}/tests/test_naming_conventions.py +0 -0
  182. {pywire-0.1.4 → pywire-0.1.5}/tests/test_parser_advanced.py +0 -0
  183. {pywire-0.1.4 → pywire-0.1.5}/tests/test_parser_compiler.py +0 -0
  184. {pywire-0.1.4 → pywire-0.1.5}/tests/test_reactive_attributes.py +0 -0
  185. {pywire-0.1.4 → pywire-0.1.5}/tests/test_rendering.py +0 -0
  186. {pywire-0.1.4 → pywire-0.1.5}/tests/test_router_exhaustive.py +0 -0
  187. {pywire-0.1.4 → pywire-0.1.5}/tests/test_router_expansion.py +0 -0
  188. {pywire-0.1.4 → pywire-0.1.5}/tests/test_router_runtime.py +0 -0
  189. {pywire-0.1.4 → pywire-0.1.5}/tests/test_runtime_helpers.py +0 -0
  190. {pywire-0.1.4 → pywire-0.1.5}/tests/test_script_injection.py +0 -0
  191. {pywire-0.1.4 → pywire-0.1.5}/tests/test_server_validation.py +0 -0
  192. {pywire-0.1.4 → pywire-0.1.5}/tests/test_static_assets.py +0 -0
  193. {pywire-0.1.4 → pywire-0.1.5}/tests/test_strict_routing.py +0 -0
  194. {pywire-0.1.4 → pywire-0.1.5}/tests/test_style_collector.py +0 -0
  195. {pywire-0.1.4 → pywire-0.1.5}/tests/test_syntax_enforcement.py +0 -0
  196. {pywire-0.1.4 → pywire-0.1.5}/tests/test_templating_features.py +0 -0
  197. {pywire-0.1.4 → pywire-0.1.5}/tests/test_transport_advanced.py +0 -0
  198. {pywire-0.1.4 → pywire-0.1.5}/tests/test_transport_exhaustive.py +0 -0
  199. {pywire-0.1.4 → pywire-0.1.5}/tests/test_validation_exhaustive.py +0 -0
  200. {pywire-0.1.4 → pywire-0.1.5}/tests/test_websocket_advanced.py +0 -0
  201. {pywire-0.1.4 → pywire-0.1.5}/tests/test_websocket_exhaustive.py +0 -0
  202. {pywire-0.1.4 → pywire-0.1.5}/tests/test_websocket_handler.py +0 -0
  203. {pywire-0.1.4 → pywire-0.1.5}/tests/test_wire_primitive.py +0 -0
  204. {pywire-0.1.4 → pywire-0.1.5}/tox.ini +0 -0
  205. {pywire-0.1.4 → pywire-0.1.5}/uv.lock +0 -0
@@ -31,12 +31,6 @@ jobs:
31
31
  with:
32
32
  enable-cache: true
33
33
 
34
- - name: Build client assets
35
- run: |
36
- cd src/pywire/client
37
- pnpm install
38
- pnpm build
39
-
40
34
  - name: Build
41
35
  run: uv build
42
36
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pywire
3
- Version: 0.1.4
3
+ Version: 0.1.5
4
4
  Summary: HTML-over-the-wire Python web framework
5
5
  Author-email: Reece Holmdahl <reece@pywire.dev>
6
6
  License-File: LICENSE
@@ -0,0 +1,140 @@
1
+ import json
2
+ import logging
3
+ import os
4
+ import sys
5
+ from contextlib import suppress
6
+ from pathlib import Path
7
+ from typing import Any, Dict
8
+
9
+ from hatchling.builders.hooks.plugin.interface import BuildHookInterface
10
+
11
+ log = logging.getLogger(__name__)
12
+
13
+ class CustomBuildHook(BuildHookInterface):
14
+ def initialize(self, version: str, build_data: Dict[str, Any]) -> None:
15
+ real_version = self.metadata.version
16
+
17
+ # We only want to do this if we actually have a version
18
+ if not real_version:
19
+ return
20
+
21
+ # Attempt to find the client package.json
22
+ # When building sdist, we might be in the source tree.
23
+ # When building wheel, we might be in a copy.
24
+
25
+ # Try explicit relative path first
26
+ pkg_path = Path(self.root) / "src" / "pywire" / "client" / "package.json"
27
+
28
+ if not pkg_path.exists():
29
+ log.warning(f"Client package.json not found at {pkg_path}")
30
+ return
31
+
32
+ try:
33
+ content = pkg_path.read_text("utf-8")
34
+ data = json.loads(content)
35
+
36
+ current_version = data.get("version")
37
+
38
+ # Normalize PEP 440 to SemVer
39
+ # 0.1.5.dev0+... -> 0.1.5-dev0+...
40
+ semver = real_version.replace(".dev", "-dev")
41
+ semver = semver.replace(".a", "-alpha")
42
+ semver = semver.replace(".b", "-beta")
43
+ semver = semver.replace(".rc", "-rc")
44
+
45
+ version_changed = False
46
+ if current_version != semver:
47
+ log.info(f"Syncing client version: {current_version} -> {semver}")
48
+ data["version"] = semver
49
+
50
+ # formatting with 2 spaces to match typical usage
51
+ pkg_path.write_text(json.dumps(data, indent=2) + "\n", "utf-8")
52
+ version_changed = True
53
+
54
+ # Now run the build
55
+ # We need to run pnpm install and pnpm run build
56
+ import subprocess
57
+ import shutil
58
+
59
+ client_dir = pkg_path.parent
60
+ static_dir = client_dir.parent / "static"
61
+
62
+ if not shutil.which("pnpm"):
63
+ log.warning("pnpm not found, skipping client build. Assets may be stale.")
64
+ return
65
+
66
+ # Check if we need to install
67
+ if self._should_install(client_dir):
68
+ log.info("Installing client dependencies...")
69
+ subprocess.run(["pnpm", "install"], cwd=client_dir, check=True, shell=False)
70
+ else:
71
+ log.debug("Client dependencies up to date.")
72
+
73
+ # Check if we need to build
74
+ if self._should_build(client_dir, static_dir) or version_changed:
75
+ log.info("Building client assets with pnpm...")
76
+ subprocess.run(["pnpm", "run", "build"], cwd=client_dir, check=True, shell=False)
77
+ log.info("Client build complete.")
78
+ else:
79
+ log.info("Client assets up to date, skipping build.")
80
+
81
+ except Exception as e:
82
+ log.error(f"Failed to sync client version: {e}")
83
+
84
+ def _should_install(self, client_dir: Path) -> bool:
85
+ """Check if node_modules is missing or package.json changed."""
86
+ node_modules = client_dir / "node_modules"
87
+ package_json = client_dir / "package.json"
88
+ lock_file = client_dir / "pnpm-lock.yaml"
89
+
90
+ if not node_modules.exists():
91
+ return True
92
+
93
+ # If no package.json, can't install (shouldn't happen here)
94
+ if not package_json.exists():
95
+ return False
96
+
97
+ # Check timestamps
98
+ # Use node_modules directory mtime as proxy for install time
99
+ nm_mtime = node_modules.stat().st_mtime
100
+ pkg_mtime = package_json.stat().st_mtime
101
+
102
+ if pkg_mtime > nm_mtime:
103
+ return True
104
+
105
+ if lock_file.exists():
106
+ if lock_file.stat().st_mtime > nm_mtime:
107
+ return True
108
+
109
+ return False
110
+
111
+ def _should_build(self, client_dir: Path, static_dir: Path) -> bool:
112
+ """Check if source files are newer than built assets."""
113
+ core_bundle = static_dir / "pywire.core.min.js"
114
+
115
+ if not core_bundle.exists():
116
+ return True
117
+
118
+ build_mtime = core_bundle.stat().st_mtime
119
+
120
+ # Check source files
121
+ src_dir = client_dir / "src"
122
+ if not src_dir.exists():
123
+ return False
124
+
125
+ # Recursive check for any file in src
126
+ for root, _, files in os.walk(src_dir):
127
+ for file in files:
128
+ file_path = Path(root) / file
129
+ if file_path.stat().st_mtime > build_mtime:
130
+ return True
131
+
132
+ # Check build script and package.json
133
+ build_script = client_dir / "build.mjs"
134
+ if build_script.exists() and build_script.stat().st_mtime > build_mtime:
135
+ return True
136
+
137
+ if (client_dir / "package.json").stat().st_mtime > build_mtime:
138
+ return True
139
+
140
+ return False
@@ -1,5 +1,5 @@
1
1
  [build-system]
2
- requires = ["hatchling"]
2
+ requires = ["hatchling", "hatch-vcs"]
3
3
  build-backend = "hatchling.build"
4
4
 
5
5
  [project]
@@ -7,7 +7,7 @@ name = "pywire"
7
7
  authors = [
8
8
  { name="Reece Holmdahl", email="reece@pywire.dev" },
9
9
  ]
10
- version = "0.1.4"
10
+ dynamic = ["version"]
11
11
  description = "HTML-over-the-wire Python web framework"
12
12
  readme = "README.md"
13
13
  requires-python = ">=3.11"
@@ -58,6 +58,14 @@ include = [
58
58
  [tool.hatch.build.targets.sdist.force-include]
59
59
  "src/pywire/static" = "src/pywire/static"
60
60
 
61
+ [tool.hatch.version]
62
+ source = "vcs"
63
+
64
+ [tool.hatch.build.hooks.vcs]
65
+ version-file = "src/pywire/_version.py"
66
+
67
+ [tool.hatch.build.hooks.custom]
68
+
61
69
  [tool.mypy]
62
70
  python_version = "3.11"
63
71
  check_untyped_defs = true
@@ -0,0 +1,12 @@
1
+ try:
2
+ from ._version import __version__
3
+ except ImportError:
4
+ from importlib.metadata import version, PackageNotFoundError
5
+
6
+ try:
7
+ __version__ = version("pywire")
8
+ except PackageNotFoundError:
9
+ __version__ = "unknown"
10
+
11
+ from .runtime.app import PyWire as PyWire
12
+ from .core.wire import wire as wire
@@ -0,0 +1,34 @@
1
+ # file generated by setuptools-scm
2
+ # don't change, don't track in version control
3
+
4
+ __all__ = [
5
+ "__version__",
6
+ "__version_tuple__",
7
+ "version",
8
+ "version_tuple",
9
+ "__commit_id__",
10
+ "commit_id",
11
+ ]
12
+
13
+ TYPE_CHECKING = False
14
+ if TYPE_CHECKING:
15
+ from typing import Tuple
16
+ from typing import Union
17
+
18
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
19
+ COMMIT_ID = Union[str, None]
20
+ else:
21
+ VERSION_TUPLE = object
22
+ COMMIT_ID = object
23
+
24
+ version: str
25
+ __version__: str
26
+ __version_tuple__: VERSION_TUPLE
27
+ version_tuple: VERSION_TUPLE
28
+ commit_id: COMMIT_ID
29
+ __commit_id__: COMMIT_ID
30
+
31
+ __version__ = version = '0.1.5'
32
+ __version_tuple__ = version_tuple = (0, 1, 5)
33
+
34
+ __commit_id__ = commit_id = None
@@ -8,6 +8,8 @@ from typing import Any, Optional
8
8
  import rich.panel
9
9
  import rich_click as click
10
10
 
11
+ from pywire import __version__
12
+
11
13
  # Astro-like styling configuration (Cyan Theme)
12
14
  click.rich_click.USE_RICH_MARKUP = True
13
15
  click.rich_click.STYLE_HELPTEXT_FIRST = True
@@ -141,18 +143,19 @@ def panel_init(self, *args, **kwargs):
141
143
  rich.panel.Panel.__init__ = panel_init # type: ignore[method-assign]
142
144
 
143
145
 
144
- @click.group()
145
- @click.version_option()
146
- def cli() -> None:
147
- """
148
- [bold white on cyan] pywire [/] [bold cyan]v0.1.3[/] Build faster python web apps.
146
+ @click.group(
147
+ help=f"""
148
+ [bold white on cyan] pywire [/] [bold cyan]v{__version__}[/] Build faster python web apps.
149
149
 
150
- Run [bold cyan]pywire dev APP[/] to start development server.
151
- Run [bold cyan]pywire run APP[/] to start production server.
150
+ Run [bold cyan]pywire dev APP[/] to start development server.
151
+ Run [bold cyan]pywire run APP[/] to start production server.
152
152
 
153
- [dim]APP should be a string in format 'module:instance', e.g. 'src.main:app' or 'main:app'
154
- If not provided, pywire tries to discover it in main.py, app.py, etc.[/dim]
155
- """
153
+ [dim]APP should be a string in format 'module:instance', e.g. 'src.main:app' or 'main:app'
154
+ If not provided, pywire tries to discover it in main.py, app.py, etc.[/dim]
155
+ """
156
+ )
157
+ @click.version_option(__version__)
158
+ def cli() -> None:
156
159
  pass
157
160
 
158
161
 
@@ -1,6 +1,9 @@
1
1
  import * as esbuild from 'esbuild'
2
2
  import { resolve, dirname } from 'path'
3
3
  import { fileURLToPath } from 'url'
4
+ import { createRequire } from 'module'
5
+ const require = createRequire(import.meta.url)
6
+ const { version } = require('./package.json')
4
7
 
5
8
  const __dirname = dirname(fileURLToPath(import.meta.url))
6
9
 
@@ -38,7 +41,7 @@ function getBuildOptions(bundle) {
38
41
  'process.env.NODE_ENV': isDev ? '"development"' : '"production"',
39
42
  },
40
43
  banner: {
41
- js: `/* PyWire Client ${bundle.name} v0.1.3 - https://github.com/pywire/pywire */`,
44
+ js: `/* PyWire Client ${bundle.name} v${version} - https://github.com/pywire/pywire */`,
42
45
  },
43
46
  }
44
47
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pywire-client",
3
- "version": "0.1.0",
3
+ "version": "0.1.5",
4
4
  "description": "PyWire client-side library with transport fallbacks",
5
5
  "type": "module",
6
6
  "private": true,
@@ -1,4 +1,5 @@
1
1
  import { TransportManager, TransportConfig } from './transport-manager'
2
+ import { version as clientVersion } from '../../package.json'
2
3
  import { DOMUpdater } from './dom-updater'
3
4
  import { ServerMessage, ClientMessage, EventData, RelocateMessage } from './transports'
4
5
  import { UnifiedEventHandler } from '../events/handler'
@@ -242,6 +243,10 @@ export class PyWireApp {
242
243
  }
243
244
  break
244
245
 
246
+ case 'init':
247
+ console.log(`PyWire Client v${clientVersion} • Server v${msg.version}`)
248
+ break
249
+
245
250
  default:
246
251
  console.warn('PyWire: Unknown message type', msg)
247
252
  }
@@ -36,13 +36,19 @@ export interface StackFrame {
36
36
  }
37
37
 
38
38
  export interface ServerMessage {
39
- type: 'update' | 'reload' | 'error' | 'console' | 'error_trace'
39
+ type: 'update' | 'reload' | 'error' | 'console' | 'error_trace' | 'init'
40
40
  html?: string
41
41
  regions?: Array<{ region: string; html: string }>
42
42
  error?: string
43
43
  level?: 'info' | 'warn' | 'error'
44
44
  lines?: string[]
45
45
  trace?: StackFrame[]
46
+ version?: string
47
+ }
48
+
49
+ export interface InitMessage {
50
+ type: 'init'
51
+ version: string
46
52
  }
47
53
 
48
54
  export interface ConsoleMessage {
@@ -35,9 +35,13 @@ export class HTTPTransport extends BaseTransport {
35
35
  }
36
36
 
37
37
  const buffer = await response.arrayBuffer()
38
- const data = decode(buffer) as { sessionId: string }
38
+ const data = decode(buffer) as { sessionId: string; version?: string }
39
39
  this.sessionId = data.sessionId
40
40
 
41
+ if (data.version) {
42
+ this.notifyHandlers({ type: 'init', version: data.version })
43
+ }
44
+
41
45
  console.log('PyWire: HTTP transport connected')
42
46
  this.notifyStatus(true)
43
47
 
@@ -10,7 +10,8 @@
10
10
  "declaration": false,
11
11
  "outDir": "./dist",
12
12
  "rootDir": "./src",
13
- "lib": ["ES2020", "DOM", "DOM.Iterable"]
13
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
14
+ "resolveJsonModule": true
14
15
  },
15
16
  "include": ["src/**/*"],
16
17
  "exclude": ["node_modules", "dist"]
@@ -12,6 +12,7 @@ from starlette.responses import JSONResponse, PlainTextResponse, Response
12
12
  from starlette.routing import Mount, Route, WebSocketRoute
13
13
  from starlette.staticfiles import StaticFiles
14
14
 
15
+ from pywire import __version__
15
16
  from pywire.runtime.error_page import ErrorPage
16
17
  from pywire.runtime.http_transport import HTTPTransportHandler
17
18
  from pywire.runtime.router import Router
@@ -208,7 +209,7 @@ class PyWire:
208
209
  "transports": ["websocket", "http"],
209
210
  # WebTransport requires HTTP/3 - only available when running with Hypercorn
210
211
  "webtransport": False,
211
- "version": "0.1.0",
212
+ "version": __version__,
212
213
  }
213
214
  )
214
215
 
@@ -12,6 +12,7 @@ from starlette.requests import Request
12
12
  from starlette.responses import Response
13
13
 
14
14
  from pywire.runtime.page import BasePage
15
+ from pywire import __version__
15
16
 
16
17
 
17
18
  @dataclass
@@ -118,7 +119,8 @@ class HTTPTransportHandler:
118
119
  self.start_cleanup_task()
119
120
 
120
121
  return Response(
121
- msgpack.packb({"sessionId": session_id}), media_type="application/x-msgpack"
122
+ msgpack.packb({"sessionId": session_id, "version": __version__}),
123
+ media_type="application/x-msgpack",
122
124
  )
123
125
 
124
126
  async def poll(self, request: Request) -> Response:
@@ -12,6 +12,7 @@ from starlette.websockets import WebSocket, WebSocketDisconnect
12
12
 
13
13
  from pywire.runtime.logging import log_callback_ctx
14
14
  from pywire.runtime.page import BasePage
15
+ from pywire import __version__
15
16
 
16
17
 
17
18
  class WebSocketHandler:
@@ -34,6 +35,11 @@ class WebSocketHandler:
34
35
  await websocket.accept()
35
36
  self.active_connections.add(websocket)
36
37
 
38
+ # Send init message
39
+ await websocket.send_bytes(
40
+ msgpack.packb({"type": "init", "version": __version__})
41
+ )
42
+
37
43
  try:
38
44
  # Create isolated page instance for this connection
39
45
  # We need to reconstruct the page based on current URL