click-extended 0.0.1__tar.gz → 0.0.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {click_extended-0.0.1/click_extended.egg-info → click_extended-0.0.3}/PKG-INFO +16 -5
- click_extended-0.0.3/README.md +38 -0
- {click_extended-0.0.1 → click_extended-0.0.3/click_extended.egg-info}/PKG-INFO +16 -5
- {click_extended-0.0.1 → click_extended-0.0.3}/click_extended.egg-info/SOURCES.txt +1 -0
- {click_extended-0.0.1 → click_extended-0.0.3}/pyproject.toml +1 -1
- {click_extended-0.0.1 → click_extended-0.0.3}/tests/test_env.py +264 -0
- click_extended-0.0.3/tests/test_global_node.py +749 -0
- {click_extended-0.0.1 → click_extended-0.0.3}/tests/test_tree.py +135 -0
- click_extended-0.0.1/README.md +0 -27
- {click_extended-0.0.1 → click_extended-0.0.3}/AUTHORS.md +0 -0
- {click_extended-0.0.1 → click_extended-0.0.3}/LICENSE +0 -0
- {click_extended-0.0.1 → click_extended-0.0.3}/click_extended/__init__.py +0 -0
- {click_extended-0.0.1 → click_extended-0.0.3}/click_extended/errors.py +0 -0
- {click_extended-0.0.1 → click_extended-0.0.3}/click_extended/types.py +0 -0
- {click_extended-0.0.1 → click_extended-0.0.3}/click_extended.egg-info/dependency_links.txt +0 -0
- {click_extended-0.0.1 → click_extended-0.0.3}/click_extended.egg-info/requires.txt +0 -0
- {click_extended-0.0.1 → click_extended-0.0.3}/click_extended.egg-info/top_level.txt +0 -0
- {click_extended-0.0.1 → click_extended-0.0.3}/setup.cfg +0 -0
- {click_extended-0.0.1 → click_extended-0.0.3}/tests/test_argument.py +0 -0
- {click_extended-0.0.1 → click_extended-0.0.3}/tests/test_child_node.py +0 -0
- {click_extended-0.0.1 → click_extended-0.0.3}/tests/test_command.py +0 -0
- {click_extended-0.0.1 → click_extended-0.0.3}/tests/test_group.py +0 -0
- {click_extended-0.0.1 → click_extended-0.0.3}/tests/test_option.py +0 -0
- {click_extended-0.0.1 → click_extended-0.0.3}/tests/test_parent_node.py +0 -0
- {click_extended-0.0.1 → click_extended-0.0.3}/tests/test_root_node.py +0 -0
- {click_extended-0.0.1 → click_extended-0.0.3}/tests/test_tag.py +0 -0
- {click_extended-0.0.1 → click_extended-0.0.3}/tests/test_transform.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: click_extended
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.3
|
|
4
4
|
Summary: An extension to Click with additional features like automatic async support, aliasing and a modular decorator system.
|
|
5
5
|
Author-email: Marcus Fredriksson <marcus@marcusfredriksson.com>
|
|
6
6
|
License: MIT License
|
|
@@ -65,7 +65,14 @@ Dynamic: license-file
|
|
|
65
65
|
|
|
66
66
|
# Click Extended
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
An extension of the [Click](https://github.com/pallets/click) library with additional features like aliasing, asynchronous support, an extended decorator system and more.
|
|
69
|
+
|
|
70
|
+
## Features
|
|
71
|
+
|
|
72
|
+
- **Aliasing**: Add multiple aliases to a group or command.
|
|
73
|
+
- **Async supprt**: Automatically run both synchronous and asynchronous functions.
|
|
74
|
+
- **Extended decorator system**: Create or use pre-made validation and transformation decorators, inject values into the context and more.
|
|
75
|
+
- **Environment variables**: Automatically validate and inject environment variables into the function.
|
|
69
76
|
|
|
70
77
|
## Installation
|
|
71
78
|
|
|
@@ -75,16 +82,20 @@ pip install click-extended
|
|
|
75
82
|
|
|
76
83
|
## Requirements
|
|
77
84
|
|
|
78
|
-
|
|
85
|
+
- **Python**: 3.10 or higher
|
|
79
86
|
|
|
80
|
-
##
|
|
87
|
+
## Usage
|
|
81
88
|
|
|
82
|
-
|
|
89
|
+
TBD
|
|
83
90
|
|
|
84
91
|
## Contributing
|
|
85
92
|
|
|
86
93
|
TBD
|
|
87
94
|
|
|
95
|
+
## License
|
|
96
|
+
|
|
97
|
+
This project is licensed under the MIT License - see the [LICENSE](./LICENSE) file for details.
|
|
98
|
+
|
|
88
99
|
## Acknowledgements
|
|
89
100
|
|
|
90
101
|
This project is built on top of the [Click](https://github.com/pallets/click) library.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
# Click Extended
|
|
4
|
+
|
|
5
|
+
An extension of the [Click](https://github.com/pallets/click) library with additional features like aliasing, asynchronous support, an extended decorator system and more.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Aliasing**: Add multiple aliases to a group or command.
|
|
10
|
+
- **Async supprt**: Automatically run both synchronous and asynchronous functions.
|
|
11
|
+
- **Extended decorator system**: Create or use pre-made validation and transformation decorators, inject values into the context and more.
|
|
12
|
+
- **Environment variables**: Automatically validate and inject environment variables into the function.
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install click-extended
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Requirements
|
|
21
|
+
|
|
22
|
+
- **Python**: 3.10 or higher
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
TBD
|
|
27
|
+
|
|
28
|
+
## Contributing
|
|
29
|
+
|
|
30
|
+
TBD
|
|
31
|
+
|
|
32
|
+
## License
|
|
33
|
+
|
|
34
|
+
This project is licensed under the MIT License - see the [LICENSE](./LICENSE) file for details.
|
|
35
|
+
|
|
36
|
+
## Acknowledgements
|
|
37
|
+
|
|
38
|
+
This project is built on top of the [Click](https://github.com/pallets/click) library.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: click_extended
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.3
|
|
4
4
|
Summary: An extension to Click with additional features like automatic async support, aliasing and a modular decorator system.
|
|
5
5
|
Author-email: Marcus Fredriksson <marcus@marcusfredriksson.com>
|
|
6
6
|
License: MIT License
|
|
@@ -65,7 +65,14 @@ Dynamic: license-file
|
|
|
65
65
|
|
|
66
66
|
# Click Extended
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
An extension of the [Click](https://github.com/pallets/click) library with additional features like aliasing, asynchronous support, an extended decorator system and more.
|
|
69
|
+
|
|
70
|
+
## Features
|
|
71
|
+
|
|
72
|
+
- **Aliasing**: Add multiple aliases to a group or command.
|
|
73
|
+
- **Async supprt**: Automatically run both synchronous and asynchronous functions.
|
|
74
|
+
- **Extended decorator system**: Create or use pre-made validation and transformation decorators, inject values into the context and more.
|
|
75
|
+
- **Environment variables**: Automatically validate and inject environment variables into the function.
|
|
69
76
|
|
|
70
77
|
## Installation
|
|
71
78
|
|
|
@@ -75,16 +82,20 @@ pip install click-extended
|
|
|
75
82
|
|
|
76
83
|
## Requirements
|
|
77
84
|
|
|
78
|
-
|
|
85
|
+
- **Python**: 3.10 or higher
|
|
79
86
|
|
|
80
|
-
##
|
|
87
|
+
## Usage
|
|
81
88
|
|
|
82
|
-
|
|
89
|
+
TBD
|
|
83
90
|
|
|
84
91
|
## Contributing
|
|
85
92
|
|
|
86
93
|
TBD
|
|
87
94
|
|
|
95
|
+
## License
|
|
96
|
+
|
|
97
|
+
This project is licensed under the MIT License - see the [LICENSE](./LICENSE) file for details.
|
|
98
|
+
|
|
88
99
|
## Acknowledgements
|
|
89
100
|
|
|
90
101
|
This project is built on top of the [Click](https://github.com/pallets/click) library.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "click_extended"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.3"
|
|
4
4
|
description = "An extension to Click with additional features like automatic async support, aliasing and a modular decorator system."
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Marcus Fredriksson", email = "marcus@marcusfredriksson.com" },
|
|
@@ -629,3 +629,267 @@ class TestEnvIntegration:
|
|
|
629
629
|
with patch.dict(os.environ, {}, clear=True):
|
|
630
630
|
e = Env(name="test", env_name="NONEXISTENT_VAR")
|
|
631
631
|
assert e.get_raw_value() is None
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
class TestEnvCheckRequired:
|
|
635
|
+
"""Test Env.check_required functionality."""
|
|
636
|
+
|
|
637
|
+
def test_check_required_returns_none_when_env_var_set(self) -> None:
|
|
638
|
+
"""Test check_required returns None when required env var is set."""
|
|
639
|
+
with patch.dict(os.environ, {"TEST_VAR": "value"}):
|
|
640
|
+
e = Env(name="test", env_name="TEST_VAR", required=True)
|
|
641
|
+
assert e.check_required() is None
|
|
642
|
+
|
|
643
|
+
def test_check_required_returns_env_name_when_missing(self) -> None:
|
|
644
|
+
"""Test check_required returns env_name when required var is missing."""
|
|
645
|
+
with patch.dict(os.environ, {}, clear=True):
|
|
646
|
+
e = Env(name="test", env_name="MISSING_VAR", required=True)
|
|
647
|
+
assert e.check_required() == "MISSING_VAR"
|
|
648
|
+
|
|
649
|
+
def test_check_required_returns_none_when_not_required(self) -> None:
|
|
650
|
+
"""Test check_required returns None when env var is not required."""
|
|
651
|
+
with patch.dict(os.environ, {}, clear=True):
|
|
652
|
+
e = Env(name="test", env_name="OPTIONAL_VAR", required=False)
|
|
653
|
+
assert e.check_required() is None
|
|
654
|
+
|
|
655
|
+
def test_check_required_with_required_false_and_env_set(self) -> None:
|
|
656
|
+
"""Test check_required with optional env var that is set."""
|
|
657
|
+
with patch.dict(os.environ, {"OPTIONAL_VAR": "value"}):
|
|
658
|
+
e = Env(name="test", env_name="OPTIONAL_VAR", required=False)
|
|
659
|
+
assert e.check_required() is None
|
|
660
|
+
|
|
661
|
+
def test_check_required_ignores_default_value(self) -> None:
|
|
662
|
+
"""Test check_required ignores default value when required=True."""
|
|
663
|
+
with patch.dict(os.environ, {}, clear=True):
|
|
664
|
+
e = Env(
|
|
665
|
+
name="test",
|
|
666
|
+
env_name="MISSING_VAR",
|
|
667
|
+
required=True,
|
|
668
|
+
default="default_value",
|
|
669
|
+
)
|
|
670
|
+
assert e.check_required() == "MISSING_VAR"
|
|
671
|
+
|
|
672
|
+
def test_check_required_does_not_raise_exception(self) -> None:
|
|
673
|
+
"""Test check_required does not raise exception (unlike get_raw_value)."""
|
|
674
|
+
with patch.dict(os.environ, {}, clear=True):
|
|
675
|
+
e = Env(name="test", env_name="MISSING_VAR", required=True)
|
|
676
|
+
result = e.check_required()
|
|
677
|
+
assert result == "MISSING_VAR"
|
|
678
|
+
|
|
679
|
+
def test_check_required_multiple_times(self) -> None:
|
|
680
|
+
"""Test check_required can be called multiple times."""
|
|
681
|
+
with patch.dict(os.environ, {}, clear=True):
|
|
682
|
+
e = Env(name="test", env_name="MISSING_VAR", required=True)
|
|
683
|
+
assert e.check_required() == "MISSING_VAR"
|
|
684
|
+
assert e.check_required() == "MISSING_VAR"
|
|
685
|
+
assert e.check_required() == "MISSING_VAR"
|
|
686
|
+
|
|
687
|
+
def test_check_required_with_empty_string_env_var(self) -> None:
|
|
688
|
+
"""Test check_required treats empty string as set."""
|
|
689
|
+
with patch.dict(os.environ, {"EMPTY_VAR": ""}):
|
|
690
|
+
e = Env(name="test", env_name="EMPTY_VAR", required=True)
|
|
691
|
+
assert e.check_required() is None
|
|
692
|
+
|
|
693
|
+
|
|
694
|
+
class TestEnvMultipleMissingVariables:
|
|
695
|
+
"""Test multiple missing required environment variables error handling."""
|
|
696
|
+
|
|
697
|
+
def test_single_missing_variable_error_message(self) -> None:
|
|
698
|
+
"""Test error message for single missing required variable."""
|
|
699
|
+
from click_extended.core.command import command
|
|
700
|
+
|
|
701
|
+
@command()
|
|
702
|
+
@env("MISSING_VAR_1", required=True)
|
|
703
|
+
def test_cmd(**kwargs: Any) -> None:
|
|
704
|
+
pass
|
|
705
|
+
|
|
706
|
+
with patch.dict(os.environ, {}, clear=True):
|
|
707
|
+
try:
|
|
708
|
+
test_cmd([], standalone_mode=False)
|
|
709
|
+
assert False, "Expected ValueError"
|
|
710
|
+
except ValueError as ex:
|
|
711
|
+
error_msg = str(ex)
|
|
712
|
+
assert (
|
|
713
|
+
"Required environment variable 'MISSING_VAR_1' is not set"
|
|
714
|
+
in error_msg
|
|
715
|
+
)
|
|
716
|
+
assert " and " not in error_msg
|
|
717
|
+
|
|
718
|
+
def test_two_missing_variables_error_message(self) -> None:
|
|
719
|
+
"""Test error message for two missing required variables."""
|
|
720
|
+
from click_extended.core.command import command
|
|
721
|
+
|
|
722
|
+
@command()
|
|
723
|
+
@env("MISSING_VAR_1", required=True)
|
|
724
|
+
@env("MISSING_VAR_2", required=True)
|
|
725
|
+
def test_cmd(**kwargs: Any) -> None:
|
|
726
|
+
pass
|
|
727
|
+
|
|
728
|
+
with patch.dict(os.environ, {}, clear=True):
|
|
729
|
+
try:
|
|
730
|
+
test_cmd([], standalone_mode=False)
|
|
731
|
+
assert False, "Expected ValueError"
|
|
732
|
+
except ValueError as ex:
|
|
733
|
+
error_msg = str(ex)
|
|
734
|
+
assert "Required environment variables" in error_msg
|
|
735
|
+
assert "'MISSING_VAR_1'" in error_msg
|
|
736
|
+
assert "'MISSING_VAR_2'" in error_msg
|
|
737
|
+
assert " and " in error_msg
|
|
738
|
+
assert error_msg.count("'") == 4
|
|
739
|
+
|
|
740
|
+
def test_three_missing_variables_error_message(self) -> None:
|
|
741
|
+
"""Test error message for three missing required variables."""
|
|
742
|
+
from click_extended.core.command import command
|
|
743
|
+
|
|
744
|
+
@command()
|
|
745
|
+
@env("MISSING_VAR_1", required=True)
|
|
746
|
+
@env("MISSING_VAR_2", required=True)
|
|
747
|
+
@env("MISSING_VAR_3", required=True)
|
|
748
|
+
def test_cmd(**kwargs: Any) -> None:
|
|
749
|
+
pass
|
|
750
|
+
|
|
751
|
+
with patch.dict(os.environ, {}, clear=True):
|
|
752
|
+
try:
|
|
753
|
+
test_cmd([], standalone_mode=False)
|
|
754
|
+
assert False, "Expected ValueError"
|
|
755
|
+
except ValueError as ex:
|
|
756
|
+
error_msg = str(ex)
|
|
757
|
+
assert "Required environment variables" in error_msg
|
|
758
|
+
assert "'MISSING_VAR_1'" in error_msg
|
|
759
|
+
assert "'MISSING_VAR_2'" in error_msg
|
|
760
|
+
assert "'MISSING_VAR_3'" in error_msg
|
|
761
|
+
assert " and " in error_msg
|
|
762
|
+
assert "," in error_msg
|
|
763
|
+
|
|
764
|
+
def test_mixed_required_and_optional_variables(self) -> None:
|
|
765
|
+
"""Test only required missing variables are reported."""
|
|
766
|
+
from click_extended.core.command import command
|
|
767
|
+
|
|
768
|
+
@command()
|
|
769
|
+
@env("REQUIRED_VAR", required=True)
|
|
770
|
+
@env("OPTIONAL_VAR", required=False)
|
|
771
|
+
@env("REQUIRED_VAR_2", required=True)
|
|
772
|
+
def test_cmd(**kwargs: Any) -> None:
|
|
773
|
+
pass
|
|
774
|
+
|
|
775
|
+
with patch.dict(os.environ, {}, clear=True):
|
|
776
|
+
try:
|
|
777
|
+
test_cmd([], standalone_mode=False)
|
|
778
|
+
assert False, "Expected ValueError"
|
|
779
|
+
except ValueError as ex:
|
|
780
|
+
error_msg = str(ex)
|
|
781
|
+
assert "'REQUIRED_VAR'" in error_msg
|
|
782
|
+
assert "'REQUIRED_VAR_2'" in error_msg
|
|
783
|
+
assert "'OPTIONAL_VAR'" not in error_msg
|
|
784
|
+
|
|
785
|
+
def test_some_required_variables_set(self) -> None:
|
|
786
|
+
"""Test error message when some required variables are set."""
|
|
787
|
+
from click_extended.core.command import command
|
|
788
|
+
|
|
789
|
+
@command()
|
|
790
|
+
@env("SET_VAR", required=True)
|
|
791
|
+
@env("MISSING_VAR_1", required=True)
|
|
792
|
+
@env("MISSING_VAR_2", required=True)
|
|
793
|
+
def test_cmd(**kwargs: Any) -> None:
|
|
794
|
+
pass
|
|
795
|
+
|
|
796
|
+
with patch.dict(os.environ, {"SET_VAR": "value"}, clear=True):
|
|
797
|
+
try:
|
|
798
|
+
test_cmd([], standalone_mode=False)
|
|
799
|
+
assert False, "Expected ValueError"
|
|
800
|
+
except ValueError as ex:
|
|
801
|
+
error_msg = str(ex)
|
|
802
|
+
assert "'MISSING_VAR_1'" in error_msg
|
|
803
|
+
assert "'MISSING_VAR_2'" in error_msg
|
|
804
|
+
assert "'SET_VAR'" not in error_msg
|
|
805
|
+
|
|
806
|
+
def test_all_required_variables_set_no_error(self) -> None:
|
|
807
|
+
"""Test no error when all required variables are set."""
|
|
808
|
+
from click_extended.core.command import command
|
|
809
|
+
|
|
810
|
+
call_count = 0
|
|
811
|
+
|
|
812
|
+
@command()
|
|
813
|
+
@env("VAR_1", required=True)
|
|
814
|
+
@env("VAR_2", required=True)
|
|
815
|
+
@env("VAR_3", required=True)
|
|
816
|
+
def test_cmd(**kwargs: Any) -> None:
|
|
817
|
+
nonlocal call_count
|
|
818
|
+
call_count += 1
|
|
819
|
+
|
|
820
|
+
with patch.dict(
|
|
821
|
+
os.environ,
|
|
822
|
+
{"VAR_1": "v1", "VAR_2": "v2", "VAR_3": "v3"},
|
|
823
|
+
clear=True,
|
|
824
|
+
):
|
|
825
|
+
test_cmd([], standalone_mode=False)
|
|
826
|
+
assert call_count == 1
|
|
827
|
+
|
|
828
|
+
def test_error_raised_before_processing(self) -> None:
|
|
829
|
+
"""Test that validation happens before any processing."""
|
|
830
|
+
from click_extended.core.command import command
|
|
831
|
+
|
|
832
|
+
process_called = False
|
|
833
|
+
|
|
834
|
+
class TestChild(ChildNode): # type: ignore
|
|
835
|
+
def process(
|
|
836
|
+
self,
|
|
837
|
+
value: Any,
|
|
838
|
+
*args: Any,
|
|
839
|
+
siblings: list[str],
|
|
840
|
+
tags: dict[str, Tag],
|
|
841
|
+
parent: ParentNode | Tag,
|
|
842
|
+
**kwargs: Any,
|
|
843
|
+
) -> Any:
|
|
844
|
+
nonlocal process_called
|
|
845
|
+
process_called = True
|
|
846
|
+
return value
|
|
847
|
+
|
|
848
|
+
@command()
|
|
849
|
+
@env("MISSING_VAR", required=True)
|
|
850
|
+
def test_cmd(**kwargs: Any) -> None:
|
|
851
|
+
pass
|
|
852
|
+
|
|
853
|
+
with patch.dict(os.environ, {}, clear=True):
|
|
854
|
+
try:
|
|
855
|
+
test_cmd([], standalone_mode=False)
|
|
856
|
+
assert False, "Expected ValueError"
|
|
857
|
+
except ValueError:
|
|
858
|
+
assert not process_called
|
|
859
|
+
|
|
860
|
+
def test_error_message_grammar_for_one_variable(self) -> None:
|
|
861
|
+
"""Test proper grammar (singular) for one missing variable."""
|
|
862
|
+
from click_extended.core.command import command
|
|
863
|
+
|
|
864
|
+
@command()
|
|
865
|
+
@env("MISSING_VAR", required=True)
|
|
866
|
+
def test_cmd(**kwargs: Any) -> None:
|
|
867
|
+
pass
|
|
868
|
+
|
|
869
|
+
with patch.dict(os.environ, {}, clear=True):
|
|
870
|
+
try:
|
|
871
|
+
test_cmd([], standalone_mode=False)
|
|
872
|
+
assert False, "Expected ValueError"
|
|
873
|
+
except ValueError as ex:
|
|
874
|
+
error_msg = str(ex)
|
|
875
|
+
assert "variable" in error_msg.lower()
|
|
876
|
+
assert "is not set" in error_msg
|
|
877
|
+
|
|
878
|
+
def test_error_message_grammar_for_multiple_variables(self) -> None:
|
|
879
|
+
"""Test proper grammar (plural) for multiple missing variables."""
|
|
880
|
+
from click_extended.core.command import command
|
|
881
|
+
|
|
882
|
+
@command()
|
|
883
|
+
@env("VAR_1", required=True)
|
|
884
|
+
@env("VAR_2", required=True)
|
|
885
|
+
def test_cmd(**kwargs: Any) -> None:
|
|
886
|
+
pass
|
|
887
|
+
|
|
888
|
+
with patch.dict(os.environ, {}, clear=True):
|
|
889
|
+
try:
|
|
890
|
+
test_cmd([], standalone_mode=False)
|
|
891
|
+
assert False, "Expected ValueError"
|
|
892
|
+
except ValueError as ex:
|
|
893
|
+
error_msg = str(ex)
|
|
894
|
+
assert "variables" in error_msg.lower()
|
|
895
|
+
assert "are not set" in error_msg
|