Python Textual 라이브러리로 TUI App 개발:배포

지진우·2023년 12월 12일
0

1. Introduction

이전 포스팅에서 textual을 이용해 간단한 TUI app을 만들어보았습니다. 이번 포스팅에서는 이 라이브러리를 사용자들이 쉽게 활용할 수 있도록 배포하는 방법에 대해 알아보겠습니다.

2. 환경설정

먼저 poetry를 사용하여 PyPI에 배포하기 위한 환경을 설정합니다.

pip install pipx

pipx install poetry

이후 바탕화면에서 새로운 poetry 프로젝트를 생성합니다.

poetry new textual_app

생성한 poetry 프로젝트로 이동하여 코드를 작성할 편집기로 폴더를 열어줍니다! (예: vscode)

cd textual-app
code .

프로젝트의 구조는 다음과 같습니다.

> tests
> textual_app
pyproject.toml
README.md

3. Textual app 생성

간단한 설정이 완료되었으므로 textual app을 만들어보겠습니다. 먼저 필요한 라이브러리를 설치합니다.

poetry add "textual[dev]"

설치된 패키지는 pyproject.toml의 dependencies에 추가됩니다. pyproject.toml은 Python 프로젝트의 빌드 시스템 요구 사항을 담은 파일입니다.

[tool.poetry]
name = "textual-app"
version = "0.1.0"
description = ""
authors = ["Earthquakoo <cream5343@gmail.com>"]
readme = "README.md"

[tool.poetry.dependencies]
python = "^3.9"
textual = {extras = ["dev"], version = "^0.44.1"}


[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

필요한 라이브러리가 설치되었다면 poetry 가상환경을 실행합니다.

poetry shell

이제 저번 포스팅에서 만들었던 코드를 가져와 textual_app 폴더에서 main.py 파일을 생성하고 붙여넣기 해줍니다.

from textual import on
from textual.app import App, ComposeResult
from textual.screen import Screen
from textual.containers import Container, Vertical
from textual.widgets import Input, RichLog, Button


class HomeScreen(Screen):
    
    def compose(self) -> ComposeResult:
        yield Container(
            Button("Get Started", id="get_started", classes="button_widget"),
            Button("Exit", id="exit", classes="button_widget"),
            classes="home_screen_container",
        )
        
    @on(Button.Pressed)
    def button_pressed_handler(self, event: Button.Pressed) -> None:
        if event.button.id == "get_started":
            self.app.push_screen(ChatScreen())
        elif event.button.id == "exit":
            self.app.exit()
            
            
class ChatScreen(Screen):
    
    def compose(self) -> ComposeResult:
        yield Vertical(
            RichLog(classes="richlog_widget"),
            Input(placeholder="Enter chat", classes="input_widget"),
            classes="vertical_layout"
        )
                
    @on(Input.Submitted)
    def input_submitted_handler(self, event: Input.Submitted):
        log = self.query_one(RichLog)
        log.write(f"earthquake: {event.value}")
        input = self.query_one(Input)
        input.value = ""    
    


class ChatApp(App):
    CSS_PATH = "main.css"
    
    def on_mount(self) -> None:
        self.app.push_screen(HomeScreen())


if __name__  == "__main__":
    app=ChatApp()
    app.run()

위와 같은 예시 프로젝트를 실행하기 위해서 터미널에 아래와 같이 입력했습니다. 하지만 이것은 사용들이 사용하기엔 조금 불편함이 있습니다. 이를 수정해보도록 하겠습니다.

python textual_app/main.py

4. 패키지 관리

이제 pyproject.toml에서 몇 가지를 수정해야 합니다.

  • version은 0.1.0으로 초기화 되어있습니다.
  • description은 app에 대한 간단한 설명을 추가할 수 있습니다.
  • [tool.poetry.scripts]로 패키지를 실행하는 명령을 정의할 수 있습니다.

여기서 중요한 것은 [tool.poetry.scripts] 입니다. 이전에 프로젝트를 실행하기 위해 터미널에서 python textual_app/main.py 로 실행했습니다. 그러나 이것은 조금 번거로우며 사용자가 길게 늘어진 패키지 타이핑을 실행하는 것은 좋지 못한 예입니다.

따라서 해당 패키지를 설치했을 때 간단한 타이핑으로 이 app을 실행할 수 있도록 scripts를 추가합니다.

[tool.poetry]
name = "textual-app"
version = "0.1.0"
description = "Simple textual app"
authors = ["Earthquakoo <cream5343@gmail.com>"]
readme = "README.md"

[tool.poetry.dependencies]
python = "^3.9"
textual = {extras = ["dev"], version = "^0.44.1"}

[tool.poetry.scripts]
textual-app = "textual_app.main:main"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

scripts를 설정했다면 main.py에서 해당 app의 경로를 정의한 것을 삭제하고 패키지를 실행하는 함수를 생성합니다.

from textual import on
from textual.app import App, ComposeResult
from textual.screen import Screen
from textual.containers import Container, Vertical
from textual.widgets import Input, RichLog, Button


class HomeScreen(Screen):
    
    def compose(self) -> ComposeResult:
        yield Container(
            Button("Get Started", id="get_started", classes="button_widget"),
            Button("Exit", id="exit", classes="button_widget"),
            classes="home_screen_container",
        )
        
    @on(Button.Pressed)
    def button_pressed_handler(self, event: Button.Pressed) -> None:
        if event.button.id == "get_started":
            self.app.push_screen(ChatScreen())
        elif event.button.id == "exit":
            self.app.exit()
            
            
class ChatScreen(Screen):
    
    def compose(self) -> ComposeResult:
        yield Vertical(
            RichLog(classes="richlog_widget"),
            Input(placeholder="Enter chat", classes="input_widget"),
            classes="vertical_layout"
        )
                
    @on(Input.Submitted)
    def input_submitted_handler(self, event: Input.Submitted):
        log = self.query_one(RichLog)
        log.write(f"earthquake: {event.value}")
        input = self.query_one(Input)
        input.value = ""    
    


class ChatApp(App):
    CSS_PATH = "main.css"
    
    def on_mount(self) -> None:
        self.app.push_screen(HomeScreen())


def main():
    app = ChatApp()
    app.run()


# if __name__ == "__main__":
#     app = ChatApp()
#     app.run()

5. 패키지 테스트 및 배포

테스트를 위해 로컬에서 패키지를 설치하고 실행해봅니다.

poetry install

패키지가 제대로 설치되었다면 아래의 textual-app 명령어로 간단히 패키지를 실행할 수 있습니다!

textual-app

이제 다른 사람들이 해당 패키지를 다운로드할 수 있도록 PyPI에 배포를 해보겠습니다.

poetry build

poetry build 명령어를 실행하면 프로젝트 구조는 아래와 같아집니다.

> dist
> tests
> textual_app
pyproject.toml
README.md

이제 PyPI사이트에서 계정을 생성하고 아래와 같이 API 토큰을 등록해주고 발급 받은 토큰은 따로 저장해둡니다.

아래의 커맨드와 함께 저장한 토큰을 입력합니다.

poetry config pypi-token.pypi <Your api token>

이제 패키지를 배포합니다.

poetry publish --build

PyPI에서 자신의 패키지명을 검색하면 배포가 된 것을 확인할 수 있습니다.

터미널에서 배포한 패키지를 설치해서 테스트합니다.

pip install textual-app

textual-app

6. 패키지 업데이트

만약 수정할 것이 있거나 새로운 기능이 생겨 업데이트가 필요하다면 pyproject.toml에서 versiondescription을 수정합니다.

[tool.poetry]
name = "textual-app"
version = "0.1.1"
description = "Update textual app"
authors = ["Earthquakoo <cream5343@gmail.com>"]
readme = "README.md"

[tool.poetry.dependencies]
python = "^3.9"
textual = {extras = ["dev"], version = "^0.44.1"}

[tool.poetry.scripts]
textual-app = "textual_app.main:main"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

또한 textual_app/__init__.py__version__도 함께 수정합니다.

__version__ = "0.1.1"

다시 아래의 커맨드로 업데이트된 패키지를 배포합니다.

poetry publish --build

이후 패키지를 업데이트하려면 다음을 실행합니다.

pip install textual-app --upgrade
profile
높이보다는 멀리, 넓게보다는 깊게

0개의 댓글