Skip to content

Commit 27bb987

Browse files
authored
click over typer (#5154)
* click over typer * these are flags * i missed a few * fix a few more * zip * factor out loglevel debug * case insensitive for everyone * do things a bit smarter * fix pyright * fix app harness * use export directly * uv lock without trailing slash * last fixes * unpin weird stuff * no need to export typer * that guy too * restore uv lokc * uv lock * precommit silly * lock * why did i do that * factor things out
1 parent 71c1de6 commit 27bb987

File tree

12 files changed

+424
-405
lines changed

12 files changed

+424
-405
lines changed

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,10 @@ dependencies = [
3232
"python-socketio >=5.12.0,<6.0",
3333
"python-multipart >=0.0.20,<1.0",
3434
"redis >=5.2.1,<6.0",
35-
"reflex-hosting-cli >=0.1.38",
35+
"reflex-hosting-cli >=0.1.43",
3636
"rich >=13,<15",
3737
"sqlmodel >=0.0.24,<0.1",
38-
"typer >=0.15.2,<1.0",
38+
"click >=8",
3939
"typing_extensions >=4.13.0",
4040
"wrapt >=1.17.0,<2.0",
4141
]

reflex/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -903,7 +903,7 @@ def __init__(self, *args, **kwargs):
903903
# Set the log level for this process
904904
env_loglevel = os.environ.get("LOGLEVEL")
905905
if env_loglevel is not None:
906-
env_loglevel = LogLevel(env_loglevel)
906+
env_loglevel = LogLevel(env_loglevel.lower())
907907
if env_loglevel or self.loglevel != LogLevel.DEFAULT:
908908
console.set_log_level(env_loglevel or self.loglevel)
909909

reflex/constants/base.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from importlib import metadata
88
from pathlib import Path
99
from types import SimpleNamespace
10+
from typing import Literal
1011

1112
from platformdirs import PlatformDirs
1213

@@ -219,6 +220,9 @@ class ColorMode(SimpleNamespace):
219220
SET = "setColorMode"
220221

221222

223+
LITERAL_ENV = Literal["dev", "prod"]
224+
225+
222226
# Env modes
223227
class Env(str, Enum):
224228
"""The environment modes."""
@@ -238,6 +242,23 @@ class LogLevel(str, Enum):
238242
ERROR = "error"
239243
CRITICAL = "critical"
240244

245+
@classmethod
246+
def from_string(cls, level: str | None) -> LogLevel | None:
247+
"""Convert a string to a log level.
248+
249+
Args:
250+
level: The log level as a string.
251+
252+
Returns:
253+
The log level.
254+
"""
255+
if not level:
256+
return None
257+
try:
258+
return LogLevel[level.upper()]
259+
except KeyError:
260+
return None
261+
241262
def __le__(self, other: LogLevel) -> bool:
242263
"""Compare log levels.
243264

reflex/custom_components/custom_components.py

Lines changed: 67 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,46 @@
99
from collections import namedtuple
1010
from contextlib import contextmanager
1111
from pathlib import Path
12+
from typing import Any
1213

14+
import click
1315
import httpx
14-
import typer
1516

1617
from reflex import constants
17-
from reflex.config import get_config
1818
from reflex.constants import CustomComponents
1919
from reflex.utils import console
2020

21-
custom_components_cli = typer.Typer()
2221

22+
def set_loglevel(ctx: Any, self: Any, value: str | None):
23+
"""Set the log level.
24+
25+
Args:
26+
ctx: The click context.
27+
self: The click command.
28+
value: The log level to set.
29+
"""
30+
if value is not None:
31+
loglevel = constants.LogLevel.from_string(value)
32+
console.set_log_level(loglevel)
33+
34+
35+
@click.group
36+
def custom_components_cli():
37+
"""CLI for creating custom components."""
38+
pass
39+
40+
41+
loglevel_option = click.option(
42+
"--loglevel",
43+
type=click.Choice(
44+
[loglevel.value for loglevel in constants.LogLevel],
45+
case_sensitive=False,
46+
),
47+
callback=set_loglevel,
48+
is_eager=True,
49+
expose_value=False,
50+
help="The log level to use.",
51+
)
2352

2453
POST_CUSTOM_COMPONENTS_GALLERY_TIMEOUT = 15
2554

@@ -163,13 +192,13 @@ def _get_default_library_name_parts() -> list[str]:
163192
console.error(
164193
f"Based on current directory name {current_dir_name}, the library name is {constants.Reflex.MODULE_NAME}. This package already exists. Please use --library-name to specify a different name."
165194
)
166-
raise typer.Exit(code=1)
195+
raise click.exceptions.Exit(code=1)
167196
if not parts:
168197
# The folder likely has a name not suitable for python paths.
169198
console.error(
170199
f"Could not find a valid library name based on the current directory: got {current_dir_name}."
171200
)
172-
raise typer.Exit(code=1)
201+
raise click.exceptions.Exit(code=1)
173202
return parts
174203

175204

@@ -205,7 +234,7 @@ def _validate_library_name(library_name: str | None) -> NameVariants:
205234
console.error(
206235
f"Please use only alphanumeric characters or dashes: got {library_name}"
207236
)
208-
raise typer.Exit(code=1)
237+
raise click.exceptions.Exit(code=1)
209238

210239
# If not specified, use the current directory name to form the module name.
211240
name_parts = (
@@ -277,36 +306,35 @@ def _populate_custom_component_project(name_variants: NameVariants):
277306

278307

279308
@custom_components_cli.command(name="init")
309+
@click.option(
310+
"--library-name",
311+
default=None,
312+
help="The name of your library. On PyPI, package will be published as `reflex-{library-name}`.",
313+
)
314+
@click.option(
315+
"--install/--no-install",
316+
default=True,
317+
help="Whether to install package from this local custom component in editable mode.",
318+
)
319+
@loglevel_option
280320
def init(
281-
library_name: str | None = typer.Option(
282-
None,
283-
help="The name of your library. On PyPI, package will be published as `reflex-{library-name}`.",
284-
),
285-
install: bool = typer.Option(
286-
True,
287-
help="Whether to install package from this local custom component in editable mode.",
288-
),
289-
loglevel: constants.LogLevel | None = typer.Option(
290-
None, help="The log level to use."
291-
),
321+
library_name: str | None,
322+
install: bool,
292323
):
293324
"""Initialize a custom component.
294325
295326
Args:
296327
library_name: The name of the library.
297328
install: Whether to install package from this local custom component in editable mode.
298-
loglevel: The log level to use.
299329
300330
Raises:
301331
Exit: If the pyproject.toml already exists.
302332
"""
303333
from reflex.utils import exec, prerequisites
304334

305-
console.set_log_level(loglevel or get_config().loglevel)
306-
307335
if CustomComponents.PYPROJECT_TOML.exists():
308336
console.error(f"A {CustomComponents.PYPROJECT_TOML} already exists. Aborting.")
309-
typer.Exit(code=1)
337+
click.exceptions.Exit(code=1)
310338

311339
# Show system info.
312340
exec.output_system_info()
@@ -331,7 +359,7 @@ def init(
331359
if _pip_install_on_demand(package_name=".", install_args=["-e"]):
332360
console.info(f"Package {package_name} installed!")
333361
else:
334-
raise typer.Exit(code=1)
362+
raise click.exceptions.Exit(code=1)
335363

336364
console.print("[bold]Custom component initialized successfully!")
337365
console.rule("[bold]Project Summary")
@@ -424,21 +452,13 @@ def _run_build():
424452
if _run_commands_in_subprocess(cmds):
425453
console.info("Custom component built successfully!")
426454
else:
427-
raise typer.Exit(code=1)
455+
raise click.exceptions.Exit(code=1)
428456

429457

430458
@custom_components_cli.command(name="build")
431-
def build(
432-
loglevel: constants.LogLevel | None = typer.Option(
433-
None, help="The log level to use."
434-
),
435-
):
436-
"""Build a custom component. Must be run from the project root directory where the pyproject.toml is.
437-
438-
Args:
439-
loglevel: The log level to use.
440-
"""
441-
console.set_log_level(loglevel or get_config().loglevel)
459+
@loglevel_option
460+
def build():
461+
"""Build a custom component. Must be run from the project root directory where the pyproject.toml is."""
442462
_run_build()
443463

444464

@@ -453,7 +473,7 @@ def publish():
453473
"The publish command is deprecated. You can use `reflex component build` followed by `twine upload` or a similar publishing command to publish your custom component."
454474
"\nIf you want to share your custom component with the Reflex community, please use `reflex component share`."
455475
)
456-
raise typer.Exit(code=1)
476+
raise click.exceptions.Exit(code=1)
457477

458478

459479
def _collect_details_for_gallery():
@@ -472,7 +492,7 @@ def _collect_details_for_gallery():
472492
console.error(
473493
"Unable to authenticate with Reflex backend services. Make sure you are logged in."
474494
)
475-
raise typer.Exit(code=1)
495+
raise click.exceptions.Exit(code=1)
476496

477497
console.rule("[bold]Custom Component Information")
478498
params = {}
@@ -502,11 +522,11 @@ def _collect_details_for_gallery():
502522
console.error(
503523
f"{package_name} is owned by another user. Unable to update the information for it."
504524
)
505-
raise typer.Exit(code=1)
525+
raise click.exceptions.Exit(code=1)
506526
response.raise_for_status()
507527
except httpx.HTTPError as he:
508528
console.error(f"Unable to complete request due to {he}.")
509-
raise typer.Exit(code=1) from he
529+
raise click.exceptions.Exit(code=1) from he
510530

511531
files = []
512532
if (image_file_and_extension := _get_file_from_prompt_in_loop()) is not None:
@@ -541,7 +561,7 @@ def _collect_details_for_gallery():
541561

542562
except httpx.HTTPError as he:
543563
console.error(f"Unable to complete request due to {he}.")
544-
raise typer.Exit(code=1) from he
564+
raise click.exceptions.Exit(code=1) from he
545565

546566
console.info("Custom component information successfully shared!")
547567

@@ -577,7 +597,7 @@ def _get_file_from_prompt_in_loop() -> tuple[bytes, str] | None:
577597
image_file = image_file_path.read_bytes()
578598
except OSError as ose:
579599
console.error(f"Unable to read the {file_extension} file due to {ose}")
580-
raise typer.Exit(code=1) from ose
600+
raise click.exceptions.Exit(code=1) from ose
581601
else:
582602
return image_file, file_extension
583603

@@ -586,38 +606,21 @@ def _get_file_from_prompt_in_loop() -> tuple[bytes, str] | None:
586606

587607

588608
@custom_components_cli.command(name="share")
589-
def share_more_detail(
590-
loglevel: constants.LogLevel | None = typer.Option(
591-
None, help="The log level to use."
592-
),
593-
):
594-
"""Collect more details on the published package for gallery.
595-
596-
Args:
597-
loglevel: The log level to use.
598-
"""
599-
console.set_log_level(loglevel or get_config().loglevel)
600-
609+
@loglevel_option
610+
def share_more_detail():
611+
"""Collect more details on the published package for gallery."""
601612
_collect_details_for_gallery()
602613

603614

604-
@custom_components_cli.command()
605-
def install(
606-
loglevel: constants.LogLevel | None = typer.Option(
607-
None, help="The log level to use."
608-
),
609-
):
615+
@custom_components_cli.command(name="install")
616+
@loglevel_option
617+
def install():
610618
"""Install package from this local custom component in editable mode.
611619
612-
Args:
613-
loglevel: The log level to use.
614-
615620
Raises:
616621
Exit: If unable to install the current directory in editable mode.
617622
"""
618-
console.set_log_level(loglevel or get_config().loglevel)
619-
620623
if _pip_install_on_demand(package_name=".", install_args=["-e"]):
621624
console.info("Package installed successfully!")
622625
else:
623-
raise typer.Exit(code=1)
626+
raise click.exceptions.Exit(code=1)

0 commit comments

Comments
 (0)