pywire 0.1.3__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.3 → pywire-0.1.5}/.github/workflows/publish.yml +0 -6
  2. {pywire-0.1.3 → pywire-0.1.5}/PKG-INFO +1 -1
  3. pywire-0.1.5/hatch_build.py +140 -0
  4. {pywire-0.1.3 → 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.3 → pywire-0.1.5}/src/pywire/cli/main.py +13 -10
  8. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/client/build.mjs +4 -1
  9. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/client/package.json +1 -1
  10. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/client/src/core/app.ts +5 -0
  11. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/client/src/core/transports/base.ts +7 -1
  12. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/client/src/core/transports/http.ts +5 -1
  13. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/client/tsconfig.json +2 -1
  14. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/compiler/codegen/generator.py +5 -40
  15. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/runtime/app.py +2 -1
  16. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/runtime/http_transport.py +3 -1
  17. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/runtime/loader.py +3 -0
  18. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/runtime/websocket.py +6 -0
  19. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/static/pywire.core.min.js +3 -3
  20. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/static/pywire.dev.min.js +4 -4
  21. {pywire-0.1.3 → pywire-0.1.5}/tests/test_app_runtime.py +2 -1
  22. {pywire-0.1.3 → pywire-0.1.5}/tests/test_pjax_error.py +3 -0
  23. {pywire-0.1.3 → pywire-0.1.5}/tests/test_relocation_error.py +3 -0
  24. pywire-0.1.3/src/pywire/__init__.py +0 -2
  25. {pywire-0.1.3 → pywire-0.1.5}/.github/CODEOWNERS +0 -0
  26. {pywire-0.1.3 → pywire-0.1.5}/.github/workflows/ci.yml +0 -0
  27. {pywire-0.1.3 → pywire-0.1.5}/.github/workflows/deploy-docs.yml +0 -0
  28. {pywire-0.1.3 → pywire-0.1.5}/.gitignore +0 -0
  29. {pywire-0.1.3 → pywire-0.1.5}/LICENSE +0 -0
  30. {pywire-0.1.3 → pywire-0.1.5}/README.md +0 -0
  31. {pywire-0.1.3 → pywire-0.1.5}/docs/.gitignore +0 -0
  32. {pywire-0.1.3 → pywire-0.1.5}/docs/.prettierignore +0 -0
  33. {pywire-0.1.3 → pywire-0.1.5}/docs/.prettierrc +0 -0
  34. {pywire-0.1.3 → pywire-0.1.5}/docs/.vscode/extensions.json +0 -0
  35. {pywire-0.1.3 → pywire-0.1.5}/docs/.vscode/launch.json +0 -0
  36. {pywire-0.1.3 → pywire-0.1.5}/docs/README.md +0 -0
  37. {pywire-0.1.3 → pywire-0.1.5}/docs/astro.config.mjs +0 -0
  38. {pywire-0.1.3 → pywire-0.1.5}/docs/eslint.config.js +0 -0
  39. {pywire-0.1.3 → pywire-0.1.5}/docs/package.json +0 -0
  40. {pywire-0.1.3 → pywire-0.1.5}/docs/pnpm-lock.yaml +0 -0
  41. {pywire-0.1.3 → pywire-0.1.5}/docs/public/favicon.svg +0 -0
  42. {pywire-0.1.3 → pywire-0.1.5}/docs/scripts/check +0 -0
  43. {pywire-0.1.3 → pywire-0.1.5}/docs/scripts/lint +0 -0
  44. {pywire-0.1.3 → pywire-0.1.5}/docs/src/assets/houston.webp +0 -0
  45. {pywire-0.1.3 → pywire-0.1.5}/docs/src/content/docs/guides/example.md +0 -0
  46. {pywire-0.1.3 → pywire-0.1.5}/docs/src/content/docs/guides/getting-started.md +0 -0
  47. {pywire-0.1.3 → pywire-0.1.5}/docs/src/content/docs/guides/walkthrough.md +0 -0
  48. {pywire-0.1.3 → pywire-0.1.5}/docs/src/content/docs/index.mdx +0 -0
  49. {pywire-0.1.3 → pywire-0.1.5}/docs/src/content/docs/reference/api.md +0 -0
  50. {pywire-0.1.3 → pywire-0.1.5}/docs/src/content/docs/reference/example.md +0 -0
  51. {pywire-0.1.3 → pywire-0.1.5}/docs/src/content.config.ts +0 -0
  52. {pywire-0.1.3 → pywire-0.1.5}/docs/src/styles/custom.css +0 -0
  53. {pywire-0.1.3 → pywire-0.1.5}/docs/tsconfig.json +0 -0
  54. {pywire-0.1.3 → pywire-0.1.5}/scripts/README.md +0 -0
  55. {pywire-0.1.3 → pywire-0.1.5}/scripts/build +0 -0
  56. {pywire-0.1.3 → pywire-0.1.5}/scripts/check +0 -0
  57. {pywire-0.1.3 → pywire-0.1.5}/scripts/coverage +0 -0
  58. {pywire-0.1.3 → pywire-0.1.5}/scripts/docs +0 -0
  59. {pywire-0.1.3 → pywire-0.1.5}/scripts/install +0 -0
  60. {pywire-0.1.3 → pywire-0.1.5}/scripts/lint +0 -0
  61. {pywire-0.1.3 → pywire-0.1.5}/scripts/test +0 -0
  62. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/cli/__init__.py +0 -0
  63. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/cli/generators.py +0 -0
  64. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/cli/tui.py +0 -0
  65. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/cli/validate.py +0 -0
  66. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/client/.prettierignore +0 -0
  67. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/client/.prettierrc +0 -0
  68. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/client/eslint.config.js +0 -0
  69. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/client/pnpm-lock.yaml +0 -0
  70. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/client/src/core/dom-updater.test.ts +0 -0
  71. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/client/src/core/dom-updater.ts +0 -0
  72. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/client/src/core/index.ts +0 -0
  73. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/client/src/core/transport-manager.test.ts +0 -0
  74. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/client/src/core/transport-manager.ts +0 -0
  75. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/client/src/core/transports/index.ts +0 -0
  76. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/client/src/core/transports/websocket.ts +0 -0
  77. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/client/src/core/transports/webtransport.ts +0 -0
  78. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/client/src/dev/dev-app.ts +0 -0
  79. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/client/src/dev/error-trace.test.ts +0 -0
  80. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/client/src/dev/error-trace.ts +0 -0
  81. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/client/src/dev/index.ts +0 -0
  82. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/client/src/dev/status-overlay.ts +0 -0
  83. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/client/src/events/handler.test.ts +0 -0
  84. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/client/src/events/handler.ts +0 -0
  85. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/client/src/pywire.core.ts +0 -0
  86. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/client/src/pywire.dev.ts +0 -0
  87. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/client/vitest.config.ts +0 -0
  88. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/compiler/__init__.py +0 -0
  89. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/compiler/ast_nodes.py +0 -0
  90. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/compiler/attributes/__init__.py +0 -0
  91. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/compiler/attributes/base.py +0 -0
  92. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/compiler/attributes/conditional.py +0 -0
  93. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/compiler/attributes/events.py +0 -0
  94. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/compiler/attributes/form.py +0 -0
  95. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/compiler/attributes/loop.py +0 -0
  96. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/compiler/attributes/reactive.py +0 -0
  97. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/compiler/build.py +0 -0
  98. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/compiler/build_artifacts.py +0 -0
  99. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/compiler/codegen/__init__.py +0 -0
  100. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/compiler/codegen/attributes/__init__.py +0 -0
  101. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/compiler/codegen/attributes/base.py +0 -0
  102. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/compiler/codegen/attributes/events.py +0 -0
  103. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/compiler/codegen/directives/__init__.py +0 -0
  104. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/compiler/codegen/directives/base.py +0 -0
  105. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/compiler/codegen/directives/path.py +0 -0
  106. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/compiler/codegen/template.py +0 -0
  107. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/compiler/directives/__init__.py +0 -0
  108. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/compiler/directives/base.py +0 -0
  109. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/compiler/directives/component.py +0 -0
  110. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/compiler/directives/context.py +0 -0
  111. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/compiler/directives/layout.py +0 -0
  112. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/compiler/directives/no_spa.py +0 -0
  113. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/compiler/directives/path.py +0 -0
  114. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/compiler/directives/props.py +0 -0
  115. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/compiler/exceptions.py +0 -0
  116. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/compiler/interpolation/__init__.py +0 -0
  117. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/compiler/interpolation/base.py +0 -0
  118. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/compiler/interpolation/jinja.py +0 -0
  119. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/compiler/parser.py +0 -0
  120. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/compiler/paths.py +0 -0
  121. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/compiler/preprocessor.py +0 -0
  122. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/core/wire.py +0 -0
  123. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/py.typed +0 -0
  124. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/runtime/__init__.py +0 -0
  125. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/runtime/aioquic_server.py +0 -0
  126. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/runtime/compile_error_page.py +0 -0
  127. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/runtime/debug.py +0 -0
  128. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/runtime/dev_server.py +0 -0
  129. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/runtime/dev_server.py.broken +0 -0
  130. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/runtime/error_page.py +0 -0
  131. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/runtime/error_renderer.py +0 -0
  132. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/runtime/escape.py +0 -0
  133. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/runtime/files.py +0 -0
  134. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/runtime/helpers.py +0 -0
  135. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/runtime/logging.py +0 -0
  136. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/runtime/page.py +0 -0
  137. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/runtime/pydantic_integration.py +0 -0
  138. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/runtime/router.py +0 -0
  139. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/runtime/server.py +0 -0
  140. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/runtime/style_collector.py +0 -0
  141. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/runtime/upload_manager.py +0 -0
  142. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/runtime/validation.py +0 -0
  143. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/runtime/webtransport_handler.py +0 -0
  144. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/templates/error/404.html +0 -0
  145. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/templates/error/500.html +0 -0
  146. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/templates/error/base.html +0 -0
  147. {pywire-0.1.3 → pywire-0.1.5}/src/pywire/templates/error/compile_error.html +0 -0
  148. {pywire-0.1.3 → pywire-0.1.5}/tests/debug_router.py +0 -0
  149. {pywire-0.1.3 → pywire-0.1.5}/tests/test_app_advanced.py +0 -0
  150. {pywire-0.1.3 → pywire-0.1.5}/tests/test_app_exhaustive.py +0 -0
  151. {pywire-0.1.3 → pywire-0.1.5}/tests/test_app_mode.py +0 -0
  152. {pywire-0.1.3 → pywire-0.1.5}/tests/test_async_features.py +0 -0
  153. {pywire-0.1.3 → pywire-0.1.5}/tests/test_codegen_generator.py +0 -0
  154. {pywire-0.1.3 → pywire-0.1.5}/tests/test_codegen_template.py +0 -0
  155. {pywire-0.1.3 → pywire-0.1.5}/tests/test_codegen_template_exhaustive.py +0 -0
  156. {pywire-0.1.3 → pywire-0.1.5}/tests/test_compile_error.py +0 -0
  157. {pywire-0.1.3 → pywire-0.1.5}/tests/test_compiler_sourcemap.py +0 -0
  158. {pywire-0.1.3 → pywire-0.1.5}/tests/test_comprehensive_python_syntax.py +0 -0
  159. {pywire-0.1.3 → pywire-0.1.5}/tests/test_config.py +0 -0
  160. {pywire-0.1.3 → pywire-0.1.5}/tests/test_custom_errors.py +0 -0
  161. {pywire-0.1.3 → pywire-0.1.5}/tests/test_debug_middleware.py +0 -0
  162. {pywire-0.1.3 → pywire-0.1.5}/tests/test_debug_mock.py +0 -0
  163. {pywire-0.1.3 → pywire-0.1.5}/tests/test_error_handling_debug.py +0 -0
  164. {pywire-0.1.3 → pywire-0.1.5}/tests/test_file_routing.py +0 -0
  165. {pywire-0.1.3 → pywire-0.1.5}/tests/test_fixes.py +0 -0
  166. {pywire-0.1.3 → pywire-0.1.5}/tests/test_form_validation.py +0 -0
  167. {pywire-0.1.3 → pywire-0.1.5}/tests/test_generator_advanced.py +0 -0
  168. {pywire-0.1.3 → pywire-0.1.5}/tests/test_generator_exhaustive.py +0 -0
  169. {pywire-0.1.3 → pywire-0.1.5}/tests/test_hooks_removal.py +0 -0
  170. {pywire-0.1.3 → pywire-0.1.5}/tests/test_http_transport.py +0 -0
  171. {pywire-0.1.3 → pywire-0.1.5}/tests/test_inline_compilation.py +0 -0
  172. {pywire-0.1.3 → pywire-0.1.5}/tests/test_interactivity_codegen_complex.py +0 -0
  173. {pywire-0.1.3 → pywire-0.1.5}/tests/test_interactivity_compiler.py +0 -0
  174. {pywire-0.1.3 → pywire-0.1.5}/tests/test_jinja_interpolation.py +0 -0
  175. {pywire-0.1.3 → pywire-0.1.5}/tests/test_layouts.py +0 -0
  176. {pywire-0.1.3 → pywire-0.1.5}/tests/test_lifecycle_hooks.py +0 -0
  177. {pywire-0.1.3 → pywire-0.1.5}/tests/test_logging_stream.py +0 -0
  178. {pywire-0.1.3 → pywire-0.1.5}/tests/test_method_binding.py +0 -0
  179. {pywire-0.1.3 → pywire-0.1.5}/tests/test_middleware_error.py +0 -0
  180. {pywire-0.1.3 → pywire-0.1.5}/tests/test_mode_gating.py +0 -0
  181. {pywire-0.1.3 → pywire-0.1.5}/tests/test_naming_conventions.py +0 -0
  182. {pywire-0.1.3 → pywire-0.1.5}/tests/test_parser_advanced.py +0 -0
  183. {pywire-0.1.3 → pywire-0.1.5}/tests/test_parser_compiler.py +0 -0
  184. {pywire-0.1.3 → pywire-0.1.5}/tests/test_reactive_attributes.py +0 -0
  185. {pywire-0.1.3 → pywire-0.1.5}/tests/test_rendering.py +0 -0
  186. {pywire-0.1.3 → pywire-0.1.5}/tests/test_router_exhaustive.py +0 -0
  187. {pywire-0.1.3 → pywire-0.1.5}/tests/test_router_expansion.py +0 -0
  188. {pywire-0.1.3 → pywire-0.1.5}/tests/test_router_runtime.py +0 -0
  189. {pywire-0.1.3 → pywire-0.1.5}/tests/test_runtime_helpers.py +0 -0
  190. {pywire-0.1.3 → pywire-0.1.5}/tests/test_script_injection.py +0 -0
  191. {pywire-0.1.3 → pywire-0.1.5}/tests/test_server_validation.py +0 -0
  192. {pywire-0.1.3 → pywire-0.1.5}/tests/test_static_assets.py +0 -0
  193. {pywire-0.1.3 → pywire-0.1.5}/tests/test_strict_routing.py +0 -0
  194. {pywire-0.1.3 → pywire-0.1.5}/tests/test_style_collector.py +0 -0
  195. {pywire-0.1.3 → pywire-0.1.5}/tests/test_syntax_enforcement.py +0 -0
  196. {pywire-0.1.3 → pywire-0.1.5}/tests/test_templating_features.py +0 -0
  197. {pywire-0.1.3 → pywire-0.1.5}/tests/test_transport_advanced.py +0 -0
  198. {pywire-0.1.3 → pywire-0.1.5}/tests/test_transport_exhaustive.py +0 -0
  199. {pywire-0.1.3 → pywire-0.1.5}/tests/test_validation_exhaustive.py +0 -0
  200. {pywire-0.1.3 → pywire-0.1.5}/tests/test_websocket_advanced.py +0 -0
  201. {pywire-0.1.3 → pywire-0.1.5}/tests/test_websocket_exhaustive.py +0 -0
  202. {pywire-0.1.3 → pywire-0.1.5}/tests/test_websocket_handler.py +0 -0
  203. {pywire-0.1.3 → pywire-0.1.5}/tests/test_wire_primitive.py +0 -0
  204. {pywire-0.1.3 → pywire-0.1.5}/tox.ini +0 -0
  205. {pywire-0.1.3 → 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.3
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.3"
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"]
@@ -1485,45 +1485,8 @@ class CodeGenerator:
1485
1485
 
1486
1486
  for node in python_ast.body:
1487
1487
  if isinstance(node, (ast.Import, ast.ImportFrom)):
1488
- # Skip imports - already handled
1488
+ # Skip imports - already handled at module level
1489
1489
  continue
1490
- elif isinstance(node, ast.Assign):
1491
- # Module-level assignments become class attributes
1492
- # UNLESS they target 'self' (e.g. self.x = 1), which makes no sense at class level
1493
- # and implies instance initialization.
1494
-
1495
- is_instance_assign = False
1496
- for target in node.targets:
1497
- # Check if target is Attribute(value=Name(id='self'))
1498
- if (
1499
- isinstance(target, ast.Attribute)
1500
- and isinstance(target.value, ast.Name)
1501
- and target.value.id == "self"
1502
- ):
1503
- is_instance_assign = True
1504
- break
1505
-
1506
- # Also check if value is a Call (like wire()) or mutable structure.
1507
- # These should be instance-level to avoid shared state.
1508
- is_mutable_init = isinstance(
1509
- node.value,
1510
- (
1511
- ast.Call,
1512
- ast.List,
1513
- ast.Dict,
1514
- ast.Set,
1515
- ast.ListComp,
1516
- ast.DictComp,
1517
- ast.SetComp,
1518
- ),
1519
- )
1520
-
1521
- if is_instance_assign or is_mutable_init:
1522
- top_level_statements.append(node)
1523
- else:
1524
- # Simple literals (int, str) stay class attributes
1525
- transformed.append(node)
1526
-
1527
1490
  elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
1528
1491
  # Check for decorators
1529
1492
  new_decorators = []
@@ -1544,8 +1507,10 @@ class CodeGenerator:
1544
1507
  # Classes are moved to module level, skip here
1545
1508
  continue
1546
1509
  else:
1547
- # Other statements (Expr, If, For, While, Try)
1548
- # Move to top-level init
1510
+ # ALL other statements (Assign, AnnAssign, AugAssign, Expr, If, For, While, Try, etc.)
1511
+ # are moved to __top_level_init__ for consistent instance-scope execution.
1512
+ # This ensures that dependent code (e.g., conn = ...; conn.row_factory = ...)
1513
+ # all runs in the same scope at instance creation time.
1549
1514
  top_level_statements.append(node)
1550
1515
 
1551
1516
  if top_level_statements:
@@ -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:
@@ -80,6 +80,9 @@ class PageLoader:
80
80
  module_any.load_layout = self.load_layout
81
81
  module_any.load_component = self.load_component
82
82
 
83
+ # Inject __file__ for relative path resolution
84
+ module.__file__ = str(pywire_file)
85
+
83
86
  exec(code, module.__dict__)
84
87
 
85
88
  page_class = self._find_page_class(module, pywire_file)
@@ -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