99from collections import namedtuple
1010from contextlib import contextmanager
1111from pathlib import Path
12+ from typing import Any
1213
14+ import click
1315import httpx
14- import typer
1516
1617from reflex import constants
17- from reflex .config import get_config
1818from reflex .constants import CustomComponents
1919from 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
2453POST_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
280320def 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 "\n If 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
459479def _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