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.
- blu_react-0.0.1rc1/.gitignore +8 -0
- blu_react-0.0.1rc1/.pre-commit-config.yaml +11 -0
- blu_react-0.0.1rc1/.vscode/launch.json +15 -0
- blu_react-0.0.1rc1/.vscode/settings.json +10 -0
- blu_react-0.0.1rc1/LICENSE +21 -0
- blu_react-0.0.1rc1/PKG-INFO +63 -0
- blu_react-0.0.1rc1/README.md +47 -0
- blu_react-0.0.1rc1/docs/Makefile +20 -0
- blu_react-0.0.1rc1/docs/_img/cursor_code_completion.png +0 -0
- blu_react-0.0.1rc1/docs/_img/directly_manipulating_rendered_elements.gif +0 -0
- blu_react-0.0.1rc1/docs/_img/effect_cleanup.gif +0 -0
- blu_react-0.0.1rc1/docs/_img/perform_an_action_immediately_after_rendering.gif +0 -0
- blu_react-0.0.1rc1/docs/_img/responding_to_user_interaction.gif +0 -0
- blu_react-0.0.1rc1/docs/_includes/client-element-call-notes.rst +48 -0
- blu_react-0.0.1rc1/docs/_includes/client-element-children.rst +86 -0
- blu_react-0.0.1rc1/docs/_includes/client-side-rendering-notes.rst +129 -0
- blu_react-0.0.1rc1/docs/_includes/hook-note.rst +3 -0
- blu_react-0.0.1rc1/docs/_includes/hooks.rst +81 -0
- blu_react-0.0.1rc1/docs/_includes/iterables-must-use-keys-note.rst +59 -0
- blu_react-0.0.1rc1/docs/_static/styles.css +26 -0
- blu_react-0.0.1rc1/docs/_templates/layout.html +30 -0
- blu_react-0.0.1rc1/docs/api-reference/blu.html.rst +4 -0
- blu_react-0.0.1rc1/docs/api-reference/blu.rst +163 -0
- blu_react-0.0.1rc1/docs/api-reference/cli.rst +18 -0
- blu_react-0.0.1rc1/docs/api-reference/file-conventions.rst +453 -0
- blu_react-0.0.1rc1/docs/api-reference/index.rst +13 -0
- blu_react-0.0.1rc1/docs/api-reference/js.rst +35 -0
- blu_react-0.0.1rc1/docs/api-reference/pyscript.rst +38 -0
- blu_react-0.0.1rc1/docs/conf.py +61 -0
- blu_react-0.0.1rc1/docs/contributing.rst +32 -0
- blu_react-0.0.1rc1/docs/index.rst +19 -0
- blu_react-0.0.1rc1/docs/install.rst +10 -0
- blu_react-0.0.1rc1/docs/make.bat +35 -0
- blu_react-0.0.1rc1/docs/quickstart.rst +42 -0
- blu_react-0.0.1rc1/docs/user-guide/client-side-rendering.rst +457 -0
- blu_react-0.0.1rc1/docs/user-guide/creating-pages.rst +213 -0
- blu_react-0.0.1rc1/docs/user-guide/deploying-your-app.rst +18 -0
- blu_react-0.0.1rc1/docs/user-guide/index.rst +14 -0
- blu_react-0.0.1rc1/docs/user-guide/routing.rst +378 -0
- blu_react-0.0.1rc1/docs/user-guide/static-files.rst +32 -0
- blu_react-0.0.1rc1/pyproject.toml +56 -0
- blu_react-0.0.1rc1/src/blu/__init__.py +74 -0
- blu_react-0.0.1rc1/src/blu/_app/__init__.py +463 -0
- blu_react-0.0.1rc1/src/blu/_app/client_main.py +159 -0
- blu_react-0.0.1rc1/src/blu/_app/render.py +107 -0
- blu_react-0.0.1rc1/src/blu/_app/router.py +267 -0
- blu_react-0.0.1rc1/src/blu/_cli.py +25 -0
- blu_react-0.0.1rc1/src/blu/_exceptions.py +5 -0
- blu_react-0.0.1rc1/src/blu/_hooks.py +323 -0
- blu_react-0.0.1rc1/src/blu/_http.py +219 -0
- blu_react-0.0.1rc1/src/blu/_nodes.py +771 -0
- blu_react-0.0.1rc1/src/blu/_server_functions.py +423 -0
- blu_react-0.0.1rc1/src/blu/_settings.py +30 -0
- blu_react-0.0.1rc1/src/blu/_utils/__init__.py +47 -0
- blu_react-0.0.1rc1/src/blu/_utils/asgi.py +189 -0
- blu_react-0.0.1rc1/src/blu/_utils/asyncio.py +60 -0
- blu_react-0.0.1rc1/src/blu/_utils/client.py +3 -0
- blu_react-0.0.1rc1/src/blu/_utils/json.py +30 -0
- blu_react-0.0.1rc1/src/blu/_utils/typing.py +33 -0
- blu_react-0.0.1rc1/src/blu/html.py +91 -0
- blu_react-0.0.1rc1/src/blu/util.js +334 -0
- blu_react-0.0.1rc1/tests/__init__.py +0 -0
- blu_react-0.0.1rc1/tests/apps/app_module_reject/module.py +1 -0
- blu_react-0.0.1rc1/tests/apps/basic/__index__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/client_false/module.py +1 -0
- blu_react-0.0.1rc1/tests/apps/client_module/module.py +3 -0
- blu_react-0.0.1rc1/tests/apps/def_async_handler/__default__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/def_empty_path_param/a/__default__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/def_extra_query_params/__default__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/def_extra_route_params/__default__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/def_missing_query_params/__default__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/def_missing_route_params/_a_/_b_/_c_/__default__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/def_pos_only_path_param/__default__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/def_pos_only_underscore/__default__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/def_query/__default__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/def_query_kwarg/__default__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/def_query_params_default/__default__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/def_route_and_query/_a_/_b_/__default__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/default_extra_segment/a/__default__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/default_full_match/a/_b_/c/__default__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/default_incomplete_match/a/b/c/__default__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/default_path_param/__default__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/default_response_ret/__default__.py +5 -0
- blu_react-0.0.1rc1/tests/apps/default_route_params/_a_/_b_/_c_/__default__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/default_single_underscore/__default__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/default_specific_priority/_a_/__default__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/default_specific_priority/_b_/c/__default__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/dynamic_non_matching_static/_dynamic_/__index__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/dynamic_non_matching_static/static/__index__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/dynamic_segment/_segment_/__index__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/dynamic_static_not_full_match/_dynamic_/bar/__index__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/dynamic_static_not_full_match/foo/baz/__index__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/e2e/app_pkg_clientside/fail/__index__.py +5 -0
- blu_react-0.0.1rc1/tests/apps/e2e/app_pkg_clientside/fail/element.py +14 -0
- blu_react-0.0.1rc1/tests/apps/e2e/app_pkg_clientside/fail/module.py +1 -0
- blu_react-0.0.1rc1/tests/apps/e2e/app_pkg_clientside/success/__index__.py +5 -0
- blu_react-0.0.1rc1/tests/apps/e2e/app_pkg_clientside/success/element.py +10 -0
- blu_react-0.0.1rc1/tests/apps/e2e/app_pkg_clientside/success/module.py +3 -0
- blu_react-0.0.1rc1/tests/apps/e2e/async_effect/__index__.py +45 -0
- blu_react-0.0.1rc1/tests/apps/e2e/client_import/__index__.py +14 -0
- blu_react-0.0.1rc1/tests/apps/e2e/dev_server/__index__.py +5 -0
- blu_react-0.0.1rc1/tests/apps/e2e/html_editing_effect/__index__.py +19 -0
- blu_react-0.0.1rc1/tests/apps/e2e/is_client/__index__.py +13 -0
- blu_react-0.0.1rc1/tests/apps/e2e/rendering/__index__.py +37 -0
- blu_react-0.0.1rc1/tests/apps/e2e/routing/_a_/__default__.py +5 -0
- blu_react-0.0.1rc1/tests/apps/e2e/routing/_a_/__index__.py +5 -0
- blu_react-0.0.1rc1/tests/apps/e2e/routing/_a_/_b_/__index__.py +5 -0
- blu_react-0.0.1rc1/tests/apps/e2e/routing/_a_/static/__index__.py +5 -0
- blu_react-0.0.1rc1/tests/apps/e2e/server_functions/__index__.py +27 -0
- blu_react-0.0.1rc1/tests/apps/e2e/server_functions/hello_module.py +8 -0
- blu_react-0.0.1rc1/tests/apps/e2e/server_functions/shared.py +6 -0
- blu_react-0.0.1rc1/tests/apps/e2e/static_file/_dynamic_path_/hello.txt +1 -0
- blu_react-0.0.1rc1/tests/apps/e2e/unusual_root_nodes/float/__index__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/e2e/unusual_root_nodes/int/__index__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/e2e/unusual_root_nodes/iterable/__index__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/e2e/unusual_root_nodes/key/__index__.py +5 -0
- blu_react-0.0.1rc1/tests/apps/e2e/unusual_root_nodes/none/__index__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/e2e/unusual_root_nodes/str/__index__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/e2e/unusual_root_nodes/tuple/__index__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/e2e/use_effect/__index__.py +30 -0
- blu_react-0.0.1rc1/tests/apps/e2e/use_ref/__index__.py +26 -0
- blu_react-0.0.1rc1/tests/apps/e2e/use_state/__index__.py +21 -0
- blu_react-0.0.1rc1/tests/apps/e2e/wrong_environment_error/__index__.py +23 -0
- blu_react-0.0.1rc1/tests/apps/e2e/yield_in_tuple/__index__.py +13 -0
- blu_react-0.0.1rc1/tests/apps/e2e_settings/__index__.py +19 -0
- blu_react-0.0.1rc1/tests/apps/e2e_settings/__settings__.py +1 -0
- blu_react-0.0.1rc1/tests/apps/empty_settings/__index__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/empty_settings/__settings__.py +0 -0
- blu_react-0.0.1rc1/tests/apps/index_abc/a/b/c/__index__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/index_async_handler/__index__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/index_dynamic_abc/_a_/_b_/_c_/__index__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/index_extra_route_params/__index__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/index_file_priority/_a_/c/__default__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/index_file_priority/_b_/c/__index__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/index_missing_route_params/_a_/__index__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/index_pos_only_underscore/__index__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/index_query_abc/__index__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/index_query_kwargs/__index__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/index_response/__index__.py +5 -0
- blu_react-0.0.1rc1/tests/apps/index_route_and_query/_a_/_b_/__index__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/index_single_underscore/__index__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/leading_underscore/_path/__index__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/literal_segment/path/__index__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/multiple_dynamic/_a_/b/__index__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/multiple_dynamic/_c_/d/__index__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/nested_dynamic_segments/_first_/_second_/__index__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/nested_literal_segments/path/to/view/__index__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/server_function_arbitrary_function_call/server_functions.py +17 -0
- blu_react-0.0.1rc1/tests/apps/server_function_arbitrary_function_call/server_only_module.py +6 -0
- blu_react-0.0.1rc1/tests/apps/server_function_csrf/__index__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/server_function_csrf/file.txt +1 -0
- blu_react-0.0.1rc1/tests/apps/server_function_csrf/server_functions.py +8 -0
- blu_react-0.0.1rc1/tests/apps/server_functions_outside_app/valid.py +9 -0
- blu_react-0.0.1rc1/tests/apps/settings_below_top_level/foo/__settings__.py +1 -0
- blu_react-0.0.1rc1/tests/apps/settings_multi_client_reqs/__settings__.py +1 -0
- blu_react-0.0.1rc1/tests/apps/settings_one_client_req/__settings__.py +1 -0
- blu_react-0.0.1rc1/tests/apps/static_dynamic_nested/static/_dynamic_/__index__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/static_dynamic_priority/_dynamic_/__index__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/static_dynamic_priority/static/__index__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/static_files/path/to/static/file.txt +1 -0
- blu_react-0.0.1rc1/tests/apps/static_files_dynamic_path/_a_/file.txt +1 -0
- blu_react-0.0.1rc1/tests/apps/top_level_segment/__index__.py +2 -0
- blu_react-0.0.1rc1/tests/apps/trailing_underscore/path_/__index__.py +2 -0
- blu_react-0.0.1rc1/tests/conftest.py +149 -0
- blu_react-0.0.1rc1/tests/fast/blu/test_ClientElement.py +266 -0
- blu_react-0.0.1rc1/tests/fast/blu/test_HTMLElement.py +144 -0
- blu_react-0.0.1rc1/tests/fast/blu/test_Key.py +78 -0
- blu_react-0.0.1rc1/tests/fast/blu/test_Response.py +68 -0
- blu_react-0.0.1rc1/tests/fast/blu/test_app.py +57 -0
- blu_react-0.0.1rc1/tests/fast/blu/test_create_rare_html_element.py +21 -0
- blu_react-0.0.1rc1/tests/fast/blu/test_html.py +62 -0
- blu_react-0.0.1rc1/tests/fast/blu/test_is_client.py +14 -0
- blu_react-0.0.1rc1/tests/fast/blu/test_server.py +109 -0
- blu_react-0.0.1rc1/tests/fast/file_conventions/_utils.py +8 -0
- blu_react-0.0.1rc1/tests/fast/file_conventions/directories/test_literal_segment.py +55 -0
- blu_react-0.0.1rc1/tests/fast/file_conventions/directories/test_route_param_name.py +53 -0
- blu_react-0.0.1rc1/tests/fast/file_conventions/special_files/test_default.py +249 -0
- blu_react-0.0.1rc1/tests/fast/file_conventions/special_files/test_index.py +188 -0
- blu_react-0.0.1rc1/tests/fast/file_conventions/special_files/test_settings.py +83 -0
- blu_react-0.0.1rc1/tests/fast/file_conventions/test_static_files.py +53 -0
- blu_react-0.0.1rc1/tests/fast/test_cli.py +47 -0
- blu_react-0.0.1rc1/tests/fast/test_security.py +309 -0
- blu_react-0.0.1rc1/tests/projects/basic/app/__index__.py +5 -0
- blu_react-0.0.1rc1/tests/projects/quickstart/app/__index__.py +8 -0
- blu_react-0.0.1rc1/tests/resources/server_fn_outside_app.py +8 -0
- blu_react-0.0.1rc1/tests/test_e2e.py +289 -0
- blu_react-0.0.1rc1/tests/utils.py +103 -0
- blu_react-0.0.1rc1/uv.lock +1677 -0
|
@@ -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)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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,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 }}">< {{ prev.title }}</a>
|
|
20
|
+
</li>
|
|
21
|
+
{% endif %}
|
|
22
|
+
{%- if next %}
|
|
23
|
+
<li class="next">
|
|
24
|
+
<a href="{{ next.link|e }}">{{ next.title }} ></a>
|
|
25
|
+
</li>
|
|
26
|
+
{%- endif %}
|
|
27
|
+
</ul>
|
|
28
|
+
</div>
|
|
29
|
+
{{ super() }}
|
|
30
|
+
{% endblock %}
|