Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Publish to PyPI

on:
release:
types: [published]

jobs:
build-n-publish:
name: Build and publish to PyPI
runs-on: ubuntu-latest
permissions:
id-token: write # Mandatory for Trusted Publishing (OIDC)
contents: read

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.x"

- name: Install build dependencies
run: |
python -m pip install --upgrade pip
pip install build

- name: Build binary wheel and source tarball
run: python -m build

- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
6 changes: 3 additions & 3 deletions .github/workflows/pylint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pylint
pip install -r requirements.txt
pip install ".[dev]"
- name: Analysing the code with pylint
run: |
pylint $(git ls-files '*.py')
pylint app tests
9 changes: 6 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,19 @@ on:
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install ".[dev]"
- name: Run tests with pytest
run: |
pytest --cov=app --cov-fail-under=100
PYTHONPATH=. pytest --cov=app --cov-fail-under=100
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,8 @@
**/__pycache__
htmlcov/
.pytest_cache/
*.egg-info/
.eggs/
*.egg
build/
dist/
47 changes: 27 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# MLB Box Score TUI

A high-performance, Python-based Terminal User Interface (TUI) for Major League Baseball (MLB) scores, schedules, and standings. Built with `pytermgui` and the `MLB-StatsAPI`, featuring a modular architecture and 100% test coverage.
A Python-based Terminal User Interface (TUI) for Major League Baseball (MLB) scores, schedules, and standings. Built with `pytermgui` and the `MLB-StatsAPI`, featuring a modular architecture and 100% test coverage.

## Features

Expand All @@ -13,12 +13,15 @@ A high-performance, Python-based Terminal User Interface (TUI) for Major League

## Installation

### Prerequisites
### From PyPI (Recommended)

- Python 3.10+
- A terminal with support for box-drawing characters and ANSI colors.
Install the application directly using `pip`:

### Setup
```bash
pip install mlb-cli-py
```

### Local Setup (Development)

1. **Clone the repository:**
```bash
Expand All @@ -32,17 +35,17 @@ A high-performance, Python-based Terminal User Interface (TUI) for Major League
source venv/bin/activate
```

3. **Install dependencies:**
3. **Install in editable mode:**
```bash
pip install -r requirements.txt
pip install -e ".[dev]"
```

## Usage

Run the application using the main entry point:
After installation, run the application from anywhere in your terminal:

```bash
python3 mlb_cli.py
mlb-cli
```

## Controls
Expand Down Expand Up @@ -75,17 +78,17 @@ When in the Calendar view, you can use specialized keys for precise date selecti

## Project Structure

The project follows a strict modular design:
The project follows a strict modular design optimized for distribution:

```text
mlb-cli-py/
├── mlb_cli.py # Main application controller and state management
├── pyproject.toml # Modern build system configuration and metadata
├── app/
│ ├── mlb_cli.py # Main application controller and TUI entry point
│ ├── models/ # Data services (API fetching, caching, date utilities)
│ ├── widgets/ # Modular TUI components (Game, Standing, Calendar widgets)
│ └── screens/ # Class-based screen definitions (Schedule, Standings, Calendar)
├── tests/ # 100% covered test suite (Unit and integration tests)
├── requirements.txt # Project dependencies
└── README.md # You are here!
```

Expand All @@ -98,23 +101,27 @@ The project maintains a perfect **10.00/10 Pylint score** and **100% test covera
To run the full test suite using `pytest`:

```bash
pytest
```

To run tests with coverage reporting:

```bash
pytest --cov=app --cov=mlb_cli tests/
PYTHONPATH=. pytest
```

### Linting

To verify code quality:

```bash
pylint mlb_cli.py app tests
pylint app tests
```

## Release Workflow

This project uses an automated release pipeline to publish updates to PyPI:

1. **Version Bump:** Update the version in `pyproject.toml`.
2. **GitHub Release:** Create and publish a new Release on GitHub with a version tag (e.g., `v0.1.0`).
3. **Automated Publish:** A GitHub Action is triggered by the release, which:
- Builds the source distribution and wheel.
- Publishes the package to PyPI using **Trusted Publishing (OIDC)**.

## Acknowledgements

This project would not be possible without the [MLB-StatsAPI](https://github.com/toddrob99/MLB-StatsAPI) and [PyTermGUI](https://github.com/bczsalba/pytermgui) projects.
Expand Down
14 changes: 7 additions & 7 deletions mlb_cli.py → app/mlb_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,21 @@
"""
from datetime import datetime, timedelta
import pytermgui as ptg
from app.models.data_service import (
from .models.data_service import (
fetch_teams,
format_date
)
from app.screens import (
from .screens import (
ScheduleScreen,
StandingsScreen,
CalendarScreen,
ErrorScreen
)
from app.widgets import CalendarWidget, slide_transition
from app.config import STATIC_WIDTH, INITIAL_HEIGHT
from app.state import ApplicationState
from app.exceptions import APIError
from app.logger import get_logger
from .widgets import CalendarWidget, slide_transition
from .config import STATIC_WIDTH, INITIAL_HEIGHT
from .state import ApplicationState
from .exceptions import APIError
from .logger import get_logger

logger = get_logger(__name__)

Expand Down
40 changes: 39 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,40 @@
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"

[project]
name = "mlb-cli-py"
version = "0.1.0"
description = "A modern TUI for MLB scores, standings, and schedules"
readme = "README.md"
authors = [{ name = "Conor Cleary" }]
license = { file = "LICENSE" }
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Environment :: Console",
"Topic :: Terminals",
]
dependencies = [
"pytermgui==7.7.4",
"MLB-StatsAPI==1.9.0",
"redis==5.0.4",
]

[project.optional-dependencies]
dev = [
"pytest==8.2.0",
"pytest-cov==5.0.0",
"fakeredis==2.23.2",
]

[project.scripts]
mlb-cli = "app.mlb_cli:main"

[tool.setuptools.packages.find]
where = ["."]
include = ["app*"]

[tool.pylint.messages_control]
disable = ["fixme"]
disable = ["fixme"]
6 changes: 0 additions & 6 deletions requirements.txt

This file was deleted.

12 changes: 6 additions & 6 deletions tests/test_app_lifecycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import unittest
from unittest.mock import patch, MagicMock
import pytermgui as ptg
from mlb_cli import MLBApp, main
from app.mlb_cli import MLBApp, main


class TestAppLifecycle(unittest.TestCase):
Expand All @@ -13,8 +13,8 @@ class TestAppLifecycle(unittest.TestCase):

def setUp(self):
"""Initialize MLBApp with mocked WindowManager."""
with patch('mlb_cli.fetch_teams'), \
patch('mlb_cli.ptg.WindowManager') as mock_manager:
with patch('app.mlb_cli.fetch_teams'), \
patch('app.mlb_cli.ptg.WindowManager') as mock_manager:
self.app = MLBApp()
self.app.manager = mock_manager.return_value
self.app.manager.terminal.width = 100
Expand Down Expand Up @@ -52,7 +52,7 @@ def test_set_window_data_same_page_with_callback(self):
self.app.set_window_data([], "Title", "page1", on_finish=mock_finish)
mock_finish.assert_called_once()

@patch('mlb_cli.slide_transition')
@patch('app.mlb_cli.slide_transition')
def test_set_window_data_transition(self, mock_transition):
"""Test set_window_data triggers slide_transition for subsequent calls."""
self.app.is_initialized = True
Expand All @@ -69,7 +69,7 @@ def test_exit_app(self):
self.app.exit_app()
self.app.manager.stop.assert_called_once()

@patch('mlb_cli.ptg.Window')
@patch('app.mlb_cli.ptg.Window')
def test_run(self, _mock_window):
"""Test main run loop setup."""
# Mock run to exit immediately
Expand All @@ -82,7 +82,7 @@ def test_run(self, _mock_window):
mock_update.assert_called_once()
self.app.manager.run.assert_called_once()

@patch('mlb_cli.MLBApp')
@patch('app.mlb_cli.MLBApp')
def test_main(self, mock_app_class):
"""Test the main() entry point."""
mock_app_instance = mock_app_class.return_value
Expand Down
10 changes: 5 additions & 5 deletions tests/test_calendar_interaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from unittest.mock import patch, MagicMock
from datetime import datetime
import pytermgui as ptg
from mlb_cli import MLBApp
from app.mlb_cli import MLBApp
from app.widgets import CalendarWidget


Expand All @@ -15,8 +15,8 @@ class TestCalendarInteraction(unittest.TestCase):

def setUp(self):
"""Initialize MLBApp with mocked WindowManager."""
with patch('mlb_cli.fetch_teams'), \
patch('mlb_cli.ptg.WindowManager') as mock_manager:
with patch('app.mlb_cli.fetch_teams'), \
patch('app.mlb_cli.ptg.WindowManager') as mock_manager:
self.app = MLBApp()
self.app.manager = mock_manager.return_value
self.app.manager.terminal.width = 100
Expand Down Expand Up @@ -92,7 +92,7 @@ def test_focus_current_date_in_calendar_targets(self):
self.assertTrue(self.app._focus_current_date_in_calendar(target="last"))
self.assertEqual(self.app.manager.focused, btn_june30)

@patch('mlb_cli.CalendarScreen.get_widgets')
@patch('app.mlb_cli.CalendarScreen.get_widgets')
def test_update_to_calendar(self, mock_get):
"""Test transition to calendar."""
mock_get.return_value = ([], "Calendar")
Expand All @@ -103,7 +103,7 @@ def test_update_to_calendar(self, mock_get):
_, kwargs = mock_set.call_args
self.assertTrue(callable(kwargs['on_finish']))

@patch('mlb_cli.CalendarScreen.get_widgets')
@patch('app.mlb_cli.CalendarScreen.get_widgets')
def test_update_to_calendar_from_standings(self, mock_get):
"""Test that date is reset when returning from standings."""
mock_get.return_value = ([], "Calendar")
Expand Down
6 changes: 3 additions & 3 deletions tests/test_error_handling.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import unittest
from unittest.mock import patch, MagicMock
import pytermgui as ptg
from mlb_cli import MLBApp
from app.mlb_cli import MLBApp
from app.exceptions import APIError
from app.screens import ErrorScreen

Expand All @@ -14,8 +14,8 @@ class TestErrorHandling(unittest.TestCase):

def setUp(self):
"""Initialize MLBApp with mocked WindowManager."""
with patch('mlb_cli.fetch_teams'), \
patch('mlb_cli.ptg.WindowManager') as mock_manager:
with patch('app.mlb_cli.fetch_teams'), \
patch('app.mlb_cli.ptg.WindowManager') as mock_manager:
self.app = MLBApp()
self.app.manager = mock_manager.return_value
self.app.manager.terminal.width = 100
Expand Down
Loading
Loading