Name | bprogress JSON |
Version |
1.0.0
JSON |
| download |
home_page | None |
Summary | Braille-based multi progress bars with ANSI colors (docker-compose style) |
upload_time | 2025-08-13 18:51:05 |
maintainer | None |
docs_url | None |
author | None |
requires_python | >=3.8 |
license | MIT License
Copyright (c) 2025 KIBA
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. |
keywords |
ansi
braille
cli
progress
terminal
|
VCS |
 |
bugtrack_url |
|
requirements |
No requirements were recorded.
|
Travis-CI |
No Travis.
|
coveralls test coverage |
No coveralls.
|
# braille-progress
<img width="1060" height="244" alt="image" src="https://github.com/user-attachments/assets/7b6d7929-3db5-4140-8570-989e5d2bcf35" />
점자 문자를 이용한 프로그레스 보드. 자유로운 레이아웃 커스터마이즈, 태스크별 로그 패널, 테스크 에러 리포트, Windows/Linux/macOS 터미널 복구까지 지원.
---
## 특징
* 줄바꿈/밀림 없는 안정 렌더링(가시폭 컷, 가드 컬럼), Windows VT I/O 지원, 종료 시 자동 복구(시그널+atexit), 마우스 모드 해제 및 입력 버퍼 플러시.
* 좌우 분할: 왼쪽 리스트(태스크), 오른쪽 패널(기본: 로그) + 세로 구분선.
* 인터랙션: 마우스 클릭, 키보드 `w/s` 또는 `↑/↓`, `Home/End`. (Linux계열 환경 한정)
* 행(Row) 레이아웃 자유 구성: 이름, 메인/서브 바, 퍼센트, 상태, 카운터, 라벨, 경과시간, 평균 처리속도, ETA, 임의 텍스트 등.
* 헤더/푸터 수직 레이아웃.
* 오른쪽 패널 렌더러 커스텀(메트릭, 표 등 임의 UI).
* 태스크 런타임 통계(EWMA 평균 처리속도, ETA).
* 종료 시 실패 태스크에 대한 컬러 트레이스백 리포트.
* 멀티프로세싱용 큐 바인더 및 메시지 스키마.
* ANSI/전각폭(CJK, ⣿) 안전한 가시폭 처리.
* 세로 정책: `fit`(내용만), `full`(터미널 높이 채움). ALT 스크린 옵션.
---
## 설치
```bash
pip install braille-progress # 또는 저장소 경로에서 설치
```
Python ≥ 3.8. Windows는 Windows Terminal 등 VT 지원 콘솔 권장.
---
## 빠른 시작
## 빠른 시작
```python
from braille_progress import Progress, ProgressTheme
p = Progress(
row_policy="fit", # 위 출력 보존, 필요한 줄만 아래에 확보
split_ratio=0.58, # 좌측:우측 폭 비율
show_vsep=True, # 세로 구분선 표시
)
t1 = p.add("download", total=100)
t2 = p.add("extract", total=30)
for i in range(10):
t1.advance(1, stage="writing", label=f"part {i}")
p.log(t1, f"chunk {i} ok")
p.loop() # w/s 또는 ↑/↓로 선택, 마우스 클릭으로도 선택 가능
p.close() # 종료 및 에러 리포트/터미널 상태 복구
```
---
## 좌우 분할 + 선택 + 오른쪽 로그
```python
from braille_progress import Progress
p = Progress(split_ratio=0.55, show_vsep=True)
a = p.add("prepare", total=10)
b = p.add("train", total=500)
for i in range(10):
a.advance(1, stage="writing"); p.log(a, f"prepare step {i}")
for i in range(50):
b.advance(1, stage="writing"); p.log(b, f"loss={1/(i+1):.4f}")
p.loop() # 마우스 클릭 또는 w/s, ↑/↓로 선택; 오른쪽에 해당 태스크 로그 표시
p.close()
```
조작:
* 키보드: `w`/`↑` 위, `s`/`↓` 아래, `Home`/`End`, `q` 또는 `Ctrl+C` 종료
* 마우스: 태스크 라인 클릭(SGR 마우스 모드 자동 활성화)
---
## stdout/stderr를 로그 패널에 출력
```python
from braille_progress import Progress
p = Progress()
h = p.add("convert", total=3)
with p.hijack_stdio(h): # print()/traceback이 해당 태스크 로그 패널로 들어감
print("converting...")
try:
1/0
except Exception as e:
h.fail(error=e) # 종료 시 예쁜 트레이스백도 최종 리포트로 출력
p.close()
```
---
## 커스텀 헤더/푸터/행(Row) 레이아웃
```python
from braille_progress import (
Progress, ProgressTheme, Layout, Name, Bar, Percent, Status,
MiniBar, Counter, Label, Text, Gap, Elapsed, AvgRate, ETA,
Rule, Now, VLayout, VGap, default_layout
)
theme = ProgressTheme.auto_fit()
row = Layout([
Name(width=22),
Text(" | "),
Bar(cells=18),
Percent(width=5),
Gap(2),
Status(width=16),
Text(" | "),
MiniBar(cells=10),
Counter(),
Gap(2),
Label(width="flex")
], theme=theme)
header = VLayout([ Rule(), Text(" Jobs"), VGap(1) ])
footer = VLayout([ VGap(1), Rule(), Text(" Ready "), Now() ])
p = Progress(layout=row, header=header, footer=footer, split_ratio=0.6, show_vsep=True)
a = p.add("aa.zip", total=100)
b = p.add("bb.zip", total=100)
a.advance(30, stage="writing", label="downloading")
b.advance(70, stage="writing", label="processing")
p.loop()
p.close()
```
메모:
* `Label(width="flex")` 가변 폭으로 남은 공간을 차지하며, 줄맞춤 필요 시 마지막에 축소됩니다.
* `Label`이 없더라도 마지막 세그먼트를 축소해 줄바꿈을 방지합니다.
* 가시폭(ANSI 제거, 전각폭 반영) 기준으로 폭을 계산합니다.
---
## 세로 크기 정책
```python
# 터미널 전체 높이 채움(대시보드)
p = Progress(row_policy="full", min_body_rows=8, use_alt_screen=True)
# 내용만 표시(화면 전체 점유 없음)
p = Progress(row_policy="fit", max_body_rows=12, use_alt_screen=False)
```
`row_policy`:
* `"full"`: 헤더+바디+푸터가 터미널 높이를 채움
* `"fit"`: 콘텐츠에 맞게 바디 행 개수를 결정(`min_body_rows`/`max_body_rows`로 상·하한 제어)
---
## 오른쪽 패널 렌더러 커스텀
```python
from braille_progress import Progress, DetailRenderer
class MetricsPanel(DetailRenderer):
def render(self, *, width, height, styler, title, lines):
out = [styler.color(f"[{title}] metrics", fg="bright_magenta").ljust(width)[:width]]
for i in range(1, height):
out.append(f"logs={len(lines)} row={i}".ljust(width)[:width])
return out
p = Progress(right_renderer=MetricsPanel(), split_ratio=0.5)
h = p.add("task", total=10)
for i in range(10): p.log(h, f"event {i}")
p.loop(); p.close()
```
내장 렌더러:
* `ConsoleRenderer`(기본): 제목 + 끝부분 로그
* `StaticRenderer(lines)`: 고정 문자열 리스트
---
## 에러 리포트
실패한 태스크는 `Progress.close()` 시 컬러 트레이스백과 함께 요약됩니다.
```python
from braille_progress import Progress
p = Progress()
try:
with p.task("upload", total=3) as h:
raise RuntimeError("remote closed")
except Exception:
pass
p.close() # 실패 태스크의 파일/라인/함수/코드가 강조된 트레이스백 출력
```
`fail(error=..., error_tb=True)`에 예외를 넘기면 트레이스백이 예쁘게 포매팅됩니다.
---
## 큐 바인더(멀티프로세싱)
```python
from braille_progress import Progress, QueueBinder, progress_message
p = Progress()
h = p.add("worker-0", total=100)
# 부모 프로세스
binder = p.bind_queue(my_queue)
while True:
changed = binder.drain()
if changed: p.render(throttle=False)
if p.all_finished(): break
# 워커 프로세스
my_queue.put(progress_message(0, stage="writing", done=5, total=100, label="chunk-5"))
my_queue.put(progress_message(0, final=True)) # DONE
```
메시지 스키마는 기본 키(`i`, `stage`, `case_done`, `case_total`, `case_label`)를 사용하며, `QueueBinder` 생성 시 키를 오버라이드할 수 있습니다.
---
# Progress 객체 API
---
## 생성자(파라미터)
```python
Progress(
theme: Optional[ProgressTheme]=None,
*,
auto_vt: bool=True,
auto_refresh: bool=True,
refresh_interval: float=0.05,
force_tty: Optional[bool]=None,
force_color: Optional[bool]=None,
ratio_strategy: Optional[RatioStrategy]=None,
layout: Optional[Layout]=None,
header: Optional[Union[VLayout, Layout, Sequence[Row]]]=None,
footer: Optional[Union[VLayout, Layout, Sequence[Row]]]=None,
# 좌우 분할/우측 패널
split_ratio: float=0.55,
show_vsep: bool=True,
right_renderer: Optional[DetailRenderer]=None,
# 세로 정책
row_policy: str="fit", # "fit" | "full"
min_body_rows: int=0, # 최소 표시 줄 수(fit/full 공통)
max_body_rows: Optional[int]=None, # fit 모드에서 최대 줄 수 제한
# 스크린/시그널
use_alt_screen: bool=False,
handle_signals: bool=True
)
```
### 표시/갱신
* `auto_refresh`: 상태 변경 시 자동 리렌더.
* `refresh_interval`: 자동 리렌더 최소 간격(초).
* `theme`: 폭 계산/색상 팔레트. `ProgressTheme.auto_fit()` 권장.
* `force_color`: `True`면 ANSI 색 강제, `False`면 비활성.
* `force_tty`: 강제로 TTY 모드로 렌더(파이프 환경 테스트용).
* `ratio_strategy`: 진행률 계산 전략 커스터마이즈.
### 레이아웃(좌측 리스트 라인)
* `layout`: 한 줄을 구성하는 빌딩블록(Layout DSL). 미지정 시 기본 레이아웃.
* `header`/`footer`: 상/하단에 세로 레이아웃 추가. `VLayout`, `Layout`, `Row` 시퀀스 지원.
### 좌우 분할/우측 패널
* `split_ratio`: 좌측 리스트 폭 비율(0.1\~0.9).
* `show_vsep`: 좌우 사이에 `│` 표시.
* `right_renderer`: 우측 패널 콘텐츠 렌더러. 기본은 콘솔 로그(`ConsoleRenderer`).
### 세로 정책
* `row_policy="fit"`: 위 기존 출력은 그대로 두고, **아래에 필요한 줄만 확보하여** 그 안에서 갱신(스크린 전체를 채우지 않음).
* `row_policy="full"`: 현재 터미널 높이에 맞춰 본문 영역을 채움.
* `min_body_rows`: 최소 줄 보장.
* `max_body_rows`: `fit` 모드에서 최대 줄 제한.
### 스크린/시그널
* `use_alt_screen`: `True`면 ALT 스크린(별도 버퍼) 사용.
* `handle_signals`: SIGINT/SIGTERM/SIGHUP에서 터미널/입력 모드 안전 복구.
---
## 메서드
### 태스크 생성/갱신
```python
h = p.add(name: str, total: int = 0) -> TaskHandle
```
* 새 태스크 추가. 반환되는 `TaskHandle`로 갱신.
```python
p.update(handle_or_id, *, advance=0, done=None, total=None,
stage=None, label=None, finished=None, failed=None) -> None
```
* 태스크 상태 갱신. `advance`는 `done`을 증가시킴.
```python
h.advance(n: int=1, *, label: Optional[str]=None, stage: Optional[str]=None) -> TaskHandle
```
* 진행 수치 간편 증가.
```python
p.done(handle_or_id) -> None
h.complete() -> None
```
* 태스크 완료 처리(표시상 `stage="done"`).
```python
p.fail(handle_or_id, *, stage: str="error",
error: Optional[Any]=None, error_tb: bool=True) -> None
h.fail(stage: str="error") -> None
```
* 실패 처리. `error`에 예외 객체를 넘기면 종료 시 **예쁜 traceback** 포함 에러 리포트 출력.
```python
p.all_finished() -> bool
```
* 모든 태스크 종료 여부.
### 컨텍스트 API
```python
with p.task("name", total=10) as h:
...
```
* 블록 내 예외 발생 시 자동 `fail()`, 정상 종료 시 자동 `done()`.
### 반복 도우미
```python
for item in p.track(iterable, total=None, description=None, label_from=None):
...
```
* 반복 중 자동 진행/라벨 갱신. 예외 시 자동 `fail()`.
### 로그/우측 패널
```python
p.log(handle_or_id, msg: str) -> None
```
* 해당 태스크의 로그를 우측 패널에 추가(최대 최근 2000줄 유지).
```python
p.set_right_renderer(renderer: DetailRenderer) -> None
```
* 우측 패널 렌더러 교체.
```python
with p.hijack_stdio(handle_or_id):
# 이 블록의 stdout/stderr는 우측 패널 로그로 유입
print("captured")
```
### 렌더/루프/종료
```python
p.render(throttle: bool=False) -> None
```
* 수동 렌더. `throttle=True`면 `refresh_interval`을 존중.
```python
p.loop() -> None
```
* 인터랙티브 UI 루프 시작.
* 키보드: `w`/`s` 또는 `↑`/`↓`로 좌측 선택 이동.
* 마우스: 좌측 리스트 영역 클릭으로 선택.
* `q` 또는 `Ctrl+C`로 종료.
```python
p.close() -> None
```
* 화면 정리, 실패 태스크 에러 리포트 출력, 마우스/입력 모드/ALT 스크린/VT 모드 등 **완전 복구**.
### 큐 바인더(선택)
```python
qb = p.bind_queue(queue, id_key="i", stage_key="stage",
done_key="case_done", total_key="case_total",
label_key="case_label")
changed = qb.drain()
```
* 외부 워커 메시지를 UI에 반영.
---
## 우측 패널 커스텀 렌더러
```python
from braille_progress import DetailRenderer
class MyPanel(DetailRenderer):
def render(self, *, width, height, styler, title, lines):
out = [styler.color(f"[{title}] metrics", fg="bright_magenta").ljust(width)[:width]]
for i in range(1, height):
txt = f"rows={height}, logs={len(lines)}"
out.append(txt.ljust(width)[:width])
return out
```
* `width`/`height` 내에서만 출력하도록 반드시 패딩/절단 처리.
---
## 좌측 라인 레이아웃 교체(요약)
* DSL 구성요소 예: `Name()`, `Bar()`, `Percent()`, `Status()`, `MiniBar()`, `Counter()`, `Label()`, `Elapsed()`, `AvgRate()`, `ETA()`, `Text(" | ")`, `Gap(w)` 등.
* 예:
```python
from braille_progress import Layout, Name, Text, Bar, Percent, Status, MiniBar, Counter, Label
layout = Layout([
Name(w=20), Text(" | "),
Bar(cells=18), Percent(), Text(" "),
Status(w=16), Text(" | "),
MiniBar(cells=10), Counter(), Text(" "),
Label(w=32)
])
p = Progress(layout=layout)
```
---
## 모드/환경 변수
* `use_alt_screen=True`: 메인 터미널과 분리된 버퍼에 렌더(외부 출력 간섭 줄임).
* `NO_COLOR=1`: 강제로 무채색.
* `BP_FORCE_TTY=1`: 강제로 TTY 모드.
* `BP_FORCE_COLOR=1`: 강제로 컬러 활성.
---
## 에러 리포트
* `p.close()` 시 실패 태스크를 수집하여:
* 태스크명/스테이지/진행/경과/속도
* 전달받은 `error` 예외의 **컬러 트레이스백**(파일/라인/함수/코드) 출력.
---
## 사용 팁
* 루프 동안 외부 `print()`가 필요하면 반드시 `hijack_stdio()`로 감싸 우측 로그로 보내라(레이아웃 깨짐 방지).
* `row_policy="fit"`은 기존 상단 출력 보존. 필요 줄이 늘면 아래로만 확장한다.
`row_policy="full"`은 터미널 높이를 채우며 헤더/푸터와 함께 스테이블하게 갱신한다.
* Windows에서는 Windows Terminal + 고정폭 폰트 사용을 권장.
## 렌더링/터미널 동작
* 렌더링 중 자동줄바꿈을 끄고, 모든 줄을 `cols-1` 내로 가시폭 기준 절단. 마지막 줄은 개행 없이 출력해 스크롤을 막습니다.
* `loop()`에서 VT 입력·마우스 활성화, `close()`/종료 시 마우스/포커스/브래킷드 페이스트 모드 해제(`?1006/?1002/?1000/?1015/?1004/?2004`), 커서 보이기·wrap 복구, 입력 버퍼 플러시(Windows `FlushConsoleInputBuffer`, POSIX `tcflush`), ALT 스크린 종료, 콘솔 모드 원복.
* 렌더링 중 `print()` 호출은 화면을 흔듭니다. `p.log(...)` 또는 `p.hijack_stdio(handle)` 사용을 권장.
---
## 환경 변수
* `BP_FORCE_TTY=1` : TTY 모드 강제
* `BP_FORCE_COLOR=1` : 컬러 강제
* `NO_COLOR=1` : 컬러 비활성화
---
## 예시
### 에러 포함 최소 예시
```python
from braille_progress import Progress
p = Progress()
h = p.add("upload", total=3)
try:
for i in range(3):
if i == 2: raise RuntimeError("remote closed")
h.advance(1, stage="writing")
except Exception as e:
h.fail(error=e)
p.close()
```
### Fit 모드 대시보드(ALT 스크린 없이)
```python
from braille_progress import Progress, default_layout, ProgressTheme
p = Progress(
layout=default_layout(ProgressTheme.auto_fit()),
row_policy="fit",
max_body_rows=10,
split_ratio=0.6,
show_vsep=True,
use_alt_screen=False
)
# ... 태스크/로그 추가 ...
p.loop(); p.close()
```
---
## 공개 심볼
```python
from braille_progress import (
Progress, ProgressTheme, TaskHandle, TaskState,
RatioStrategy, DefaultRatio, QueueBinder, progress_message,
Layout, default_layout, RenderContext,
Name, Bar, Percent, Status, MiniBar, Counter, Label, Text, Gap,
Elapsed, AvgRate, ETA, Spacer, Rule, Now, VGap, VLayout,
DetailRenderer, ConsoleRenderer, StaticRenderer
)
```
Raw data
{
"_id": null,
"home_page": null,
"name": "bprogress",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": null,
"keywords": "ansi, braille, cli, progress, terminal",
"author": null,
"author_email": "Your Name <you@example.com>",
"download_url": "https://files.pythonhosted.org/packages/98/5f/27ecaaac9cd340e4b5611432ebcf1da4c28d45bffbd44c82e9ef8b51beea/bprogress-1.0.0.tar.gz",
"platform": null,
"description": "# braille-progress\n<img width=\"1060\" height=\"244\" alt=\"image\" src=\"https://github.com/user-attachments/assets/7b6d7929-3db5-4140-8570-989e5d2bcf35\" />\n\n\uc810\uc790 \ubb38\uc790\ub97c \uc774\uc6a9\ud55c \ud504\ub85c\uadf8\ub808\uc2a4 \ubcf4\ub4dc. \uc790\uc720\ub85c\uc6b4 \ub808\uc774\uc544\uc6c3 \ucee4\uc2a4\ud130\ub9c8\uc774\uc988, \ud0dc\uc2a4\ud06c\ubcc4 \ub85c\uadf8 \ud328\ub110, \ud14c\uc2a4\ud06c \uc5d0\ub7ec \ub9ac\ud3ec\ud2b8, Windows/Linux/macOS \ud130\ubbf8\ub110 \ubcf5\uad6c\uae4c\uc9c0 \uc9c0\uc6d0.\n\n---\n\n## \ud2b9\uc9d5\n\n* \uc904\ubc14\uafc8/\ubc00\ub9bc \uc5c6\ub294 \uc548\uc815 \ub80c\ub354\ub9c1(\uac00\uc2dc\ud3ed \ucef7, \uac00\ub4dc \uceec\ub7fc), Windows VT I/O \uc9c0\uc6d0, \uc885\ub8cc \uc2dc \uc790\ub3d9 \ubcf5\uad6c(\uc2dc\uadf8\ub110+atexit), \ub9c8\uc6b0\uc2a4 \ubaa8\ub4dc \ud574\uc81c \ubc0f \uc785\ub825 \ubc84\ud37c \ud50c\ub7ec\uc2dc.\n* \uc88c\uc6b0 \ubd84\ud560: \uc67c\ucabd \ub9ac\uc2a4\ud2b8(\ud0dc\uc2a4\ud06c), \uc624\ub978\ucabd \ud328\ub110(\uae30\ubcf8: \ub85c\uadf8) + \uc138\ub85c \uad6c\ubd84\uc120.\n* \uc778\ud130\ub799\uc158: \ub9c8\uc6b0\uc2a4 \ud074\ub9ad, \ud0a4\ubcf4\ub4dc `w/s` \ub610\ub294 `\u2191/\u2193`, `Home/End`. (Linux\uacc4\uc5f4 \ud658\uacbd \ud55c\uc815)\n* \ud589(Row) \ub808\uc774\uc544\uc6c3 \uc790\uc720 \uad6c\uc131: \uc774\ub984, \uba54\uc778/\uc11c\ube0c \ubc14, \ud37c\uc13c\ud2b8, \uc0c1\ud0dc, \uce74\uc6b4\ud130, \ub77c\ubca8, \uacbd\uacfc\uc2dc\uac04, \ud3c9\uade0 \ucc98\ub9ac\uc18d\ub3c4, ETA, \uc784\uc758 \ud14d\uc2a4\ud2b8 \ub4f1.\n* \ud5e4\ub354/\ud478\ud130 \uc218\uc9c1 \ub808\uc774\uc544\uc6c3.\n* \uc624\ub978\ucabd \ud328\ub110 \ub80c\ub354\ub7ec \ucee4\uc2a4\ud140(\uba54\ud2b8\ub9ad, \ud45c \ub4f1 \uc784\uc758 UI).\n* \ud0dc\uc2a4\ud06c \ub7f0\ud0c0\uc784 \ud1b5\uacc4(EWMA \ud3c9\uade0 \ucc98\ub9ac\uc18d\ub3c4, ETA).\n* \uc885\ub8cc \uc2dc \uc2e4\ud328 \ud0dc\uc2a4\ud06c\uc5d0 \ub300\ud55c \uceec\ub7ec \ud2b8\ub808\uc774\uc2a4\ubc31 \ub9ac\ud3ec\ud2b8.\n* \uba40\ud2f0\ud504\ub85c\uc138\uc2f1\uc6a9 \ud050 \ubc14\uc778\ub354 \ubc0f \uba54\uc2dc\uc9c0 \uc2a4\ud0a4\ub9c8.\n* ANSI/\uc804\uac01\ud3ed(CJK, \u28ff) \uc548\uc804\ud55c \uac00\uc2dc\ud3ed \ucc98\ub9ac.\n* \uc138\ub85c \uc815\ucc45: `fit`(\ub0b4\uc6a9\ub9cc), `full`(\ud130\ubbf8\ub110 \ub192\uc774 \ucc44\uc6c0). ALT \uc2a4\ud06c\ub9b0 \uc635\uc158.\n\n---\n\n## \uc124\uce58\n\n```bash\npip install braille-progress # \ub610\ub294 \uc800\uc7a5\uc18c \uacbd\ub85c\uc5d0\uc11c \uc124\uce58\n```\n\nPython \u2265 3.8. Windows\ub294 Windows Terminal \ub4f1 VT \uc9c0\uc6d0 \ucf58\uc194 \uad8c\uc7a5.\n\n---\n\n## \ube60\ub978 \uc2dc\uc791\n\n\n## \ube60\ub978 \uc2dc\uc791\n\n```python\nfrom braille_progress import Progress, ProgressTheme\n\np = Progress(\n row_policy=\"fit\", # \uc704 \ucd9c\ub825 \ubcf4\uc874, \ud544\uc694\ud55c \uc904\ub9cc \uc544\ub798\uc5d0 \ud655\ubcf4\n split_ratio=0.58, # \uc88c\uce21:\uc6b0\uce21 \ud3ed \ube44\uc728\n show_vsep=True, # \uc138\ub85c \uad6c\ubd84\uc120 \ud45c\uc2dc\n)\n\nt1 = p.add(\"download\", total=100)\nt2 = p.add(\"extract\", total=30)\n\nfor i in range(10):\n t1.advance(1, stage=\"writing\", label=f\"part {i}\")\n p.log(t1, f\"chunk {i} ok\")\n\np.loop() # w/s \ub610\ub294 \u2191/\u2193\ub85c \uc120\ud0dd, \ub9c8\uc6b0\uc2a4 \ud074\ub9ad\uc73c\ub85c\ub3c4 \uc120\ud0dd \uac00\ub2a5\np.close() # \uc885\ub8cc \ubc0f \uc5d0\ub7ec \ub9ac\ud3ec\ud2b8/\ud130\ubbf8\ub110 \uc0c1\ud0dc \ubcf5\uad6c\n```\n\n---\n\n## \uc88c\uc6b0 \ubd84\ud560 + \uc120\ud0dd + \uc624\ub978\ucabd \ub85c\uadf8\n\n```python\nfrom braille_progress import Progress\n\np = Progress(split_ratio=0.55, show_vsep=True)\na = p.add(\"prepare\", total=10)\nb = p.add(\"train\", total=500)\n\nfor i in range(10):\n a.advance(1, stage=\"writing\"); p.log(a, f\"prepare step {i}\")\nfor i in range(50):\n b.advance(1, stage=\"writing\"); p.log(b, f\"loss={1/(i+1):.4f}\")\n\np.loop() # \ub9c8\uc6b0\uc2a4 \ud074\ub9ad \ub610\ub294 w/s, \u2191/\u2193\ub85c \uc120\ud0dd; \uc624\ub978\ucabd\uc5d0 \ud574\ub2f9 \ud0dc\uc2a4\ud06c \ub85c\uadf8 \ud45c\uc2dc\np.close()\n```\n\n\uc870\uc791:\n\n* \ud0a4\ubcf4\ub4dc: `w`/`\u2191` \uc704, `s`/`\u2193` \uc544\ub798, `Home`/`End`, `q` \ub610\ub294 `Ctrl+C` \uc885\ub8cc\n* \ub9c8\uc6b0\uc2a4: \ud0dc\uc2a4\ud06c \ub77c\uc778 \ud074\ub9ad(SGR \ub9c8\uc6b0\uc2a4 \ubaa8\ub4dc \uc790\ub3d9 \ud65c\uc131\ud654)\n\n---\n\n## stdout/stderr\ub97c \ub85c\uadf8 \ud328\ub110\uc5d0 \ucd9c\ub825\n\n```python\nfrom braille_progress import Progress\n\np = Progress()\nh = p.add(\"convert\", total=3)\n\nwith p.hijack_stdio(h): # print()/traceback\uc774 \ud574\ub2f9 \ud0dc\uc2a4\ud06c \ub85c\uadf8 \ud328\ub110\ub85c \ub4e4\uc5b4\uac10\n print(\"converting...\")\n try:\n 1/0\n except Exception as e:\n h.fail(error=e) # \uc885\ub8cc \uc2dc \uc608\uc05c \ud2b8\ub808\uc774\uc2a4\ubc31\ub3c4 \ucd5c\uc885 \ub9ac\ud3ec\ud2b8\ub85c \ucd9c\ub825\n\np.close()\n```\n\n---\n\n## \ucee4\uc2a4\ud140 \ud5e4\ub354/\ud478\ud130/\ud589(Row) \ub808\uc774\uc544\uc6c3\n\n```python\nfrom braille_progress import (\n Progress, ProgressTheme, Layout, Name, Bar, Percent, Status,\n MiniBar, Counter, Label, Text, Gap, Elapsed, AvgRate, ETA,\n Rule, Now, VLayout, VGap, default_layout\n)\n\ntheme = ProgressTheme.auto_fit()\n\nrow = Layout([\n Name(width=22),\n Text(\" | \"),\n Bar(cells=18),\n Percent(width=5),\n Gap(2),\n Status(width=16),\n Text(\" | \"),\n MiniBar(cells=10),\n Counter(),\n Gap(2),\n Label(width=\"flex\")\n], theme=theme)\n\nheader = VLayout([ Rule(), Text(\" Jobs\"), VGap(1) ])\nfooter = VLayout([ VGap(1), Rule(), Text(\" Ready \"), Now() ])\n\np = Progress(layout=row, header=header, footer=footer, split_ratio=0.6, show_vsep=True)\na = p.add(\"aa.zip\", total=100)\nb = p.add(\"bb.zip\", total=100)\na.advance(30, stage=\"writing\", label=\"downloading\")\nb.advance(70, stage=\"writing\", label=\"processing\")\np.loop()\np.close()\n```\n\n\uba54\ubaa8:\n\n* `Label(width=\"flex\")` \uac00\ubcc0 \ud3ed\uc73c\ub85c \ub0a8\uc740 \uacf5\uac04\uc744 \ucc28\uc9c0\ud558\uba70, \uc904\ub9de\ucda4 \ud544\uc694 \uc2dc \ub9c8\uc9c0\ub9c9\uc5d0 \ucd95\uc18c\ub429\ub2c8\ub2e4.\n* `Label`\uc774 \uc5c6\ub354\ub77c\ub3c4 \ub9c8\uc9c0\ub9c9 \uc138\uadf8\uba3c\ud2b8\ub97c \ucd95\uc18c\ud574 \uc904\ubc14\uafc8\uc744 \ubc29\uc9c0\ud569\ub2c8\ub2e4.\n* \uac00\uc2dc\ud3ed(ANSI \uc81c\uac70, \uc804\uac01\ud3ed \ubc18\uc601) \uae30\uc900\uc73c\ub85c \ud3ed\uc744 \uacc4\uc0b0\ud569\ub2c8\ub2e4.\n\n---\n\n## \uc138\ub85c \ud06c\uae30 \uc815\ucc45\n\n```python\n# \ud130\ubbf8\ub110 \uc804\uccb4 \ub192\uc774 \ucc44\uc6c0(\ub300\uc2dc\ubcf4\ub4dc)\np = Progress(row_policy=\"full\", min_body_rows=8, use_alt_screen=True)\n\n# \ub0b4\uc6a9\ub9cc \ud45c\uc2dc(\ud654\uba74 \uc804\uccb4 \uc810\uc720 \uc5c6\uc74c)\np = Progress(row_policy=\"fit\", max_body_rows=12, use_alt_screen=False)\n```\n\n`row_policy`:\n\n* `\"full\"`: \ud5e4\ub354+\ubc14\ub514+\ud478\ud130\uac00 \ud130\ubbf8\ub110 \ub192\uc774\ub97c \ucc44\uc6c0\n* `\"fit\"`: \ucf58\ud150\uce20\uc5d0 \ub9de\uac8c \ubc14\ub514 \ud589 \uac1c\uc218\ub97c \uacb0\uc815(`min_body_rows`/`max_body_rows`\ub85c \uc0c1\u00b7\ud558\ud55c \uc81c\uc5b4)\n\n---\n\n## \uc624\ub978\ucabd \ud328\ub110 \ub80c\ub354\ub7ec \ucee4\uc2a4\ud140\n\n```python\nfrom braille_progress import Progress, DetailRenderer\n\nclass MetricsPanel(DetailRenderer):\n def render(self, *, width, height, styler, title, lines):\n out = [styler.color(f\"[{title}] metrics\", fg=\"bright_magenta\").ljust(width)[:width]]\n for i in range(1, height):\n out.append(f\"logs={len(lines)} row={i}\".ljust(width)[:width])\n return out\n\np = Progress(right_renderer=MetricsPanel(), split_ratio=0.5)\nh = p.add(\"task\", total=10)\nfor i in range(10): p.log(h, f\"event {i}\")\np.loop(); p.close()\n```\n\n\ub0b4\uc7a5 \ub80c\ub354\ub7ec:\n\n* `ConsoleRenderer`(\uae30\ubcf8): \uc81c\ubaa9 + \ub05d\ubd80\ubd84 \ub85c\uadf8\n* `StaticRenderer(lines)`: \uace0\uc815 \ubb38\uc790\uc5f4 \ub9ac\uc2a4\ud2b8\n\n---\n\n## \uc5d0\ub7ec \ub9ac\ud3ec\ud2b8\n\n\uc2e4\ud328\ud55c \ud0dc\uc2a4\ud06c\ub294 `Progress.close()` \uc2dc \uceec\ub7ec \ud2b8\ub808\uc774\uc2a4\ubc31\uacfc \ud568\uaed8 \uc694\uc57d\ub429\ub2c8\ub2e4.\n\n```python\nfrom braille_progress import Progress\n\np = Progress()\ntry:\n with p.task(\"upload\", total=3) as h:\n raise RuntimeError(\"remote closed\")\nexcept Exception:\n pass\n\np.close() # \uc2e4\ud328 \ud0dc\uc2a4\ud06c\uc758 \ud30c\uc77c/\ub77c\uc778/\ud568\uc218/\ucf54\ub4dc\uac00 \uac15\uc870\ub41c \ud2b8\ub808\uc774\uc2a4\ubc31 \ucd9c\ub825\n```\n\n`fail(error=..., error_tb=True)`\uc5d0 \uc608\uc678\ub97c \ub118\uae30\uba74 \ud2b8\ub808\uc774\uc2a4\ubc31\uc774 \uc608\uc058\uac8c \ud3ec\ub9e4\ud305\ub429\ub2c8\ub2e4.\n\n---\n\n## \ud050 \ubc14\uc778\ub354(\uba40\ud2f0\ud504\ub85c\uc138\uc2f1)\n\n```python\nfrom braille_progress import Progress, QueueBinder, progress_message\n\np = Progress()\nh = p.add(\"worker-0\", total=100)\n\n# \ubd80\ubaa8 \ud504\ub85c\uc138\uc2a4\nbinder = p.bind_queue(my_queue)\nwhile True:\n changed = binder.drain()\n if changed: p.render(throttle=False)\n if p.all_finished(): break\n\n# \uc6cc\ucee4 \ud504\ub85c\uc138\uc2a4\nmy_queue.put(progress_message(0, stage=\"writing\", done=5, total=100, label=\"chunk-5\"))\nmy_queue.put(progress_message(0, final=True)) # DONE\n```\n\n\uba54\uc2dc\uc9c0 \uc2a4\ud0a4\ub9c8\ub294 \uae30\ubcf8 \ud0a4(`i`, `stage`, `case_done`, `case_total`, `case_label`)\ub97c \uc0ac\uc6a9\ud558\uba70, `QueueBinder` \uc0dd\uc131 \uc2dc \ud0a4\ub97c \uc624\ubc84\ub77c\uc774\ub4dc\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.\n\n---\n\n# Progress \uac1d\uccb4 API\n\n---\n\n## \uc0dd\uc131\uc790(\ud30c\ub77c\ubbf8\ud130)\n\n```python\nProgress(\n theme: Optional[ProgressTheme]=None,\n *,\n auto_vt: bool=True,\n auto_refresh: bool=True,\n refresh_interval: float=0.05,\n force_tty: Optional[bool]=None,\n force_color: Optional[bool]=None,\n ratio_strategy: Optional[RatioStrategy]=None,\n\n layout: Optional[Layout]=None,\n header: Optional[Union[VLayout, Layout, Sequence[Row]]]=None,\n footer: Optional[Union[VLayout, Layout, Sequence[Row]]]=None,\n\n # \uc88c\uc6b0 \ubd84\ud560/\uc6b0\uce21 \ud328\ub110\n split_ratio: float=0.55,\n show_vsep: bool=True,\n right_renderer: Optional[DetailRenderer]=None,\n\n # \uc138\ub85c \uc815\ucc45\n row_policy: str=\"fit\", # \"fit\" | \"full\"\n min_body_rows: int=0, # \ucd5c\uc18c \ud45c\uc2dc \uc904 \uc218(fit/full \uacf5\ud1b5)\n max_body_rows: Optional[int]=None, # fit \ubaa8\ub4dc\uc5d0\uc11c \ucd5c\ub300 \uc904 \uc218 \uc81c\ud55c\n\n # \uc2a4\ud06c\ub9b0/\uc2dc\uadf8\ub110\n use_alt_screen: bool=False,\n handle_signals: bool=True\n)\n```\n\n### \ud45c\uc2dc/\uac31\uc2e0\n\n* `auto_refresh`: \uc0c1\ud0dc \ubcc0\uacbd \uc2dc \uc790\ub3d9 \ub9ac\ub80c\ub354.\n* `refresh_interval`: \uc790\ub3d9 \ub9ac\ub80c\ub354 \ucd5c\uc18c \uac04\uaca9(\ucd08).\n* `theme`: \ud3ed \uacc4\uc0b0/\uc0c9\uc0c1 \ud314\ub808\ud2b8. `ProgressTheme.auto_fit()` \uad8c\uc7a5.\n* `force_color`: `True`\uba74 ANSI \uc0c9 \uac15\uc81c, `False`\uba74 \ube44\ud65c\uc131.\n* `force_tty`: \uac15\uc81c\ub85c TTY \ubaa8\ub4dc\ub85c \ub80c\ub354(\ud30c\uc774\ud504 \ud658\uacbd \ud14c\uc2a4\ud2b8\uc6a9).\n* `ratio_strategy`: \uc9c4\ud589\ub960 \uacc4\uc0b0 \uc804\ub7b5 \ucee4\uc2a4\ud130\ub9c8\uc774\uc988.\n\n### \ub808\uc774\uc544\uc6c3(\uc88c\uce21 \ub9ac\uc2a4\ud2b8 \ub77c\uc778)\n\n* `layout`: \ud55c \uc904\uc744 \uad6c\uc131\ud558\ub294 \ube4c\ub529\ube14\ub85d(Layout DSL). \ubbf8\uc9c0\uc815 \uc2dc \uae30\ubcf8 \ub808\uc774\uc544\uc6c3.\n* `header`/`footer`: \uc0c1/\ud558\ub2e8\uc5d0 \uc138\ub85c \ub808\uc774\uc544\uc6c3 \ucd94\uac00. `VLayout`, `Layout`, `Row` \uc2dc\ud000\uc2a4 \uc9c0\uc6d0.\n\n### \uc88c\uc6b0 \ubd84\ud560/\uc6b0\uce21 \ud328\ub110\n\n* `split_ratio`: \uc88c\uce21 \ub9ac\uc2a4\ud2b8 \ud3ed \ube44\uc728(0.1\\~0.9).\n* `show_vsep`: \uc88c\uc6b0 \uc0ac\uc774\uc5d0 `\u2502` \ud45c\uc2dc.\n* `right_renderer`: \uc6b0\uce21 \ud328\ub110 \ucf58\ud150\uce20 \ub80c\ub354\ub7ec. \uae30\ubcf8\uc740 \ucf58\uc194 \ub85c\uadf8(`ConsoleRenderer`).\n\n### \uc138\ub85c \uc815\ucc45\n\n* `row_policy=\"fit\"`: \uc704 \uae30\uc874 \ucd9c\ub825\uc740 \uadf8\ub300\ub85c \ub450\uace0, **\uc544\ub798\uc5d0 \ud544\uc694\ud55c \uc904\ub9cc \ud655\ubcf4\ud558\uc5ec** \uadf8 \uc548\uc5d0\uc11c \uac31\uc2e0(\uc2a4\ud06c\ub9b0 \uc804\uccb4\ub97c \ucc44\uc6b0\uc9c0 \uc54a\uc74c).\n* `row_policy=\"full\"`: \ud604\uc7ac \ud130\ubbf8\ub110 \ub192\uc774\uc5d0 \ub9de\ucdb0 \ubcf8\ubb38 \uc601\uc5ed\uc744 \ucc44\uc6c0.\n* `min_body_rows`: \ucd5c\uc18c \uc904 \ubcf4\uc7a5.\n* `max_body_rows`: `fit` \ubaa8\ub4dc\uc5d0\uc11c \ucd5c\ub300 \uc904 \uc81c\ud55c.\n\n### \uc2a4\ud06c\ub9b0/\uc2dc\uadf8\ub110\n\n* `use_alt_screen`: `True`\uba74 ALT \uc2a4\ud06c\ub9b0(\ubcc4\ub3c4 \ubc84\ud37c) \uc0ac\uc6a9.\n* `handle_signals`: SIGINT/SIGTERM/SIGHUP\uc5d0\uc11c \ud130\ubbf8\ub110/\uc785\ub825 \ubaa8\ub4dc \uc548\uc804 \ubcf5\uad6c.\n\n---\n\n## \uba54\uc11c\ub4dc\n\n### \ud0dc\uc2a4\ud06c \uc0dd\uc131/\uac31\uc2e0\n\n```python\nh = p.add(name: str, total: int = 0) -> TaskHandle\n```\n\n* \uc0c8 \ud0dc\uc2a4\ud06c \ucd94\uac00. \ubc18\ud658\ub418\ub294 `TaskHandle`\ub85c \uac31\uc2e0.\n\n```python\np.update(handle_or_id, *, advance=0, done=None, total=None,\n stage=None, label=None, finished=None, failed=None) -> None\n```\n\n* \ud0dc\uc2a4\ud06c \uc0c1\ud0dc \uac31\uc2e0. `advance`\ub294 `done`\uc744 \uc99d\uac00\uc2dc\ud0b4.\n\n```python\nh.advance(n: int=1, *, label: Optional[str]=None, stage: Optional[str]=None) -> TaskHandle\n```\n\n* \uc9c4\ud589 \uc218\uce58 \uac04\ud3b8 \uc99d\uac00.\n\n```python\np.done(handle_or_id) -> None\nh.complete() -> None\n```\n\n* \ud0dc\uc2a4\ud06c \uc644\ub8cc \ucc98\ub9ac(\ud45c\uc2dc\uc0c1 `stage=\"done\"`).\n\n```python\np.fail(handle_or_id, *, stage: str=\"error\",\n error: Optional[Any]=None, error_tb: bool=True) -> None\nh.fail(stage: str=\"error\") -> None\n```\n\n* \uc2e4\ud328 \ucc98\ub9ac. `error`\uc5d0 \uc608\uc678 \uac1d\uccb4\ub97c \ub118\uae30\uba74 \uc885\ub8cc \uc2dc **\uc608\uc05c traceback** \ud3ec\ud568 \uc5d0\ub7ec \ub9ac\ud3ec\ud2b8 \ucd9c\ub825.\n\n```python\np.all_finished() -> bool\n```\n\n* \ubaa8\ub4e0 \ud0dc\uc2a4\ud06c \uc885\ub8cc \uc5ec\ubd80.\n\n### \ucee8\ud14d\uc2a4\ud2b8 API\n\n```python\nwith p.task(\"name\", total=10) as h:\n ...\n```\n\n* \ube14\ub85d \ub0b4 \uc608\uc678 \ubc1c\uc0dd \uc2dc \uc790\ub3d9 `fail()`, \uc815\uc0c1 \uc885\ub8cc \uc2dc \uc790\ub3d9 `done()`.\n\n### \ubc18\ubcf5 \ub3c4\uc6b0\ubbf8\n\n```python\nfor item in p.track(iterable, total=None, description=None, label_from=None):\n ...\n```\n\n* \ubc18\ubcf5 \uc911 \uc790\ub3d9 \uc9c4\ud589/\ub77c\ubca8 \uac31\uc2e0. \uc608\uc678 \uc2dc \uc790\ub3d9 `fail()`.\n\n### \ub85c\uadf8/\uc6b0\uce21 \ud328\ub110\n\n```python\np.log(handle_or_id, msg: str) -> None\n```\n\n* \ud574\ub2f9 \ud0dc\uc2a4\ud06c\uc758 \ub85c\uadf8\ub97c \uc6b0\uce21 \ud328\ub110\uc5d0 \ucd94\uac00(\ucd5c\ub300 \ucd5c\uadfc 2000\uc904 \uc720\uc9c0).\n\n```python\np.set_right_renderer(renderer: DetailRenderer) -> None\n```\n\n* \uc6b0\uce21 \ud328\ub110 \ub80c\ub354\ub7ec \uad50\uccb4.\n\n```python\nwith p.hijack_stdio(handle_or_id):\n # \uc774 \ube14\ub85d\uc758 stdout/stderr\ub294 \uc6b0\uce21 \ud328\ub110 \ub85c\uadf8\ub85c \uc720\uc785\n print(\"captured\")\n```\n\n### \ub80c\ub354/\ub8e8\ud504/\uc885\ub8cc\n\n```python\np.render(throttle: bool=False) -> None\n```\n\n* \uc218\ub3d9 \ub80c\ub354. `throttle=True`\uba74 `refresh_interval`\uc744 \uc874\uc911.\n\n```python\np.loop() -> None\n```\n\n* \uc778\ud130\ub799\ud2f0\ube0c UI \ub8e8\ud504 \uc2dc\uc791.\n\n * \ud0a4\ubcf4\ub4dc: `w`/`s` \ub610\ub294 `\u2191`/`\u2193`\ub85c \uc88c\uce21 \uc120\ud0dd \uc774\ub3d9.\n * \ub9c8\uc6b0\uc2a4: \uc88c\uce21 \ub9ac\uc2a4\ud2b8 \uc601\uc5ed \ud074\ub9ad\uc73c\ub85c \uc120\ud0dd.\n * `q` \ub610\ub294 `Ctrl+C`\ub85c \uc885\ub8cc.\n\n```python\np.close() -> None\n```\n\n* \ud654\uba74 \uc815\ub9ac, \uc2e4\ud328 \ud0dc\uc2a4\ud06c \uc5d0\ub7ec \ub9ac\ud3ec\ud2b8 \ucd9c\ub825, \ub9c8\uc6b0\uc2a4/\uc785\ub825 \ubaa8\ub4dc/ALT \uc2a4\ud06c\ub9b0/VT \ubaa8\ub4dc \ub4f1 **\uc644\uc804 \ubcf5\uad6c**.\n\n### \ud050 \ubc14\uc778\ub354(\uc120\ud0dd)\n\n```python\nqb = p.bind_queue(queue, id_key=\"i\", stage_key=\"stage\",\n done_key=\"case_done\", total_key=\"case_total\",\n label_key=\"case_label\")\nchanged = qb.drain()\n```\n\n* \uc678\ubd80 \uc6cc\ucee4 \uba54\uc2dc\uc9c0\ub97c UI\uc5d0 \ubc18\uc601.\n\n---\n\n## \uc6b0\uce21 \ud328\ub110 \ucee4\uc2a4\ud140 \ub80c\ub354\ub7ec\n\n```python\nfrom braille_progress import DetailRenderer\n\nclass MyPanel(DetailRenderer):\n def render(self, *, width, height, styler, title, lines):\n out = [styler.color(f\"[{title}] metrics\", fg=\"bright_magenta\").ljust(width)[:width]]\n for i in range(1, height):\n txt = f\"rows={height}, logs={len(lines)}\"\n out.append(txt.ljust(width)[:width])\n return out\n```\n\n* `width`/`height` \ub0b4\uc5d0\uc11c\ub9cc \ucd9c\ub825\ud558\ub3c4\ub85d \ubc18\ub4dc\uc2dc \ud328\ub529/\uc808\ub2e8 \ucc98\ub9ac.\n\n---\n\n## \uc88c\uce21 \ub77c\uc778 \ub808\uc774\uc544\uc6c3 \uad50\uccb4(\uc694\uc57d)\n\n* DSL \uad6c\uc131\uc694\uc18c \uc608: `Name()`, `Bar()`, `Percent()`, `Status()`, `MiniBar()`, `Counter()`, `Label()`, `Elapsed()`, `AvgRate()`, `ETA()`, `Text(\" | \")`, `Gap(w)` \ub4f1.\n* \uc608:\n\n```python\nfrom braille_progress import Layout, Name, Text, Bar, Percent, Status, MiniBar, Counter, Label\n\nlayout = Layout([\n Name(w=20), Text(\" | \"),\n Bar(cells=18), Percent(), Text(\" \"),\n Status(w=16), Text(\" | \"),\n MiniBar(cells=10), Counter(), Text(\" \"),\n Label(w=32)\n])\np = Progress(layout=layout)\n```\n\n---\n\n## \ubaa8\ub4dc/\ud658\uacbd \ubcc0\uc218\n\n* `use_alt_screen=True`: \uba54\uc778 \ud130\ubbf8\ub110\uacfc \ubd84\ub9ac\ub41c \ubc84\ud37c\uc5d0 \ub80c\ub354(\uc678\ubd80 \ucd9c\ub825 \uac04\uc12d \uc904\uc784).\n* `NO_COLOR=1`: \uac15\uc81c\ub85c \ubb34\ucc44\uc0c9.\n* `BP_FORCE_TTY=1`: \uac15\uc81c\ub85c TTY \ubaa8\ub4dc.\n* `BP_FORCE_COLOR=1`: \uac15\uc81c\ub85c \uceec\ub7ec \ud65c\uc131.\n\n---\n\n## \uc5d0\ub7ec \ub9ac\ud3ec\ud2b8\n\n* `p.close()` \uc2dc \uc2e4\ud328 \ud0dc\uc2a4\ud06c\ub97c \uc218\uc9d1\ud558\uc5ec:\n\n * \ud0dc\uc2a4\ud06c\uba85/\uc2a4\ud14c\uc774\uc9c0/\uc9c4\ud589/\uacbd\uacfc/\uc18d\ub3c4\n * \uc804\ub2ec\ubc1b\uc740 `error` \uc608\uc678\uc758 **\uceec\ub7ec \ud2b8\ub808\uc774\uc2a4\ubc31**(\ud30c\uc77c/\ub77c\uc778/\ud568\uc218/\ucf54\ub4dc) \ucd9c\ub825.\n\n---\n\n## \uc0ac\uc6a9 \ud301\n\n* \ub8e8\ud504 \ub3d9\uc548 \uc678\ubd80 `print()`\uac00 \ud544\uc694\ud558\uba74 \ubc18\ub4dc\uc2dc `hijack_stdio()`\ub85c \uac10\uc2f8 \uc6b0\uce21 \ub85c\uadf8\ub85c \ubcf4\ub0b4\ub77c(\ub808\uc774\uc544\uc6c3 \uae68\uc9d0 \ubc29\uc9c0).\n* `row_policy=\"fit\"`\uc740 \uae30\uc874 \uc0c1\ub2e8 \ucd9c\ub825 \ubcf4\uc874. \ud544\uc694 \uc904\uc774 \ub298\uba74 \uc544\ub798\ub85c\ub9cc \ud655\uc7a5\ud55c\ub2e4.\n `row_policy=\"full\"`\uc740 \ud130\ubbf8\ub110 \ub192\uc774\ub97c \ucc44\uc6b0\uba70 \ud5e4\ub354/\ud478\ud130\uc640 \ud568\uaed8 \uc2a4\ud14c\uc774\ube14\ud558\uac8c \uac31\uc2e0\ud55c\ub2e4.\n* Windows\uc5d0\uc11c\ub294 Windows Terminal + \uace0\uc815\ud3ed \ud3f0\ud2b8 \uc0ac\uc6a9\uc744 \uad8c\uc7a5.\n\n\n## \ub80c\ub354\ub9c1/\ud130\ubbf8\ub110 \ub3d9\uc791\n\n* \ub80c\ub354\ub9c1 \uc911 \uc790\ub3d9\uc904\ubc14\uafc8\uc744 \ub044\uace0, \ubaa8\ub4e0 \uc904\uc744 `cols-1` \ub0b4\ub85c \uac00\uc2dc\ud3ed \uae30\uc900 \uc808\ub2e8. \ub9c8\uc9c0\ub9c9 \uc904\uc740 \uac1c\ud589 \uc5c6\uc774 \ucd9c\ub825\ud574 \uc2a4\ud06c\ub864\uc744 \ub9c9\uc2b5\ub2c8\ub2e4.\n* `loop()`\uc5d0\uc11c VT \uc785\ub825\u00b7\ub9c8\uc6b0\uc2a4 \ud65c\uc131\ud654, `close()`/\uc885\ub8cc \uc2dc \ub9c8\uc6b0\uc2a4/\ud3ec\ucee4\uc2a4/\ube0c\ub798\ud0b7\ub4dc \ud398\uc774\uc2a4\ud2b8 \ubaa8\ub4dc \ud574\uc81c(`?1006/?1002/?1000/?1015/?1004/?2004`), \ucee4\uc11c \ubcf4\uc774\uae30\u00b7wrap \ubcf5\uad6c, \uc785\ub825 \ubc84\ud37c \ud50c\ub7ec\uc2dc(Windows `FlushConsoleInputBuffer`, POSIX `tcflush`), ALT \uc2a4\ud06c\ub9b0 \uc885\ub8cc, \ucf58\uc194 \ubaa8\ub4dc \uc6d0\ubcf5.\n* \ub80c\ub354\ub9c1 \uc911 `print()` \ud638\ucd9c\uc740 \ud654\uba74\uc744 \ud754\ub4ed\ub2c8\ub2e4. `p.log(...)` \ub610\ub294 `p.hijack_stdio(handle)` \uc0ac\uc6a9\uc744 \uad8c\uc7a5.\n\n---\n\n## \ud658\uacbd \ubcc0\uc218\n\n* `BP_FORCE_TTY=1` : TTY \ubaa8\ub4dc \uac15\uc81c\n* `BP_FORCE_COLOR=1` : \uceec\ub7ec \uac15\uc81c\n* `NO_COLOR=1` : \uceec\ub7ec \ube44\ud65c\uc131\ud654\n\n---\n\n## \uc608\uc2dc\n\n### \uc5d0\ub7ec \ud3ec\ud568 \ucd5c\uc18c \uc608\uc2dc\n\n```python\nfrom braille_progress import Progress\n\np = Progress()\nh = p.add(\"upload\", total=3)\ntry:\n for i in range(3):\n if i == 2: raise RuntimeError(\"remote closed\")\n h.advance(1, stage=\"writing\")\nexcept Exception as e:\n h.fail(error=e)\np.close()\n```\n\n### Fit \ubaa8\ub4dc \ub300\uc2dc\ubcf4\ub4dc(ALT \uc2a4\ud06c\ub9b0 \uc5c6\uc774)\n\n```python\nfrom braille_progress import Progress, default_layout, ProgressTheme\n\np = Progress(\n layout=default_layout(ProgressTheme.auto_fit()),\n row_policy=\"fit\",\n max_body_rows=10,\n split_ratio=0.6,\n show_vsep=True,\n use_alt_screen=False\n)\n# ... \ud0dc\uc2a4\ud06c/\ub85c\uadf8 \ucd94\uac00 ...\np.loop(); p.close()\n```\n\n---\n\n## \uacf5\uac1c \uc2ec\ubcfc\n\n```python\nfrom braille_progress import (\n Progress, ProgressTheme, TaskHandle, TaskState,\n RatioStrategy, DefaultRatio, QueueBinder, progress_message,\n Layout, default_layout, RenderContext,\n Name, Bar, Percent, Status, MiniBar, Counter, Label, Text, Gap,\n Elapsed, AvgRate, ETA, Spacer, Rule, Now, VGap, VLayout,\n DetailRenderer, ConsoleRenderer, StaticRenderer\n)\n```\n",
"bugtrack_url": null,
"license": "MIT License\n \n Copyright (c) 2025 KIBA\n \n Permission is hereby granted, free of charge, to any person obtaining a copy\n of this software and associated documentation files (the \"Software\"), to deal\n in the Software without restriction, including without limitation the rights\n to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n copies of the Software, and to permit persons to whom the Software is\n furnished to do so, subject to the following conditions:\n \n The above copyright notice and this permission notice shall be included in all\n copies or substantial portions of the Software.\n \n THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n SOFTWARE.",
"summary": "Braille-based multi progress bars with ANSI colors (docker-compose style)",
"version": "1.0.0",
"project_urls": {
"Homepage": "https://github.com/kibalab/braille-progress",
"Repository": "https://github.com/kibalab/braille-progress"
},
"split_keywords": [
"ansi",
" braille",
" cli",
" progress",
" terminal"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "267c63fcc5675272473071020065b771ddeea6d52a722cf549a0b960fb1e46b5",
"md5": "cd68f07a89c28ab09550678605e66339",
"sha256": "dc78e9ce96461bca8117e722184c6eb31b6ab4ba2fed7c9f449d6ccf5dffad28"
},
"downloads": -1,
"filename": "bprogress-1.0.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "cd68f07a89c28ab09550678605e66339",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 30533,
"upload_time": "2025-08-13T18:50:36",
"upload_time_iso_8601": "2025-08-13T18:50:36.575470Z",
"url": "https://files.pythonhosted.org/packages/26/7c/63fcc5675272473071020065b771ddeea6d52a722cf549a0b960fb1e46b5/bprogress-1.0.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "985f27ecaaac9cd340e4b5611432ebcf1da4c28d45bffbd44c82e9ef8b51beea",
"md5": "4cefdc372967475940bfafe838c34ff5",
"sha256": "8c8252369231432cdcbf67a16b39f8c6378220fa59d95b9bf899fb4f57ec60c8"
},
"downloads": -1,
"filename": "bprogress-1.0.0.tar.gz",
"has_sig": false,
"md5_digest": "4cefdc372967475940bfafe838c34ff5",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 24390,
"upload_time": "2025-08-13T18:51:05",
"upload_time_iso_8601": "2025-08-13T18:51:05.737087Z",
"url": "https://files.pythonhosted.org/packages/98/5f/27ecaaac9cd340e4b5611432ebcf1da4c28d45bffbd44c82e9ef8b51beea/bprogress-1.0.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-08-13 18:51:05",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "kibalab",
"github_project": "braille-progress",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"lcname": "bprogress"
}