blu-react 0.0.1rc1__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 (188) hide show
  1. blu_react-0.0.1rc1/.gitignore +8 -0
  2. blu_react-0.0.1rc1/.pre-commit-config.yaml +11 -0
  3. blu_react-0.0.1rc1/.vscode/launch.json +15 -0
  4. blu_react-0.0.1rc1/.vscode/settings.json +10 -0
  5. blu_react-0.0.1rc1/LICENSE +21 -0
  6. blu_react-0.0.1rc1/PKG-INFO +63 -0
  7. blu_react-0.0.1rc1/README.md +47 -0
  8. blu_react-0.0.1rc1/docs/Makefile +20 -0
  9. blu_react-0.0.1rc1/docs/_img/cursor_code_completion.png +0 -0
  10. blu_react-0.0.1rc1/docs/_img/directly_manipulating_rendered_elements.gif +0 -0
  11. blu_react-0.0.1rc1/docs/_img/effect_cleanup.gif +0 -0
  12. blu_react-0.0.1rc1/docs/_img/perform_an_action_immediately_after_rendering.gif +0 -0
  13. blu_react-0.0.1rc1/docs/_img/responding_to_user_interaction.gif +0 -0
  14. blu_react-0.0.1rc1/docs/_includes/client-element-call-notes.rst +48 -0
  15. blu_react-0.0.1rc1/docs/_includes/client-element-children.rst +86 -0
  16. blu_react-0.0.1rc1/docs/_includes/client-side-rendering-notes.rst +129 -0
  17. blu_react-0.0.1rc1/docs/_includes/hook-note.rst +3 -0
  18. blu_react-0.0.1rc1/docs/_includes/hooks.rst +81 -0
  19. blu_react-0.0.1rc1/docs/_includes/iterables-must-use-keys-note.rst +59 -0
  20. blu_react-0.0.1rc1/docs/_static/styles.css +26 -0
  21. blu_react-0.0.1rc1/docs/_templates/layout.html +30 -0
  22. blu_react-0.0.1rc1/docs/api-reference/blu.html.rst +4 -0
  23. blu_react-0.0.1rc1/docs/api-reference/blu.rst +163 -0
  24. blu_react-0.0.1rc1/docs/api-reference/cli.rst +18 -0
  25. blu_react-0.0.1rc1/docs/api-reference/file-conventions.rst +453 -0
  26. blu_react-0.0.1rc1/docs/api-reference/index.rst +13 -0
  27. blu_react-0.0.1rc1/docs/api-reference/js.rst +35 -0
  28. blu_react-0.0.1rc1/docs/api-reference/pyscript.rst +38 -0
  29. blu_react-0.0.1rc1/docs/conf.py +61 -0
  30. blu_react-0.0.1rc1/docs/contributing.rst +32 -0
  31. blu_react-0.0.1rc1/docs/index.rst +19 -0
  32. blu_react-0.0.1rc1/docs/install.rst +10 -0
  33. blu_react-0.0.1rc1/docs/make.bat +35 -0
  34. blu_react-0.0.1rc1/docs/quickstart.rst +42 -0
  35. blu_react-0.0.1rc1/docs/user-guide/client-side-rendering.rst +457 -0
  36. blu_react-0.0.1rc1/docs/user-guide/creating-pages.rst +213 -0
  37. blu_react-0.0.1rc1/docs/user-guide/deploying-your-app.rst +18 -0
  38. blu_react-0.0.1rc1/docs/user-guide/index.rst +14 -0
  39. blu_react-0.0.1rc1/docs/user-guide/routing.rst +378 -0
  40. blu_react-0.0.1rc1/docs/user-guide/static-files.rst +32 -0
  41. blu_react-0.0.1rc1/pyproject.toml +56 -0
  42. blu_react-0.0.1rc1/src/blu/__init__.py +74 -0
  43. blu_react-0.0.1rc1/src/blu/_app/__init__.py +463 -0
  44. blu_react-0.0.1rc1/src/blu/_app/client_main.py +159 -0
  45. blu_react-0.0.1rc1/src/blu/_app/render.py +107 -0
  46. blu_react-0.0.1rc1/src/blu/_app/router.py +267 -0
  47. blu_react-0.0.1rc1/src/blu/_cli.py +25 -0
  48. blu_react-0.0.1rc1/src/blu/_exceptions.py +5 -0
  49. blu_react-0.0.1rc1/src/blu/_hooks.py +323 -0
  50. blu_react-0.0.1rc1/src/blu/_http.py +219 -0
  51. blu_react-0.0.1rc1/src/blu/_nodes.py +771 -0
  52. blu_react-0.0.1rc1/src/blu/_server_functions.py +423 -0
  53. blu_react-0.0.1rc1/src/blu/_settings.py +30 -0
  54. blu_react-0.0.1rc1/src/blu/_utils/__init__.py +47 -0
  55. blu_react-0.0.1rc1/src/blu/_utils/asgi.py +189 -0
  56. blu_react-0.0.1rc1/src/blu/_utils/asyncio.py +60 -0
  57. blu_react-0.0.1rc1/src/blu/_utils/client.py +3 -0
  58. blu_react-0.0.1rc1/src/blu/_utils/json.py +30 -0
  59. blu_react-0.0.1rc1/src/blu/_utils/typing.py +33 -0
  60. blu_react-0.0.1rc1/src/blu/html.py +91 -0
  61. blu_react-0.0.1rc1/src/blu/util.js +334 -0
  62. blu_react-0.0.1rc1/tests/__init__.py +0 -0
  63. blu_react-0.0.1rc1/tests/apps/app_module_reject/module.py +1 -0
  64. blu_react-0.0.1rc1/tests/apps/basic/__index__.py +2 -0
  65. blu_react-0.0.1rc1/tests/apps/client_false/module.py +1 -0
  66. blu_react-0.0.1rc1/tests/apps/client_module/module.py +3 -0
  67. blu_react-0.0.1rc1/tests/apps/def_async_handler/__default__.py +2 -0
  68. blu_react-0.0.1rc1/tests/apps/def_empty_path_param/a/__default__.py +2 -0
  69. blu_react-0.0.1rc1/tests/apps/def_extra_query_params/__default__.py +2 -0
  70. blu_react-0.0.1rc1/tests/apps/def_extra_route_params/__default__.py +2 -0
  71. blu_react-0.0.1rc1/tests/apps/def_missing_query_params/__default__.py +2 -0
  72. blu_react-0.0.1rc1/tests/apps/def_missing_route_params/_a_/_b_/_c_/__default__.py +2 -0
  73. blu_react-0.0.1rc1/tests/apps/def_pos_only_path_param/__default__.py +2 -0
  74. blu_react-0.0.1rc1/tests/apps/def_pos_only_underscore/__default__.py +2 -0
  75. blu_react-0.0.1rc1/tests/apps/def_query/__default__.py +2 -0
  76. blu_react-0.0.1rc1/tests/apps/def_query_kwarg/__default__.py +2 -0
  77. blu_react-0.0.1rc1/tests/apps/def_query_params_default/__default__.py +2 -0
  78. blu_react-0.0.1rc1/tests/apps/def_route_and_query/_a_/_b_/__default__.py +2 -0
  79. blu_react-0.0.1rc1/tests/apps/default_extra_segment/a/__default__.py +2 -0
  80. blu_react-0.0.1rc1/tests/apps/default_full_match/a/_b_/c/__default__.py +2 -0
  81. blu_react-0.0.1rc1/tests/apps/default_incomplete_match/a/b/c/__default__.py +2 -0
  82. blu_react-0.0.1rc1/tests/apps/default_path_param/__default__.py +2 -0
  83. blu_react-0.0.1rc1/tests/apps/default_response_ret/__default__.py +5 -0
  84. blu_react-0.0.1rc1/tests/apps/default_route_params/_a_/_b_/_c_/__default__.py +2 -0
  85. blu_react-0.0.1rc1/tests/apps/default_single_underscore/__default__.py +2 -0
  86. blu_react-0.0.1rc1/tests/apps/default_specific_priority/_a_/__default__.py +2 -0
  87. blu_react-0.0.1rc1/tests/apps/default_specific_priority/_b_/c/__default__.py +2 -0
  88. blu_react-0.0.1rc1/tests/apps/dynamic_non_matching_static/_dynamic_/__index__.py +2 -0
  89. blu_react-0.0.1rc1/tests/apps/dynamic_non_matching_static/static/__index__.py +2 -0
  90. blu_react-0.0.1rc1/tests/apps/dynamic_segment/_segment_/__index__.py +2 -0
  91. blu_react-0.0.1rc1/tests/apps/dynamic_static_not_full_match/_dynamic_/bar/__index__.py +2 -0
  92. blu_react-0.0.1rc1/tests/apps/dynamic_static_not_full_match/foo/baz/__index__.py +2 -0
  93. blu_react-0.0.1rc1/tests/apps/e2e/app_pkg_clientside/fail/__index__.py +5 -0
  94. blu_react-0.0.1rc1/tests/apps/e2e/app_pkg_clientside/fail/element.py +14 -0
  95. blu_react-0.0.1rc1/tests/apps/e2e/app_pkg_clientside/fail/module.py +1 -0
  96. blu_react-0.0.1rc1/tests/apps/e2e/app_pkg_clientside/success/__index__.py +5 -0
  97. blu_react-0.0.1rc1/tests/apps/e2e/app_pkg_clientside/success/element.py +10 -0
  98. blu_react-0.0.1rc1/tests/apps/e2e/app_pkg_clientside/success/module.py +3 -0
  99. blu_react-0.0.1rc1/tests/apps/e2e/async_effect/__index__.py +45 -0
  100. blu_react-0.0.1rc1/tests/apps/e2e/client_import/__index__.py +14 -0
  101. blu_react-0.0.1rc1/tests/apps/e2e/dev_server/__index__.py +5 -0
  102. blu_react-0.0.1rc1/tests/apps/e2e/html_editing_effect/__index__.py +19 -0
  103. blu_react-0.0.1rc1/tests/apps/e2e/is_client/__index__.py +13 -0
  104. blu_react-0.0.1rc1/tests/apps/e2e/rendering/__index__.py +37 -0
  105. blu_react-0.0.1rc1/tests/apps/e2e/routing/_a_/__default__.py +5 -0
  106. blu_react-0.0.1rc1/tests/apps/e2e/routing/_a_/__index__.py +5 -0
  107. blu_react-0.0.1rc1/tests/apps/e2e/routing/_a_/_b_/__index__.py +5 -0
  108. blu_react-0.0.1rc1/tests/apps/e2e/routing/_a_/static/__index__.py +5 -0
  109. blu_react-0.0.1rc1/tests/apps/e2e/server_functions/__index__.py +27 -0
  110. blu_react-0.0.1rc1/tests/apps/e2e/server_functions/hello_module.py +8 -0
  111. blu_react-0.0.1rc1/tests/apps/e2e/server_functions/shared.py +6 -0
  112. blu_react-0.0.1rc1/tests/apps/e2e/static_file/_dynamic_path_/hello.txt +1 -0
  113. blu_react-0.0.1rc1/tests/apps/e2e/unusual_root_nodes/float/__index__.py +2 -0
  114. blu_react-0.0.1rc1/tests/apps/e2e/unusual_root_nodes/int/__index__.py +2 -0
  115. blu_react-0.0.1rc1/tests/apps/e2e/unusual_root_nodes/iterable/__index__.py +2 -0
  116. blu_react-0.0.1rc1/tests/apps/e2e/unusual_root_nodes/key/__index__.py +5 -0
  117. blu_react-0.0.1rc1/tests/apps/e2e/unusual_root_nodes/none/__index__.py +2 -0
  118. blu_react-0.0.1rc1/tests/apps/e2e/unusual_root_nodes/str/__index__.py +2 -0
  119. blu_react-0.0.1rc1/tests/apps/e2e/unusual_root_nodes/tuple/__index__.py +2 -0
  120. blu_react-0.0.1rc1/tests/apps/e2e/use_effect/__index__.py +30 -0
  121. blu_react-0.0.1rc1/tests/apps/e2e/use_ref/__index__.py +26 -0
  122. blu_react-0.0.1rc1/tests/apps/e2e/use_state/__index__.py +21 -0
  123. blu_react-0.0.1rc1/tests/apps/e2e/wrong_environment_error/__index__.py +23 -0
  124. blu_react-0.0.1rc1/tests/apps/e2e/yield_in_tuple/__index__.py +13 -0
  125. blu_react-0.0.1rc1/tests/apps/e2e_settings/__index__.py +19 -0
  126. blu_react-0.0.1rc1/tests/apps/e2e_settings/__settings__.py +1 -0
  127. blu_react-0.0.1rc1/tests/apps/empty_settings/__index__.py +2 -0
  128. blu_react-0.0.1rc1/tests/apps/empty_settings/__settings__.py +0 -0
  129. blu_react-0.0.1rc1/tests/apps/index_abc/a/b/c/__index__.py +2 -0
  130. blu_react-0.0.1rc1/tests/apps/index_async_handler/__index__.py +2 -0
  131. blu_react-0.0.1rc1/tests/apps/index_dynamic_abc/_a_/_b_/_c_/__index__.py +2 -0
  132. blu_react-0.0.1rc1/tests/apps/index_extra_route_params/__index__.py +2 -0
  133. blu_react-0.0.1rc1/tests/apps/index_file_priority/_a_/c/__default__.py +2 -0
  134. blu_react-0.0.1rc1/tests/apps/index_file_priority/_b_/c/__index__.py +2 -0
  135. blu_react-0.0.1rc1/tests/apps/index_missing_route_params/_a_/__index__.py +2 -0
  136. blu_react-0.0.1rc1/tests/apps/index_pos_only_underscore/__index__.py +2 -0
  137. blu_react-0.0.1rc1/tests/apps/index_query_abc/__index__.py +2 -0
  138. blu_react-0.0.1rc1/tests/apps/index_query_kwargs/__index__.py +2 -0
  139. blu_react-0.0.1rc1/tests/apps/index_response/__index__.py +5 -0
  140. blu_react-0.0.1rc1/tests/apps/index_route_and_query/_a_/_b_/__index__.py +2 -0
  141. blu_react-0.0.1rc1/tests/apps/index_single_underscore/__index__.py +2 -0
  142. blu_react-0.0.1rc1/tests/apps/leading_underscore/_path/__index__.py +2 -0
  143. blu_react-0.0.1rc1/tests/apps/literal_segment/path/__index__.py +2 -0
  144. blu_react-0.0.1rc1/tests/apps/multiple_dynamic/_a_/b/__index__.py +2 -0
  145. blu_react-0.0.1rc1/tests/apps/multiple_dynamic/_c_/d/__index__.py +2 -0
  146. blu_react-0.0.1rc1/tests/apps/nested_dynamic_segments/_first_/_second_/__index__.py +2 -0
  147. blu_react-0.0.1rc1/tests/apps/nested_literal_segments/path/to/view/__index__.py +2 -0
  148. blu_react-0.0.1rc1/tests/apps/server_function_arbitrary_function_call/server_functions.py +17 -0
  149. blu_react-0.0.1rc1/tests/apps/server_function_arbitrary_function_call/server_only_module.py +6 -0
  150. blu_react-0.0.1rc1/tests/apps/server_function_csrf/__index__.py +2 -0
  151. blu_react-0.0.1rc1/tests/apps/server_function_csrf/file.txt +1 -0
  152. blu_react-0.0.1rc1/tests/apps/server_function_csrf/server_functions.py +8 -0
  153. blu_react-0.0.1rc1/tests/apps/server_functions_outside_app/valid.py +9 -0
  154. blu_react-0.0.1rc1/tests/apps/settings_below_top_level/foo/__settings__.py +1 -0
  155. blu_react-0.0.1rc1/tests/apps/settings_multi_client_reqs/__settings__.py +1 -0
  156. blu_react-0.0.1rc1/tests/apps/settings_one_client_req/__settings__.py +1 -0
  157. blu_react-0.0.1rc1/tests/apps/static_dynamic_nested/static/_dynamic_/__index__.py +2 -0
  158. blu_react-0.0.1rc1/tests/apps/static_dynamic_priority/_dynamic_/__index__.py +2 -0
  159. blu_react-0.0.1rc1/tests/apps/static_dynamic_priority/static/__index__.py +2 -0
  160. blu_react-0.0.1rc1/tests/apps/static_files/path/to/static/file.txt +1 -0
  161. blu_react-0.0.1rc1/tests/apps/static_files_dynamic_path/_a_/file.txt +1 -0
  162. blu_react-0.0.1rc1/tests/apps/top_level_segment/__index__.py +2 -0
  163. blu_react-0.0.1rc1/tests/apps/trailing_underscore/path_/__index__.py +2 -0
  164. blu_react-0.0.1rc1/tests/conftest.py +149 -0
  165. blu_react-0.0.1rc1/tests/fast/blu/test_ClientElement.py +266 -0
  166. blu_react-0.0.1rc1/tests/fast/blu/test_HTMLElement.py +144 -0
  167. blu_react-0.0.1rc1/tests/fast/blu/test_Key.py +78 -0
  168. blu_react-0.0.1rc1/tests/fast/blu/test_Response.py +68 -0
  169. blu_react-0.0.1rc1/tests/fast/blu/test_app.py +57 -0
  170. blu_react-0.0.1rc1/tests/fast/blu/test_create_rare_html_element.py +21 -0
  171. blu_react-0.0.1rc1/tests/fast/blu/test_html.py +62 -0
  172. blu_react-0.0.1rc1/tests/fast/blu/test_is_client.py +14 -0
  173. blu_react-0.0.1rc1/tests/fast/blu/test_server.py +109 -0
  174. blu_react-0.0.1rc1/tests/fast/file_conventions/_utils.py +8 -0
  175. blu_react-0.0.1rc1/tests/fast/file_conventions/directories/test_literal_segment.py +55 -0
  176. blu_react-0.0.1rc1/tests/fast/file_conventions/directories/test_route_param_name.py +53 -0
  177. blu_react-0.0.1rc1/tests/fast/file_conventions/special_files/test_default.py +249 -0
  178. blu_react-0.0.1rc1/tests/fast/file_conventions/special_files/test_index.py +188 -0
  179. blu_react-0.0.1rc1/tests/fast/file_conventions/special_files/test_settings.py +83 -0
  180. blu_react-0.0.1rc1/tests/fast/file_conventions/test_static_files.py +53 -0
  181. blu_react-0.0.1rc1/tests/fast/test_cli.py +47 -0
  182. blu_react-0.0.1rc1/tests/fast/test_security.py +309 -0
  183. blu_react-0.0.1rc1/tests/projects/basic/app/__index__.py +5 -0
  184. blu_react-0.0.1rc1/tests/projects/quickstart/app/__index__.py +8 -0
  185. blu_react-0.0.1rc1/tests/resources/server_fn_outside_app.py +8 -0
  186. blu_react-0.0.1rc1/tests/test_e2e.py +289 -0
  187. blu_react-0.0.1rc1/tests/utils.py +103 -0
  188. blu_react-0.0.1rc1/uv.lock +1677 -0
@@ -0,0 +1,8 @@
1
+ /.venv/*
2
+ !/.venv/.gitkeep
3
+ /dist
4
+ /docs/_build
5
+ /docs/build
6
+
7
+ .DS_Store
8
+ __pycache__
@@ -0,0 +1,11 @@
1
+ repos:
2
+ - repo: https://github.com/PyCQA/autoflake
3
+ rev: v2.3.3
4
+ hooks:
5
+ - id: autoflake
6
+ args: [--in-place, --remove-all-unused-imports]
7
+ - repo: https://github.com/psf/black
8
+ rev: 24.1.1 # pin to a specific version
9
+ hooks:
10
+ - id: black
11
+ language_version: python3
@@ -0,0 +1,15 @@
1
+ {
2
+ // Use IntelliSense to learn about possible attributes.
3
+ // Hover to view descriptions of existing attributes.
4
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5
+ "version": "0.2.0",
6
+ "configurations": [
7
+ {
8
+ "name": "Python Debugger: Current File",
9
+ "type": "debugpy",
10
+ "request": "launch",
11
+ "program": "${file}",
12
+ "console": "integratedTerminal"
13
+ }
14
+ ]
15
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "python.testing.unittestEnabled": false,
3
+ "python.testing.pytestEnabled": true,
4
+ "python.analysis.typeCheckingMode": "strict",
5
+ "python.analysis.fixAll": [
6
+ "source.unusedImports"
7
+ ],
8
+ "python-envs.defaultEnvManager": "ms-python.python:venv",
9
+ "cursorpyright.analysis.typeCheckingMode": "strict"
10
+ }
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 John Larson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,63 @@
1
+ Metadata-Version: 2.4
2
+ Name: blu-react
3
+ Version: 0.0.1rc1
4
+ Summary: A full stack React framework for Python
5
+ Project-URL: Homepage, https://github.com/johnlarson/blu
6
+ Project-URL: Issues, https://github.com/johnlarson/blu/issues
7
+ Author-email: John Larson <john.robert.larson@gmail.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Requires-Python: >=3.14
13
+ Requires-Dist: aiofiles
14
+ Requires-Dist: uvicorn
15
+ Description-Content-Type: text/markdown
16
+
17
+
18
+
19
+ # Blu
20
+
21
+ *Blu is still in early testing and should not be used in production applications.*
22
+
23
+ A full stack React framework for Python.
24
+
25
+
26
+ ```python
27
+ from blu import client
28
+ from blu.html import html, head, body, h1, button
29
+
30
+ __client__ = True
31
+
32
+
33
+ def __page__():
34
+ return html[
35
+ head,
36
+ body[
37
+ h1[
38
+ ClickCounter
39
+ ]
40
+ ]
41
+ ]
42
+ return ClickCounter
43
+
44
+
45
+ @client
46
+ def ClickCounter():
47
+ count, set_count = use_state(0)
48
+
49
+ def handle_click(e):
50
+ set_count(count + 1)
51
+
52
+ return button(onClick=handle_click)[f"Clicks: {count}"]
53
+ ```
54
+
55
+ ## Installation
56
+
57
+ ```bash
58
+ pip install -U blu
59
+ ```
60
+
61
+ ## Documentation
62
+
63
+ Documentation available on readthedocs.io {TODO add link here.}
@@ -0,0 +1,47 @@
1
+
2
+
3
+ # Blu
4
+
5
+ *Blu is still in early testing and should not be used in production applications.*
6
+
7
+ A full stack React framework for Python.
8
+
9
+
10
+ ```python
11
+ from blu import client
12
+ from blu.html import html, head, body, h1, button
13
+
14
+ __client__ = True
15
+
16
+
17
+ def __page__():
18
+ return html[
19
+ head,
20
+ body[
21
+ h1[
22
+ ClickCounter
23
+ ]
24
+ ]
25
+ ]
26
+ return ClickCounter
27
+
28
+
29
+ @client
30
+ def ClickCounter():
31
+ count, set_count = use_state(0)
32
+
33
+ def handle_click(e):
34
+ set_count(count + 1)
35
+
36
+ return button(onClick=handle_click)[f"Clicks: {count}"]
37
+ ```
38
+
39
+ ## Installation
40
+
41
+ ```bash
42
+ pip install -U blu
43
+ ```
44
+
45
+ ## Documentation
46
+
47
+ Documentation available on readthedocs.io {TODO add link here.}
@@ -0,0 +1,20 @@
1
+ # Minimal makefile for Sphinx documentation
2
+ #
3
+
4
+ # You can set these variables from the command line, and also
5
+ # from the environment for the first two.
6
+ SPHINXOPTS ?=
7
+ SPHINXBUILD ?= sphinx-build
8
+ SOURCEDIR = .
9
+ BUILDDIR = _build
10
+
11
+ # Put it first so that "make" without argument is like "make help".
12
+ help:
13
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14
+
15
+ .PHONY: help Makefile
16
+
17
+ # Catch-all target: route all unknown targets to Sphinx using the new
18
+ # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19
+ %: Makefile
20
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
@@ -0,0 +1,48 @@
1
+ .. note::
2
+
3
+ Calling a client element as a function does not immediately result in its rendering function being called; that call is deferred until the element is rendered on the client.
4
+
5
+ .. note::
6
+
7
+ Calling a client element as a function does not mutate the original client element; instead, it returns a copy of the original client element with the new render arguments.
8
+
9
+ .. code-block:: python
10
+
11
+ from blu import client
12
+ from blu.html import p
13
+
14
+ __client__ = True
15
+
16
+
17
+ @client
18
+ Greeting(name):
19
+ return p[f'Hello, {name}!']
20
+
21
+ original = Greeting('Adam')
22
+ copy = original('Brittany')
23
+
24
+ original # <p>Hello, Adam!</p>
25
+ copy # <p>Hello, Brittany!</p>
26
+
27
+ .. note::
28
+
29
+ Calling a client element as a function completely replaces any existing render arguments.
30
+
31
+ .. code-block:: python
32
+
33
+ from blu import client
34
+ from blu.html import p
35
+
36
+ __client__ = True
37
+
38
+
39
+ @client
40
+ MyClientElement(a, b=2):
41
+ return p[f'{a},{b}']
42
+
43
+
44
+ original = MyClientElement(3, b=4)
45
+ copy = original(1)
46
+
47
+ original # <p>3,4</p>
48
+ copy # <p>1,2</p>
@@ -0,0 +1,86 @@
1
+ You can add children to an html element using square bracket notation:
2
+
3
+ .. code-block:: python
4
+
5
+ from blu.html import div, p
6
+
7
+ div[
8
+ p['Hi.'],
9
+ p['Hello.']
10
+ ]
11
+
12
+ .. code-block:: html
13
+
14
+ <div>
15
+ <p>Hi.</p>
16
+ <p>Hello.</p>
17
+ </div>
18
+
19
+ .. note::
20
+
21
+ Using square bracket notation on an HTML element does not mutate the original; instead, it returns a copy with the new attributes.
22
+
23
+ .. code-block:: python
24
+
25
+ from blu.html import div
26
+
27
+ without_children = div
28
+ with_children = div['Hello, World!']
29
+
30
+ without_children # <div></div>
31
+ with_children # <div>Hello, World!</div>
32
+
33
+ .. note::
34
+
35
+ When you set children on an HTML element, you completely replace any existing children, rather than appending to them:
36
+
37
+ .. code-block:: python
38
+
39
+ from blu.html import div
40
+
41
+ original = div['A', 'B']
42
+
43
+ original['C', 'D'] # <div>CD</div>
44
+
45
+
46
+ A child of an HTML element can be any of the following types:
47
+
48
+ - Another :class:`blu.HTMLElement`.
49
+ - A :py:class:`str`. In this case, it will be rendered as an HTML text node.
50
+ - :py:data:`None`. Nothing is rendered in the place where a :py:data:`None` value is found.
51
+ - A :py:class:`int`. This renders as a text node whose text is the :py:class:`str` representation of the integer.
52
+ - A :py:class:`float`. This renders as a text node whose text is the :py:class:`str` representation of the float.
53
+ - An :py:class:`Iterable <collections.abc.Iterable>` of valid children. This renders as all the child nodes contained in the :py:class:`Iterable <collections.abc.Iterable>`.
54
+ - A :class:`blu.ClientElement` (see :ref:`Client-Side Rendering`).
55
+ - A :class:`blu.Key`.
56
+
57
+ .. code-block:: python
58
+
59
+ from blu.html import div, span, p
60
+
61
+ div[
62
+ span,
63
+ 'Hello!',
64
+ None,
65
+ 1,
66
+ 1.0,
67
+ (
68
+ p,
69
+ 'Hello again!',
70
+ None,
71
+ 2,
72
+ 2.0,
73
+ )
74
+ ]
75
+
76
+ .. code-block:: html
77
+
78
+ <div>
79
+ <span></span>
80
+ Hello!11.0
81
+ <p></p>
82
+ Hello again!22.0
83
+ </div>
84
+
85
+
86
+ .. include:: /_includes/iterables-must-use-keys-note.rst
@@ -0,0 +1,129 @@
1
+ .. note::
2
+
3
+ Why does the code in the previous example include the line ``__client__ = True``?
4
+
5
+ By default, none of the `.py` files in your app will be sent to the user's web browser and therefore cannot be run client-side. To allow a module to run client-side, you must explicitly mark it by setting the module-level attribute ``__client__`` to ``True``.
6
+
7
+ In order for the client element to be rendered client-side, the code to do so must be available to the user's web browser, so the module where the client element is found, as well as any of its dependencies, must include ``__client__ = True``.
8
+
9
+ Because Blu checks for ``__client__ = True``, using static analysis, the module must include the exact text ``__client__ = True`` at the top level of its source code. For example, the following will *not* work:
10
+
11
+ .. code:: python
12
+
13
+ __client__ = 1 + 1 == 2
14
+
15
+
16
+ .. code:: python
17
+
18
+ if 1 + 1 == 2:
19
+ __client__ = True
20
+
21
+ If you're familiar with full stack React frameworks for JavaScript, note that this is *not* the same as the ``"use client"`` directive in JavaScript React applications.
22
+
23
+ .. note::
24
+
25
+ Client elements cannot be created from asynchronous functions.
26
+
27
+ .. code-block:: python
28
+
29
+ from blu import client
30
+ from blu.html import p
31
+
32
+ # Wrong!
33
+ @client
34
+ async def Hello():
35
+ return p['Hello, World!']
36
+
37
+
38
+ # Right.
39
+ @client
40
+ def Hello():
41
+ return p['Hello, World!']
42
+
43
+
44
+ .. note::
45
+
46
+ Client-side code runs in a different environment than server-side code. If your client-side code uses an external library, make sure it is installed in the client environment by adding it to the list of client dependencies in ``app/__settings__.py`` (if the ``__settings__.py`` doesn't exist, create it)::
47
+
48
+ app/
49
+ __index__.py
50
+ __settings__.py
51
+
52
+ .. code-block:: python
53
+ :caption: app/__index__.py
54
+
55
+ import arrr
56
+ from blu import client
57
+
58
+ __client__ = True
59
+
60
+
61
+ def __page__():
62
+ return MyClientElement
63
+
64
+
65
+ @client
66
+ def MyClientElement():
67
+ return p[
68
+ arrr.translate('Hello there. How are you?')
69
+ ]
70
+
71
+
72
+ .. code-block:: python
73
+ :caption: app/__settings__.py
74
+
75
+ CLIENT_REQUIREMENTS = ['arrr']
76
+
77
+
78
+ .. note::
79
+
80
+ Client-side code runs in a different environment than server-side code. If your client-side code uses dependencies that aren't available on the server, make sure you check the environment before importing those dependencies:
81
+
82
+ .. code-block:: python
83
+ :caption: Wrong!
84
+ :emphasize-lines: 4
85
+
86
+ from blu import client
87
+ from blu.html import button
88
+
89
+ from pyscript import window
90
+
91
+ __client__ = True
92
+
93
+
94
+ def __page__():
95
+ return GoToPyPIButton
96
+
97
+
98
+ @client
99
+ def GoToPythonDotOrgButton():
100
+ return button(onClick=navigate_to_python_dot_org)
101
+
102
+
103
+ def navigate_to_python_dot_org(e):
104
+ window.location = 'https://www.python.org'
105
+
106
+ .. code-block:: python
107
+ :caption: Right.
108
+ :emphasize-lines: 4-5
109
+
110
+ from blu import client, is_client
111
+ from blu.html import button
112
+
113
+ if is_client:
114
+ from pyscript import window
115
+
116
+ __client__ = True
117
+
118
+
119
+ def __page__():
120
+ return GoToPythonDotOrgButton
121
+
122
+
123
+ @client
124
+ def GoToPythonDotOrgButton():
125
+ return button(onClick=navigate_to_python_dot_org)
126
+
127
+
128
+ def navigate_to_python_dot_org(e):
129
+ window.location = 'https://www.python.org'
@@ -0,0 +1,3 @@
1
+ .. note::
2
+
3
+ This function is a *hook*. For more information on hooks and the rules of calling them, see :ref:`note-on-hooks`.
@@ -0,0 +1,81 @@
1
+ Blu is a React framework, and React provides a special type of function called a *hook*. These are the functions you import from Blu whose names start with ``use``. They are used for managing UI state and adding callbacks for lifecycle phases, and there are certain restrictions on how they can be used:
2
+
3
+ 1. A hook can only be called in the scope of a client element's rendering function body, or the scope of a custom hook's function body (a *custom hook* is any function whose name starts with ``use`` that calls other hooks).
4
+ 2. A client element rendering function or custom hook must call the same hooks in the same order *every time* the rendering function or custom hook is called. The best way to follow this rule is just to never call a hook in a conditional block or loop.
5
+
6
+ Breaking either of these rules will result in undefined behavior.
7
+
8
+ .. code-block:: python
9
+
10
+ from blu import client, use_ref
11
+ from blu.html import p
12
+
13
+
14
+ # Wrong! This function is not a client element or custom hook, so
15
+ # it can't call hooks.
16
+ def some_function():
17
+ some_ref = use_ref()
18
+
19
+
20
+ # Right. This is a client element whose rendering function calls
21
+ # the use_ref hook.
22
+ @client
23
+ def SomeClientElement():
24
+ some_ref = use_ref()
25
+ return p['(client element content)']
26
+
27
+
28
+ # Right. Because this function's name starts with "use", it is a
29
+ # valid custom hook and can call other hooks.
30
+ def use_some_hook():
31
+ return use_ref()
32
+
33
+
34
+ # Wrong! Hook called in a conditional block.
35
+ @client
36
+ def SomeClientElement(some_condition):
37
+ if some_condition:
38
+ my_ref = use_ref()
39
+ else:
40
+ my_ref = None
41
+ return p['(client element content)']
42
+
43
+
44
+ # Wrong! Hook called in a conditional block.
45
+ def use_some_hook(some_condition):
46
+ if some_condition:
47
+ return None
48
+ else:
49
+ return use_ref()
50
+
51
+
52
+ # Wrong! Hook called in a loop.
53
+ def use_some_hook(some_list):
54
+ result = []
55
+ for item in some_list:
56
+ result.append(use_ref())
57
+ return result
58
+
59
+
60
+ # Wrong! Hook called in a loop.
61
+ @client
62
+ def SomeClientElement(num_iterations):
63
+ i = 0
64
+ while i < num_iterations:
65
+ my_ref = use_ref()
66
+ return p['(client element content)']
67
+
68
+
69
+ # Wrong! use_my_custom_hook is a custom hook, so it must *not* be
70
+ # called in a conditinal block.
71
+ @client
72
+ def SomeClientElement(some_condition):
73
+ if some_condition:
74
+ value = use_my_custom_hook()
75
+ else:
76
+ value = None
77
+ return p['(client element content)']
78
+
79
+
80
+ def use_my_custom_hook():
81
+ return use_ref()
@@ -0,0 +1,59 @@
1
+ .. note::
2
+
3
+ Items in :py:class:`Iterable <collections.abc.Iterable>` children (other than :py:class:`str`\ s and :py:class:`tuple`\ s) must be keyed:
4
+
5
+ .. code-block::
6
+
7
+ from blu import Key
8
+ from blu.html import div
9
+
10
+ PEOPLE = [
11
+ {'id': 0, 'name': 'Ana'},
12
+ {'id': 1, 'name': 'Bill'},
13
+ {'id': 2, 'name': 'Charlotte'},
14
+ ]
15
+
16
+ # Wrong!
17
+ div[
18
+ [
19
+ div[f'Hello, {person["name"]}!']
20
+ for person in PEOPLE
21
+ ],
22
+ ]
23
+
24
+ # Right.
25
+ div[
26
+ [
27
+ div(key=person['id'])[f'Hello, {person["name"]}!']
28
+ for person in PEOPLE
29
+ ],
30
+ ]
31
+
32
+ # Wrong!
33
+ div[
34
+ [f'Hello, {person["name"]}!' for person in PEOPLE],
35
+ ]
36
+
37
+ # Right.
38
+ div[
39
+ [
40
+ Key(person["id"])[
41
+ f'Hello, {person["name"]}',
42
+ ]
43
+ for person in PEOPLE
44
+ ],
45
+ ]
46
+
47
+ # Right.
48
+ div[
49
+ (
50
+ 'Hello, Ana!',
51
+ 'Hello, Bill!',
52
+ 'Hello, Charlotte!',
53
+ ),
54
+ ]
55
+
56
+ # Right.
57
+ div['Hello, Ana! Hello, Bill! Hello, Charlotte!']
58
+
59
+ The rationale here is that, in :ref:`client-side rendering <Client-Side Rendering>`, items in a sequence may be moved around in response to user interaction. Giving keys to these items allows React to maintain state and render efficiently even when an item's position in a sequence changes.
@@ -0,0 +1,26 @@
1
+ .custom-footer {
2
+ max-width: 940px;
3
+ margin-left: auto;
4
+ margin-right: auto;
5
+ display: flex;
6
+ flex-direction: row;
7
+ }
8
+
9
+ .custom-footer-spacer {
10
+ max-width: 220px;
11
+ width: 100%;
12
+ height: 10px;
13
+ }
14
+
15
+ .footer-nav {
16
+ width: 100%;
17
+ list-style-type: none;
18
+ padding: 0;
19
+ margin-left: 30px;
20
+ margin-right: 30px;
21
+ margin-bottom: 40px;
22
+ margin-top: 40px;
23
+ display: flex;
24
+ flex-direction: row;
25
+ justify-content: space-between;
26
+ }
@@ -0,0 +1,30 @@
1
+ {% extends "!layout.html" %}
2
+
3
+ {% block document %}
4
+ {{ super() }}
5
+ {% endblock %}
6
+ {% block content %}
7
+ <div class="admonition warning">
8
+ <p class="admonition-title">Warning</p>
9
+ <p>Blu is still in early testing and should not be used in production applications.</p>
10
+ </div>
11
+ {{ super() }}
12
+ {% endblock %}
13
+ {% block footer %}
14
+ <div class="custom-footer">
15
+ <div class="custom-footer-spacer"></div>
16
+ <ul class="footer-nav">
17
+ {%- if prev %}
18
+ <li class="prev">
19
+ <a href="{{ prev.link|e }}">&lt; {{ prev.title }}</a>
20
+ </li>
21
+ {% endif %}
22
+ {%- if next %}
23
+ <li class="next">
24
+ <a href="{{ next.link|e }}">{{ next.title }} &gt;</a>
25
+ </li>
26
+ {%- endif %}
27
+ </ul>
28
+ </div>
29
+ {{ super() }}
30
+ {% endblock %}
@@ -0,0 +1,4 @@
1
+ ``blu.html``
2
+ ============
3
+ .. automodule:: blu.html
4
+ :special-members: __getattr__