Revamp Imports (#926)
This commit is contained in:
parent
4f182b3170
commit
18c715670a
@ -11,20 +11,25 @@ from pynecone.components.component import Component, CustomComponent
|
||||
from pynecone.state import State
|
||||
from pynecone.style import Style
|
||||
from pynecone.utils import imports, path_ops
|
||||
from pynecone.var import ImportVar
|
||||
|
||||
# Imports to be included in every Pynecone app.
|
||||
DEFAULT_IMPORTS: imports.ImportDict = {
|
||||
"react": {"useEffect", "useRef", "useState"},
|
||||
"next/router": {"useRouter"},
|
||||
f"/{constants.STATE_PATH}": {
|
||||
"connect",
|
||||
"updateState",
|
||||
"uploadFiles",
|
||||
"E",
|
||||
"isTrue",
|
||||
"react": {
|
||||
ImportVar(tag="useEffect"),
|
||||
ImportVar(tag="useRef"),
|
||||
ImportVar(tag="useState"),
|
||||
},
|
||||
"": {"focus-visible/dist/focus-visible"},
|
||||
"@chakra-ui/react": {constants.USE_COLOR_MODE},
|
||||
"next/router": {ImportVar(tag="useRouter")},
|
||||
f"/{constants.STATE_PATH}": {
|
||||
ImportVar(tag="connect"),
|
||||
ImportVar(tag="updateState"),
|
||||
ImportVar(tag="uploadFiles"),
|
||||
ImportVar(tag="E"),
|
||||
ImportVar(tag="isTrue"),
|
||||
},
|
||||
"": {ImportVar(tag="focus-visible/dist/focus-visible")},
|
||||
"@chakra-ui/react": {ImportVar(tag=constants.USE_COLOR_MODE)},
|
||||
}
|
||||
|
||||
|
||||
@ -91,8 +96,8 @@ def _compile_components(components: Set[CustomComponent]) -> str:
|
||||
The compiled components.
|
||||
"""
|
||||
imports = {
|
||||
"react": {"memo"},
|
||||
f"/{constants.STATE_PATH}": {"E", "isTrue"},
|
||||
"react": {ImportVar(tag="memo")},
|
||||
f"/{constants.STATE_PATH}": {ImportVar(tag="E"), ImportVar(tag="isTrue")},
|
||||
}
|
||||
component_defs = []
|
||||
|
||||
|
@ -25,12 +25,13 @@ from pynecone.event import get_hydrate_event
|
||||
from pynecone.state import State
|
||||
from pynecone.style import Style
|
||||
from pynecone.utils import format, imports, path_ops
|
||||
from pynecone.var import ImportVar
|
||||
|
||||
# To re-export this function.
|
||||
merge_imports = imports.merge_imports
|
||||
|
||||
|
||||
def compile_import_statement(lib: str, fields: Set[str]) -> str:
|
||||
def compile_import_statement(lib: str, fields: Set[ImportVar]) -> str:
|
||||
"""Compile an import statement.
|
||||
|
||||
Args:
|
||||
@ -41,16 +42,12 @@ def compile_import_statement(lib: str, fields: Set[str]) -> str:
|
||||
The compiled import statement.
|
||||
"""
|
||||
# Check for default imports.
|
||||
defaults = {
|
||||
field
|
||||
for field in fields
|
||||
if field.lower() == lib.lower().replace("-", "").replace("/", "")
|
||||
}
|
||||
defaults = {field for field in fields if field.is_default}
|
||||
assert len(defaults) < 2
|
||||
|
||||
# Get the default import, and the specific imports.
|
||||
default = next(iter(defaults), "")
|
||||
rest = fields - defaults
|
||||
default = next(iter({field.name for field in defaults}), "")
|
||||
rest = {field.name for field in fields - defaults}
|
||||
return templates.format_import(lib=lib, default=default, rest=rest)
|
||||
|
||||
|
||||
|
@ -13,3 +13,5 @@ class Head(NextHeadLib):
|
||||
"""Head Component."""
|
||||
|
||||
tag = "NextHead"
|
||||
|
||||
is_default = True
|
||||
|
@ -22,7 +22,7 @@ from pynecone.event import (
|
||||
)
|
||||
from pynecone.style import Style
|
||||
from pynecone.utils import format, imports, path_ops, types
|
||||
from pynecone.var import BaseVar, Var
|
||||
from pynecone.var import BaseVar, ImportVar, Var
|
||||
|
||||
|
||||
class Component(Base, ABC):
|
||||
@ -43,6 +43,12 @@ class Component(Base, ABC):
|
||||
# The tag to use when rendering the component.
|
||||
tag: Optional[str] = None
|
||||
|
||||
# The alias for the tag.
|
||||
alias: Optional[str] = None
|
||||
|
||||
# Whether the import is default or named.
|
||||
is_default: Optional[bool] = False
|
||||
|
||||
# A unique key for the component.
|
||||
key: Any = None
|
||||
|
||||
@ -275,15 +281,6 @@ class Component(Base, ABC):
|
||||
"""
|
||||
return {}
|
||||
|
||||
@classmethod
|
||||
def get_alias(cls) -> Optional[str]:
|
||||
"""Get the alias for the component.
|
||||
|
||||
Returns:
|
||||
The alias.
|
||||
"""
|
||||
return None
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""Represent the component in React.
|
||||
|
||||
@ -307,9 +304,10 @@ class Component(Base, ABC):
|
||||
The tag to render.
|
||||
"""
|
||||
# Create the base tag.
|
||||
alias = self.get_alias()
|
||||
name = alias if alias is not None else self.tag
|
||||
tag = Tag(name=name, special_props=self.special_props)
|
||||
tag = Tag(
|
||||
name=self.tag if not self.alias else self.alias,
|
||||
special_props=self.special_props,
|
||||
)
|
||||
|
||||
# Add component props to the tag.
|
||||
props = {attr: getattr(self, attr) for attr in self.get_props()}
|
||||
@ -445,9 +443,7 @@ class Component(Base, ABC):
|
||||
|
||||
def _get_imports(self) -> imports.ImportDict:
|
||||
if self.library is not None and self.tag is not None:
|
||||
alias = self.get_alias()
|
||||
tag = self.tag if alias is None else " as ".join([self.tag, alias])
|
||||
return {self.library: {tag}}
|
||||
return {self.library: {self.import_var}}
|
||||
return {}
|
||||
|
||||
def get_imports(self) -> imports.ImportDict:
|
||||
@ -521,6 +517,15 @@ class Component(Base, ABC):
|
||||
custom_components |= child.get_custom_components(seen=seen)
|
||||
return custom_components
|
||||
|
||||
@property
|
||||
def import_var(self):
|
||||
"""The tag to import.
|
||||
|
||||
Returns:
|
||||
An import var.
|
||||
"""
|
||||
return ImportVar(tag=self.tag, is_default=self.is_default, alias=self.alias)
|
||||
|
||||
def is_full_control(self, kwargs: dict) -> bool:
|
||||
"""Return if the component is fully controlled input.
|
||||
|
||||
|
@ -6,7 +6,7 @@ from pynecone.components.component import Component
|
||||
from pynecone.components.libs.chakra import ChakraComponent
|
||||
from pynecone.style import Style
|
||||
from pynecone.utils import imports
|
||||
from pynecone.var import Var
|
||||
from pynecone.var import ImportVar, Var
|
||||
|
||||
# Path to the prism styles.
|
||||
PRISM_STYLES_PATH = "/styles/code/prism"
|
||||
@ -19,6 +19,8 @@ class CodeBlock(Component):
|
||||
|
||||
tag = "Prism"
|
||||
|
||||
is_default = True
|
||||
|
||||
# The theme to use ("light" or "dark").
|
||||
theme: Var[str]
|
||||
|
||||
@ -44,7 +46,7 @@ class CodeBlock(Component):
|
||||
merged_imports = super()._get_imports()
|
||||
if self.theme is not None:
|
||||
merged_imports = imports.merge_imports(
|
||||
merged_imports, {PRISM_STYLES_PATH: {self.theme.name}}
|
||||
merged_imports, {PRISM_STYLES_PATH: {ImportVar(tag=self.theme.name)}}
|
||||
)
|
||||
return merged_imports
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
"""Table components."""
|
||||
|
||||
from typing import Any, List, Optional
|
||||
from typing import Any, List
|
||||
|
||||
from pynecone.components.component import Component
|
||||
from pynecone.components.tags import Tag
|
||||
from pynecone.utils import format, imports, types
|
||||
from pynecone.var import BaseVar, ComputedVar, Var
|
||||
from pynecone.var import BaseVar, ComputedVar, ImportVar, Var
|
||||
|
||||
|
||||
class Gridjs(Component):
|
||||
@ -19,6 +19,8 @@ class DataTable(Gridjs):
|
||||
|
||||
tag = "Grid"
|
||||
|
||||
alias = "DataTableGrid"
|
||||
|
||||
# The data to display. Either a list of lists or a pandas dataframe.
|
||||
data: Any
|
||||
|
||||
@ -38,15 +40,6 @@ class DataTable(Gridjs):
|
||||
# Enable pagination.
|
||||
pagination: Var[bool]
|
||||
|
||||
@classmethod
|
||||
def get_alias(cls) -> Optional[str]:
|
||||
"""Get the alias for the component.
|
||||
|
||||
Returns:
|
||||
The alias.
|
||||
"""
|
||||
return "DataTableGrid"
|
||||
|
||||
@classmethod
|
||||
def create(cls, *children, **props):
|
||||
"""Create a datatable component.
|
||||
@ -102,7 +95,8 @@ class DataTable(Gridjs):
|
||||
|
||||
def _get_imports(self) -> imports.ImportDict:
|
||||
return imports.merge_imports(
|
||||
super()._get_imports(), {"": {"gridjs/dist/theme/mermaid.css"}}
|
||||
super()._get_imports(),
|
||||
{"": {ImportVar(tag="gridjs/dist/theme/mermaid.css")}},
|
||||
)
|
||||
|
||||
def _render(self) -> Tag:
|
||||
|
@ -5,7 +5,7 @@ from typing import Dict
|
||||
from pynecone.components.component import EVENT_ARG
|
||||
from pynecone.components.libs.chakra import ChakraComponent
|
||||
from pynecone.utils import imports
|
||||
from pynecone.var import Var
|
||||
from pynecone.var import ImportVar, Var
|
||||
|
||||
|
||||
class Input(ChakraComponent):
|
||||
@ -13,7 +13,7 @@ class Input(ChakraComponent):
|
||||
|
||||
tag = "Input"
|
||||
|
||||
# State var to bind the the input.
|
||||
# State var to bind the input.
|
||||
value: Var[str]
|
||||
|
||||
# The default value of the input.
|
||||
@ -52,7 +52,7 @@ class Input(ChakraComponent):
|
||||
def _get_imports(self) -> imports.ImportDict:
|
||||
return imports.merge_imports(
|
||||
super()._get_imports(),
|
||||
{"/utils/state": {"set_val"}},
|
||||
{"/utils/state": {ImportVar(tag="set_val")}},
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
@ -13,7 +13,7 @@ class NumberInput(ChakraComponent):
|
||||
|
||||
tag = "NumberInput"
|
||||
|
||||
# State var to bind the the input.
|
||||
# State var to bind the input.
|
||||
value: Var[int]
|
||||
|
||||
# If true, the input's value will change based on mouse wheel.
|
||||
|
@ -15,7 +15,7 @@ class Select(ChakraComponent):
|
||||
|
||||
tag = "Select"
|
||||
|
||||
# State var to bind the the select.
|
||||
# State var to bind the select.
|
||||
value: Var[str]
|
||||
|
||||
# The default value of the select.
|
||||
|
@ -13,7 +13,7 @@ class Slider(ChakraComponent):
|
||||
|
||||
tag = "Slider"
|
||||
|
||||
# State var to bind the the input.
|
||||
# State var to bind the input.
|
||||
value: Var[int]
|
||||
|
||||
# The color scheme.
|
||||
|
@ -12,7 +12,7 @@ class TextArea(ChakraComponent):
|
||||
|
||||
tag = "Textarea"
|
||||
|
||||
# State var to bind the the input.
|
||||
# State var to bind the input.
|
||||
value: Var[str]
|
||||
|
||||
# The default value of the textarea.
|
||||
|
@ -18,6 +18,8 @@ class Upload(Component):
|
||||
|
||||
tag = "ReactDropzone"
|
||||
|
||||
is_default = True
|
||||
|
||||
# The list of accepted file types. This should be a dictionary of MIME types as keys and array of file formats as
|
||||
# values.
|
||||
# supported MIME types: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
|
||||
|
@ -11,6 +11,8 @@ class NextLink(Component):
|
||||
|
||||
tag = "NextLink"
|
||||
|
||||
is_default = True
|
||||
|
||||
# The page to link to.
|
||||
href: Var[str]
|
||||
|
||||
|
@ -5,7 +5,7 @@ from typing import List, Union
|
||||
|
||||
from pynecone.components.component import Component
|
||||
from pynecone.utils import types
|
||||
from pynecone.var import BaseVar, Var
|
||||
from pynecone.var import BaseVar, ImportVar, Var
|
||||
|
||||
|
||||
class Markdown(Component):
|
||||
@ -15,6 +15,8 @@ class Markdown(Component):
|
||||
|
||||
tag = "ReactMarkdown"
|
||||
|
||||
is_default = True
|
||||
|
||||
@classmethod
|
||||
def create(cls, *children, **props) -> Component:
|
||||
"""Create a markdown component.
|
||||
@ -39,20 +41,20 @@ class Markdown(Component):
|
||||
def _get_imports(self):
|
||||
imports = super()._get_imports()
|
||||
imports["@chakra-ui/react"] = {
|
||||
"Heading",
|
||||
"Code",
|
||||
"Text",
|
||||
"Link",
|
||||
"UnorderedList",
|
||||
"OrderedList",
|
||||
"ListItem",
|
||||
ImportVar(tag="Heading"),
|
||||
ImportVar(tag="Code"),
|
||||
ImportVar(tag="Text"),
|
||||
ImportVar(tag="Link"),
|
||||
ImportVar(tag="UnorderedList"),
|
||||
ImportVar(tag="OrderedList"),
|
||||
ImportVar(tag="ListItem"),
|
||||
}
|
||||
imports["react-syntax-highlighter"] = {"Prism"}
|
||||
imports["remark-math"] = {"remarkMath"}
|
||||
imports["remark-gfm"] = {"remarkGfm"}
|
||||
imports["rehype-katex"] = {"rehypeKatex"}
|
||||
imports["rehype-raw"] = {"rehypeRaw"}
|
||||
imports[""] = {"katex/dist/katex.min.css"}
|
||||
imports["react-syntax-highlighter"] = {ImportVar(tag="Prism", is_default=True)}
|
||||
imports["remark-math"] = {ImportVar(tag="remarkMath", is_default=True)}
|
||||
imports["remark-gfm"] = {ImportVar(tag="remarkGfm", is_default=True)}
|
||||
imports["rehype-katex"] = {ImportVar(tag="rehypeKatex", is_default=True)}
|
||||
imports["rehype-raw"] = {ImportVar(tag="rehypeRaw", is_default=True)}
|
||||
imports[""] = {ImportVar(tag="katex/dist/katex.min.css")}
|
||||
return imports
|
||||
|
||||
def _render(self):
|
||||
|
@ -6,7 +6,7 @@ from pynecone.var import Var as PCVar
|
||||
|
||||
|
||||
class A(Element): # noqa: E742
|
||||
"""Display the a element."""
|
||||
"""Display the 'a' element."""
|
||||
|
||||
tag = "a"
|
||||
|
||||
|
@ -3,7 +3,9 @@
|
||||
from collections import defaultdict
|
||||
from typing import Dict, Set
|
||||
|
||||
ImportDict = Dict[str, Set[str]]
|
||||
from pynecone.var import ImportVar
|
||||
|
||||
ImportDict = Dict[str, Set[ImportVar]]
|
||||
|
||||
|
||||
def merge_imports(*imports) -> ImportDict:
|
||||
|
@ -34,7 +34,6 @@ from pynecone.utils import format, types
|
||||
if TYPE_CHECKING:
|
||||
from pynecone.state import State
|
||||
|
||||
|
||||
# Set of unique variable names.
|
||||
USED_VARIABLES = set()
|
||||
|
||||
@ -1071,3 +1070,33 @@ class PCDict(dict):
|
||||
"""
|
||||
super().__delitem__(*args, **kwargs)
|
||||
self._reassign_field()
|
||||
|
||||
|
||||
class ImportVar(Base):
|
||||
"""An import var."""
|
||||
|
||||
# The name of the import tag.
|
||||
tag: Optional[str]
|
||||
|
||||
# whether the import is default or named.
|
||||
is_default: Optional[bool] = False
|
||||
|
||||
# The tag alias.
|
||||
alias: Optional[str] = None
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""The name of the import.
|
||||
|
||||
Returns:
|
||||
The name(tag name with alias) of tag.
|
||||
"""
|
||||
return self.tag if not self.alias else " as ".join([self.tag, self.alias]) # type: ignore
|
||||
|
||||
def __hash__(self) -> int:
|
||||
"""Define a hash function for the import var.
|
||||
|
||||
Returns:
|
||||
The hash of the var.
|
||||
"""
|
||||
return hash((self.tag, self.is_default, self.alias))
|
||||
|
@ -4,17 +4,34 @@ import pytest
|
||||
|
||||
from pynecone.compiler import utils
|
||||
from pynecone.utils import imports
|
||||
from pynecone.var import ImportVar
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"lib,fields,output",
|
||||
[
|
||||
("axios", {"axios"}, 'import axios from "axios"'),
|
||||
("axios", {"foo", "bar"}, 'import {bar, foo} from "axios"'),
|
||||
("axios", {"axios", "foo", "bar"}, 'import axios, {bar, foo} from "axios"'),
|
||||
(
|
||||
"axios",
|
||||
{ImportVar(tag="axios", is_default=True)},
|
||||
'import axios from "axios"',
|
||||
),
|
||||
(
|
||||
"axios",
|
||||
{ImportVar(tag="foo"), ImportVar(tag="bar")},
|
||||
'import {bar, foo} from "axios"',
|
||||
),
|
||||
(
|
||||
"axios",
|
||||
{
|
||||
ImportVar(tag="axios", is_default=True),
|
||||
ImportVar(tag="foo"),
|
||||
ImportVar(tag="bar"),
|
||||
},
|
||||
"import " "axios, " "{bar, " "foo} from " '"axios"',
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_compile_import_statement(lib: str, fields: Set[str], output: str):
|
||||
def test_compile_import_statement(lib: str, fields: Set[ImportVar], output: str):
|
||||
"""Test the compile_import_statement function.
|
||||
|
||||
Args:
|
||||
@ -29,15 +46,34 @@ def test_compile_import_statement(lib: str, fields: Set[str], output: str):
|
||||
"import_dict,output",
|
||||
[
|
||||
({}, ""),
|
||||
({"axios": {"axios"}}, 'import axios from "axios"'),
|
||||
({"axios": {"foo", "bar"}}, 'import {bar, foo} from "axios"'),
|
||||
(
|
||||
{"axios": {"axios", "foo", "bar"}, "react": {"react"}},
|
||||
{"axios": {ImportVar(tag="axios", is_default=True)}},
|
||||
'import axios from "axios"',
|
||||
),
|
||||
(
|
||||
{"axios": {ImportVar(tag="foo"), ImportVar(tag="bar")}},
|
||||
'import {bar, foo} from "axios"',
|
||||
),
|
||||
(
|
||||
{
|
||||
"axios": {
|
||||
ImportVar(tag="axios", is_default=True),
|
||||
ImportVar(tag="foo"),
|
||||
ImportVar(tag="bar"),
|
||||
},
|
||||
"react": {ImportVar(tag="react", is_default=True)},
|
||||
},
|
||||
'import axios, {bar, foo} from "axios"\nimport react from "react"',
|
||||
),
|
||||
({"": {"lib1.js", "lib2.js"}}, 'import "lib1.js"\nimport "lib2.js"'),
|
||||
(
|
||||
{"": {"lib1.js", "lib2.js"}, "axios": {"axios"}},
|
||||
{"": {ImportVar(tag="lib1.js"), ImportVar(tag="lib2.js")}},
|
||||
'import "lib1.js"\nimport "lib2.js"',
|
||||
),
|
||||
(
|
||||
{
|
||||
"": {ImportVar(tag="lib1.js"), ImportVar(tag="lib2.js")},
|
||||
"axios": {ImportVar(tag="axios", is_default=True)},
|
||||
},
|
||||
'import "lib1.js"\nimport "lib2.js"\nimport axios from "axios"',
|
||||
),
|
||||
],
|
||||
|
@ -9,7 +9,7 @@ from pynecone.event import EVENT_ARG, EVENT_TRIGGERS, EventHandler
|
||||
from pynecone.state import State
|
||||
from pynecone.style import Style
|
||||
from pynecone.utils import imports
|
||||
from pynecone.var import Var
|
||||
from pynecone.var import ImportVar, Var
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@ -42,7 +42,7 @@ def component1() -> Type[Component]:
|
||||
number: Var[int]
|
||||
|
||||
def _get_imports(self) -> imports.ImportDict:
|
||||
return {"react": {"Component"}}
|
||||
return {"react": {ImportVar(tag="Component")}}
|
||||
|
||||
def _get_custom_code(self) -> str:
|
||||
return "console.log('component1')"
|
||||
@ -75,7 +75,7 @@ def component2() -> Type[Component]:
|
||||
}
|
||||
|
||||
def _get_imports(self) -> imports.ImportDict:
|
||||
return {"react-redux": {"connect"}}
|
||||
return {"react-redux": {ImportVar(tag="connect")}}
|
||||
|
||||
def _get_custom_code(self) -> str:
|
||||
return "console.log('component2')"
|
||||
@ -206,8 +206,11 @@ def test_get_imports(component1, component2):
|
||||
"""
|
||||
c1 = component1.create()
|
||||
c2 = component2.create(c1)
|
||||
assert c1.get_imports() == {"react": {"Component"}}
|
||||
assert c2.get_imports() == {"react-redux": {"connect"}, "react": {"Component"}}
|
||||
assert c1.get_imports() == {"react": {ImportVar(tag="Component")}}
|
||||
assert c2.get_imports() == {
|
||||
"react-redux": {ImportVar(tag="connect")},
|
||||
"react": {ImportVar(tag="Component")},
|
||||
}
|
||||
|
||||
|
||||
def test_get_custom_code(component1, component2):
|
||||
|
@ -4,7 +4,7 @@ import cloudpickle
|
||||
import pytest
|
||||
|
||||
from pynecone.base import Base
|
||||
from pynecone.var import BaseVar, PCDict, PCList, Var
|
||||
from pynecone.var import BaseVar, ImportVar, PCDict, PCList, Var
|
||||
|
||||
test_vars = [
|
||||
BaseVar(name="prop1", type_=int),
|
||||
@ -14,6 +14,8 @@ test_vars = [
|
||||
BaseVar(name="local2", type_=str, is_local=True),
|
||||
]
|
||||
|
||||
test_import_vars = [ImportVar(tag="DataGrid"), ImportVar(tag="DataGrid", alias="Grid")]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def TestObj():
|
||||
@ -245,3 +247,23 @@ def test_pickleable_pc_dict():
|
||||
|
||||
pickled_dict = cloudpickle.dumps(pc_dict)
|
||||
assert cloudpickle.loads(pickled_dict) == pc_dict
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"import_var,expected",
|
||||
zip(
|
||||
test_import_vars,
|
||||
[
|
||||
"DataGrid",
|
||||
"DataGrid as Grid",
|
||||
],
|
||||
),
|
||||
)
|
||||
def test_import_var(import_var, expected):
|
||||
"""Test that the import var name is computed correctly.
|
||||
|
||||
Args:
|
||||
import_var: The import var.
|
||||
expected: expected name
|
||||
"""
|
||||
assert import_var.name == expected
|
||||
|
Loading…
Reference in New Issue
Block a user