pyclifer 0.4.1__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.
- pyclifer-0.4.1/.claude/CLAUDE.md +243 -0
- pyclifer-0.4.1/.claude/specs/archived/2026-06-01-deps-upgrade-fr.md +279 -0
- pyclifer-0.4.1/.claude/specs/archived/core-simplification.md +333 -0
- pyclifer-0.4.1/.claude/specs/archived/demo-taskman.md +716 -0
- pyclifer-0.4.1/.claude/specs/archived/error_handling.md +548 -0
- pyclifer-0.4.1/.claude/specs/archived/exit_codes.md +298 -0
- pyclifer-0.4.1/.claude/specs/archived/flat-commands.md +154 -0
- pyclifer-0.4.1/.claude/specs/archived/howto-guides.md +25 -0
- pyclifer-0.4.1/.claude/specs/archived/improvements.md +484 -0
- pyclifer-0.4.1/.claude/specs/archived/models.md +213 -0
- pyclifer-0.4.1/.claude/specs/archived/output_formats.md +183 -0
- pyclifer-0.4.1/.claude/specs/archived/scaffolding-add-command-nested.md +110 -0
- pyclifer-0.4.1/.claude/specs/archived/scaffolding-add-group.md +224 -0
- pyclifer-0.4.1/.claude/specs/decorators-make-context-refactor.md +158 -0
- pyclifer-0.4.1/.github/workflows/ci.yml +40 -0
- pyclifer-0.4.1/.github/workflows/docs.yml +50 -0
- pyclifer-0.4.1/.github/workflows/release.yml +71 -0
- pyclifer-0.4.1/.gitignore +210 -0
- pyclifer-0.4.1/.pre-commit-config.yaml +16 -0
- pyclifer-0.4.1/CHANGELOG.md +315 -0
- pyclifer-0.4.1/CLAUDE.md +208 -0
- pyclifer-0.4.1/LICENSE +21 -0
- pyclifer-0.4.1/PKG-INFO +202 -0
- pyclifer-0.4.1/README.md +156 -0
- pyclifer-0.4.1/Taskfile.yml +75 -0
- pyclifer-0.4.1/cliff.toml +47 -0
- pyclifer-0.4.1/docs/api/classes.md +38 -0
- pyclifer-0.4.1/docs/api/context.md +9 -0
- pyclifer-0.4.1/docs/api/decorators.md +63 -0
- pyclifer-0.4.1/docs/api/interfaces.md +135 -0
- pyclifer-0.4.1/docs/api/logging.md +60 -0
- pyclifer-0.4.1/docs/api/mixins.md +37 -0
- pyclifer-0.4.1/docs/api/models.md +8 -0
- pyclifer-0.4.1/docs/api/output.md +156 -0
- pyclifer-0.4.1/docs/assets/favicon-96x96.png +0 -0
- pyclifer-0.4.1/docs/assets/logo.png +0 -0
- pyclifer-0.4.1/docs/configuration.md +307 -0
- pyclifer-0.4.1/docs/development.md +238 -0
- pyclifer-0.4.1/docs/error-handling.md +214 -0
- pyclifer-0.4.1/docs/examples.md +296 -0
- pyclifer-0.4.1/docs/getting-started.md +196 -0
- pyclifer-0.4.1/docs/how-to/error-handling.md +169 -0
- pyclifer-0.4.1/docs/how-to/index.md +37 -0
- pyclifer-0.4.1/docs/how-to/multi-integration-commands.md +162 -0
- pyclifer-0.4.1/docs/how-to/output-format.md +207 -0
- pyclifer-0.4.1/docs/how-to/response-patterns.md +217 -0
- pyclifer-0.4.1/docs/how-to/rich-progressive-output.md +203 -0
- pyclifer-0.4.1/docs/index.md +100 -0
- pyclifer-0.4.1/docs/logging.md +245 -0
- pyclifer-0.4.1/docs/output-formatting.md +545 -0
- pyclifer-0.4.1/docs/scaffolding.md +420 -0
- pyclifer-0.4.1/docs/versions.json +1 -0
- pyclifer-0.4.1/mkdocs.yml +103 -0
- pyclifer-0.4.1/pyproject.toml +177 -0
- pyclifer-0.4.1/src/pyclifer/__init__.py +159 -0
- pyclifer-0.4.1/src/pyclifer/apps/__init__.py +8 -0
- pyclifer-0.4.1/src/pyclifer/apps/demo/__init__.py +25 -0
- pyclifer-0.4.1/src/pyclifer/apps/demo/apps/__init__.py +0 -0
- pyclifer-0.4.1/src/pyclifer/apps/demo/apps/tasks/__init__.py +19 -0
- pyclifer-0.4.1/src/pyclifer/apps/demo/apps/tasks/commands/__init__.py +10 -0
- pyclifer-0.4.1/src/pyclifer/apps/demo/apps/tasks/commands/add.py +27 -0
- pyclifer-0.4.1/src/pyclifer/apps/demo/apps/tasks/commands/complete.py +12 -0
- pyclifer-0.4.1/src/pyclifer/apps/demo/apps/tasks/commands/delete.py +15 -0
- pyclifer-0.4.1/src/pyclifer/apps/demo/apps/tasks/commands/list.py +38 -0
- pyclifer-0.4.1/src/pyclifer/apps/demo/apps/tasks/commands/show.py +12 -0
- pyclifer-0.4.1/src/pyclifer/apps/demo/apps/tasks/commands/sync.py +16 -0
- pyclifer-0.4.1/src/pyclifer/apps/demo/apps/tasks/interfaces.py +208 -0
- pyclifer-0.4.1/src/pyclifer/apps/demo/apps/tasks/models.py +61 -0
- pyclifer-0.4.1/src/pyclifer/apps/demo/apps/tasks/renderers.py +152 -0
- pyclifer-0.4.1/src/pyclifer/apps/demo/apps/tasks/tables.py +5 -0
- pyclifer-0.4.1/src/pyclifer/apps/demo/apps/users/__init__.py +19 -0
- pyclifer-0.4.1/src/pyclifer/apps/demo/apps/users/commands/__init__.py +6 -0
- pyclifer-0.4.1/src/pyclifer/apps/demo/apps/users/commands/list.py +12 -0
- pyclifer-0.4.1/src/pyclifer/apps/demo/apps/users/commands/whoami.py +11 -0
- pyclifer-0.4.1/src/pyclifer/apps/demo/apps/users/interfaces.py +127 -0
- pyclifer-0.4.1/src/pyclifer/apps/demo/apps/users/models.py +18 -0
- pyclifer-0.4.1/src/pyclifer/apps/demo/apps/users/tables.py +5 -0
- pyclifer-0.4.1/src/pyclifer/apps/demo/commands/__init__.py +3 -0
- pyclifer-0.4.1/src/pyclifer/apps/demo/core/__init__.py +1 -0
- pyclifer-0.4.1/src/pyclifer/apps/demo/core/constants.py +9 -0
- pyclifer-0.4.1/src/pyclifer/apps/demo/core/context.py +28 -0
- pyclifer-0.4.1/src/pyclifer/apps/demo/core/options.py +12 -0
- pyclifer-0.4.1/src/pyclifer/apps/demo/core/storage.py +156 -0
- pyclifer-0.4.1/src/pyclifer/apps/demo/interfaces.py +24 -0
- pyclifer-0.4.1/src/pyclifer/apps/demo/models.py +12 -0
- pyclifer-0.4.1/src/pyclifer/apps/demo/tables.py +5 -0
- pyclifer-0.4.1/src/pyclifer/apps/project/__init__.py +14 -0
- pyclifer-0.4.1/src/pyclifer/apps/project/commands/__init__.py +9 -0
- pyclifer-0.4.1/src/pyclifer/apps/project/commands/add/__init__.py +19 -0
- pyclifer-0.4.1/src/pyclifer/apps/project/commands/add/app.py +27 -0
- pyclifer-0.4.1/src/pyclifer/apps/project/commands/add/command.py +19 -0
- pyclifer-0.4.1/src/pyclifer/apps/project/commands/add/group_cmd.py +14 -0
- pyclifer-0.4.1/src/pyclifer/apps/project/commands/add/integration.py +16 -0
- pyclifer-0.4.1/src/pyclifer/apps/project/commands/init.py +36 -0
- pyclifer-0.4.1/src/pyclifer/apps/project/interfaces.py +633 -0
- pyclifer-0.4.1/src/pyclifer/apps/project/renderers.py +121 -0
- pyclifer-0.4.1/src/pyclifer/apps/project/tables.py +57 -0
- pyclifer-0.4.1/src/pyclifer/apps/project/templates/app_commands_init.py.jinja2 +3 -0
- pyclifer-0.4.1/src/pyclifer/apps/project/templates/app_core_constants.py.jinja2 +1 -0
- pyclifer-0.4.1/src/pyclifer/apps/project/templates/app_core_context.py.jinja2 +10 -0
- pyclifer-0.4.1/src/pyclifer/apps/project/templates/app_core_init.py.jinja2 +1 -0
- pyclifer-0.4.1/src/pyclifer/apps/project/templates/app_core_options.py.jinja2 +1 -0
- pyclifer-0.4.1/src/pyclifer/apps/project/templates/app_init.py.jinja2 +19 -0
- pyclifer-0.4.1/src/pyclifer/apps/project/templates/app_init_flat.py.jinja2 +3 -0
- pyclifer-0.4.1/src/pyclifer/apps/project/templates/app_init_with_core.py.jinja2 +21 -0
- pyclifer-0.4.1/src/pyclifer/apps/project/templates/app_interfaces.py.jinja2 +26 -0
- pyclifer-0.4.1/src/pyclifer/apps/project/templates/app_interfaces_with_core.py.jinja2 +29 -0
- pyclifer-0.4.1/src/pyclifer/apps/project/templates/app_models.py.jinja2 +12 -0
- pyclifer-0.4.1/src/pyclifer/apps/project/templates/app_tables.py.jinja2 +5 -0
- pyclifer-0.4.1/src/pyclifer/apps/project/templates/command.py.jinja2 +11 -0
- pyclifer-0.4.1/src/pyclifer/apps/project/templates/gitignore.jinja2 +19 -0
- pyclifer-0.4.1/src/pyclifer/apps/project/templates/integration_package_client.py.jinja2 +8 -0
- pyclifer-0.4.1/src/pyclifer/apps/project/templates/integration_package_helpers.py.jinja2 +1 -0
- pyclifer-0.4.1/src/pyclifer/apps/project/templates/integration_package_init.py.jinja2 +10 -0
- pyclifer-0.4.1/src/pyclifer/apps/project/templates/integration_package_models.py.jinja2 +1 -0
- pyclifer-0.4.1/src/pyclifer/apps/project/templates/integration_simple.py.jinja2 +8 -0
- pyclifer-0.4.1/src/pyclifer/apps/project/templates/project_apps_init.py.jinja2 +3 -0
- pyclifer-0.4.1/src/pyclifer/apps/project/templates/project_cli.py.jinja2 +16 -0
- pyclifer-0.4.1/src/pyclifer/apps/project/templates/project_constants.py.jinja2 +1 -0
- pyclifer-0.4.1/src/pyclifer/apps/project/templates/project_context.py.jinja2 +11 -0
- pyclifer-0.4.1/src/pyclifer/apps/project/templates/project_integrations_init.py.jinja2 +1 -0
- pyclifer-0.4.1/src/pyclifer/apps/project/templates/project_options.py.jinja2 +1 -0
- pyclifer-0.4.1/src/pyclifer/apps/project/templates/project_package_init.py.jinja2 +1 -0
- pyclifer-0.4.1/src/pyclifer/apps/project/templates/pyproject_poetry.toml.jinja2 +48 -0
- pyclifer-0.4.1/src/pyclifer/apps/project/templates/pyproject_uv.toml.jinja2 +51 -0
- pyclifer-0.4.1/src/pyclifer/apps/project/templates/readme.md.jinja2 +41 -0
- pyclifer-0.4.1/src/pyclifer/apps/project/templates/tests_conftest.py.jinja2 +18 -0
- pyclifer-0.4.1/src/pyclifer/apps/project/templates/tests_init.py.jinja2 +0 -0
- pyclifer-0.4.1/src/pyclifer/cli.py +18 -0
- pyclifer-0.4.1/src/pyclifer/core/__init__.py +23 -0
- pyclifer-0.4.1/src/pyclifer/core/callbacks.py +42 -0
- pyclifer-0.4.1/src/pyclifer/core/classes.py +270 -0
- pyclifer-0.4.1/src/pyclifer/core/context.py +34 -0
- pyclifer-0.4.1/src/pyclifer/core/decorators.py +735 -0
- pyclifer-0.4.1/src/pyclifer/core/interfaces/__init__.py +5 -0
- pyclifer-0.4.1/src/pyclifer/core/interfaces/base.py +88 -0
- pyclifer-0.4.1/src/pyclifer/core/log/__init__.py +44 -0
- pyclifer-0.4.1/src/pyclifer/core/log/config.py +319 -0
- pyclifer-0.4.1/src/pyclifer/core/log/filters.py +169 -0
- pyclifer-0.4.1/src/pyclifer/core/log/formatters.py +45 -0
- pyclifer-0.4.1/src/pyclifer/core/log/handlers.py +71 -0
- pyclifer-0.4.1/src/pyclifer/core/log/levels.py +59 -0
- pyclifer-0.4.1/src/pyclifer/core/mixins/__init__.py +14 -0
- pyclifer-0.4.1/src/pyclifer/core/mixins/cli.py +70 -0
- pyclifer-0.4.1/src/pyclifer/core/mixins/output.py +284 -0
- pyclifer-0.4.1/src/pyclifer/core/mixins/response.py +150 -0
- pyclifer-0.4.1/src/pyclifer/core/mixins/rich.py +105 -0
- pyclifer-0.4.1/src/pyclifer/core/models.py +49 -0
- pyclifer-0.4.1/src/pyclifer/core/output/__init__.py +18 -0
- pyclifer-0.4.1/src/pyclifer/core/output/exit_codes.py +59 -0
- pyclifer-0.4.1/src/pyclifer/core/output/renderer.py +328 -0
- pyclifer-0.4.1/src/pyclifer/core/output/responses.py +229 -0
- pyclifer-0.4.1/src/pyclifer/core/output/tables.py +200 -0
- pyclifer-0.4.1/src/pyclifer/core/rich_help_config.py +119 -0
- pyclifer-0.4.1/tests/__init__.py +0 -0
- pyclifer-0.4.1/tests/apps/__init__.py +0 -0
- pyclifer-0.4.1/tests/apps/demo/__init__.py +0 -0
- pyclifer-0.4.1/tests/apps/demo/apps/__init__.py +0 -0
- pyclifer-0.4.1/tests/apps/demo/apps/tasks/__init__.py +0 -0
- pyclifer-0.4.1/tests/apps/demo/apps/tasks/test_commands.py +193 -0
- pyclifer-0.4.1/tests/apps/demo/apps/tasks/test_interfaces.py +167 -0
- pyclifer-0.4.1/tests/apps/demo/apps/tasks/test_models.py +45 -0
- pyclifer-0.4.1/tests/apps/demo/apps/tasks/test_renderers.py +147 -0
- pyclifer-0.4.1/tests/apps/demo/apps/users/__init__.py +0 -0
- pyclifer-0.4.1/tests/apps/demo/apps/users/test_commands.py +81 -0
- pyclifer-0.4.1/tests/apps/demo/apps/users/test_interfaces.py +90 -0
- pyclifer-0.4.1/tests/apps/demo/apps/users/test_models.py +24 -0
- pyclifer-0.4.1/tests/apps/demo/conftest.py +29 -0
- pyclifer-0.4.1/tests/apps/demo/core/__init__.py +0 -0
- pyclifer-0.4.1/tests/apps/demo/core/test_storage.py +112 -0
- pyclifer-0.4.1/tests/apps/project/__init__.py +0 -0
- pyclifer-0.4.1/tests/apps/project/test_interfaces.py +540 -0
- pyclifer-0.4.1/tests/apps/project/test_tables.py +67 -0
- pyclifer-0.4.1/tests/conftest.py +48 -0
- pyclifer-0.4.1/tests/core/__init__.py +0 -0
- pyclifer-0.4.1/tests/core/log/__init__.py +0 -0
- pyclifer-0.4.1/tests/core/log/test_file_logging.py +171 -0
- pyclifer-0.4.1/tests/core/log/test_rich_logging.py +695 -0
- pyclifer-0.4.1/tests/core/log/test_verbosity_default.py +278 -0
- pyclifer-0.4.1/tests/core/mixins/__init__.py +0 -0
- pyclifer-0.4.1/tests/core/mixins/test_output.py +451 -0
- pyclifer-0.4.1/tests/core/mixins/test_response.py +90 -0
- pyclifer-0.4.1/tests/core/mixins/test_rich.py +105 -0
- pyclifer-0.4.1/tests/core/options/__init__.py +0 -0
- pyclifer-0.4.1/tests/core/options/test_custom_config_option.py +345 -0
- pyclifer-0.4.1/tests/core/options/test_global_options.py +151 -0
- pyclifer-0.4.1/tests/core/output/__init__.py +0 -0
- pyclifer-0.4.1/tests/core/output/test_exit_codes.py +116 -0
- pyclifer-0.4.1/tests/core/output/test_responses.py +385 -0
- pyclifer-0.4.1/tests/core/output/test_tables.py +208 -0
- pyclifer-0.4.1/tests/core/test_callbacks.py +81 -0
- pyclifer-0.4.1/tests/core/test_context.py +59 -0
- pyclifer-0.4.1/tests/core/test_decorators.py +440 -0
- pyclifer-0.4.1/tests/core/test_interfaces.py +278 -0
- pyclifer-0.4.1/tests/core/test_models.py +132 -0
- pyclifer-0.4.1/tests/core/test_renderer.py +343 -0
- pyclifer-0.4.1/tests/core/test_returns_response.py +579 -0
- pyclifer-0.4.1/tests/core/test_rich_help_formatting.py +182 -0
- pyclifer-0.4.1/tests/core/test_timer_option.py +94 -0
- pyclifer-0.4.1/uv.lock +1523 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# Code Style Rules
|
|
2
|
+
|
|
3
|
+
Rules for Claude Code when writing or modifying code in this repository.
|
|
4
|
+
|
|
5
|
+
## Type Hints
|
|
6
|
+
|
|
7
|
+
Use **PEP 585 built-in generics** (Python 3.10+) — do NOT import from `typing` for these:
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
# Correct
|
|
11
|
+
def foo(items: list[str], mapping: dict[str, Any]) -> str | None: ...
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Wrong
|
|
15
|
+
from typing import List, Dict, Optional
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def foo(items: List[str], mapping: Dict[str, Any]) -> Optional[str]: ...
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Always annotate return types. Use `Any` from `typing` when genuinely dynamic.
|
|
22
|
+
`Callable`, `Union` (when needed), and `TYPE_CHECKING` still come from `typing`.
|
|
23
|
+
|
|
24
|
+
## Docstrings
|
|
25
|
+
|
|
26
|
+
**Google style**, required on all public classes, methods, functions, and nested functions.
|
|
27
|
+
Private helpers (`_name`) use a single-line docstring only if the purpose is not obvious from the name.
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
def my_function(value: str, count: int = 1) -> list[str]:
|
|
31
|
+
"""Short imperative description (no period at the end for one-liners).
|
|
32
|
+
|
|
33
|
+
Longer explanation if needed. Omit if the one-liner is enough.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
value: Description of value.
|
|
37
|
+
count: Description of count.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Description of the return value.
|
|
41
|
+
|
|
42
|
+
Raises:
|
|
43
|
+
ValueError: When the value is empty.
|
|
44
|
+
"""
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
- First line: imperative mood, no trailing period, fits on one line.
|
|
48
|
+
- Blank line between summary and `Args:` / `Returns:` / `Raises:` sections.
|
|
49
|
+
- Do NOT include type information in `Args:` / `Returns:` — it is already in the signature.
|
|
50
|
+
- No `Methods:` section in class docstrings — method docstrings are self-contained.
|
|
51
|
+
|
|
52
|
+
### Markup: plain text only
|
|
53
|
+
|
|
54
|
+
Docstrings must use **plain prose** — no reStructuredText or Sphinx markup.
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
# Wrong — reStructuredText syntax
|
|
58
|
+
"""Do something with ctx.meta.
|
|
59
|
+
|
|
60
|
+
:param name: The name.
|
|
61
|
+
:type name: str
|
|
62
|
+
:returns: The result.
|
|
63
|
+
:rtype: str
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
# Wrong — double-backtick / cross-reference markup
|
|
67
|
+
"""Store value in ``ctx.meta``. See :class:`~pyclifer.Response`."""
|
|
68
|
+
|
|
69
|
+
# Wrong — directive blocks
|
|
70
|
+
"""Example:
|
|
71
|
+
|
|
72
|
+
.. code-block:: bash
|
|
73
|
+
|
|
74
|
+
myapp hello
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
# Correct — plain text, inline code with single backticks only in narrative prose
|
|
78
|
+
"""Store value in ctx.meta."""
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Allowed: single backticks `` ` `` inside narrative prose when referring to code names.
|
|
82
|
+
Forbidden: `` `` `` (double backtick), `:param:`, `:type:`, `:rtype:`, `:class:`, `:func:`,
|
|
83
|
+
`:meth:`, `.. directive::`.
|
|
84
|
+
|
|
85
|
+
## Imports
|
|
86
|
+
|
|
87
|
+
PEP 8 order with a blank line between groups:
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
# 1. Standard library
|
|
91
|
+
import logging
|
|
92
|
+
from dataclasses import dataclass
|
|
93
|
+
from typing import Any, Callable, TYPE_CHECKING
|
|
94
|
+
|
|
95
|
+
# 2. Third-party
|
|
96
|
+
import click_extra
|
|
97
|
+
from rich.console import Console
|
|
98
|
+
|
|
99
|
+
# 3. Local (relative preferred inside the package)
|
|
100
|
+
from .mixins import OutputFormatMixin
|
|
101
|
+
from pyclifer.core.output import Response
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
- Prefer `from x import y` over `import x.y`.
|
|
105
|
+
- **Prefer `click_extra` over `click` directly** — `click_extra` re-exports the full Click API
|
|
106
|
+
(`Context`, `Parameter`, `get_current_context`, etc.) and is the declared project dependency.
|
|
107
|
+
Never `import click` when `click_extra` provides the same symbol.
|
|
108
|
+
- Use **lazy imports** (inside the function body) only to break circular dependencies — always
|
|
109
|
+
add a comment explaining why.
|
|
110
|
+
|
|
111
|
+
## Naming
|
|
112
|
+
|
|
113
|
+
| Kind | Convention | Example |
|
|
114
|
+
|-----------------------|----------------------|----------------------------|
|
|
115
|
+
| Module / file | `snake_case` | `rich_help_config.py` |
|
|
116
|
+
| Class | `PascalCase` | `HandleResponseMixin` |
|
|
117
|
+
| Function / method | `snake_case` | `configure_rich_logging()` |
|
|
118
|
+
| Private method / attr | `_single_underscore` | `_apply_click_group()` |
|
|
119
|
+
| Constant | `UPPER_SNAKE` | `PYCLIFER_LOG_LEVELS` |
|
|
120
|
+
|
|
121
|
+
Never use double-underscore (`__dunder`) for private names — reserve those for Python protocol
|
|
122
|
+
methods.
|
|
123
|
+
|
|
124
|
+
## Classes
|
|
125
|
+
|
|
126
|
+
- **Mixin inheritance order**: mixins first, concrete base class last.
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
class PycliferRichGroup(HandleResponseMixin, GlobalOptionsMixin, RichGroup):
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
- Use `@dataclass` for configuration / data-holding objects (`GroupConfig`, `Response`).
|
|
133
|
+
- Avoid no-op `__post_init__` bodies.
|
|
134
|
+
|
|
135
|
+
## Decorator Ordering on Commands
|
|
136
|
+
|
|
137
|
+
Top to bottom: registration decorator → framework/option decorators → `@pass_cli_context`
|
|
138
|
+
or `@click.pass_context` → function.
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
@app.command() # registers with group
|
|
142
|
+
@output_filter_option() # custom pyclifer decorators
|
|
143
|
+
@returns_response # closest to function
|
|
144
|
+
@option("--name", default="world") # click options
|
|
145
|
+
@pass_cli_context # always last before def (or @click.pass_context in cli.py)
|
|
146
|
+
def hello(ctx, name): ...
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Note: `@click.pass_context` is used only in `cli.py` to build `ctx.obj`. Everywhere else,
|
|
150
|
+
use `pass_cli_context` (generated by `make_pass_decorator`) for typed context access.
|
|
151
|
+
|
|
152
|
+
## Exception Handling
|
|
153
|
+
|
|
154
|
+
- Catch specific exception types; avoid bare `except Exception` unless you log and re-raise
|
|
155
|
+
or have a documented reason.
|
|
156
|
+
- When catching multiple types, list them as a tuple with a comment explaining each.
|
|
157
|
+
- Raise `RuntimeError` for programming errors (missing callbacks, invalid state).
|
|
158
|
+
- Raise `ValueError` for bad input.
|
|
159
|
+
|
|
160
|
+
## Code quality
|
|
161
|
+
|
|
162
|
+
Every change must pass `ruff check` and `ruff format` before being committed. Run both on the
|
|
163
|
+
affected files before declaring a task done:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
ruff check src/ tests/
|
|
167
|
+
ruff format src/ tests/
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Fix all reported issues — do not suppress warnings with `# noqa` unless the rule is genuinely
|
|
171
|
+
inapplicable and the reason is documented on the same line.
|
|
172
|
+
|
|
173
|
+
## Tests
|
|
174
|
+
|
|
175
|
+
- One test file per source module, mirroring the `src/` layout under `tests/`.
|
|
176
|
+
- Test classes: `Test<Subject>` (e.g., `TestOutputFilterOption`).
|
|
177
|
+
- Test methods: `test_<what_is_being_tested>` — descriptive, no abbreviations.
|
|
178
|
+
- Use `pytest.raises(ExcType, match="…")` — always include a `match` pattern.
|
|
179
|
+
- Prefer `MagicMock` over `Mock`; use `@patch` for module-level targets.
|
|
180
|
+
- Do not assert on mock call count unless the count itself is the behavior under test.
|
|
181
|
+
- Target 100% branch coverage for every module touched by a change. If a branch is genuinely
|
|
182
|
+
untestable (e.g., a defensive guard for an impossible state), mark it with `# pragma: no cover`
|
|
183
|
+
and add a comment explaining why.
|
|
184
|
+
|
|
185
|
+
## File placement
|
|
186
|
+
|
|
187
|
+
Code must go in the file that matches its kind — never mix concerns across modules:
|
|
188
|
+
|
|
189
|
+
| Kind | File |
|
|
190
|
+
|-----------------------------------------------|---------------------------|
|
|
191
|
+
| Decorators (`@app_group`, `@command`, …) | `core/decorators.py` |
|
|
192
|
+
| Click subclasses, config dataclasses | `core/classes.py` |
|
|
193
|
+
| Reusable mixin classes | `core/mixins/` |
|
|
194
|
+
| `BaseContext` and context helpers | `core/context.py` |
|
|
195
|
+
| `Response`, `OperationResult`, output helpers | `core/output/` |
|
|
196
|
+
| `BaseRenderer`, `ResponseRenderer` Protocol | `core/output/renderer.py` |
|
|
197
|
+
| `BaseInterface` and `respond()` machinery | `core/interfaces/base.py` |
|
|
198
|
+
| Logging helpers, `get_logger` | `core/log/` |
|
|
199
|
+
|
|
200
|
+
When adding a new class or function, pick the file whose existing contents are the closest
|
|
201
|
+
match. If none fits, create a new module rather than placing it in a nearby but wrong one.
|
|
202
|
+
|
|
203
|
+
The API docs in `docs/api/` mirror this structure — a symbol documented in the wrong page
|
|
204
|
+
(e.g., a class from `classes.py` appearing in `decorators.md`) is a bug:
|
|
205
|
+
|
|
206
|
+
| Doc page | What belongs there |
|
|
207
|
+
|---------------------|-----------------------------------------------------------------------------------------------------|
|
|
208
|
+
| `api/decorators.md` | The four decorators + `returns_response` + `output_filter_option` |
|
|
209
|
+
| `api/classes.md` | `PycliferOption`, `PycliferGroup`, `CustomConfigOption`, `PycliferTimerOption` and any new Click subclass |
|
|
210
|
+
| `api/mixins.md` | All mixin classes from `core/mixins/` |
|
|
211
|
+
| `api/context.md` | `BaseContext` and context helpers |
|
|
212
|
+
| `api/output.md` | `Response`, `OperationResult`, tables, `BaseRenderer`, `ResponseRenderer` |
|
|
213
|
+
| `api/interfaces.md` | `BaseInterface` and `respond()` machinery |
|
|
214
|
+
| `api/logging.md` | Logging helpers and `get_logger` |
|
|
215
|
+
|
|
216
|
+
## Documentation
|
|
217
|
+
|
|
218
|
+
- Every new `docs/*.md` page must be added to the `nav` section of `mkdocs.yml` in the same
|
|
219
|
+
commit. Never create a documentation page without registering it.
|
|
220
|
+
|
|
221
|
+
## Git Workflow
|
|
222
|
+
|
|
223
|
+
- Always create a feature branch (`feat/<name>`) before starting implementation of a spec or
|
|
224
|
+
any non-trivial change. Do this before touching the first file.
|
|
225
|
+
- After merging into `main`, delete the feature branch immediately.
|
|
226
|
+
- Never push or merge without the user's explicit validation.
|
|
227
|
+
|
|
228
|
+
### Commit message format
|
|
229
|
+
|
|
230
|
+
Every commit must follow this exact structure:
|
|
231
|
+
|
|
232
|
+
```
|
|
233
|
+
<gitmoji> <type>(<scope>): <imperative summary>
|
|
234
|
+
|
|
235
|
+
- bullet describing why / what changed
|
|
236
|
+
- bullet for each meaningful change
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
- **gitmoji**: coherent with the type (`✨` feat, `🐛` fix, `♻️` refactor, `📝` docs, `✅` test, `🔧` chore, etc.)
|
|
240
|
+
- **type**: `feat`, `fix`, `refactor`, `perf`, `docs`, `style`, `test`, `chore`, `ci`, `build`
|
|
241
|
+
- **scope**: optional, use the module name (e.g. `tables`, `decorators`, `log`)
|
|
242
|
+
- **summary**: imperative mood, ≤50 chars, no trailing period
|
|
243
|
+
- **body**: bullet list, each item explains *why* not just *what*; omit if summary is self-explanatory
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
# Mise à jour des dépendances : click-extra, rich-click, rich
|
|
2
|
+
|
|
3
|
+
> **Pour les agents :** SUB-SKILL REQUIS : Utiliser superpowers:subagent-driven-development (recommandé) ou superpowers:executing-plans pour exécuter ce plan tâche par tâche. Les étapes utilisent la syntaxe checkbox (`- [ ]`) pour le suivi.
|
|
4
|
+
|
|
5
|
+
**Objectif :** Monter `click-extra` 7.16.1→7.18.0, `rich` 13.9.4→15.0.0, `rich-click` 1.9.7→1.9.8 sans régression.
|
|
6
|
+
|
|
7
|
+
**Architecture :** Simple montée de version — aucun changement de code source attendu. Les trois mises à jour sont couplées : click-extra 7.17.0+ tire Click 8.4.1 (contre 8.3.3 actuellement), et rich-click 1.9.8 a été publié précisément pour corriger la compatibilité avec Click 8.4.x. Les trois doivent arriver ensemble. La contrainte `rich>=13.0` autorise déjà la 15.x ; seules les bornes minimales changent.
|
|
8
|
+
|
|
9
|
+
**Stack :** uv, pytest, ruff, click-extra, rich, rich-click, click (transitif)
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Audit des breaking changes (pré-recherche, ne pas sauter)
|
|
14
|
+
|
|
15
|
+
### rich 13.9.4 → 15.0.0
|
|
16
|
+
|
|
17
|
+
| Version | Breaking change | Impact sur pyclifer |
|
|
18
|
+
|---------|----------------|-----------------|
|
|
19
|
+
| 14.0.0 | `NO_COLOR=` (vide) ne désactive plus les couleurs ; avant, toute présence de la variable suffisait | Aucun test ni CI ne définit `NO_COLOR=` — confirmé par grep |
|
|
20
|
+
| 14.0.0 | `FORCE_COLOR=` (vide) — même règle | Idem — aucun impact |
|
|
21
|
+
| 15.0.0 | Python 3.8 abandonné | pyclifer requiert ≥3.10 — aucun impact |
|
|
22
|
+
|
|
23
|
+
Aucune API supprimée ou renommée utilisée par pyclifer (`Console`, `Panel`, `Table`, `RichHandler`, `Live`, `Status`, `Prompt`, `Rule`, `Syntax`, `Text`, `box` — tous inchangés).
|
|
24
|
+
|
|
25
|
+
Effets visuels (pas des breaking changes, mais observables) :
|
|
26
|
+
- Fond du titre des `Panel` corrigé (14.1.0) — les panneaux s'afficheront plus correctement
|
|
27
|
+
- L'imbrication de `Live` est maintenant autorisée (14.1.0)
|
|
28
|
+
|
|
29
|
+
### click-extra 7.16.1 → 7.18.0
|
|
30
|
+
|
|
31
|
+
| Symbole supprimé | Utilisé dans pyclifer ? | Verdict |
|
|
32
|
+
|-----------------|----------------------|---------|
|
|
33
|
+
| Module `click_extra.themes` | Non | Sûr |
|
|
34
|
+
| Constantes `DARK`, `DRACULA`, `LIGHT`, `MONOKAI`, `NORD`, `SOLARIZED_DARK` | Non | Sûr |
|
|
35
|
+
| Attribut `default_theme` | Non — pyclifer utilise déjà `get_default_theme()` dans `core/log/formatters.py:5` | Sûr |
|
|
36
|
+
| Constantes pré-rendues `OK`, `KO` | Non | Sûr |
|
|
37
|
+
| Attribut `ctx.telemetry` | Non | Sûr |
|
|
38
|
+
| Callback `disable_colors` sur `ColorOption` | Non | Sûr |
|
|
39
|
+
| **Chemins de sous-modules** `click_extra.jobs`, `click_extra.timer` supprimés | `TimerOption` est utilisé (`classes.py:11`, `decorators.py:113`) mais importé via `from click_extra import TimerOption` (racine) — pas via le chemin de sous-module supprimé. | Sûr |
|
|
40
|
+
| `ValueError` au chargement de `--config` → maintenant `SystemExit` | Aucun test n'exerce ce chemin d'erreur | Sûr |
|
|
41
|
+
|
|
42
|
+
Upgrade transitif : Click 8.3.3 → 8.4.1 (requis par click-extra 7.17.0+). Aucune suppression d'API Click 8.4.x n'affecte pyclifer.
|
|
43
|
+
|
|
44
|
+
### rich-click 1.9.7 → 1.9.8
|
|
45
|
+
|
|
46
|
+
Uniquement un correctif : répare le mécanisme de patching de rich-click cassé par Click 8.4.0. **Doit être mis à jour en même temps que click-extra.**
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Fichiers à modifier
|
|
51
|
+
|
|
52
|
+
| Fichier | Changement |
|
|
53
|
+
|---------|-----------|
|
|
54
|
+
| `pyproject.toml` | Monter trois bornes minimales de dépendances |
|
|
55
|
+
| `uv.lock` | Régénéré automatiquement par `uv sync` |
|
|
56
|
+
|
|
57
|
+
Aucun changement de fichier source ou de test prévu. Si un test échoue après la mise à jour, une tâche de correction est ajoutée à ce moment-là.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Tâche 1 : Établir la baseline (les tests passent sur les versions actuelles)
|
|
62
|
+
|
|
63
|
+
**Fichiers :** (lecture seule)
|
|
64
|
+
|
|
65
|
+
- [ ] **Étape 1 : Lancer la suite de tests complète sur les versions actuelles**
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
python -m pytest tests/ -v
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Attendu : tous les tests passent. Si des tests échouent avant la mise à jour, stop — les corriger séparément d'abord.
|
|
72
|
+
|
|
73
|
+
- [ ] **Étape 2 : Enregistrer les versions installées pour le message de commit**
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
pip show click-extra rich rich-click | grep -E "Name:|Version:"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Sortie attendue (actuelle) :
|
|
80
|
+
```
|
|
81
|
+
Name: click-extra
|
|
82
|
+
Version: 7.16.1
|
|
83
|
+
Name: rich
|
|
84
|
+
Version: 13.9.4
|
|
85
|
+
Name: rich-click
|
|
86
|
+
Version: 1.9.7
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Tâche 2 : Mettre à jour les contraintes de version dans pyproject.toml
|
|
92
|
+
|
|
93
|
+
**Fichiers :**
|
|
94
|
+
- Modifier : `pyproject.toml` lignes 12–14
|
|
95
|
+
|
|
96
|
+
Mise à jour des bornes minimales vers les versions testées, pour que `uv sync --resolution lowest` reste sur des versions connues et stables.
|
|
97
|
+
|
|
98
|
+
- [ ] **Étape 1 : Modifier les trois lignes de dépendances**
|
|
99
|
+
|
|
100
|
+
Dans `pyproject.toml`, remplacer :
|
|
101
|
+
|
|
102
|
+
```toml
|
|
103
|
+
"click-extra>=7.16.1,<8.0.0",
|
|
104
|
+
"rich>=13.0",
|
|
105
|
+
"rich-click>=1.9.7,<2.0.0",
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
par :
|
|
109
|
+
|
|
110
|
+
```toml
|
|
111
|
+
"click-extra>=7.18.0,<8.0.0",
|
|
112
|
+
"rich>=15.0.0",
|
|
113
|
+
"rich-click>=1.9.8,<2.0.0",
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
- [ ] **Étape 2 : Vérifier la modification**
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
grep -E "click-extra|rich-click|rich" pyproject.toml
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Attendu :
|
|
123
|
+
```
|
|
124
|
+
"click-extra>=7.18.0,<8.0.0",
|
|
125
|
+
"rich>=15.0.0",
|
|
126
|
+
"rich-click>=1.9.8,<2.0.0",
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Tâche 3 : Installer les dépendances mises à jour
|
|
132
|
+
|
|
133
|
+
**Fichiers :** `uv.lock` (régénéré automatiquement)
|
|
134
|
+
|
|
135
|
+
- [ ] **Étape 1 : Synchroniser avec les nouvelles contraintes**
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
uv sync --extra dev,docs
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Attendu : uv résout et installe click-extra 7.18.0, rich 15.0.0, rich-click 1.9.8, et click 8.4.1 (transitif). Aucune erreur.
|
|
142
|
+
|
|
143
|
+
- [ ] **Étape 2 : Confirmer les versions installées**
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
pip show click-extra rich rich-click click | grep -E "Name:|Version:"
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Attendu :
|
|
150
|
+
```
|
|
151
|
+
Name: click
|
|
152
|
+
Version: 8.4.1
|
|
153
|
+
Name: click-extra
|
|
154
|
+
Version: 7.18.0
|
|
155
|
+
Name: rich
|
|
156
|
+
Version: 15.0.0
|
|
157
|
+
Name: rich-click
|
|
158
|
+
Version: 1.9.8
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Tâche 4 : Lancer la suite de tests complète sur les nouvelles versions
|
|
164
|
+
|
|
165
|
+
**Fichiers :** (lecture seule)
|
|
166
|
+
|
|
167
|
+
- [ ] **Étape 1 : Lancer tous les tests**
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
python -m pytest tests/ -v
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Attendu : tous les tests passent, couverture ≥80%.
|
|
174
|
+
|
|
175
|
+
Si un test échoue → aller à la Tâche 5. Si tout passe → aller directement à la Tâche 6.
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Tâche 5 : Corriger les échecs de tests (conditionnelle — uniquement si la Tâche 4 a des échecs)
|
|
180
|
+
|
|
181
|
+
Sauter entièrement si la Tâche 4 passe.
|
|
182
|
+
|
|
183
|
+
Catégories d'échecs probables et corrections :
|
|
184
|
+
|
|
185
|
+
**A. Test qui vérifie le rendu exact d'un `Panel`**
|
|
186
|
+
|
|
187
|
+
Cause : rich 14.1.0 a corrigé le style du fond du titre des `Panel` — la sortie rendue peut différer.
|
|
188
|
+
|
|
189
|
+
Correction : mettre à jour la chaîne attendue dans l'assertion pour correspondre à la nouvelle sortie (correcte).
|
|
190
|
+
|
|
191
|
+
Motif à rechercher :
|
|
192
|
+
```bash
|
|
193
|
+
grep -rn "Panel\|panel" tests/ | grep -i "assert\|expect"
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**B. Test qui vérifie `click.UsageError` depuis `help` sur une sous-commande inconnue**
|
|
197
|
+
|
|
198
|
+
Cause : click-extra 7.17.0 a changé `HelpCommand` pour lever `click.NoSuchCommand` au lieu de `click.UsageError`.
|
|
199
|
+
|
|
200
|
+
Correction :
|
|
201
|
+
```python
|
|
202
|
+
# Avant
|
|
203
|
+
with pytest.raises(click.UsageError, match="No such command"):
|
|
204
|
+
|
|
205
|
+
# Après
|
|
206
|
+
with pytest.raises(click.exceptions.NoSuchCommand, match="..."):
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
**C. Test qui vérifie `ValueError` au chargement de `--config`**
|
|
210
|
+
|
|
211
|
+
Cause : click-extra 7.18.0 a changé les erreurs de chargement de config de `ValueError` en `SystemExit`.
|
|
212
|
+
|
|
213
|
+
Correction :
|
|
214
|
+
```python
|
|
215
|
+
# Avant
|
|
216
|
+
with pytest.raises(ValueError, match="..."):
|
|
217
|
+
|
|
218
|
+
# Après
|
|
219
|
+
with pytest.raises(SystemExit):
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Après chaque correction :
|
|
223
|
+
```bash
|
|
224
|
+
python -m pytest tests/ -v
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Lancer ruff avant de committer :
|
|
228
|
+
```bash
|
|
229
|
+
ruff check src/ tests/
|
|
230
|
+
ruff format src/ tests/
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## Tâche 6 : Vérification du linting
|
|
236
|
+
|
|
237
|
+
- [ ] **Étape 1 : Lancer ruff**
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
ruff check src/ tests/
|
|
241
|
+
ruff format src/ tests/
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Attendu : aucune erreur, aucun reformatage nécessaire.
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Tâche 7 : Commit
|
|
249
|
+
|
|
250
|
+
- [ ] **Étape 1 : Stager les fichiers modifiés**
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
git add pyproject.toml uv.lock
|
|
254
|
+
# Si la Tâche 5 a tourné, ajouter aussi : git add <fichiers de tests corrigés>
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
- [ ] **Étape 2 : Créer le commit**
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
git commit -m "$(cat <<'EOF'
|
|
261
|
+
🔧 chore(deps): upgrade click-extra, rich, rich-click
|
|
262
|
+
|
|
263
|
+
- click-extra 7.16.1 → 7.18.0 (tire Click 8.3.3 → 8.4.1 transitivement)
|
|
264
|
+
- rich 13.9.4 → 15.0.0 (abandonne Python 3.8 ; pyclifer requiert 3.10+)
|
|
265
|
+
- rich-click 1.9.7 → 1.9.8 (corrige la régression de compatibilité Click 8.4.x)
|
|
266
|
+
EOF
|
|
267
|
+
)"
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## Checklist de relecture
|
|
273
|
+
|
|
274
|
+
- [x] Les trois packages couverts avec des numéros de version précis
|
|
275
|
+
- [x] Breaking changes recherchés et mappés sur la surface de pyclifer
|
|
276
|
+
- [x] Upgrade couplé (click-extra + rich-click + montée Click transitive) documenté
|
|
277
|
+
- [x] Tâche de correction conditionnelle (Tâche 5) couvre les trois catégories d'échecs les plus probables
|
|
278
|
+
- [x] Aucun placeholder — chaque étape a des commandes exactes et une sortie attendue
|
|
279
|
+
- [x] Message de commit respecte le format du projet défini dans `.claude/CLAUDE.md`
|