ask-shell 0.7.0__tar.gz → 0.8.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.
- {ask_shell-0.7.0 → ask_shell-0.8.0}/PKG-INFO +1 -1
- {ask_shell-0.7.0 → ask_shell-0.8.0}/ask_shell/__init__.py +1 -1
- {ask_shell-0.7.0 → ask_shell-0.8.0}/ask_shell/_internal/_run.py +3 -3
- {ask_shell-0.7.0 → ask_shell-0.8.0}/ask_shell/_internal/_run_env.py +9 -0
- {ask_shell-0.7.0 → ask_shell-0.8.0}/ask_shell/_internal/rich_live.py +10 -1
- {ask_shell-0.7.0 → ask_shell-0.8.0}/ask_shell/settings.py +4 -0
- {ask_shell-0.7.0 → ask_shell-0.8.0}/ask_shell/test_docs.py +8 -2
- ask_shell-0.8.0/examples/apply_live/__init__.py +0 -0
- ask_shell-0.8.0/examples/apply_live/cli.py +73 -0
- ask_shell-0.8.0/examples/apply_live/stream_handler.py +180 -0
- {ask_shell-0.7.0 → ask_shell-0.8.0}/pyproject.toml +18 -1
- {ask_shell-0.7.0 → ask_shell-0.8.0}/.gitignore +0 -0
- {ask_shell-0.7.0 → ask_shell-0.8.0}/LICENSE +0 -0
- {ask_shell-0.7.0 → ask_shell-0.8.0}/README.md +0 -0
- {ask_shell-0.7.0 → ask_shell-0.8.0}/ask_shell/__main__.py +0 -0
- {ask_shell-0.7.0 → ask_shell-0.8.0}/ask_shell/_internal/__init__.py +0 -0
- {ask_shell-0.7.0 → ask_shell-0.8.0}/ask_shell/_internal/events.py +0 -0
- {ask_shell-0.7.0 → ask_shell-0.8.0}/ask_shell/_internal/global_callbacks.py +0 -0
- {ask_shell-0.7.0 → ask_shell-0.8.0}/ask_shell/_internal/interactive.py +0 -0
- {ask_shell-0.7.0 → ask_shell-0.8.0}/ask_shell/_internal/models.py +0 -0
- {ask_shell-0.7.0 → ask_shell-0.8.0}/ask_shell/_internal/rich_live_callback.py +0 -0
- {ask_shell-0.7.0 → ask_shell-0.8.0}/ask_shell/_internal/rich_progress.py +0 -0
- {ask_shell-0.7.0 → ask_shell-0.8.0}/ask_shell/_internal/rich_run_state.py +0 -0
- {ask_shell-0.7.0 → ask_shell-0.8.0}/ask_shell/_internal/run_pool.py +0 -0
- {ask_shell-0.7.0 → ask_shell-0.8.0}/ask_shell/_internal/typer_command.py +0 -0
- {ask_shell-0.7.0 → ask_shell-0.8.0}/ask_shell/ask.py +0 -0
- {ask_shell-0.7.0 → ask_shell-0.8.0}/ask_shell/console.py +0 -0
- {ask_shell-0.7.0 → ask_shell-0.8.0}/ask_shell/py.typed +0 -0
- {ask_shell-0.7.0 → ask_shell-0.8.0}/ask_shell/shell.py +0 -0
- {ask_shell-0.7.0 → ask_shell-0.8.0}/ask_shell/shell_events.py +0 -0
- {ask_shell-0.7.0 → ask_shell-0.8.0}/scripts/fix_source_links.py +0 -0
|
@@ -34,6 +34,7 @@ from zero_3rdparty.future import add_done_callback, add_error_logging, chain_fut
|
|
|
34
34
|
from ask_shell._internal.events import (
|
|
35
35
|
ShellRunAfter,
|
|
36
36
|
ShellRunBefore,
|
|
37
|
+
ShellRunCallbackT,
|
|
37
38
|
ShellRunPOpenStarted,
|
|
38
39
|
ShellRunRetryAttempt,
|
|
39
40
|
ShellRunStdOutput,
|
|
@@ -45,7 +46,6 @@ from ask_shell._internal.models import (
|
|
|
45
46
|
RunIncompleteError,
|
|
46
47
|
ShellConfig,
|
|
47
48
|
ShellRun,
|
|
48
|
-
ShellRunEventT,
|
|
49
49
|
ShellRunQueueT,
|
|
50
50
|
)
|
|
51
51
|
from ask_shell.settings import AskShellSettings, _global_settings
|
|
@@ -467,7 +467,7 @@ def run(
|
|
|
467
467
|
env: dict[str, str] | None = None,
|
|
468
468
|
extra_popen_kwargs: dict | None = None,
|
|
469
469
|
is_binary_call: bool | None = None,
|
|
470
|
-
message_callbacks: list[
|
|
470
|
+
message_callbacks: list[ShellRunCallbackT] | None = None,
|
|
471
471
|
print_prefix: str | None = None,
|
|
472
472
|
retry_initial_wait: float | None = None,
|
|
473
473
|
retry_jitter: float | None = None,
|
|
@@ -534,7 +534,7 @@ def run_and_wait(
|
|
|
534
534
|
env: dict[str, str] | None = None,
|
|
535
535
|
extra_popen_kwargs: dict | None = None,
|
|
536
536
|
is_binary_call: bool | None = None,
|
|
537
|
-
message_callbacks: list[
|
|
537
|
+
message_callbacks: list[ShellRunCallbackT] | None = None,
|
|
538
538
|
print_prefix: str | None = None,
|
|
539
539
|
retry_initial_wait: float | None = None,
|
|
540
540
|
retry_jitter: float | None = None,
|
|
@@ -35,3 +35,12 @@ def interactive_shell() -> bool:
|
|
|
35
35
|
logger.debug(f"Interactive shell not available: {non_interactive_reason}")
|
|
36
36
|
return False
|
|
37
37
|
return True
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def resolve_terminal_dimensions(
|
|
41
|
+
settings: AskShellSettings | None = None,
|
|
42
|
+
) -> tuple[int | None, int | None]:
|
|
43
|
+
if interactive_shell():
|
|
44
|
+
return None, None
|
|
45
|
+
settings = settings or AskShellSettings.from_env()
|
|
46
|
+
return settings.terminal_width, settings.terminal_height
|
|
@@ -13,6 +13,8 @@ from rich.style import Style
|
|
|
13
13
|
from zero_3rdparty.error import error_and_traceback
|
|
14
14
|
from zero_3rdparty.id_creator import simple_id
|
|
15
15
|
|
|
16
|
+
from ask_shell._internal._run_env import resolve_terminal_dimensions
|
|
17
|
+
|
|
16
18
|
_live: Live | None = None
|
|
17
19
|
_lock = RLock()
|
|
18
20
|
|
|
@@ -61,13 +63,20 @@ def _console_hook(func: Callable) -> Callable:
|
|
|
61
63
|
return wrapper
|
|
62
64
|
|
|
63
65
|
|
|
66
|
+
def _live_console() -> Console:
|
|
67
|
+
width, height = resolve_terminal_dimensions()
|
|
68
|
+
if width is not None and height is not None:
|
|
69
|
+
return Console(width=width, height=height)
|
|
70
|
+
return Console()
|
|
71
|
+
|
|
72
|
+
|
|
64
73
|
def get_live() -> Live:
|
|
65
74
|
global _live
|
|
66
75
|
if _live is not None:
|
|
67
76
|
return _live
|
|
68
77
|
with _lock:
|
|
69
78
|
if _live is None:
|
|
70
|
-
_live = Live(transient=True)
|
|
79
|
+
_live = Live(transient=True, console=_live_console())
|
|
71
80
|
live_render = _live._live_render
|
|
72
81
|
old_console = live_render.__rich_console__
|
|
73
82
|
live_render.__rich_console__ = _console_hook(old_console)
|
|
@@ -147,6 +147,10 @@ class AskShellSettings(StaticSettings):
|
|
|
147
147
|
default=ShellRunSummary.ALL,
|
|
148
148
|
alias=ENV_NAME_SHELL_RUN_SUMMARY,
|
|
149
149
|
)
|
|
150
|
+
ENV_NAME_TERMINAL_WIDTH: ClassVar[str] = f"{ENV_PREFIX}TERMINAL_WIDTH"
|
|
151
|
+
terminal_width: int = Field(default=120, alias=ENV_NAME_TERMINAL_WIDTH)
|
|
152
|
+
ENV_NAME_TERMINAL_HEIGHT: ClassVar[str] = f"{ENV_PREFIX}TERMINAL_HEIGHT"
|
|
153
|
+
terminal_height: int = Field(default=40, alias=ENV_NAME_TERMINAL_HEIGHT)
|
|
150
154
|
|
|
151
155
|
@model_validator(mode="after")
|
|
152
156
|
def ensure_vars_set(self) -> Self:
|
|
@@ -3,12 +3,18 @@ from pathlib import Path
|
|
|
3
3
|
import pytest
|
|
4
4
|
from pytest_examples import CodeExample, EvalExample, find_examples
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
PACKAGE_ROOT = Path(__file__).parent.parent
|
|
7
|
+
EXAMPLES_DIR = PACKAGE_ROOT / "docs" / "examples"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.fixture(autouse=True)
|
|
11
|
+
def _examples_package_cwd(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
12
|
+
monkeypatch.chdir(PACKAGE_ROOT)
|
|
7
13
|
|
|
8
14
|
|
|
9
15
|
@pytest.mark.parametrize("example", find_examples(EXAMPLES_DIR), ids=str)
|
|
10
16
|
def test_examples(example: CodeExample, eval_example: EvalExample):
|
|
11
|
-
eval_example.set_config(line_length=120, target_version="py313", ruff_ignore=["
|
|
17
|
+
eval_example.set_config(line_length=120, target_version="py313", ruff_ignore=["T"]) # pyright: ignore[reportArgumentType]
|
|
12
18
|
prefix = example.prefix_settings()
|
|
13
19
|
if prefix.get("test", "").startswith("skip"):
|
|
14
20
|
pytest.skip(prefix["test"])
|
|
File without changes
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from apply_live.stream_handler import PlanStreamHandler, plan_stream_callback
|
|
9
|
+
from ask_shell import console
|
|
10
|
+
from ask_shell.shell import run_and_wait
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
app = typer.Typer(
|
|
15
|
+
name="apply-live",
|
|
16
|
+
help="Demo terraform apply -json streaming with ask-shell live rendering.",
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
_DEFAULT_WORKSPACE = Path(__file__).resolve().parent / "workspace"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _run_terraform(command: str, *, workspace: Path) -> int:
|
|
23
|
+
run = run_and_wait(
|
|
24
|
+
command,
|
|
25
|
+
cwd=workspace,
|
|
26
|
+
allow_non_zero_exit=True,
|
|
27
|
+
skip_progress_output=True,
|
|
28
|
+
)
|
|
29
|
+
return run.exit_code or 0
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _terraform_init(*, workspace: Path) -> None:
|
|
33
|
+
logger.info("running terraform init")
|
|
34
|
+
if (exit_code := _run_terraform("terraform init -input=false", workspace=workspace)) != 0:
|
|
35
|
+
raise typer.Exit(exit_code)
|
|
36
|
+
logger.info("terraform init complete")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@app.command()
|
|
40
|
+
def apply(
|
|
41
|
+
workspace: Path = typer.Option(
|
|
42
|
+
_DEFAULT_WORKSPACE,
|
|
43
|
+
"--workspace",
|
|
44
|
+
"-w",
|
|
45
|
+
help="Terraform workspace with chained time_sleep resources.",
|
|
46
|
+
exists=True,
|
|
47
|
+
file_okay=False,
|
|
48
|
+
),
|
|
49
|
+
) -> None:
|
|
50
|
+
_terraform_init(workspace=workspace)
|
|
51
|
+
|
|
52
|
+
handler = PlanStreamHandler()
|
|
53
|
+
callback = plan_stream_callback(handler)
|
|
54
|
+
run = run_and_wait(
|
|
55
|
+
"terraform apply -json -auto-approve",
|
|
56
|
+
cwd=workspace,
|
|
57
|
+
allow_non_zero_exit=True,
|
|
58
|
+
skip_progress_output=True,
|
|
59
|
+
message_callbacks=[callback],
|
|
60
|
+
)
|
|
61
|
+
handler.flush()
|
|
62
|
+
if (exit_code := run.exit_code or 0) != 0:
|
|
63
|
+
raise typer.Exit(exit_code)
|
|
64
|
+
logger.info("apply complete")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def main() -> None:
|
|
68
|
+
console.configure_logging(app)
|
|
69
|
+
app()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
if __name__ == "__main__":
|
|
73
|
+
main()
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
import time
|
|
6
|
+
from enum import StrEnum
|
|
7
|
+
|
|
8
|
+
from rich.console import Console, ConsoleOptions, RenderResult
|
|
9
|
+
from rich.text import Text
|
|
10
|
+
|
|
11
|
+
from ask_shell import console as ask_console
|
|
12
|
+
from ask_shell.console import RemoveLivePart, interactive_shell
|
|
13
|
+
from ask_shell.shell_events import ShellRunCallbackT, ShellRunStdOutput
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
REFRESH_HEARTBEAT_INTERVAL_S = 5.0
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class _Phase(StrEnum):
|
|
21
|
+
REFRESH = "refresh"
|
|
22
|
+
PLANNING = "planning"
|
|
23
|
+
DONE = "done"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class _PlanStatusRenderable:
|
|
27
|
+
def __init__(self, handler: PlanStreamHandler) -> None:
|
|
28
|
+
self._handler = handler
|
|
29
|
+
|
|
30
|
+
def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
|
|
31
|
+
status = self._handler._status_line()
|
|
32
|
+
if status:
|
|
33
|
+
yield status
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class PlanStreamHandler:
|
|
37
|
+
def __init__(self) -> None:
|
|
38
|
+
self._started = time.monotonic()
|
|
39
|
+
self._phase = _Phase.REFRESH
|
|
40
|
+
self._in_flight: set[str] = set()
|
|
41
|
+
self._done = 0
|
|
42
|
+
self._planning_emitted = False
|
|
43
|
+
self._diagnostic_emitted = False
|
|
44
|
+
self._last_heartbeat = self._started
|
|
45
|
+
self._carry = ""
|
|
46
|
+
self._status = _PlanStatusRenderable(self)
|
|
47
|
+
self._remove_panel: RemoveLivePart | None = ask_console.add_renderable(
|
|
48
|
+
self._status, order=10, name="plan-status"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
def feed_line(self, chunk: str) -> None:
|
|
52
|
+
self._carry += chunk
|
|
53
|
+
while "\n" in self._carry:
|
|
54
|
+
line, self._carry = self._carry.split("\n", 1)
|
|
55
|
+
stripped = line.strip()
|
|
56
|
+
if stripped:
|
|
57
|
+
self._handle_line(stripped)
|
|
58
|
+
|
|
59
|
+
def flush(self) -> None:
|
|
60
|
+
if self._carry.strip():
|
|
61
|
+
self._handle_line(self._carry.strip())
|
|
62
|
+
self._carry = ""
|
|
63
|
+
self._remove_status_panel()
|
|
64
|
+
|
|
65
|
+
def _handle_line(self, line: str) -> None:
|
|
66
|
+
try:
|
|
67
|
+
data = json.loads(line)
|
|
68
|
+
except json.JSONDecodeError:
|
|
69
|
+
logger.debug(f"skipping non-json plan stream line: {line[:120]!r}")
|
|
70
|
+
return
|
|
71
|
+
msg_type = data.get("type")
|
|
72
|
+
match msg_type:
|
|
73
|
+
case "diagnostic":
|
|
74
|
+
self._emit_diagnostic(data)
|
|
75
|
+
case "refresh_start" | "apply_start":
|
|
76
|
+
self._on_refresh_start(data)
|
|
77
|
+
case "refresh_complete" | "apply_complete":
|
|
78
|
+
self._on_refresh_complete(data)
|
|
79
|
+
case "change_summary":
|
|
80
|
+
if (data.get("changes") or {}).get("operation") == "apply":
|
|
81
|
+
self._phase = _Phase.DONE
|
|
82
|
+
case "resource_drift" | "outputs" | "log":
|
|
83
|
+
self._leave_refresh_phase()
|
|
84
|
+
case _:
|
|
85
|
+
pass
|
|
86
|
+
self._maybe_heartbeat()
|
|
87
|
+
|
|
88
|
+
def _emit_diagnostic(self, data: dict) -> None:
|
|
89
|
+
diagnostic = data.get("diagnostic") or {}
|
|
90
|
+
summary = diagnostic.get("summary")
|
|
91
|
+
if not summary:
|
|
92
|
+
return
|
|
93
|
+
if not self._diagnostic_emitted:
|
|
94
|
+
self._diagnostic_emitted = True
|
|
95
|
+
ask_console.print_to_live("")
|
|
96
|
+
severity = (diagnostic.get("severity") or "error").capitalize()
|
|
97
|
+
ask_console.print_to_live(f"{severity}: {summary}")
|
|
98
|
+
if detail := diagnostic.get("detail"):
|
|
99
|
+
ask_console.print_to_live(detail)
|
|
100
|
+
|
|
101
|
+
def _on_refresh_start(self, data: dict) -> None:
|
|
102
|
+
if addr := _resource_addr(data):
|
|
103
|
+
self._in_flight.add(addr)
|
|
104
|
+
|
|
105
|
+
def _on_refresh_complete(self, data: dict) -> None:
|
|
106
|
+
if addr := _resource_addr(data):
|
|
107
|
+
self._in_flight.discard(addr)
|
|
108
|
+
self._done += 1
|
|
109
|
+
|
|
110
|
+
def _leave_refresh_phase(self) -> None:
|
|
111
|
+
if self._phase != _Phase.REFRESH:
|
|
112
|
+
return
|
|
113
|
+
self._phase = _Phase.PLANNING
|
|
114
|
+
self._emit_planning_once()
|
|
115
|
+
|
|
116
|
+
def _emit_planning_once(self) -> None:
|
|
117
|
+
if self._planning_emitted:
|
|
118
|
+
return
|
|
119
|
+
self._planning_emitted = True
|
|
120
|
+
ask_console.print_to_live("plan: computing changes…")
|
|
121
|
+
|
|
122
|
+
def _status_line(self) -> Text | None:
|
|
123
|
+
if self._phase == _Phase.DONE:
|
|
124
|
+
return None
|
|
125
|
+
if self._phase == _Phase.PLANNING:
|
|
126
|
+
return Text("planning…", style="cyan")
|
|
127
|
+
return self._refresh_status(time.monotonic() - self._started)
|
|
128
|
+
|
|
129
|
+
def _refresh_status(self, elapsed_s: float) -> Text:
|
|
130
|
+
mins, secs = divmod(int(elapsed_s), 60)
|
|
131
|
+
elapsed = f"{mins}m {secs:02d}s" if mins else f"{secs}s"
|
|
132
|
+
return Text.assemble(
|
|
133
|
+
("refresh", "cyan"),
|
|
134
|
+
" · ",
|
|
135
|
+
(str(self._done), "bold"),
|
|
136
|
+
" done · ",
|
|
137
|
+
(str(len(self._in_flight)), "bold"),
|
|
138
|
+
" running · ",
|
|
139
|
+
(elapsed, "dim"),
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
def _maybe_heartbeat(self) -> None:
|
|
143
|
+
if interactive_shell() or self._phase != _Phase.REFRESH:
|
|
144
|
+
return
|
|
145
|
+
now = time.monotonic()
|
|
146
|
+
if now - self._last_heartbeat < REFRESH_HEARTBEAT_INTERVAL_S:
|
|
147
|
+
return
|
|
148
|
+
self._last_heartbeat = now
|
|
149
|
+
logger.info(self._heartbeat_message(now - self._started))
|
|
150
|
+
|
|
151
|
+
def _heartbeat_message(self, elapsed_s: float) -> str:
|
|
152
|
+
mins, secs = divmod(int(elapsed_s), 60)
|
|
153
|
+
elapsed = f"{mins}m {secs:02d}s" if mins else f"{secs}s"
|
|
154
|
+
return f"refresh: {self._done} complete, {len(self._in_flight)} in progress ({elapsed})"
|
|
155
|
+
|
|
156
|
+
def _remove_status_panel(self) -> None:
|
|
157
|
+
if self._remove_panel is None:
|
|
158
|
+
return
|
|
159
|
+
self._remove_panel()
|
|
160
|
+
self._remove_panel = None
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _resource_addr(data: dict) -> str | None:
|
|
164
|
+
hook = data.get("hook") or {}
|
|
165
|
+
resource = hook.get("resource") or {}
|
|
166
|
+
match resource.get("addr"):
|
|
167
|
+
case str(addr):
|
|
168
|
+
return addr
|
|
169
|
+
case _:
|
|
170
|
+
return None
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def plan_stream_callback(handler: PlanStreamHandler) -> ShellRunCallbackT:
|
|
174
|
+
def callback(message: object) -> bool:
|
|
175
|
+
match message:
|
|
176
|
+
case ShellRunStdOutput(is_stdout=True, content=content):
|
|
177
|
+
handler.feed_line(content)
|
|
178
|
+
return False
|
|
179
|
+
|
|
180
|
+
return callback
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[project]
|
|
4
4
|
name = "ask-shell"
|
|
5
|
-
version = "0.
|
|
5
|
+
version = "0.8.0"
|
|
6
6
|
description = "CLIs with prompts, shell runs, and testable flows"
|
|
7
7
|
requires-python = ">=3.13"
|
|
8
8
|
license = "MIT"
|
|
@@ -41,6 +41,11 @@ examples_include = [
|
|
|
41
41
|
"AbortRetryError",
|
|
42
42
|
]
|
|
43
43
|
|
|
44
|
+
[tool.pkg-ext.groups.console]
|
|
45
|
+
examples_include = [
|
|
46
|
+
"add_renderable",
|
|
47
|
+
]
|
|
48
|
+
|
|
44
49
|
|
|
45
50
|
# === DO_NOT_EDIT: path-sync ruff ===
|
|
46
51
|
[tool.ruff]
|
|
@@ -59,6 +64,9 @@ extend-ignore = ["E501", "UP006", "UP007", "UP035", "UP040", "UP046", "UP047"]
|
|
|
59
64
|
extend-select = ["Q", "RUF100", "C90", "UP", "I", "T", "FURB"]
|
|
60
65
|
# === OK_EDIT: path-sync ruff ===
|
|
61
66
|
|
|
67
|
+
[tool.ruff.lint.isort]
|
|
68
|
+
known-first-party = ["ask_shell"]
|
|
69
|
+
|
|
62
70
|
# === DO_NOT_EDIT: path-sync pytest ===
|
|
63
71
|
[tool.pytest.ini_options]
|
|
64
72
|
addopts = "-s -vv --log-cli-level=INFO -m 'not manual'"
|
|
@@ -107,3 +115,12 @@ docs = [
|
|
|
107
115
|
"mkdocs-material>=9.5",
|
|
108
116
|
]
|
|
109
117
|
# === OK_EDIT: path-sync docs ===
|
|
118
|
+
# === DO_NOT_EDIT: path-sync wheel ===
|
|
119
|
+
[tool.hatch.build.targets.wheel]
|
|
120
|
+
exclude = [
|
|
121
|
+
"*_examples.py",
|
|
122
|
+
"*_test.py",
|
|
123
|
+
"conftest.py",
|
|
124
|
+
"*_test/",
|
|
125
|
+
]
|
|
126
|
+
# === OK_EDIT: path-sync wheel ===
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|