Skip to content

Commit

Permalink
Merge pull request #6 from reflex-dev/main
Browse files Browse the repository at this point in the history
Update to 0.2.6.
  • Loading branch information
nevdelap authored Aug 25, 2023
2 parents 8ca4887 + 76b8af3 commit a6e890a
Show file tree
Hide file tree
Showing 13 changed files with 269 additions and 172 deletions.
28 changes: 28 additions & 0 deletions docs/DEBUGGING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Debugging

It is possible to run Reflex apps in dev mode under a debugger.

1. Run Reflex as a module: `python -m reflex run --env dev`
2. Set current working directory to the dir containing `rxconfig.py`

## VSCode

The following launch configuration can be used to interactively debug a Reflex
app with breakpoints.

```json
{
"version": "0.2.0",
"configurations": [
{
"name": "Reflex App",
"type": "python",
"request": "launch",
"module": "reflex",
"args": "run --env dev",
"justMyCode": true,
"cwd": "${fileDirname}/.."
}
]
}
```
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "reflex"
version = "0.2.5"
version = "0.2.6"
description = "Web apps in pure Python."
license = "Apache-2.0"
authors = [
Expand Down
50 changes: 36 additions & 14 deletions reflex/components/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ class Component(Base, ABC):

# components that cannot be children
invalid_children: List[str] = []

# components that are only allowed as children
valid_children: List[str] = []

# custom attribute
custom_attrs: Dict[str, str] = {}

Expand Down Expand Up @@ -103,9 +107,10 @@ def __init__(self, *args, **kwargs):
TypeError: If an invalid prop is passed.
"""
# Set the id and children initially.
children = kwargs.get("children", [])
initial_kwargs = {
"id": kwargs.get("id"),
"children": kwargs.get("children", []),
"children": children,
**{
prop: Var.create(kwargs[prop])
for prop in self.get_initial_props()
Expand All @@ -114,6 +119,8 @@ def __init__(self, *args, **kwargs):
}
super().__init__(**initial_kwargs)

self._validate_component_children(children)

# Get the component fields, triggers, and props.
fields = self.get_fields()
triggers = self.get_triggers()
Expand Down Expand Up @@ -381,6 +388,7 @@ def create(cls, *children, **props) -> Component:
else Bare.create(contents=Var.create(child, is_string=True))
for child in children
]

return cls(children=children, **props)

def _add_style(self, style):
Expand Down Expand Up @@ -435,30 +443,44 @@ def render(self) -> Dict:
),
autofocus=self.autofocus,
)
self._validate_component_children(
rendered_dict["name"], rendered_dict["children"]
)
return rendered_dict

def _validate_component_children(self, comp_name: str, children: List[Dict]):
def _validate_component_children(self, children: List[Component]):
"""Validate the children components.
Args:
comp_name: name of the component.
children: list of children components.
children: The children of the component.
Raises:
ValueError: when an unsupported component is matched.
"""
if not self.invalid_children:
if not self.invalid_children and not self.valid_children:
return
for child in children:
name = child["name"]
if name in self.invalid_children:

comp_name = type(self).__name__

def validate_invalid_child(child_name):
if child_name in self.invalid_children:
raise ValueError(
f"The component `{comp_name.lower()}` cannot have `{name.lower()}` as a child component"
f"The component `{comp_name}` cannot have `{child_name}` as a child component"
)

def validate_valid_child(child_name):
if child_name not in self.valid_children:
valid_child_list = ", ".join(
[f"`{v_child}`" for v_child in self.valid_children]
)
raise ValueError(
f"The component `{comp_name}` only allows the components: {valid_child_list} as children. Got `{child_name}` instead."
)

for child in children:
name = type(child).__name__

if self.invalid_children:
validate_invalid_child(name)

if self.valid_children:
validate_valid_child(name)

def _get_custom_code(self) -> Optional[str]:
"""Get custom code for the component.
Expand Down
4 changes: 4 additions & 0 deletions reflex/components/forms/button.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""A button component."""
from typing import List

from reflex.components.libs.chakra import ChakraComponent
from reflex.vars import Var
Expand Down Expand Up @@ -42,6 +43,9 @@ class Button(ChakraComponent):
# The type of button.
type_: Var[str]

# Components that are not allowed as children.
invalid_children: List[str] = ["Button", "MenuButton"]


class ButtonGroup(ChakraComponent):
"""A group of buttons."""
Expand Down
25 changes: 0 additions & 25 deletions reflex/components/forms/editable.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

from typing import Dict

from reflex.components.component import Component
from reflex.components.forms.debounce import DebounceInput
from reflex.components.libs.chakra import ChakraComponent
from reflex.event import EVENT_ARG
from reflex.vars import Var
Expand Down Expand Up @@ -38,29 +36,6 @@ class Editable(ChakraComponent):
# The initial value of the Editable in both edit and preview mode.
default_value: Var[str]

@classmethod
def create(cls, *children, **props) -> Component:
"""Create an Editable component.
Args:
children: The children of the component.
props: The properties of the component.
Returns:
The component.
"""
if (
isinstance(props.get("value"), Var) and props.get("on_change")
) or "debounce_timeout" in props:
# Create a debounced input if the user requests full control to avoid typing jank
# Currently default to 50ms, which appears to be a good balance
debounce_timeout = props.pop("debounce_timeout", 50)
return DebounceInput.create(
super().create(*children, **props),
debounce_timeout=debounce_timeout,
)
return super().create(*children, **props)

def get_controlled_triggers(self) -> Dict[str, Var]:
"""Get the event triggers that pass the component's value to the handler.
Expand Down
5 changes: 4 additions & 1 deletion reflex/components/overlay/menu.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Menu components."""

from typing import Set
from typing import List, Set

from reflex.components.component import Component
from reflex.components.libs.chakra import ChakraComponent
Expand Down Expand Up @@ -100,6 +100,9 @@ class MenuButton(ChakraComponent):
# The variant of the menu button.
variant: Var[str]

# Components that are not allowed as children.
invalid_children: List[str] = ["Button", "MenuButton"]

# The tag to use for the menu button.
as_: Var[str]

Expand Down
21 changes: 6 additions & 15 deletions reflex/reflex.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Reflex CLI to create, run, and deploy apps."""

import atexit
import os
import signal
from pathlib import Path

import httpx
Expand Down Expand Up @@ -164,16 +164,18 @@ def run(
telemetry.send(f"run-{env.value}", config.telemetry_enabled)

# Display custom message when there is a keyboard interrupt.
signal.signal(signal.SIGINT, processes.catch_keyboard_interrupt)
atexit.register(processes.atexit_handler)

# Run the frontend and backend together.
commands = []
if frontend:
setup_frontend(Path.cwd())
commands.append((frontend_cmd, Path.cwd(), frontend_port))
if backend:
if backend and env == constants.Env.PROD:
commands.append((backend_cmd, app.__name__, backend_host, backend_port))
processes.run_concurrently(*commands)
with processes.run_concurrently_context(*commands):
if env == constants.Env.DEV:
backend_cmd(app.__name__, backend_host, int(backend_port))


@cli.command()
Expand Down Expand Up @@ -262,17 +264,6 @@ def export(
# Post a telemetry event.
telemetry.send("export", config.telemetry_enabled)

if zipping:
console.log(
"""Backend & Frontend compiled. See [green bold]backend.zip[/green bold]
and [green bold]frontend.zip[/green bold]."""
)
else:
console.log(
"""Backend & Frontend compiled. See [green bold]app[/green bold]
and [green bold].web/_static[/green bold] directories."""
)


db_cli = typer.Typer()

Expand Down
115 changes: 79 additions & 36 deletions reflex/utils/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@
import os
import random
import subprocess
import zipfile
from enum import Enum
from pathlib import Path
from typing import Optional, Union

from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn

from reflex import constants
from reflex.config import get_config
from reflex.utils import console, path_ops, prerequisites, processes
Expand Down Expand Up @@ -85,6 +89,65 @@ def generate_sitemap_config(deploy_url: str):
f.write(templates.SITEMAP_CONFIG(config=config))


class _ComponentName(Enum):
BACKEND = "Backend"
FRONTEND = "Frontend"


def _zip(
component_name: _ComponentName,
target: str,
root_dir: str,
dirs_to_exclude: set[str] | None = None,
files_to_exclude: set[str] | None = None,
) -> None:
"""Zip utility function.
Args:
component_name: The name of the component: backend or frontend.
target: The target zip file.
root_dir: The root directory to zip.
dirs_to_exclude: The directories to exclude.
files_to_exclude: The files to exclude.
"""
dirs_to_exclude = dirs_to_exclude or set()
files_to_exclude = files_to_exclude or set()
files_to_zip: list[str] = []
# Traverse the root directory in a top-down manner. In this traversal order,
# we can modify the dirs list in-place to remove directories we don't want to include.
for root, dirs, files in os.walk(root_dir, topdown=True):
# Modify the dirs in-place so excluded and hidden directories are skipped in next traversal.
dirs[:] = [
d
for d in dirs
if (basename := os.path.basename(os.path.normpath(d)))
not in dirs_to_exclude
and not basename.startswith(".")
]
# Modify the files in-place so the hidden files are excluded.
files[:] = [f for f in files if not f.startswith(".")]
files_to_zip += [
os.path.join(root, file) for file in files if file not in files_to_exclude
]

# Create a progress bar for zipping the component.
progress = Progress(
*Progress.get_default_columns()[:-1],
MofNCompleteColumn(),
TimeElapsedColumn(),
)
task = progress.add_task(
f"Zipping {component_name.value}:", total=len(files_to_zip)
)

with progress, zipfile.ZipFile(target, "w", zipfile.ZIP_DEFLATED) as zipf:
for file in files_to_zip:
console.debug(f"{target}: {file}")
progress.advance(task)
zipf.write(file, os.path.relpath(file, root_dir))


def export(
backend: bool = True,
frontend: bool = True,
Expand Down Expand Up @@ -132,42 +195,22 @@ def export(

# Zip up the app.
if zip:
if os.name == "posix":
posix_export(backend, frontend)
if os.name == "nt":
nt_export(backend, frontend)


def nt_export(backend: bool = True, frontend: bool = True):
"""Export for nt (Windows) systems.
Args:
backend: Whether to zip up the backend app.
frontend: Whether to zip up the frontend app.
"""
cmd = r""
if frontend:
cmd = r'''powershell -Command "Set-Location .web/_static; Compress-Archive -Path .\* -DestinationPath ..\..\frontend.zip -Force"'''
os.system(cmd)
if backend:
cmd = r'''powershell -Command "Get-ChildItem -File | Where-Object { $_.Name -notin @('.web', 'assets', 'frontend.zip', 'backend.zip') } | Compress-Archive -DestinationPath backend.zip -Update"'''
os.system(cmd)


def posix_export(backend: bool = True, frontend: bool = True):
"""Export for posix (Linux, OSX) systems.
Args:
backend: Whether to zip up the backend app.
frontend: Whether to zip up the frontend app.
"""
cmd = r""
if frontend:
cmd = r"cd .web/_static && zip -r ../../frontend.zip ./*"
os.system(cmd)
if backend:
cmd = r"zip -r backend.zip ./* -x .web/\* ./assets\* ./frontend.zip\* ./backend.zip\*"
os.system(cmd)
files_to_exclude = {constants.FRONTEND_ZIP, constants.BACKEND_ZIP}
if frontend:
_zip(
component_name=_ComponentName.FRONTEND,
target=constants.FRONTEND_ZIP,
root_dir=".web/_static",
files_to_exclude=files_to_exclude,
)
if backend:
_zip(
component_name=_ComponentName.BACKEND,
target=constants.BACKEND_ZIP,
root_dir=".",
dirs_to_exclude={"assets", "__pycache__"},
files_to_exclude=files_to_exclude,
)


def setup_frontend(
Expand Down
Loading

0 comments on commit a6e890a

Please sign in to comment.