panda-cli 0.1.3__tar.gz → 0.3.0__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.
Files changed (34) hide show
  1. panda_cli-0.3.0/.gitignore +162 -0
  2. panda_cli-0.3.0/PKG-INFO +199 -0
  3. panda_cli-0.3.0/README.md +180 -0
  4. panda_cli-0.3.0/examples/web.py +47 -0
  5. panda_cli-0.3.0/makefile +40 -0
  6. panda_cli-0.3.0/pyproject.toml +83 -0
  7. panda_cli-0.3.0/src/panda_cli/__init__.py +15 -0
  8. panda_cli-0.3.0/src/panda_cli/annotations.py +79 -0
  9. panda_cli-0.3.0/src/panda_cli/backends/__init__.py +5 -0
  10. panda_cli-0.3.0/src/panda_cli/backends/click_backend.py +202 -0
  11. panda_cli-0.3.0/src/panda_cli/base.py +151 -0
  12. panda_cli-0.3.0/src/panda_cli/errors.py +38 -0
  13. panda_cli-0.3.0/src/panda_cli/introspect.py +179 -0
  14. panda_cli-0.3.0/src/panda_cli/option.py +142 -0
  15. panda_cli-0.3.0/src/panda_cli/runtime.py +67 -0
  16. panda_cli-0.3.0/src/panda_cli/spec.py +144 -0
  17. {panda_cli-0.1.3 → panda_cli-0.3.0}/uv.lock +152 -1
  18. panda_cli-0.1.3/PKG-INFO +0 -8
  19. panda_cli-0.1.3/README.md +0 -0
  20. panda_cli-0.1.3/_temp/__init__.py +0 -6
  21. panda_cli-0.1.3/_temp/base_option.py +0 -99
  22. panda_cli-0.1.3/_temp/base_tree.py +0 -127
  23. panda_cli-0.1.3/_temp/option_contstructor.py +0 -56
  24. panda_cli-0.1.3/_temp/tools.py +0 -54
  25. panda_cli-0.1.3/pyproject.toml +0 -28
  26. panda_cli-0.1.3/src/panda_cli/__init__.py +0 -6
  27. panda_cli-0.1.3/src/panda_cli/base.py +0 -176
  28. panda_cli-0.1.3/src/panda_cli/completion.py +0 -0
  29. panda_cli-0.1.3/src/panda_cli/option.py +0 -180
  30. panda_cli-0.1.3/src/panda_cli/parameters.py +0 -28
  31. panda_cli-0.1.3/src/panda_cli/py.typed +0 -0
  32. panda_cli-0.1.3/tests/test.py +0 -34
  33. {panda_cli-0.1.3 → panda_cli-0.3.0}/.python-version +0 -0
  34. {panda_cli-0.1.3/_temp → panda_cli-0.3.0/src/panda_cli}/py.typed +0 -0
@@ -0,0 +1,162 @@
1
+ *.log
2
+ *.pot
3
+ *.pyc
4
+ __pycache__/
5
+ __pycache__
6
+ local_settings.py
7
+ db.sqlite3
8
+ db.sqlite3-journal
9
+ media
10
+ .env
11
+ *.py[cod]
12
+ *$py.class
13
+
14
+ *.so
15
+ .idea/
16
+ .DS_Store
17
+ .Python
18
+ build/
19
+ develop-eggs/
20
+ dist/
21
+ downloads/
22
+ eggs/
23
+ .eggs/
24
+ lib/
25
+ lib64/
26
+ parts/
27
+ sdist/
28
+ var/
29
+ wheels/
30
+ share/python-wheels/
31
+ *.egg-info/
32
+ .installed.cfg
33
+ *.egg
34
+ MANIFEST
35
+
36
+
37
+ *.manifest
38
+ *.spec
39
+
40
+
41
+ pip-log.txt
42
+ pip-delete-this-directory.txt
43
+
44
+
45
+ htmlcov/
46
+ .tox/
47
+ .nox/
48
+ .coverage
49
+ .coverage.*
50
+ .cache
51
+ nosetests.xml
52
+ coverage.xml
53
+ *.cover
54
+ *.py,cover
55
+ .hypothesis/
56
+ .pytest_cache/
57
+ cover/
58
+
59
+
60
+ *.mo
61
+
62
+
63
+
64
+ instance/
65
+ .webassets-cache
66
+
67
+
68
+ .scrapy
69
+
70
+
71
+ docs/_build/
72
+
73
+
74
+ .pybuilder/
75
+ target/
76
+
77
+
78
+ .ipynb_checkpoints
79
+
80
+
81
+ profile_default/
82
+ ipython_config.py
83
+
84
+
85
+ .pdm.toml
86
+
87
+
88
+ __pypackages__/
89
+
90
+
91
+ celerybeat-schedule
92
+ celerybeat.pid
93
+
94
+
95
+ *.sage.py
96
+
97
+
98
+
99
+ .venv
100
+ venv/
101
+ env.bak/
102
+ venv.bak/
103
+
104
+ .spyderproject
105
+ .spyproject
106
+
107
+
108
+ .ropeproject
109
+
110
+
111
+ /site
112
+
113
+
114
+ .mypy_cache/
115
+ .dmypy.json
116
+ dmypy.json
117
+
118
+
119
+ .pyre/
120
+
121
+
122
+ .pytype/
123
+
124
+
125
+ cython_debug/
126
+
127
+
128
+ .idea/**/workspace.xml
129
+ .idea/**/tasks.xml
130
+ .idea/**/usage.statistics.xml
131
+ .idea/**/dictionaries
132
+ .idea/**/shelf
133
+ .idea
134
+ .DS_Store
135
+
136
+
137
+ .idea/**/aws.xml
138
+
139
+
140
+ .idea/**/contentModel.xml
141
+
142
+
143
+ .idea/**/dataSources/
144
+ .idea/**/dataSources.ids
145
+ .idea/**/dataSources.local.xml
146
+ .idea/**/sqlDataSources.xml
147
+ .idea/**/dynamic.xml
148
+ .idea/**/uiDesigner.xml
149
+ .idea/**/dbnavigator.xml
150
+
151
+
152
+ .idea/**/gradle.xml
153
+ .idea/**/libraries
154
+
155
+
156
+ cmake-build-*/
157
+ .stignore
158
+
159
+
160
+ _temp
161
+ dist
162
+ tests
@@ -0,0 +1,199 @@
1
+ Metadata-Version: 2.5
2
+ Name: panda-cli
3
+ Version: 0.3.0
4
+ Summary: Declarative CLI framework: commands are Pydantic models, built on Click
5
+ Author-email: meleshuk0804@gmail.com
6
+ Keywords: cli,click,command-line,declarative,pydantic
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Environment :: Console
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
14
+ Classifier: Typing :: Typed
15
+ Requires-Python: >=3.12
16
+ Requires-Dist: click>=8.3.1
17
+ Requires-Dist: pydantic>=2.12.5
18
+ Description-Content-Type: text/markdown
19
+
20
+ # panda-cli
21
+
22
+ A lightweight declarative CLI framework built on top of Click and Pydantic.
23
+
24
+ Define commands as Python classes, describe arguments with type hints,
25
+ and build CLI trees using simple class composition.
26
+
27
+ ---
28
+
29
+ ## Features
30
+
31
+ - Declarative CLI commands
32
+ - Type-safe arguments
33
+ - Automatic CLI tree generation
34
+ - Built-in validation via Pydantic
35
+ - Validation errors reported in command-line terms (`--port`, not `port`)
36
+ - Minimal boilerplate
37
+ - Nested command groups
38
+
39
+ ---
40
+
41
+ ## Installation
42
+
43
+ ```bash
44
+ pip install panda-cli
45
+ ```
46
+
47
+
48
+ ## Quick Example
49
+ ```python
50
+ from panda_cli import BaseCommand, Option, BaseGroup
51
+ from panda_cli import Config
52
+
53
+
54
+ class WebCli(BaseCommand):
55
+ """ Web server """ # automaticly add to web --help
56
+ __cli_name__ = "web"
57
+
58
+ config=Config(fields_name_to_flag=True) # if True auto gen flag by field name host -> --host
59
+
60
+ host: str = Option(
61
+ "--host",
62
+ default="127.0.0.1",
63
+ description="Host to listen on",
64
+ )
65
+
66
+ port: int = Option(
67
+ "--port",
68
+ default=8000,
69
+ )
70
+
71
+ methods: list[str] # --methods GET --methods POST -> ['GET', 'POST']
72
+
73
+ map: dict[str, str] # --map key=value --map key2=value2 -> {'key': 'value', 'key2': 'value2'}
74
+
75
+ def __exec__(self) -> None:
76
+ """ Automaticly runs when command is called """
77
+
78
+ print(f"Host: {self.host}")
79
+ print(f"Port: {self.port}")
80
+ print(f"Methods: {self.methods}")
81
+ print(f"Map: {self.map}")
82
+
83
+
84
+ class MainCli(BaseGroup):
85
+ __cli_name__ = "main"
86
+
87
+
88
+ web: WebCli
89
+
90
+
91
+ def main():
92
+ MainCli.run()
93
+
94
+
95
+ if __name__ == "__main__":
96
+ main()
97
+ ```
98
+
99
+
100
+ ## CLI
101
+ ```bash
102
+ uv run examples/web.py --help
103
+
104
+ Usage: main [OPTIONS] COMMAND [ARGS]...
105
+
106
+ Options:
107
+ --help Show this message and exit.
108
+
109
+ Commands:
110
+ web Web server
111
+ ```
112
+
113
+ ```bash
114
+ uv run examples/web.py web --help
115
+
116
+ Usage: main web [OPTIONS]
117
+
118
+ Web server
119
+
120
+ Options:
121
+ --host TEXT Host to listen on [default: 127.0.0.1]
122
+ --port INTEGER [default: 8000]
123
+ --methods Iterable TEXT... [required]
124
+ --map TEXT=TEXT [required]
125
+ --help Show this message and exit.
126
+ ```
127
+
128
+
129
+ ## Validation
130
+
131
+ Any Pydantic validator works, and failures are shown the way a CLI user
132
+ expects them — not as a traceback:
133
+
134
+ ```python
135
+ from pydantic import Field
136
+
137
+ class WebCli(BaseCommand):
138
+ __cli_name__ = "web"
139
+ port: int = Field(default=8000, ge=1, le=65535)
140
+
141
+ def __exec__(self) -> None: ...
142
+ ```
143
+
144
+ ```bash
145
+ $ web --port 99999
146
+
147
+ Usage: web [OPTIONS]
148
+ Try 'web --help' for help.
149
+
150
+ Error: Invalid value for '--port': Input should be less than or equal to 65535
151
+ ```
152
+
153
+
154
+ ## Environment variables
155
+
156
+ ```python
157
+ host: str = Option("--host", default="127.0.0.1", envvar="APP_HOST")
158
+ ```
159
+
160
+
161
+ ## Embedding into an existing Click app
162
+
163
+ `run()` is the entry point for `console_scripts`, but the built command is
164
+ also available on its own — useful for mounting a panda-cli tree inside a
165
+ Click application, and for testing:
166
+
167
+ ```python
168
+ import click
169
+
170
+ cli = click.Group("myapp")
171
+ cli.add_command(MainCli.as_click())
172
+ ```
173
+
174
+ `MainCli.spec()` returns the same tree as backend-independent dataclasses,
175
+ without touching Click at all.
176
+
177
+
178
+ ## Architecture
179
+
180
+ ```
181
+ declaration BaseCommand / BaseGroup — what the user writes
182
+ introspect model -> Spec — the only module that imports pydantic
183
+ backend Spec -> click.Command — the only module that imports click
184
+ runtime argv -> validate -> __exec__
185
+ ```
186
+
187
+ Subcommands are declared as fields (`web: WebCli`) but are not model fields:
188
+ the metaclass moves them into the command tree before Pydantic sees them, so
189
+ a group stays an ordinary model with its own options.
190
+
191
+
192
+ ## Development
193
+
194
+ ```bash
195
+ make test # run the test suite
196
+ make cov # coverage
197
+ make regression # tests guarding previously fixed bugs
198
+ make check # lint + tests
199
+ ```
@@ -0,0 +1,180 @@
1
+ # panda-cli
2
+
3
+ A lightweight declarative CLI framework built on top of Click and Pydantic.
4
+
5
+ Define commands as Python classes, describe arguments with type hints,
6
+ and build CLI trees using simple class composition.
7
+
8
+ ---
9
+
10
+ ## Features
11
+
12
+ - Declarative CLI commands
13
+ - Type-safe arguments
14
+ - Automatic CLI tree generation
15
+ - Built-in validation via Pydantic
16
+ - Validation errors reported in command-line terms (`--port`, not `port`)
17
+ - Minimal boilerplate
18
+ - Nested command groups
19
+
20
+ ---
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ pip install panda-cli
26
+ ```
27
+
28
+
29
+ ## Quick Example
30
+ ```python
31
+ from panda_cli import BaseCommand, Option, BaseGroup
32
+ from panda_cli import Config
33
+
34
+
35
+ class WebCli(BaseCommand):
36
+ """ Web server """ # automaticly add to web --help
37
+ __cli_name__ = "web"
38
+
39
+ config=Config(fields_name_to_flag=True) # if True auto gen flag by field name host -> --host
40
+
41
+ host: str = Option(
42
+ "--host",
43
+ default="127.0.0.1",
44
+ description="Host to listen on",
45
+ )
46
+
47
+ port: int = Option(
48
+ "--port",
49
+ default=8000,
50
+ )
51
+
52
+ methods: list[str] # --methods GET --methods POST -> ['GET', 'POST']
53
+
54
+ map: dict[str, str] # --map key=value --map key2=value2 -> {'key': 'value', 'key2': 'value2'}
55
+
56
+ def __exec__(self) -> None:
57
+ """ Automaticly runs when command is called """
58
+
59
+ print(f"Host: {self.host}")
60
+ print(f"Port: {self.port}")
61
+ print(f"Methods: {self.methods}")
62
+ print(f"Map: {self.map}")
63
+
64
+
65
+ class MainCli(BaseGroup):
66
+ __cli_name__ = "main"
67
+
68
+
69
+ web: WebCli
70
+
71
+
72
+ def main():
73
+ MainCli.run()
74
+
75
+
76
+ if __name__ == "__main__":
77
+ main()
78
+ ```
79
+
80
+
81
+ ## CLI
82
+ ```bash
83
+ uv run examples/web.py --help
84
+
85
+ Usage: main [OPTIONS] COMMAND [ARGS]...
86
+
87
+ Options:
88
+ --help Show this message and exit.
89
+
90
+ Commands:
91
+ web Web server
92
+ ```
93
+
94
+ ```bash
95
+ uv run examples/web.py web --help
96
+
97
+ Usage: main web [OPTIONS]
98
+
99
+ Web server
100
+
101
+ Options:
102
+ --host TEXT Host to listen on [default: 127.0.0.1]
103
+ --port INTEGER [default: 8000]
104
+ --methods Iterable TEXT... [required]
105
+ --map TEXT=TEXT [required]
106
+ --help Show this message and exit.
107
+ ```
108
+
109
+
110
+ ## Validation
111
+
112
+ Any Pydantic validator works, and failures are shown the way a CLI user
113
+ expects them — not as a traceback:
114
+
115
+ ```python
116
+ from pydantic import Field
117
+
118
+ class WebCli(BaseCommand):
119
+ __cli_name__ = "web"
120
+ port: int = Field(default=8000, ge=1, le=65535)
121
+
122
+ def __exec__(self) -> None: ...
123
+ ```
124
+
125
+ ```bash
126
+ $ web --port 99999
127
+
128
+ Usage: web [OPTIONS]
129
+ Try 'web --help' for help.
130
+
131
+ Error: Invalid value for '--port': Input should be less than or equal to 65535
132
+ ```
133
+
134
+
135
+ ## Environment variables
136
+
137
+ ```python
138
+ host: str = Option("--host", default="127.0.0.1", envvar="APP_HOST")
139
+ ```
140
+
141
+
142
+ ## Embedding into an existing Click app
143
+
144
+ `run()` is the entry point for `console_scripts`, but the built command is
145
+ also available on its own — useful for mounting a panda-cli tree inside a
146
+ Click application, and for testing:
147
+
148
+ ```python
149
+ import click
150
+
151
+ cli = click.Group("myapp")
152
+ cli.add_command(MainCli.as_click())
153
+ ```
154
+
155
+ `MainCli.spec()` returns the same tree as backend-independent dataclasses,
156
+ without touching Click at all.
157
+
158
+
159
+ ## Architecture
160
+
161
+ ```
162
+ declaration BaseCommand / BaseGroup — what the user writes
163
+ introspect model -> Spec — the only module that imports pydantic
164
+ backend Spec -> click.Command — the only module that imports click
165
+ runtime argv -> validate -> __exec__
166
+ ```
167
+
168
+ Subcommands are declared as fields (`web: WebCli`) but are not model fields:
169
+ the metaclass moves them into the command tree before Pydantic sees them, so
170
+ a group stays an ordinary model with its own options.
171
+
172
+
173
+ ## Development
174
+
175
+ ```bash
176
+ make test # run the test suite
177
+ make cov # coverage
178
+ make regression # tests guarding previously fixed bugs
179
+ make check # lint + tests
180
+ ```
@@ -0,0 +1,47 @@
1
+ from panda_cli import BaseCommand, Option, BaseGroup
2
+ from panda_cli import Config
3
+
4
+
5
+ class WebCli(BaseCommand):
6
+ """Web server""" # automaticly add to web --help
7
+
8
+ __cli_name__ = "web"
9
+
10
+ config = Config(fields_name_to_flag=True) # if True auto gen flag by field name host -> --host
11
+
12
+ host: str = Option(
13
+ "--host",
14
+ default="127.0.0.1",
15
+ description="Host to listen on",
16
+ )
17
+
18
+ port: int = Option(
19
+ "--port",
20
+ default=8000,
21
+ )
22
+
23
+ methods: list[str] # --methods GET --methods POST -> ['GET', 'POST']
24
+
25
+ map: dict[str, str] # --map key=value --map key2=value2 -> {'key': 'value', 'key2': 'value2'}
26
+
27
+ def __exec__(self) -> None:
28
+ """Automaticly runs when command is called"""
29
+
30
+ print(f"Host: {self.host}")
31
+ print(f"Port: {self.port}")
32
+ print(f"Methods: {self.methods}")
33
+ print(f"Map: {self.map}")
34
+
35
+
36
+ class MainCli(BaseGroup):
37
+ __cli_name__ = "main"
38
+
39
+ web: WebCli
40
+
41
+
42
+ def main():
43
+ MainCli.run()
44
+
45
+
46
+ if __name__ == "__main__":
47
+ main()
@@ -0,0 +1,40 @@
1
+ .PHONY: major minor patch test cov regression lint check
2
+
3
+ PYPROJECT := pyproject.toml
4
+ INIT_FILE := src/panda_cli/__init__.py
5
+
6
+ # Читаем текущую версию из pyproject.toml
7
+ CURRENT := $(shell grep '^version = ' $(PYPROJECT) | sed 's/version = "\(.*\)"/\1/')
8
+ MAJOR := $(shell echo $(CURRENT) | cut -d. -f1)
9
+ MINOR := $(shell echo $(CURRENT) | cut -d. -f2)
10
+ PATCH := $(shell echo $(CURRENT) | cut -d. -f3)
11
+
12
+ define bump
13
+ @echo UPDATED "$(CURRENT) → $(1)"
14
+ @sed -i 's/^version = ".*"/version = "$(1)"/' $(PYPROJECT)
15
+ @sed -i 's/^__version__ = ".*"/__version__ = "$(1)"/' $(INIT_FILE)
16
+ endef
17
+
18
+ major:
19
+ $(call bump,$(shell echo $$(($(MAJOR)+1))).0.0)
20
+
21
+ minor:
22
+ $(call bump,$(MAJOR).$(shell echo $$(($(MINOR)+1))).0)
23
+
24
+ patch:
25
+ $(call bump,$(MAJOR).$(MINOR).$(shell echo $$(($(PATCH)+1))))
26
+
27
+ test:
28
+ uv run pytest
29
+
30
+ cov:
31
+ uv run pytest --cov
32
+
33
+ regression: # тесты на закрытые баги
34
+ uv run pytest -m regression -rX
35
+
36
+ lint:
37
+ uv run ruff check .
38
+ uv run ruff format --check .
39
+
40
+ check: lint test
@@ -0,0 +1,83 @@
1
+ [project]
2
+ name = "panda-cli"
3
+ version = "0.3.0"
4
+ description = "Declarative CLI framework: commands are Pydantic models, built on Click"
5
+ readme = "README.md"
6
+ authors = [
7
+ { email = "meleshuk0804@gmail.com" }
8
+ ]
9
+ requires-python = ">=3.12"
10
+ keywords = ["cli", "click", "pydantic", "command-line", "declarative"]
11
+ classifiers = [
12
+ "Development Status :: 3 - Alpha",
13
+ "Environment :: Console",
14
+ "Intended Audience :: Developers",
15
+ "Programming Language :: Python :: 3",
16
+ "Programming Language :: Python :: 3.12",
17
+ "Programming Language :: Python :: 3.13",
18
+ "Topic :: Software Development :: Libraries :: Application Frameworks",
19
+ "Typing :: Typed",
20
+ ]
21
+ dependencies = [
22
+ "click>=8.3.1",
23
+ "pydantic>=2.12.5",
24
+ ]
25
+
26
+ [build-system]
27
+ requires = ["hatchling"]
28
+ build-backend = "hatchling.build"
29
+
30
+
31
+ [tool.hatch.build.targets.wheel]
32
+ packages = ["src/panda_cli"]
33
+
34
+ [dependency-groups]
35
+ dev = [
36
+ "mypy>=1.19.1",
37
+ "pytest>=9.1.1",
38
+ "pytest-cov>=7.1.0",
39
+ "ruff>=0.15.4",
40
+ "twine>=6.2.0",
41
+ ]
42
+
43
+ [tool.pytest.ini_options]
44
+ testpaths = ["tests"]
45
+ addopts = "-q --strict-markers --strict-config"
46
+ markers = [
47
+ "regression: тест на конкретный закрытый баг — не удалять при рефакторинге",
48
+ ]
49
+ xfail_strict = true
50
+
51
+ [tool.coverage.run]
52
+ source = ["panda_cli"]
53
+ branch = true
54
+
55
+ [tool.coverage.report]
56
+ show_missing = true
57
+ exclude_lines = ["pragma: no cover", "raise NotImplementedError", "if t.TYPE_CHECKING:"]
58
+
59
+ [tool.ruff]
60
+ line-length = 100
61
+ src = [".", "src"]
62
+ exclude = ["_temp", "dist", ".venv"]
63
+
64
+ [tool.ruff.lint]
65
+ select = ["E", "F", "I", "UP", "B", "SIM", "RUF"]
66
+ ignore = [
67
+ "RUF003", # кириллица в комментариях
68
+ "RUF001", # кириллица в строках — здесь это нормально
69
+ "RUF002", # кириллица в docstring'ах
70
+ "RUF012", # ClassVar для mutable-атрибутов: ложное срабатывание на полях pydantic
71
+ ]
72
+
73
+ [tool.ruff.lint.isort]
74
+ known-first-party = ["panda_cli", "tests"]
75
+
76
+ [tool.ruff.lint.per-file-ignores]
77
+ # пример повторяет README дословно — форматирование импортов оставлено как в доке
78
+ "examples/*.py" = ["I001"]
79
+
80
+ [tool.mypy]
81
+ python_version = "3.12"
82
+ strict = true
83
+ files = ["src/panda_cli", "tests"]