[Fix 477] Use jinja2 for templating (#915)
This commit is contained in:
parent
dc2dff9323
commit
3b88e7c329
88
poetry.lock
generated
88
poetry.lock
generated
@ -463,6 +463,84 @@ files = [
|
|||||||
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
|
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jinja2"
|
||||||
|
version = "3.1.2"
|
||||||
|
description = "A very fast and expressive template engine."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"},
|
||||||
|
{file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
MarkupSafe = ">=2.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
i18n = ["Babel (>=2.7)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "markupsafe"
|
||||||
|
version = "2.1.2"
|
||||||
|
description = "Safely add untrusted strings to HTML/XML markup."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp310-cp310-win32.whl", hash = "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp311-cp311-win32.whl", hash = "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp37-cp37m-win32.whl", hash = "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp38-cp38-win32.whl", hash = "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp39-cp39-win32.whl", hash = "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7"},
|
||||||
|
{file = "MarkupSafe-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed"},
|
||||||
|
{file = "MarkupSafe-2.1.2.tar.gz", hash = "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mypy-extensions"
|
name = "mypy-extensions"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@ -1063,18 +1141,18 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redis"
|
name = "redis"
|
||||||
version = "4.5.4"
|
version = "4.5.5"
|
||||||
description = "Python client for Redis database and key-value store"
|
description = "Python client for Redis database and key-value store"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "redis-4.5.4-py3-none-any.whl", hash = "sha256:2c19e6767c474f2e85167909061d525ed65bea9301c0770bb151e041b7ac89a2"},
|
{file = "redis-4.5.5-py3-none-any.whl", hash = "sha256:77929bc7f5dab9adf3acba2d3bb7d7658f1e0c2f1cafe7eb36434e751c471119"},
|
||||||
{file = "redis-4.5.4.tar.gz", hash = "sha256:73ec35da4da267d6847e47f68730fdd5f62e2ca69e3ef5885c6a78a9374c3893"},
|
{file = "redis-4.5.5.tar.gz", hash = "sha256:dc87a0bdef6c8bfe1ef1e1c40be7034390c2ae02d92dcd0c7ca1729443899880"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
async-timeout = {version = ">=4.0.2", markers = "python_version <= \"3.11.2\""}
|
async-timeout = {version = ">=4.0.2", markers = "python_full_version <= \"3.11.2\""}
|
||||||
importlib-metadata = {version = ">=1.0", markers = "python_version < \"3.8\""}
|
importlib-metadata = {version = ">=1.0", markers = "python_version < \"3.8\""}
|
||||||
typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
|
typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
|
||||||
|
|
||||||
@ -1600,4 +1678,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.7"
|
python-versions = "^3.7"
|
||||||
content-hash = "8c9f764a830657316f774fb679895c89d6874460582f4dbf8b2edbd4f534b262"
|
content-hash = "43e53d9fff649b6a939ec953411b8932e512600b5af3edc0cbbbbb6c5576168b"
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
"""Compiler for the pynecone apps."""
|
"""Compiler for the pynecone apps."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from typing import Callable, List, Set, Tuple, Type
|
from typing import Callable, List, Set, Tuple, Type
|
||||||
|
|
||||||
@ -10,7 +9,7 @@ from pynecone.compiler import templates, utils
|
|||||||
from pynecone.components.component import Component, CustomComponent
|
from pynecone.components.component import Component, CustomComponent
|
||||||
from pynecone.state import State
|
from pynecone.state import State
|
||||||
from pynecone.style import Style
|
from pynecone.style import Style
|
||||||
from pynecone.utils import imports, path_ops
|
from pynecone.utils import imports
|
||||||
from pynecone.var import ImportVar
|
from pynecone.var import ImportVar
|
||||||
|
|
||||||
# Imports to be included in every Pynecone app.
|
# Imports to be included in every Pynecone app.
|
||||||
@ -42,7 +41,7 @@ def _compile_document_root(root: Component) -> str:
|
|||||||
Returns:
|
Returns:
|
||||||
The compiled document root.
|
The compiled document root.
|
||||||
"""
|
"""
|
||||||
return templates.DOCUMENT_ROOT(
|
return templates.DOCUMENT_ROOT.render(
|
||||||
imports=utils.compile_imports(root.get_imports()),
|
imports=utils.compile_imports(root.get_imports()),
|
||||||
document=root.render(),
|
document=root.render(),
|
||||||
)
|
)
|
||||||
@ -57,7 +56,7 @@ def _compile_theme(theme: dict) -> str:
|
|||||||
Returns:
|
Returns:
|
||||||
The compiled theme.
|
The compiled theme.
|
||||||
"""
|
"""
|
||||||
return templates.THEME(theme=json.dumps(theme))
|
return templates.THEME.render(theme=theme)
|
||||||
|
|
||||||
|
|
||||||
def _compile_page(component: Component, state: Type[State]) -> str:
|
def _compile_page(component: Component, state: Type[State]) -> str:
|
||||||
@ -72,17 +71,20 @@ def _compile_page(component: Component, state: Type[State]) -> str:
|
|||||||
"""
|
"""
|
||||||
# Merge the default imports with the app-specific imports.
|
# Merge the default imports with the app-specific imports.
|
||||||
imports = utils.merge_imports(DEFAULT_IMPORTS, component.get_imports())
|
imports = utils.merge_imports(DEFAULT_IMPORTS, component.get_imports())
|
||||||
|
imports = utils.compile_imports(imports)
|
||||||
|
|
||||||
# Compile the code to render the component.
|
# Compile the code to render the component.
|
||||||
return templates.PAGE(
|
return templates.PAGE.render(
|
||||||
imports=utils.compile_imports(imports),
|
imports=imports,
|
||||||
custom_code=path_ops.join(component.get_custom_code()),
|
custom_codes=component.get_custom_code(),
|
||||||
constants=utils.compile_constants(),
|
endpoints={
|
||||||
state=utils.compile_state(state),
|
constant.name: constant.get_url() for constant in constants.Endpoint
|
||||||
events=utils.compile_events(state),
|
},
|
||||||
effects=utils.compile_effects(state),
|
initial_state=utils.compile_state(state),
|
||||||
hooks=path_ops.join(component.get_hooks()),
|
state_name=state.get_name(),
|
||||||
|
hooks=component.get_hooks(),
|
||||||
render=component.render(),
|
render=component.render(),
|
||||||
|
transports=constants.Transports.POLLING_WEBSOCKET.get_transports(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -99,18 +101,18 @@ def _compile_components(components: Set[CustomComponent]) -> str:
|
|||||||
"react": {ImportVar(tag="memo")},
|
"react": {ImportVar(tag="memo")},
|
||||||
f"/{constants.STATE_PATH}": {ImportVar(tag="E"), ImportVar(tag="isTrue")},
|
f"/{constants.STATE_PATH}": {ImportVar(tag="E"), ImportVar(tag="isTrue")},
|
||||||
}
|
}
|
||||||
component_defs = []
|
component_renders = []
|
||||||
|
|
||||||
# Compile each component.
|
# Compile each component.
|
||||||
for component in components:
|
for component in components:
|
||||||
component_def, component_imports = utils.compile_custom_component(component)
|
component_render, component_imports = utils.compile_custom_component(component)
|
||||||
component_defs.append(component_def)
|
component_renders.append(component_render)
|
||||||
imports = utils.merge_imports(imports, component_imports)
|
imports = utils.merge_imports(imports, component_imports)
|
||||||
|
|
||||||
# Compile the components page.
|
# Compile the components page.
|
||||||
return templates.COMPONENTS(
|
return templates.COMPONENTS.render(
|
||||||
imports=utils.compile_imports(imports),
|
imports=utils.compile_imports(imports),
|
||||||
components=path_ops.join(component_defs),
|
components=component_renders,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,163 +1,75 @@
|
|||||||
"""Templates to use in the pynecone compiler."""
|
"""Templates to use in the pynecone compiler."""
|
||||||
|
|
||||||
from typing import Optional, Set
|
from jinja2 import Environment, FileSystemLoader, Template
|
||||||
|
|
||||||
from pynecone import constants
|
from pynecone import constants
|
||||||
from pynecone.utils import path_ops
|
from pynecone.utils import path_ops
|
||||||
|
from pynecone.utils.format import json_dumps
|
||||||
|
|
||||||
|
|
||||||
|
class PyneconeJinjaEnvironment(Environment):
|
||||||
|
"""The template class for jinja environment."""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
"""Set default environment."""
|
||||||
|
extensions = ["jinja2.ext.debug"]
|
||||||
|
super().__init__(
|
||||||
|
extensions=extensions,
|
||||||
|
trim_blocks=True,
|
||||||
|
lstrip_blocks=True,
|
||||||
|
)
|
||||||
|
self.filters["json_dumps"] = json_dumps
|
||||||
|
self.filters["react_setter"] = lambda state: f"set{state.capitalize()}"
|
||||||
|
self.loader = FileSystemLoader(constants.JINJA_TEMPLATE_DIR)
|
||||||
|
self.globals["const"] = {
|
||||||
|
"socket": constants.SOCKET,
|
||||||
|
"result": constants.RESULT,
|
||||||
|
"router": constants.ROUTER,
|
||||||
|
"event_endpoint": constants.Endpoint.EVENT.name,
|
||||||
|
"events": constants.EVENTS,
|
||||||
|
"state": constants.STATE,
|
||||||
|
"processing": constants.PROCESSING,
|
||||||
|
"initial_result": {
|
||||||
|
constants.STATE: None,
|
||||||
|
constants.EVENTS: [],
|
||||||
|
constants.PROCESSING: False,
|
||||||
|
},
|
||||||
|
"color_mode": constants.COLOR_MODE,
|
||||||
|
"toggle_color_mode": constants.TOGGLE_COLOR_MODE,
|
||||||
|
"use_color_mode": constants.USE_COLOR_MODE,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_template(name: str) -> Template:
|
||||||
|
"""Get render function that work with a template.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: The template name. "/" is used as the path separator.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A render function.
|
||||||
|
"""
|
||||||
|
return PyneconeJinjaEnvironment().get_template(name=name)
|
||||||
|
|
||||||
|
|
||||||
# Template for the Pynecone config file.
|
# Template for the Pynecone config file.
|
||||||
PCCONFIG = f"""import pynecone as pc
|
PCCONFIG = get_template("app/pcconfig.py.jinja2")
|
||||||
|
|
||||||
class {{config_name}}(pc.Config):
|
|
||||||
pass
|
|
||||||
|
|
||||||
config = {{config_name}}(
|
|
||||||
app_name="{{app_name}}",
|
|
||||||
db_url="{constants.DB_URL}",
|
|
||||||
env=pc.Env.DEV,
|
|
||||||
)
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Javascript formatting.
|
|
||||||
CONST = "const {name} = {value}".format
|
|
||||||
PROP = "{object}.{property}".format
|
|
||||||
IMPORT_LIB = 'import "{lib}"'.format
|
|
||||||
IMPORT_FIELDS = 'import {default}{others} from "{lib}"'.format
|
|
||||||
|
|
||||||
|
|
||||||
def format_import(lib: str, default: str = "", rest: Optional[Set[str]] = None) -> str:
|
|
||||||
"""Format an import statement.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
lib: The library to import from.
|
|
||||||
default: The default field to import.
|
|
||||||
rest: The set of fields to import from the library.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The compiled import statement.
|
|
||||||
"""
|
|
||||||
# Handle the case of direct imports with no libraries.
|
|
||||||
if not lib:
|
|
||||||
assert not default, "No default field allowed for empty library."
|
|
||||||
assert rest is not None and len(rest) > 0, "No fields to import."
|
|
||||||
return path_ops.join([IMPORT_LIB(lib=lib) for lib in sorted(rest)])
|
|
||||||
|
|
||||||
# Handle importing from a library.
|
|
||||||
rest = rest or set()
|
|
||||||
if len(default) == 0 and len(rest) == 0:
|
|
||||||
# Handle the case of importing a library with no fields.
|
|
||||||
return IMPORT_LIB(lib=lib)
|
|
||||||
# Handle importing specific fields from a library.
|
|
||||||
others = f'{{{", ".join(sorted(rest))}}}' if len(rest) > 0 else ""
|
|
||||||
if default != "" and len(rest) > 0:
|
|
||||||
default += ", "
|
|
||||||
return IMPORT_FIELDS(default=default, others=others, lib=lib)
|
|
||||||
|
|
||||||
|
|
||||||
# Code to render a NextJS Document root.
|
# Code to render a NextJS Document root.
|
||||||
DOCUMENT_ROOT = path_ops.join(
|
DOCUMENT_ROOT = get_template("web/pages/_document.js.jinja2")
|
||||||
[
|
|
||||||
"{imports}",
|
|
||||||
"export default function Document() {{",
|
|
||||||
"return (",
|
|
||||||
"{document}",
|
|
||||||
")",
|
|
||||||
"}}",
|
|
||||||
]
|
|
||||||
).format
|
|
||||||
|
|
||||||
# Template for the theme file.
|
# Template for the theme file.
|
||||||
THEME = "export default {theme}".format
|
THEME = get_template("web/utils/theme.js.jinja2")
|
||||||
|
|
||||||
# Code to render a single NextJS page.
|
# Code to render a single NextJS page.
|
||||||
PAGE = path_ops.join(
|
PAGE = get_template("web/pages/index.js.jinja2")
|
||||||
[
|
|
||||||
"{imports}",
|
|
||||||
"{custom_code}",
|
|
||||||
"{constants}",
|
|
||||||
"export default function Component() {{",
|
|
||||||
"{state}",
|
|
||||||
"{events}",
|
|
||||||
"{effects}",
|
|
||||||
"{hooks}",
|
|
||||||
"return (",
|
|
||||||
"{render}",
|
|
||||||
")",
|
|
||||||
"}}",
|
|
||||||
]
|
|
||||||
).format
|
|
||||||
|
|
||||||
# Code to render a single exported custom component.
|
|
||||||
COMPONENT = path_ops.join(
|
|
||||||
[
|
|
||||||
"export const {name} = memo(({{{props}}}) => (",
|
|
||||||
"{render}",
|
|
||||||
"))",
|
|
||||||
]
|
|
||||||
).format
|
|
||||||
|
|
||||||
# Code to render the custom components page.
|
# Code to render the custom components page.
|
||||||
COMPONENTS = path_ops.join(
|
COMPONENTS = get_template("web/pages/custom_component.js.jinja2")
|
||||||
[
|
|
||||||
"{imports}",
|
|
||||||
"{components}",
|
|
||||||
]
|
|
||||||
).format
|
|
||||||
|
|
||||||
|
# Sitemap config file.
|
||||||
|
SITEMAP_CONFIG = "module.exports = {config}".format
|
||||||
|
|
||||||
# React state declarations.
|
|
||||||
USE_STATE = CONST(
|
|
||||||
name="[{state}, {set_state}]", value="useState({initial_state})"
|
|
||||||
).format
|
|
||||||
|
|
||||||
|
|
||||||
def format_state_setter(state: str) -> str:
|
|
||||||
"""Format a state setter.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
state: The name of the state variable.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The compiled state setter.
|
|
||||||
"""
|
|
||||||
return f"set{state[0].upper() + state[1:]}"
|
|
||||||
|
|
||||||
|
|
||||||
def format_state(
|
|
||||||
state: str,
|
|
||||||
initial_state: str,
|
|
||||||
) -> str:
|
|
||||||
"""Format a state declaration.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
state: The name of the state variable.
|
|
||||||
initial_state: The initial state of the state variable.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The compiled state declaration.
|
|
||||||
"""
|
|
||||||
set_state = format_state_setter(state)
|
|
||||||
return USE_STATE(state=state, set_state=set_state, initial_state=initial_state)
|
|
||||||
|
|
||||||
|
|
||||||
# Events.
|
|
||||||
EVENT_ENDPOINT = constants.Endpoint.EVENT.name
|
|
||||||
EVENT_FN = path_ops.join(
|
|
||||||
[
|
|
||||||
"const Event = events => {set_state}({{",
|
|
||||||
" ...{state},",
|
|
||||||
" events: [...{state}.events, ...events],",
|
|
||||||
"}})",
|
|
||||||
]
|
|
||||||
).format
|
|
||||||
UPLOAD_FN = path_ops.join(
|
|
||||||
[
|
|
||||||
"const File = files => {set_state}({{",
|
|
||||||
" ...{state},",
|
|
||||||
" files,",
|
|
||||||
"}})",
|
|
||||||
]
|
|
||||||
).format
|
|
||||||
FULL_CONTROL = path_ops.join(
|
FULL_CONTROL = path_ops.join(
|
||||||
[
|
[
|
||||||
"{{setState(prev => ({{",
|
"{{setState(prev => ({{",
|
||||||
@ -167,52 +79,3 @@ FULL_CONTROL = path_ops.join(
|
|||||||
")}}",
|
")}}",
|
||||||
]
|
]
|
||||||
).format
|
).format
|
||||||
|
|
||||||
# Effects.
|
|
||||||
ROUTER = constants.ROUTER
|
|
||||||
RESULT = constants.RESULT
|
|
||||||
PROCESSING = constants.PROCESSING
|
|
||||||
SOCKET = constants.SOCKET
|
|
||||||
STATE = constants.STATE
|
|
||||||
EVENTS = constants.EVENTS
|
|
||||||
SET_RESULT = format_state_setter(RESULT)
|
|
||||||
READY = f"const {{ isReady }} = {ROUTER};"
|
|
||||||
USE_EFFECT = path_ops.join(
|
|
||||||
[
|
|
||||||
"useEffect(() => {{",
|
|
||||||
" if(!isReady) {{",
|
|
||||||
" return;",
|
|
||||||
" }}",
|
|
||||||
f" if (!{SOCKET}.current) {{{{",
|
|
||||||
f" connect({SOCKET}, {{state}}, {{set_state}}, {RESULT}, {SET_RESULT}, {ROUTER}, {EVENT_ENDPOINT}, {{transports}})",
|
|
||||||
" }}",
|
|
||||||
" const update = async () => {{",
|
|
||||||
f" if ({RESULT}.{STATE} != null) {{{{",
|
|
||||||
f" {{set_state}}({{{{",
|
|
||||||
f" ...{RESULT}.{STATE},",
|
|
||||||
f" events: [...{{state}}.{EVENTS}, ...{RESULT}.{EVENTS}],",
|
|
||||||
" }})",
|
|
||||||
f" {SET_RESULT}({{{{",
|
|
||||||
f" {STATE}: null,",
|
|
||||||
f" {EVENTS}: [],",
|
|
||||||
f" {PROCESSING}: false,",
|
|
||||||
" }})",
|
|
||||||
" }}",
|
|
||||||
f" await updateState({{state}}, {{set_state}}, {RESULT}, {SET_RESULT}, {ROUTER}, {SOCKET}.current)",
|
|
||||||
" }}",
|
|
||||||
" update()",
|
|
||||||
"}})",
|
|
||||||
]
|
|
||||||
).format
|
|
||||||
|
|
||||||
# Routing
|
|
||||||
ROUTER = f"const {constants.ROUTER} = useRouter()"
|
|
||||||
|
|
||||||
# Sockets.
|
|
||||||
SOCKET = "const socket = useRef(null)"
|
|
||||||
|
|
||||||
# Color toggle
|
|
||||||
COLORTOGGLE = f"const {{ {constants.COLOR_MODE}, {constants.TOGGLE_COLOR_MODE} }} = {constants.USE_COLOR_MODE}()"
|
|
||||||
|
|
||||||
# Sitemap config file.
|
|
||||||
SITEMAP_CONFIG = "module.exports = {config}".format
|
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
"""Common utility functions used in the compiler."""
|
"""Common utility functions used in the compiler."""
|
||||||
|
|
||||||
import json
|
|
||||||
import os
|
import os
|
||||||
from typing import Dict, List, Optional, Set, Tuple, Type
|
from typing import Dict, List, Optional, Set, Tuple, Type
|
||||||
|
|
||||||
from pynecone import constants
|
from pynecone import constants
|
||||||
from pynecone.compiler import templates
|
|
||||||
from pynecone.components.base import (
|
from pynecone.components.base import (
|
||||||
Body,
|
Body,
|
||||||
ColorModeScript,
|
ColorModeScript,
|
||||||
@ -31,15 +29,16 @@ from pynecone.var import ImportVar
|
|||||||
merge_imports = imports.merge_imports
|
merge_imports = imports.merge_imports
|
||||||
|
|
||||||
|
|
||||||
def compile_import_statement(lib: str, fields: Set[ImportVar]) -> str:
|
def compile_import_statement(fields: Set[ImportVar]) -> Tuple[str, Set[str]]:
|
||||||
"""Compile an import statement.
|
"""Compile an import statement.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
lib: The library to import from.
|
|
||||||
fields: The set of fields to import from the library.
|
fields: The set of fields to import from the library.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The compiled import statement.
|
The libraries for default and rest.
|
||||||
|
default: default library. When install "import def from library".
|
||||||
|
rest: rest of libraries. When install "import {rest1, rest2} from library"
|
||||||
"""
|
"""
|
||||||
# Check for default imports.
|
# Check for default imports.
|
||||||
defaults = {field for field in fields if field.is_default}
|
defaults = {field for field in fields if field.is_default}
|
||||||
@ -48,58 +47,59 @@ def compile_import_statement(lib: str, fields: Set[ImportVar]) -> str:
|
|||||||
# Get the default import, and the specific imports.
|
# Get the default import, and the specific imports.
|
||||||
default = next(iter({field.name for field in defaults}), "")
|
default = next(iter({field.name for field in defaults}), "")
|
||||||
rest = {field.name for field in fields - defaults}
|
rest = {field.name for field in fields - defaults}
|
||||||
return templates.format_import(lib=lib, default=default, rest=rest)
|
|
||||||
|
return default, rest
|
||||||
|
|
||||||
|
|
||||||
def compile_imports(imports: imports.ImportDict) -> str:
|
def compile_imports(imports: imports.ImportDict) -> List[dict]:
|
||||||
"""Compile an import dict.
|
"""Compile an import dict.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
imports: The import dict to compile.
|
imports: The import dict to compile.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The compiled import dict.
|
The list of import dict.
|
||||||
"""
|
"""
|
||||||
return path_ops.join(
|
import_dicts = []
|
||||||
[compile_import_statement(lib, fields) for lib, fields in imports.items()]
|
for lib, fields in imports.items():
|
||||||
)
|
default, rest = compile_import_statement(fields)
|
||||||
|
if not lib:
|
||||||
|
assert not default, "No default field allowed for empty library."
|
||||||
|
assert rest is not None and len(rest) > 0, "No fields to import."
|
||||||
|
for module in sorted(rest):
|
||||||
|
import_dicts.append(get_import_dict(module))
|
||||||
|
continue
|
||||||
|
|
||||||
|
import_dicts.append(get_import_dict(lib, default, rest))
|
||||||
|
return import_dicts
|
||||||
|
|
||||||
|
|
||||||
def compile_constant_declaration(name: str, value: str) -> str:
|
def get_import_dict(lib: str, default: str = "", rest: Optional[Set] = None) -> Dict:
|
||||||
"""Compile a constant declaration.
|
"""Get dictionary for import template.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
name: The name of the constant.
|
lib: The importing react library.
|
||||||
value: The value of the constant.
|
default: The default module to import.
|
||||||
|
rest: The rest module to import.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The compiled constant declaration.
|
A dictionary for import template.
|
||||||
"""
|
"""
|
||||||
return templates.CONST(name=name, value=json.dumps(value))
|
return {
|
||||||
|
"lib": lib,
|
||||||
|
"default": default,
|
||||||
|
"rest": rest if rest else set(),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def compile_constants() -> str:
|
def compile_state(state: Type[State]) -> Dict:
|
||||||
"""Compile all the necessary constants.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
A string of all the compiled constants.
|
|
||||||
"""
|
|
||||||
return path_ops.join(
|
|
||||||
[
|
|
||||||
compile_constant_declaration(name=endpoint.name, value=endpoint.get_url())
|
|
||||||
for endpoint in constants.Endpoint
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def compile_state(state: Type[State]) -> str:
|
|
||||||
"""Compile the state of the app.
|
"""Compile the state of the app.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
state: The app state object.
|
state: The app state object.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A string of the compiled state.
|
A dictionary of the compiled state.
|
||||||
"""
|
"""
|
||||||
initial_state = state().dict()
|
initial_state = state().dict()
|
||||||
initial_state.update(
|
initial_state.update(
|
||||||
@ -108,77 +108,12 @@ def compile_state(state: Type[State]) -> str:
|
|||||||
"files": [],
|
"files": [],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
initial_state = format.format_state(initial_state)
|
return format.format_state(initial_state)
|
||||||
synced_state = templates.format_state(
|
|
||||||
state=state.get_name(), initial_state=json.dumps(initial_state)
|
|
||||||
)
|
|
||||||
initial_result = {
|
|
||||||
constants.STATE: None,
|
|
||||||
constants.EVENTS: [],
|
|
||||||
constants.PROCESSING: False,
|
|
||||||
}
|
|
||||||
result = templates.format_state(
|
|
||||||
state="result",
|
|
||||||
initial_state=json.dumps(initial_result),
|
|
||||||
)
|
|
||||||
router = templates.ROUTER
|
|
||||||
socket = templates.SOCKET
|
|
||||||
ready = templates.READY
|
|
||||||
color_toggle = templates.COLORTOGGLE
|
|
||||||
return path_ops.join([synced_state, result, router, socket, ready, color_toggle])
|
|
||||||
|
|
||||||
|
|
||||||
def compile_events(state: Type[State]) -> str:
|
|
||||||
"""Compile all the events for a given component.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
state: The state class for the component.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
A string of the compiled events for the component.
|
|
||||||
"""
|
|
||||||
state_name = state.get_name()
|
|
||||||
state_setter = templates.format_state_setter(state_name)
|
|
||||||
return path_ops.join(
|
|
||||||
[
|
|
||||||
templates.EVENT_FN(state=state_name, set_state=state_setter),
|
|
||||||
templates.UPLOAD_FN(state=state_name, set_state=state_setter),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def compile_effects(state: Type[State]) -> str:
|
|
||||||
"""Compile all the effects for a given component.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
state: The state class for the component.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
A string of the compiled effects for the component.
|
|
||||||
"""
|
|
||||||
state_name = state.get_name()
|
|
||||||
set_state = templates.format_state_setter(state_name)
|
|
||||||
transports = constants.Transports.POLLING_WEBSOCKET.get_transports()
|
|
||||||
return templates.USE_EFFECT(
|
|
||||||
state=state_name, set_state=set_state, transports=transports
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def compile_render(component: Component) -> str:
|
|
||||||
"""Compile the component's render method.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
component: The component to compile the render method for.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
A string of the compiled render method.
|
|
||||||
"""
|
|
||||||
return component.render()
|
|
||||||
|
|
||||||
|
|
||||||
def compile_custom_component(
|
def compile_custom_component(
|
||||||
component: CustomComponent,
|
component: CustomComponent,
|
||||||
) -> Tuple[str, imports.ImportDict]:
|
) -> Tuple[dict, imports.ImportDict]:
|
||||||
"""Compile a custom component.
|
"""Compile a custom component.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -198,15 +133,15 @@ def compile_custom_component(
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Concatenate the props.
|
# Concatenate the props.
|
||||||
props = ", ".join([prop.name for prop in component.get_prop_vars()])
|
props = [prop.name for prop in component.get_prop_vars()]
|
||||||
|
|
||||||
# Compile the component.
|
# Compile the component.
|
||||||
return (
|
return (
|
||||||
templates.COMPONENT(
|
{
|
||||||
name=component.tag,
|
"name": component.tag,
|
||||||
props=props,
|
"props": props,
|
||||||
render=render,
|
"render": render.render(),
|
||||||
),
|
},
|
||||||
imports,
|
imports,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
"""Display the title of the current page."""
|
"""Display the title of the current page."""
|
||||||
|
|
||||||
from typing import Optional
|
from typing import Dict, Optional
|
||||||
|
|
||||||
from pynecone.components.base.bare import Bare
|
from pynecone.components.base.bare import Bare
|
||||||
from pynecone.components.component import Component
|
from pynecone.components.component import Component
|
||||||
@ -11,18 +11,17 @@ class Title(Component):
|
|||||||
|
|
||||||
tag = "title"
|
tag = "title"
|
||||||
|
|
||||||
def render(self) -> str:
|
def render(self) -> Dict:
|
||||||
"""Render the title component.
|
"""Render the title component.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The rendered title component.
|
The rendered title component.
|
||||||
"""
|
"""
|
||||||
tag = self._render()
|
|
||||||
# Make sure the title is a single string.
|
# Make sure the title is a single string.
|
||||||
assert len(self.children) == 1 and isinstance(
|
assert len(self.children) == 1 and isinstance(
|
||||||
self.children[0], Bare
|
self.children[0], Bare
|
||||||
), "Title must be a single string."
|
), "Title must be a single string."
|
||||||
return str(tag.set(contents=str(self.children[0].contents)))
|
return super().render()
|
||||||
|
|
||||||
|
|
||||||
class Meta(Component):
|
class Meta(Component):
|
||||||
|
@ -21,7 +21,7 @@ from pynecone.event import (
|
|||||||
get_handler_args,
|
get_handler_args,
|
||||||
)
|
)
|
||||||
from pynecone.style import Style
|
from pynecone.style import Style
|
||||||
from pynecone.utils import format, imports, path_ops, types
|
from pynecone.utils import format, imports, types
|
||||||
from pynecone.var import BaseVar, ImportVar, Var
|
from pynecone.var import BaseVar, ImportVar, Var
|
||||||
|
|
||||||
|
|
||||||
@ -289,7 +289,7 @@ class Component(Base, ABC):
|
|||||||
Returns:
|
Returns:
|
||||||
The code to render the component.
|
The code to render the component.
|
||||||
"""
|
"""
|
||||||
return self.render()
|
return format.json_dumps(self.render())
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
"""Represent the component in React.
|
"""Represent the component in React.
|
||||||
@ -297,7 +297,7 @@ class Component(Base, ABC):
|
|||||||
Returns:
|
Returns:
|
||||||
The code to render the component.
|
The code to render the component.
|
||||||
"""
|
"""
|
||||||
return self.render()
|
return format.json_dumps(self.render())
|
||||||
|
|
||||||
def _render(self) -> Tag:
|
def _render(self) -> Tag:
|
||||||
"""Define how to render the component in React.
|
"""Define how to render the component in React.
|
||||||
@ -393,14 +393,14 @@ class Component(Base, ABC):
|
|||||||
child.add_style(style)
|
child.add_style(style)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def render(self) -> str:
|
def render(self) -> Dict:
|
||||||
"""Render the component.
|
"""Render the component.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The code to render the component.
|
The dictionary for template of component.
|
||||||
"""
|
"""
|
||||||
tag = self._render()
|
tag = self._render()
|
||||||
return str(
|
return dict(
|
||||||
tag.add_props(
|
tag.add_props(
|
||||||
**self.event_triggers,
|
**self.event_triggers,
|
||||||
key=self.key,
|
key=self.key,
|
||||||
@ -408,10 +408,10 @@ class Component(Base, ABC):
|
|||||||
id=self.id,
|
id=self.id,
|
||||||
class_name=self.class_name,
|
class_name=self.class_name,
|
||||||
).set(
|
).set(
|
||||||
contents=path_ops.join(
|
children=[child.render() for child in self.children],
|
||||||
[str(tag.contents)] + [child.render() for child in self.children]
|
contents=str(tag.contents),
|
||||||
).strip(),
|
props=tag.format_props(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
def _get_custom_code(self) -> Optional[str]:
|
def _get_custom_code(self) -> Optional[str]:
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""Create a list of components from an iterable."""
|
"""Create a list of components from an iterable."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any, Optional
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
from pynecone.components.component import Component
|
from pynecone.components.component import Component
|
||||||
from pynecone.components.layout.fragment import Fragment
|
from pynecone.components.layout.fragment import Fragment
|
||||||
@ -24,7 +24,7 @@ class Cond(Component):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(
|
def create(
|
||||||
cls, cond: Var, comp1: Component, comp2: Optional[Component] = None
|
cls, cond: Var, comp1: Component, comp2: Optional[Component]
|
||||||
) -> Component:
|
) -> Component:
|
||||||
"""Create a conditional component.
|
"""Create a conditional component.
|
||||||
|
|
||||||
@ -37,8 +37,10 @@ class Cond(Component):
|
|||||||
The conditional component.
|
The conditional component.
|
||||||
"""
|
"""
|
||||||
# Wrap everything in fragments.
|
# Wrap everything in fragments.
|
||||||
comp1 = Fragment.create(comp1)
|
if comp1.__class__.__name__ != "Fragment":
|
||||||
comp2 = Fragment.create(comp2) if comp2 else Fragment.create()
|
comp1 = Fragment.create(comp1)
|
||||||
|
if comp2 is None or comp2.__class__.__name__ != "Fragment":
|
||||||
|
comp2 = Fragment.create(comp2) if comp2 else Fragment.create()
|
||||||
return Fragment.create(
|
return Fragment.create(
|
||||||
cls(
|
cls(
|
||||||
cond=cond,
|
cond=cond,
|
||||||
@ -55,6 +57,26 @@ class Cond(Component):
|
|||||||
false_value=self.comp2.render(),
|
false_value=self.comp2.render(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def render(self) -> Dict:
|
||||||
|
"""Render the component.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The dictionary for template of component.
|
||||||
|
"""
|
||||||
|
tag = self._render()
|
||||||
|
return dict(
|
||||||
|
tag.add_props(
|
||||||
|
**self.event_triggers,
|
||||||
|
key=self.key,
|
||||||
|
sx=self.style,
|
||||||
|
id=self.id,
|
||||||
|
class_name=self.class_name,
|
||||||
|
).set(
|
||||||
|
props=tag.format_props(),
|
||||||
|
),
|
||||||
|
cond_state=f"isTrue({self.cond.full_name})",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def cond(condition: Any, c1: Any, c2: Any = None):
|
def cond(condition: Any, c1: Any, c2: Any = None):
|
||||||
"""Create a conditional component or Prop.
|
"""Create a conditional component or Prop.
|
||||||
|
@ -4,8 +4,8 @@ from __future__ import annotations
|
|||||||
from typing import Any, Callable, List
|
from typing import Any, Callable, List
|
||||||
|
|
||||||
from pynecone.components.component import Component
|
from pynecone.components.component import Component
|
||||||
from pynecone.components.tags import IterTag, Tag
|
from pynecone.components.tags import IterTag
|
||||||
from pynecone.var import BaseVar, Var
|
from pynecone.var import BaseVar, Var, get_unique_variable_name
|
||||||
|
|
||||||
|
|
||||||
class Foreach(Component):
|
class Foreach(Component):
|
||||||
@ -49,5 +49,38 @@ class Foreach(Component):
|
|||||||
**props,
|
**props,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _render(self) -> Tag:
|
def _render(self) -> IterTag:
|
||||||
return IterTag(iterable=self.iterable, render_fn=self.render_fn)
|
return IterTag(iterable=self.iterable, render_fn=self.render_fn)
|
||||||
|
|
||||||
|
def render(self):
|
||||||
|
"""Render the component.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The dictionary for template of component.
|
||||||
|
"""
|
||||||
|
tag = self._render()
|
||||||
|
try:
|
||||||
|
type_ = self.iterable.type_.__args__[0]
|
||||||
|
except Exception:
|
||||||
|
type_ = Any
|
||||||
|
arg = BaseVar(
|
||||||
|
name=get_unique_variable_name(),
|
||||||
|
type_=type_,
|
||||||
|
)
|
||||||
|
index_arg = tag.get_index_var_arg()
|
||||||
|
component = tag.render_component(self.render_fn, arg)
|
||||||
|
return dict(
|
||||||
|
tag.add_props(
|
||||||
|
**self.event_triggers,
|
||||||
|
key=self.key,
|
||||||
|
sx=self.style,
|
||||||
|
id=self.id,
|
||||||
|
class_name=self.class_name,
|
||||||
|
).set(
|
||||||
|
children=[component.render()],
|
||||||
|
props=tag.format_props(),
|
||||||
|
),
|
||||||
|
iterable_state=tag.iterable.full_name,
|
||||||
|
arg_name=arg.name,
|
||||||
|
arg_index=index_arg,
|
||||||
|
)
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
"""Tag to conditionally render components."""
|
"""Tag to conditionally render components."""
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
from pynecone.components.tags.tag import Tag
|
from pynecone.components.tags.tag import Tag
|
||||||
from pynecone.utils import format
|
|
||||||
from pynecone.var import Var
|
from pynecone.var import Var
|
||||||
|
|
||||||
|
|
||||||
@ -14,20 +13,7 @@ class CondTag(Tag):
|
|||||||
cond: Var[Any]
|
cond: Var[Any]
|
||||||
|
|
||||||
# The code to render if the condition is true.
|
# The code to render if the condition is true.
|
||||||
true_value: str
|
true_value: Dict
|
||||||
|
|
||||||
# The code to render if the condition is false.
|
# The code to render if the condition is false.
|
||||||
false_value: str
|
false_value: Optional[Dict]
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
"""Render the tag as a React string.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The React code to render the tag.
|
|
||||||
"""
|
|
||||||
assert self.cond is not None, "The condition must be set."
|
|
||||||
return format.format_cond(
|
|
||||||
cond=self.cond.full_name,
|
|
||||||
true_value=self.true_value,
|
|
||||||
false_value=self.false_value,
|
|
||||||
)
|
|
||||||
|
@ -2,11 +2,10 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import inspect
|
import inspect
|
||||||
from typing import TYPE_CHECKING, Any, Callable, List
|
from typing import TYPE_CHECKING, Callable, List
|
||||||
|
|
||||||
from pynecone.components.tags.tag import Tag
|
from pynecone.components.tags.tag import Tag
|
||||||
from pynecone.utils import format
|
from pynecone.var import Var
|
||||||
from pynecone.var import BaseVar, Var, get_unique_variable_name
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from pynecone.components.component import Component
|
from pynecone.components.component import Component
|
||||||
@ -83,24 +82,3 @@ class IterTag(Tag):
|
|||||||
component.key = index
|
component.key = index
|
||||||
|
|
||||||
return component
|
return component
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
"""Render the tag as a React string.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The React code to render the tag.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
type_ = self.iterable.type_.__args__[0]
|
|
||||||
except Exception:
|
|
||||||
type_ = Any
|
|
||||||
arg = BaseVar(
|
|
||||||
name=get_unique_variable_name(),
|
|
||||||
type_=type_,
|
|
||||||
)
|
|
||||||
index_arg = self.get_index_var_arg()
|
|
||||||
component = self.render_component(self.render_fn, arg)
|
|
||||||
return format.wrap(
|
|
||||||
f"{self.iterable.full_name}.map(({arg.name}, {index_arg}) => {component})",
|
|
||||||
"{",
|
|
||||||
)
|
|
||||||
|
@ -3,9 +3,8 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
|
||||||
import re
|
import re
|
||||||
from typing import TYPE_CHECKING, Any, Dict, Optional, Set, Tuple, Union
|
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple, Union
|
||||||
|
|
||||||
from plotly.graph_objects import Figure
|
from plotly.graph_objects import Figure
|
||||||
from plotly.io import to_json
|
from plotly.io import to_json
|
||||||
@ -37,6 +36,9 @@ class Tag(Base):
|
|||||||
# Special props that aren't key value pairs.
|
# Special props that aren't key value pairs.
|
||||||
special_props: Set[Var] = set()
|
special_props: Set[Var] = set()
|
||||||
|
|
||||||
|
# The children components.
|
||||||
|
children: List[Any] = []
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
"""Initialize the tag.
|
"""Initialize the tag.
|
||||||
|
|
||||||
@ -117,55 +119,22 @@ class Tag(Base):
|
|||||||
assert isinstance(prop, str), "The prop must be a string."
|
assert isinstance(prop, str), "The prop must be a string."
|
||||||
return format.wrap(prop, "{", check_first=False)
|
return format.wrap(prop, "{", check_first=False)
|
||||||
|
|
||||||
def format_props(self) -> str:
|
def format_props(self) -> List:
|
||||||
"""Format the tag's props.
|
"""Format the tag's props.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The formatted props.
|
The formatted props list.
|
||||||
"""
|
"""
|
||||||
# If there are no props, return an empty string.
|
# If there are no props, return an empty string.
|
||||||
if len(self.props) == 0:
|
if len(self.props) == 0:
|
||||||
return ""
|
return []
|
||||||
|
|
||||||
# Format all the props.
|
# Format all the props.
|
||||||
return os.linesep.join(
|
return [
|
||||||
f"{name}={self.format_prop(prop)}"
|
f"{name}={self.format_prop(prop)}"
|
||||||
for name, prop in sorted(self.props.items())
|
for name, prop in sorted(self.props.items())
|
||||||
if prop is not None
|
if prop is not None
|
||||||
)
|
] + [str(prop) for prop in self.special_props]
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
"""Render the tag as a React string.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The React code to render the tag.
|
|
||||||
"""
|
|
||||||
# Get the tag props.
|
|
||||||
props_str = self.format_props()
|
|
||||||
|
|
||||||
# Add the special props.
|
|
||||||
props_str += " ".join([str(prop) for prop in self.special_props])
|
|
||||||
|
|
||||||
# Add a space if there are props.
|
|
||||||
if len(props_str) > 0:
|
|
||||||
props_str = " " + props_str
|
|
||||||
|
|
||||||
if len(self.contents) == 0:
|
|
||||||
# If there is no inner content, we don't need a closing tag.
|
|
||||||
tag_str = format.wrap(f"{self.name}{props_str}/", "<")
|
|
||||||
else:
|
|
||||||
if self.args is not None:
|
|
||||||
# If there are args, wrap the tag in a function call.
|
|
||||||
args_str = ", ".join(self.args)
|
|
||||||
contents = f"{{({{{args_str}}}) => ({self.contents})}}"
|
|
||||||
else:
|
|
||||||
contents = self.contents
|
|
||||||
# Otherwise wrap it in opening and closing tags.
|
|
||||||
open = format.wrap(f"{self.name}{props_str}", "<")
|
|
||||||
close = format.wrap(f"/{self.name}", "<")
|
|
||||||
tag_str = format.wrap(contents, open, close)
|
|
||||||
|
|
||||||
return tag_str
|
|
||||||
|
|
||||||
def add_props(self, **kwargs: Optional[Any]) -> Tag:
|
def add_props(self, **kwargs: Optional[Any]) -> Tag:
|
||||||
"""Add props to the tag.
|
"""Add props to the tag.
|
||||||
|
@ -33,6 +33,8 @@ TEMPLATE_DIR = os.path.join(ROOT_DIR, MODULE_NAME, ".templates")
|
|||||||
WEB_TEMPLATE_DIR = os.path.join(TEMPLATE_DIR, "web")
|
WEB_TEMPLATE_DIR = os.path.join(TEMPLATE_DIR, "web")
|
||||||
# The assets subdirectory of the template directory.
|
# The assets subdirectory of the template directory.
|
||||||
ASSETS_TEMPLATE_DIR = os.path.join(TEMPLATE_DIR, APP_ASSETS_DIR)
|
ASSETS_TEMPLATE_DIR = os.path.join(TEMPLATE_DIR, APP_ASSETS_DIR)
|
||||||
|
# The jinja template directory.
|
||||||
|
JINJA_TEMPLATE_DIR = os.path.join(ROOT_DIR, MODULE_NAME, "templates")
|
||||||
|
|
||||||
# The frontend directories in a project.
|
# The frontend directories in a project.
|
||||||
# The web folder where the NextJS app is compiled to.
|
# The web folder where the NextJS app is compiled to.
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
"""Base class definition for raw HTML elements."""
|
"""Base class definition for raw HTML elements."""
|
||||||
|
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
from pynecone.components.component import Component
|
from pynecone.components.component import Component
|
||||||
from pynecone.utils import path_ops
|
|
||||||
|
|
||||||
|
|
||||||
class Element(Component):
|
class Element(Component):
|
||||||
@ -12,14 +13,14 @@ class Element(Component):
|
|||||||
prop.
|
prop.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def render(self) -> str:
|
def render(self) -> Dict:
|
||||||
"""Render the element.
|
"""Render the element.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The code to render the element.
|
The code to render the element.
|
||||||
"""
|
"""
|
||||||
tag = self._render()
|
tag = self._render()
|
||||||
return str(
|
return dict(
|
||||||
tag.add_props(
|
tag.add_props(
|
||||||
**self.event_triggers,
|
**self.event_triggers,
|
||||||
key=self.key,
|
key=self.key,
|
||||||
@ -27,9 +28,8 @@ class Element(Component):
|
|||||||
style=self.style,
|
style=self.style,
|
||||||
class_name=self.class_name,
|
class_name=self.class_name,
|
||||||
).set(
|
).set(
|
||||||
contents=path_ops.join(
|
contents=str(tag.contents),
|
||||||
[str(tag.contents)] + [child.render() for child in self.children]
|
children=[child.render() for child in self.children],
|
||||||
).strip(),
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
10
pynecone/templates/app/pcconfig.py.jinja2
Normal file
10
pynecone/templates/app/pcconfig.py.jinja2
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import pynecone as pc
|
||||||
|
|
||||||
|
class {{ config_name }}(pc.Config):
|
||||||
|
pass
|
||||||
|
|
||||||
|
config = {{ config_name }}(
|
||||||
|
app_name="{{ app_name }}",
|
||||||
|
db_url="{{ db_url }}",
|
||||||
|
env=pc.Env.DEV,
|
||||||
|
)
|
9
pynecone/templates/web/pages/_document.js.jinja2
Normal file
9
pynecone/templates/web/pages/_document.js.jinja2
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{% extends "web/pages/base_page.js.jinja2" %}
|
||||||
|
|
||||||
|
{% block export %}
|
||||||
|
export default function Document() {
|
||||||
|
return (
|
||||||
|
{{utils.render(document, indent_width=4)}}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{% endblock %}
|
13
pynecone/templates/web/pages/base_page.js.jinja2
Normal file
13
pynecone/templates/web/pages/base_page.js.jinja2
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{% import 'web/pages/utils.js.jinja2' as utils %}
|
||||||
|
|
||||||
|
{%- block imports_libs %}
|
||||||
|
{% for module in imports%}
|
||||||
|
{{- utils.get_import(module) }}
|
||||||
|
{% endfor %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block declaration %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block export %}
|
||||||
|
{% endblock %}
|
10
pynecone/templates/web/pages/custom_component.js.jinja2
Normal file
10
pynecone/templates/web/pages/custom_component.js.jinja2
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{% extends "web/pages/base_page.js.jinja2" %}
|
||||||
|
|
||||||
|
{% block export %}
|
||||||
|
{% for component in components %}
|
||||||
|
|
||||||
|
export const {{component.name}} = memo(({ {{-component.props|join(", ")-}} }) => (
|
||||||
|
{{utils.render(component.render)}}
|
||||||
|
))
|
||||||
|
{% endfor %}
|
||||||
|
{% endblock %}
|
66
pynecone/templates/web/pages/index.js.jinja2
Normal file
66
pynecone/templates/web/pages/index.js.jinja2
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
{% extends "web/pages/base_page.js.jinja2" %}
|
||||||
|
|
||||||
|
{% block declaration %}
|
||||||
|
{% for custom_code in custom_codes %}
|
||||||
|
{{custom_code}}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% for name, url in endpoints.items() %}
|
||||||
|
const {{name}} = {{url|json_dumps}}
|
||||||
|
{% endfor %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block export %}
|
||||||
|
export default function Component() {
|
||||||
|
const [{{state_name}}, {{state_name|react_setter}}] = useState({{initial_state|json_dumps}})
|
||||||
|
const [{{const.result}}, {{const.result|react_setter}}] = useState({{const.initial_result|json_dumps}})
|
||||||
|
const {{const.router}} = useRouter()
|
||||||
|
const {{const.socket}} = useRef(null)
|
||||||
|
const { isReady } = {{const.router}}
|
||||||
|
const { {{const.color_mode}}, {{const.toggle_color_mode}} } = {{const.use_color_mode}}()
|
||||||
|
|
||||||
|
const Event = events => {{state_name|react_setter}}({
|
||||||
|
...{{state_name}},
|
||||||
|
events: [...{{state_name}}.events, ...events],
|
||||||
|
})
|
||||||
|
|
||||||
|
const File = files => {{state_name|react_setter}}({
|
||||||
|
...{{state_name}},
|
||||||
|
files,
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(()=>{
|
||||||
|
if(!isReady) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!{{const.socket}}.current) {
|
||||||
|
connect({{const.socket}}, {{state_name}}, {{state_name|react_setter}}, {{const.result}}, {{const.result|react_setter}}, {{const.router}}, {{const.event_endpoint}}, {{transports}})
|
||||||
|
}
|
||||||
|
const update = async () => {
|
||||||
|
if ({{const.result}}.{{const.state}} != null){
|
||||||
|
{{state_name|react_setter}}({
|
||||||
|
...{{const.result}}.{{const.state}},
|
||||||
|
events: [...{{state_name}}.{{const.events}}, ...{{const.result}}.{{const.events}}],
|
||||||
|
})
|
||||||
|
|
||||||
|
{{const.result|react_setter}}({
|
||||||
|
{{const.state}}: null,
|
||||||
|
{{const.events}}: [],
|
||||||
|
{{const.processing}}: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
await updateState({{state_name}}, {{state_name|react_setter}}, {{const.result}}, {{const.result|react_setter}}, {{const.router}}, {{const.socket}}.current)
|
||||||
|
}
|
||||||
|
update()
|
||||||
|
})
|
||||||
|
|
||||||
|
{% for hook in hooks %}
|
||||||
|
{{ hook }}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
return (
|
||||||
|
{{utils.render(render, indent_width=4)}}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{% endblock %}
|
110
pynecone/templates/web/pages/utils.js.jinja2
Normal file
110
pynecone/templates/web/pages/utils.js.jinja2
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
{# Renderting components recursively. #}
|
||||||
|
{# Args: #}
|
||||||
|
{# component: component dictionary #}
|
||||||
|
{# indent_width: indent width #}
|
||||||
|
{% macro render(component, indent_width=2) %}
|
||||||
|
{% filter indent(width=indent_width) %}
|
||||||
|
{%- if component is not mapping %}
|
||||||
|
{{- component }}
|
||||||
|
{%- elif component.iterable %}
|
||||||
|
{{- render_iterable_tag(component) }}
|
||||||
|
{%- elif component.cond %}
|
||||||
|
{{- render_condition_tag(component) }}
|
||||||
|
{%- elif component.children|length %}
|
||||||
|
{{- render_tag(component) }}
|
||||||
|
{%- else %}
|
||||||
|
{{- render_self_close_tag(component) }}
|
||||||
|
{%- endif %}
|
||||||
|
{% endfilter %}
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{# Renderting self close tag. #}
|
||||||
|
{# Args: #}
|
||||||
|
{# component: component dictionary #}
|
||||||
|
{% macro render_self_close_tag(component) %}
|
||||||
|
{%- if component.name|length %}
|
||||||
|
<{{ component.name }} {{- render_props(component.props) }}/>
|
||||||
|
{%- else %}
|
||||||
|
{{- component.contents }}
|
||||||
|
{%- endif %}
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{# Renderting close tag with args and props. #}
|
||||||
|
{# Args: #}
|
||||||
|
{# component: component dictionary #}
|
||||||
|
{% macro render_tag(component) %}
|
||||||
|
<{{component.name}} {{- render_props(component.props) }}>
|
||||||
|
{%- if component.args is not none -%}
|
||||||
|
{{- render_arg_content(component) }}
|
||||||
|
{%- else -%}
|
||||||
|
{{ component.contents }}
|
||||||
|
{% for child in component.children %}
|
||||||
|
{{ render(child) }}
|
||||||
|
{% endfor %}
|
||||||
|
{%- endif -%}
|
||||||
|
</{{component.name}}>
|
||||||
|
{%- endmacro %}
|
||||||
|
|
||||||
|
|
||||||
|
{# Renderting condition component. #}
|
||||||
|
{# Args: #}
|
||||||
|
{# component: component dictionary #}
|
||||||
|
{% macro render_condition_tag(component) %}
|
||||||
|
{ {{- component.cond_state }} ? (
|
||||||
|
{{ render(component.true_value) }}
|
||||||
|
) : (
|
||||||
|
{{ render(component.false_value) }}
|
||||||
|
)}
|
||||||
|
{%- endmacro %}
|
||||||
|
|
||||||
|
|
||||||
|
{# Renderting iterable component. #}
|
||||||
|
{# Args: #}
|
||||||
|
{# component: component dictionary #}
|
||||||
|
{% macro render_iterable_tag(component) %}
|
||||||
|
{ {{- component.iterable_state }}.map(({{ component.arg_name }}, {{ component.arg_index }}) => (
|
||||||
|
{% for child in component.children %}
|
||||||
|
{{ render(child) }}
|
||||||
|
{% endfor %}
|
||||||
|
))}
|
||||||
|
{%- endmacro %}
|
||||||
|
|
||||||
|
|
||||||
|
{# Renderting props of a component. #}
|
||||||
|
{# Args: #}
|
||||||
|
{# component: component dictionary #}
|
||||||
|
{% macro render_props(props) %}
|
||||||
|
{% if props|length %} {{ props|join(" ") }}{% endif %}
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
|
||||||
|
{# Renderting content with args. #}
|
||||||
|
{# Args: #}
|
||||||
|
{# component: component dictionary #}
|
||||||
|
{% macro render_arg_content(component) %}
|
||||||
|
{% filter indent(width=2) %}
|
||||||
|
{# no string below for a line break #}
|
||||||
|
|
||||||
|
{({ {{component.args|join(", ")}} }) => (
|
||||||
|
{% for child in component.children %}
|
||||||
|
{{ render(child) }}
|
||||||
|
{% endfor %}
|
||||||
|
)}
|
||||||
|
{% endfilter %}
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
|
||||||
|
{# Get react libraries import . #}
|
||||||
|
{# Args: #}
|
||||||
|
{# module: react module dictionary #}
|
||||||
|
{% macro get_import(module)%}
|
||||||
|
{%- if module.default|length and module.rest|length -%}
|
||||||
|
import {{module.default}}, { {{module.rest|sort|join(", ")}} } from "{{module.lib}}"
|
||||||
|
{%- elif module.default|length -%}
|
||||||
|
import {{module.default}} from "{{module.lib}}"
|
||||||
|
{%- elif module.rest|length -%}
|
||||||
|
import { {{module.rest|sort|join(", ")}} } from "{{module.lib}}"
|
||||||
|
{%- else -%}
|
||||||
|
import "{{module.lib}}"
|
||||||
|
{%- endif -%}
|
||||||
|
{% endmacro %}
|
1
pynecone/templates/web/utils/theme.js.jinja2
Normal file
1
pynecone/templates/web/utils/theme.js.jinja2
Normal file
@ -0,0 +1 @@
|
|||||||
|
export default {{ theme|json_dumps }}
|
@ -306,11 +306,9 @@ def format_upload_event(event_spec: EventSpec) -> str:
|
|||||||
Returns:
|
Returns:
|
||||||
The compiled event.
|
The compiled event.
|
||||||
"""
|
"""
|
||||||
from pynecone.compiler import templates
|
|
||||||
|
|
||||||
state, name = get_event_handler_parts(event_spec.handler)
|
state, name = get_event_handler_parts(event_spec.handler)
|
||||||
parent_state = state.split(".")[0]
|
parent_state = state.split(".")[0]
|
||||||
return f'uploadFiles({parent_state}, {templates.RESULT}, {templates.SET_RESULT}, {parent_state}.files, "{state}.{name}",UPLOAD)'
|
return f'uploadFiles({parent_state}, {constants.RESULT}, set{constants.RESULT.capitalize()}, {parent_state}.files, "{state}.{name}",UPLOAD)'
|
||||||
|
|
||||||
|
|
||||||
def format_full_control_event(event_chain: EventChain) -> str:
|
def format_full_control_event(event_chain: EventChain) -> str:
|
||||||
|
@ -151,7 +151,7 @@ def create_config(app_name: str):
|
|||||||
|
|
||||||
config_name = f"{re.sub(r'[^a-zA-Z]', '', app_name).capitalize()}Config"
|
config_name = f"{re.sub(r'[^a-zA-Z]', '', app_name).capitalize()}Config"
|
||||||
with open(constants.CONFIG_FILE, "w") as f:
|
with open(constants.CONFIG_FILE, "w") as f:
|
||||||
f.write(templates.PCCONFIG.format(app_name=app_name, config_name=config_name))
|
f.write(templates.PCCONFIG.render(app_name=app_name, config_name=config_name))
|
||||||
|
|
||||||
|
|
||||||
def create_web_directory(root: Path) -> str:
|
def create_web_directory(root: Path) -> str:
|
||||||
|
@ -40,6 +40,7 @@ websockets = "^10.4"
|
|||||||
cloudpickle = "^2.2.1"
|
cloudpickle = "^2.2.1"
|
||||||
python-multipart = "^0.0.5"
|
python-multipart = "^0.0.5"
|
||||||
watchdog = "^2.3.1"
|
watchdog = "^2.3.1"
|
||||||
|
jinja2 = "^3.1.2"
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
pytest = "^7.1.2"
|
pytest = "^7.1.2"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from typing import Set
|
from typing import List, Set
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@ -8,51 +8,55 @@ from pynecone.var import ImportVar
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"lib,fields,output",
|
"fields,test_default,test_rest",
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
"axios",
|
|
||||||
{ImportVar(tag="axios", is_default=True)},
|
{ImportVar(tag="axios", is_default=True)},
|
||||||
'import axios from "axios"',
|
"axios",
|
||||||
|
set(),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"axios",
|
|
||||||
{ImportVar(tag="foo"), ImportVar(tag="bar")},
|
{ImportVar(tag="foo"), ImportVar(tag="bar")},
|
||||||
'import {bar, foo} from "axios"',
|
"",
|
||||||
|
{"foo", "bar"},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"axios",
|
|
||||||
{
|
{
|
||||||
ImportVar(tag="axios", is_default=True),
|
ImportVar(tag="axios", is_default=True),
|
||||||
ImportVar(tag="foo"),
|
ImportVar(tag="foo"),
|
||||||
ImportVar(tag="bar"),
|
ImportVar(tag="bar"),
|
||||||
},
|
},
|
||||||
"import " "axios, " "{bar, " "foo} from " '"axios"',
|
"axios",
|
||||||
|
{"foo", "bar"},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_compile_import_statement(lib: str, fields: Set[ImportVar], output: str):
|
def test_compile_import_statement(
|
||||||
|
fields: Set[ImportVar], test_default: str, test_rest: str
|
||||||
|
):
|
||||||
"""Test the compile_import_statement function.
|
"""Test the compile_import_statement function.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
lib: The library name.
|
|
||||||
fields: The fields to import.
|
fields: The fields to import.
|
||||||
output: The expected output.
|
test_default: The expected output of default library.
|
||||||
|
test_rest: The expected output rest libraries.
|
||||||
"""
|
"""
|
||||||
assert utils.compile_import_statement(lib, fields) == output
|
default, rest = utils.compile_import_statement(fields)
|
||||||
|
assert default == test_default
|
||||||
|
assert rest == test_rest
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"import_dict,output",
|
"import_dict,test_dicts",
|
||||||
[
|
[
|
||||||
({}, ""),
|
({}, []),
|
||||||
(
|
(
|
||||||
{"axios": {ImportVar(tag="axios", is_default=True)}},
|
{"axios": {ImportVar(tag="axios", is_default=True)}},
|
||||||
'import axios from "axios"',
|
[{"lib": "axios", "default": "axios", "rest": set()}],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
{"axios": {ImportVar(tag="foo"), ImportVar(tag="bar")}},
|
{"axios": {ImportVar(tag="foo"), ImportVar(tag="bar")}},
|
||||||
'import {bar, foo} from "axios"',
|
[{"lib": "axios", "default": "", "rest": {"foo", "bar"}}],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
@ -63,52 +67,61 @@ def test_compile_import_statement(lib: str, fields: Set[ImportVar], output: str)
|
|||||||
},
|
},
|
||||||
"react": {ImportVar(tag="react", is_default=True)},
|
"react": {ImportVar(tag="react", is_default=True)},
|
||||||
},
|
},
|
||||||
'import axios, {bar, foo} from "axios"\nimport react from "react"',
|
[
|
||||||
|
{"lib": "axios", "default": "axios", "rest": {"foo", "bar"}},
|
||||||
|
{"lib": "react", "default": "react", "rest": set()},
|
||||||
|
],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
{"": {ImportVar(tag="lib1.js"), ImportVar(tag="lib2.js")}},
|
{"": {ImportVar(tag="lib1.js"), ImportVar(tag="lib2.js")}},
|
||||||
'import "lib1.js"\nimport "lib2.js"',
|
[
|
||||||
|
{"lib": "lib1.js", "default": "", "rest": set()},
|
||||||
|
{"lib": "lib2.js", "default": "", "rest": set()},
|
||||||
|
],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
"": {ImportVar(tag="lib1.js"), ImportVar(tag="lib2.js")},
|
"": {ImportVar(tag="lib1.js"), ImportVar(tag="lib2.js")},
|
||||||
"axios": {ImportVar(tag="axios", is_default=True)},
|
"axios": {ImportVar(tag="axios", is_default=True)},
|
||||||
},
|
},
|
||||||
'import "lib1.js"\nimport "lib2.js"\nimport axios from "axios"',
|
[
|
||||||
|
{"lib": "lib1.js", "default": "", "rest": set()},
|
||||||
|
{"lib": "lib2.js", "default": "", "rest": set()},
|
||||||
|
{"lib": "axios", "default": "axios", "rest": set()},
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_compile_imports(
|
def test_compile_imports(import_dict: imports.ImportDict, test_dicts: List[dict]):
|
||||||
import_dict: imports.ImportDict, output: str, windows_platform: bool
|
|
||||||
):
|
|
||||||
"""Test the compile_imports function.
|
"""Test the compile_imports function.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
import_dict: The import dictionary.
|
import_dict: The import dictionary.
|
||||||
output: The expected output.
|
test_dicts: The expected output.
|
||||||
windows_platform: whether system is windows.
|
|
||||||
"""
|
"""
|
||||||
assert utils.compile_imports(import_dict) == (
|
imports = utils.compile_imports(import_dict)
|
||||||
output.replace("\n", "\r\n") if windows_platform else output
|
for import_dict, test_dict in zip(imports, test_dicts):
|
||||||
)
|
assert import_dict["lib"] == test_dict["lib"]
|
||||||
|
assert import_dict["default"] == test_dict["default"]
|
||||||
|
assert import_dict["rest"] == test_dict["rest"]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
# @pytest.mark.parametrize(
|
||||||
"name,value,output",
|
# "name,value,output",
|
||||||
[
|
# [
|
||||||
("foo", "bar", 'const foo = "bar"'),
|
# ("foo", "bar", 'const foo = "bar"'),
|
||||||
("num", 1, "const num = 1"),
|
# ("num", 1, "const num = 1"),
|
||||||
("check", False, "const check = false"),
|
# ("check", False, "const check = false"),
|
||||||
("arr", [1, 2, 3], "const arr = [1, 2, 3]"),
|
# ("arr", [1, 2, 3], "const arr = [1, 2, 3]"),
|
||||||
("obj", {"foo": "bar"}, 'const obj = {"foo": "bar"}'),
|
# ("obj", {"foo": "bar"}, 'const obj = {"foo": "bar"}'),
|
||||||
],
|
# ],
|
||||||
)
|
# )
|
||||||
def test_compile_constant_declaration(name: str, value: str, output: str):
|
# def test_compile_constant_declaration(name: str, value: str, output: str):
|
||||||
"""Test the compile_constant_declaration function.
|
# """Test the compile_constant_declaration function.
|
||||||
|
|
||||||
Args:
|
# Args:
|
||||||
name: The name of the constant.
|
# name: The name of the constant.
|
||||||
value: The value of the constant.
|
# value: The value of the constant.
|
||||||
output: The expected output.
|
# output: The expected output.
|
||||||
"""
|
# """
|
||||||
assert utils.compile_constant_declaration(name, value) == output
|
# assert utils.compile_constant_declaration(name, value) == output
|
||||||
|
@ -19,5 +19,5 @@ def test_fstrings(contents, expected):
|
|||||||
contents: The contents of the component.
|
contents: The contents of the component.
|
||||||
expected: The expected output.
|
expected: The expected output.
|
||||||
"""
|
"""
|
||||||
comp = Bare.create(contents)
|
comp = Bare.create(contents).render()
|
||||||
assert str(comp) == expected
|
assert comp["contents"] == expected
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import os
|
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@ -37,11 +35,12 @@ def test_validate_data_table(data_table_state: pc.Var, expected):
|
|||||||
props["columns"] = data_table_state.columns
|
props["columns"] = data_table_state.columns
|
||||||
data_table_component = data_table(**props)
|
data_table_component = data_table(**props)
|
||||||
|
|
||||||
assert (
|
data_table_dict = data_table_component.render()
|
||||||
str(data_table_component)
|
|
||||||
== f"<DataTableGrid columns={{{expected}.columns}}{os.linesep}data={{"
|
assert data_table_dict["props"] == [
|
||||||
f"{expected}.data}}/>"
|
f"columns={{{expected}.columns}}",
|
||||||
)
|
f"data={{{expected}.data}}",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import os
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import pynecone as pc
|
import pynecone as pc
|
||||||
@ -49,14 +47,36 @@ def test_upload_component_render(upload_component):
|
|||||||
Args:
|
Args:
|
||||||
upload_component: component fixture
|
upload_component: component fixture
|
||||||
"""
|
"""
|
||||||
|
uplaod = upload_component.render()
|
||||||
|
|
||||||
|
# upload
|
||||||
|
assert uplaod["name"] == "ReactDropzone"
|
||||||
|
assert uplaod["props"] == [
|
||||||
|
"multiple={true}",
|
||||||
|
"onDrop={e => File(e)}",
|
||||||
|
]
|
||||||
|
assert uplaod["args"] == ("getRootProps", "getInputProps")
|
||||||
|
|
||||||
|
# box inside of upload
|
||||||
|
[box] = uplaod["children"]
|
||||||
|
assert box["name"] == "Box"
|
||||||
|
assert box["props"] == [
|
||||||
|
'sx={{"border": "1px dotted black"}}',
|
||||||
|
"{...getRootProps()}",
|
||||||
|
]
|
||||||
|
|
||||||
|
# input, button and text inside of box
|
||||||
|
[input, button, text] = box["children"]
|
||||||
|
assert input["name"] == "Input"
|
||||||
|
assert input["props"] == ['type="file"', "{...getInputProps()}"]
|
||||||
|
|
||||||
|
assert button["name"] == "Button"
|
||||||
|
assert button["children"][0]["contents"] == "{`select file`}"
|
||||||
|
|
||||||
|
assert text["name"] == "Text"
|
||||||
assert (
|
assert (
|
||||||
str(upload_component) == f"<ReactDropzone multiple={{true}}{os.linesep}"
|
text["children"][0]["contents"]
|
||||||
"onDrop={e => File(e)}>{({getRootProps, getInputProps}) => (<Box "
|
== "{`Drag and drop files here or click to select files`}"
|
||||||
'sx={{"border": "1px dotted black"}}{...getRootProps()}><Input '
|
|
||||||
f'type="file"{{...getInputProps()}}/>{os.linesep}'
|
|
||||||
f"<Button>{{`select file`}}</Button>{os.linesep}"
|
|
||||||
"<Text>{`Drag and drop files here or click to select "
|
|
||||||
"files`}</Text></Box>)}</ReactDropzone>"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -66,14 +86,11 @@ def test_upload_component_with_props_render(upload_component_with_props):
|
|||||||
Args:
|
Args:
|
||||||
upload_component_with_props: component fixture
|
upload_component_with_props: component fixture
|
||||||
"""
|
"""
|
||||||
assert (
|
uplaod = upload_component_with_props.render()
|
||||||
str(upload_component_with_props) == f"<ReactDropzone maxFiles={{2}}{os.linesep}"
|
|
||||||
f"multiple={{true}}{os.linesep}"
|
assert uplaod["props"] == [
|
||||||
f"noDrag={{true}}{os.linesep}"
|
"maxFiles={2}",
|
||||||
"onDrop={e => File(e)}>{({getRootProps, getInputProps}) => (<Box "
|
"multiple={true}",
|
||||||
'sx={{"border": "1px dotted black"}}{...getRootProps()}><Input '
|
"noDrag={true}",
|
||||||
f'type="file"{{...getInputProps()}}/>{os.linesep}'
|
"onDrop={e => File(e)}",
|
||||||
f"<Button>{{`select file`}}</Button>{os.linesep}"
|
]
|
||||||
"<Text>{`Drag and drop files here or click to select "
|
|
||||||
"files`}</Text></Box>)}</ReactDropzone>"
|
|
||||||
)
|
|
||||||
|
@ -38,12 +38,27 @@ def test_validate_cond(cond_state: pc.Var):
|
|||||||
Text.create("cond is True"),
|
Text.create("cond is True"),
|
||||||
Text.create("cond is False"),
|
Text.create("cond is False"),
|
||||||
)
|
)
|
||||||
|
cond_dict = cond_component.render() if type(cond_component) == Fragment else {}
|
||||||
|
assert cond_dict["name"] == "Fragment"
|
||||||
|
|
||||||
assert str(cond_component) == (
|
[condition] = cond_dict["children"]
|
||||||
"<Fragment>{isTrue(cond_state.value) ? "
|
assert condition["cond_state"] == "isTrue(cond_state.value)"
|
||||||
"<Fragment><Text>{`cond is True`}</Text></Fragment> : "
|
|
||||||
"<Fragment><Text>{`cond is False`}</Text></Fragment>}</Fragment>"
|
# true value
|
||||||
)
|
true_value = condition["true_value"]
|
||||||
|
assert true_value["name"] == "Fragment"
|
||||||
|
|
||||||
|
[true_value_text] = true_value["children"]
|
||||||
|
assert true_value_text["name"] == "Text"
|
||||||
|
assert true_value_text["children"][0]["contents"] == "{`cond is True`}"
|
||||||
|
|
||||||
|
# false value
|
||||||
|
false_value = condition["false_value"]
|
||||||
|
assert false_value["name"] == "Fragment"
|
||||||
|
|
||||||
|
[false_value_text] = false_value["children"]
|
||||||
|
assert false_value_text["name"] == "Text"
|
||||||
|
assert false_value_text["children"][0]["contents"] == "{`cond is False`}"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from typing import Any, Dict
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@ -71,25 +71,24 @@ def test_format_prop(prop: Var, formatted: str):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"props,formatted",
|
"props,test_props",
|
||||||
[
|
[
|
||||||
({}, ""),
|
({}, []),
|
||||||
({"key": 1}, "key={1}"),
|
({"key": 1}, ["key={1}"]),
|
||||||
({"key": "value"}, 'key="value"'),
|
({"key": "value"}, ['key="value"']),
|
||||||
({"key": True, "key2": "value2"}, 'key={true}\nkey2="value2"'),
|
({"key": True, "key2": "value2"}, ["key={true}", 'key2="value2"']),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_format_props(props: Dict[str, Var], formatted: str, windows_platform: bool):
|
def test_format_props(props: Dict[str, Var], test_props: List):
|
||||||
"""Test that the formatted props are correct.
|
"""Test that the formatted props are correct.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
props: The props to test.
|
props: The props to test.
|
||||||
formatted: The expected formatted props.
|
test_props: The expected props.
|
||||||
windows_platform: Whether the system is windows.
|
|
||||||
"""
|
"""
|
||||||
assert Tag(props=props).format_props() == (
|
tag_props = Tag(props=props).format_props()
|
||||||
formatted.replace("\n", "\r\n") if windows_platform else formatted
|
for i, tag_prop in enumerate(tag_props):
|
||||||
)
|
assert tag_prop == test_props[i]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
@ -126,13 +125,20 @@ def test_add_props():
|
|||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"tag,expected",
|
"tag,expected",
|
||||||
[
|
[
|
||||||
(Tag(), "</>"),
|
(Tag(), {"name": "", "contents": "", "props": {}}),
|
||||||
(Tag(name="br"), "<br/>"),
|
(Tag(name="br"), {"name": "br", "contents": "", "props": {}}),
|
||||||
(Tag(contents="hello"), "<>hello</>"),
|
(Tag(contents="hello"), {"name": "", "contents": "hello", "props": {}}),
|
||||||
(Tag(name="h1", contents="hello"), "<h1>hello</h1>"),
|
(
|
||||||
|
Tag(name="h1", contents="hello"),
|
||||||
|
{"name": "h1", "contents": "hello", "props": {}},
|
||||||
|
),
|
||||||
(
|
(
|
||||||
Tag(name="box", props={"color": "red", "textAlign": "center"}),
|
Tag(name="box", props={"color": "red", "textAlign": "center"}),
|
||||||
'<box color="red"\ntextAlign="center"/>',
|
{
|
||||||
|
"name": "box",
|
||||||
|
"contents": "",
|
||||||
|
"props": {"color": "red", "textAlign": "center"},
|
||||||
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
Tag(
|
Tag(
|
||||||
@ -140,30 +146,44 @@ def test_add_props():
|
|||||||
props={"color": "red", "textAlign": "center"},
|
props={"color": "red", "textAlign": "center"},
|
||||||
contents="text",
|
contents="text",
|
||||||
),
|
),
|
||||||
'<box color="red"\ntextAlign="center">text</box>',
|
{
|
||||||
|
"name": "box",
|
||||||
|
"contents": "text",
|
||||||
|
"props": {"color": "red", "textAlign": "center"},
|
||||||
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_format_tag(tag: Tag, expected: str, windows_platform: bool):
|
def test_format_tag(tag: Tag, expected: Dict):
|
||||||
"""Test that the formatted tag is correct.
|
"""Test that the tag dict is correct.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
tag: The tag to test.
|
tag: The tag to test.
|
||||||
expected: The expected formatted tag.
|
expected: The expected tag dictionary.
|
||||||
windows_platform: Whether the system is windows.
|
|
||||||
"""
|
"""
|
||||||
expected = expected.replace("\n", "\r\n") if windows_platform else expected
|
tag_dict = dict(tag)
|
||||||
assert str(tag) == expected
|
assert tag_dict["name"] == expected["name"]
|
||||||
|
assert tag_dict["contents"] == expected["contents"]
|
||||||
|
assert tag_dict["props"] == expected["props"]
|
||||||
|
|
||||||
|
|
||||||
def test_format_cond_tag():
|
def test_format_cond_tag():
|
||||||
"""Test that the formatted cond tag is correct."""
|
"""Test that the cond tag dict is correct."""
|
||||||
tag = CondTag(
|
tag = CondTag(
|
||||||
true_value=str(Tag(name="h1", contents="True content")),
|
true_value=dict(Tag(name="h1", contents="True content")),
|
||||||
false_value=str(Tag(name="h2", contents="False content")),
|
false_value=dict(Tag(name="h2", contents="False content")),
|
||||||
cond=BaseVar(name="logged_in", type_=bool),
|
cond=BaseVar(name="logged_in", type_=bool),
|
||||||
)
|
)
|
||||||
assert (
|
tag_dict = dict(tag)
|
||||||
str(tag)
|
cond, true_value, false_value = (
|
||||||
== "{isTrue(logged_in) ? <h1>True content</h1> : <h2>False content</h2>}"
|
tag_dict["cond"],
|
||||||
|
tag_dict["true_value"],
|
||||||
|
tag_dict["false_value"],
|
||||||
)
|
)
|
||||||
|
assert cond == "logged_in"
|
||||||
|
|
||||||
|
assert true_value["name"] == "h1"
|
||||||
|
assert true_value["contents"] == "True content"
|
||||||
|
|
||||||
|
assert false_value["name"] == "h2"
|
||||||
|
assert false_value["contents"] == "False content"
|
||||||
|
@ -335,7 +335,7 @@ def test_create_config(app_name, expected_config_name, mocker):
|
|||||||
mocker.patch("builtins.open")
|
mocker.patch("builtins.open")
|
||||||
tmpl_mock = mocker.patch("pynecone.compiler.templates.PCCONFIG")
|
tmpl_mock = mocker.patch("pynecone.compiler.templates.PCCONFIG")
|
||||||
prerequisites.create_config(app_name)
|
prerequisites.create_config(app_name)
|
||||||
tmpl_mock.format.assert_called_with(
|
tmpl_mock.render.assert_called_with(
|
||||||
app_name=app_name, config_name=expected_config_name
|
app_name=app_name, config_name=expected_config_name
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user