From 22652d04560dee20a5cb551d27200c8d0e64cccb Mon Sep 17 00:00:00 2001 From: mikaeld Date: Thu, 1 May 2025 13:31:14 -0400 Subject: [PATCH 1/5] chore(ci): Add github actions with codecov. --- .github/workflows/ci.yaml | 47 ++++++++++++++++++++++++++ .gitignore | 4 +++ README.md | 3 ++ app.py | 19 ++++++----- pyproject.toml | 39 +++++++++++++++++++++ secrets_manager/models/gcp_projects.py | 11 +++--- secrets_manager/utils/gcp.py | 13 +++---- secrets_manager/utils/helpers.py | 11 +++--- tests/conftest.py | 18 +++++----- tests/test_models/test_gcp_projects.py | 17 +++++++--- tests/test_utils/test_gcp.py | 30 ++++++++-------- tests/test_utils/test_helpers.py | 4 +-- uv.lock | 44 ++++++++++++++++++++++++ 13 files changed, 200 insertions(+), 60 deletions(-) create mode 100644 .github/workflows/ci.yaml create mode 100644 .gitignore diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..6047173 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,47 @@ +name: CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.13"] + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: 'uv' + + - name: Install uv + run: | + pip install uv + + - name: Lint with Ruff + run: | + uv run ruff check . + uv run ruff format --check . + + - name: Run tests + run: | + uv run pytest --cov --cov-branch --cov-report=xml --junitxml=junit.xml -o junit_family=legacy + + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Upload test results to Codecov + if: ${{ !cancelled() }} + uses: codecov/test-results-action@v1 + with: + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2a630fa --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +coverage.xml +.coverage +htmlcov/ +junit.xml diff --git a/README.md b/README.md index 5b29ed6..5f18771 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +![CI](https://github.com/mikaeld/secrets-manager/workflows/CI/badge.svg) +[![codecov](https://codecov.io/gh/mikaeld/secrets-manager/branch/main/graph/badge.svg)](https://codecov.io/gh//secrets-manager) + ![tui.png](imgs/tui.png) # Secrets Manager diff --git a/app.py b/app.py index d154a53..a5bff28 100644 --- a/app.py +++ b/app.py @@ -1,16 +1,15 @@ -from google.cloud import secretmanager from google.api_core.exceptions import GoogleAPICallError +from google.cloud import secretmanager from textual import work from textual.app import App, ComposeResult from textual.binding import Binding from textual.containers import Horizontal, Vertical from textual.reactive import reactive -from textual.widgets import Header, Footer, Tree, DataTable, Input -from textual.markup import escape +from textual.widgets import DataTable, Footer, Header, Input, Tree -from secrets_manager.utils.gcp import search_gcp_projects, list_secrets, get_secret_versions -from secrets_manager.utils.helpers import sanitize_project_id_search, format_error_message from secrets_manager.models.gcp_projects import GCPProject +from secrets_manager.utils.gcp import get_secret_versions, list_secrets, search_gcp_projects +from secrets_manager.utils.helpers import format_error_message, sanitize_project_id_search class SecretsManager(App): @@ -88,7 +87,8 @@ def _do_search(self, search_term: str) -> None: except Exception as e: self.notify( f"Failed to search projects: {format_error_message(str(e), 200)}", - severity="error", markup=False, + severity="error", + markup=False, ) return None @@ -114,7 +114,9 @@ def _load_secrets(self) -> None: # First secret in list is always the latest secret latest_version = secret_versions[0] latest_version_number = latest_version.name.split("/")[-1] - table.add_row(secret_name, latest_version_number, latest_version.state.name, create_time) + table.add_row( + secret_name, latest_version_number, latest_version.state.name, create_time + ) except GoogleAPICallError as e: self.notify( @@ -124,7 +126,8 @@ def _load_secrets(self) -> None: except Exception as e: self.notify( f"Failed to load secrets: {format_error_message(str(e), 200)}", - severity="error", markup=False, + severity="error", + markup=False, ) diff --git a/pyproject.toml b/pyproject.toml index 33d8079..45892af 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,7 @@ dependencies = [ [dependency-groups] dev = [ "pytest>=8.3.5", + "pytest-cov>=6.1.1", "ruff>=0.11.7", ] @@ -24,3 +25,41 @@ pythonpath = ["."] [tool.pytest.ini_options] addopts = "-v --tb=short" testpaths = ["tests"] + +[tool.ruff] +target-version = "py313" +line-length = 100 +fix = true + +[tool.ruff.lint] +select = [ + "E", # pycodestyle errors + "W", # pycodestyle warnings + "F", # pyflakes + "I", # isort + "C", # flake8-comprehensions + "B", # flake8-bugbear + "UP", # pyupgrade +] +ignore = [ + "E501", # line too long, handled by formatter +] + +[tool.ruff.format] +quote-style = "double" +indent-style = "space" +skip-magic-trailing-comma = false +line-ending = "auto" + +[tool.coverage.run] +source = ["secrets_manager"] +omit = ["tests/*", "setup.py"] + +[tool.coverage.report] +exclude_lines = [ + "pragma: no cover", + "def __repr__", + "if __name__ == .__main__.:", + "raise NotImplementedError", + "pass", +] diff --git a/secrets_manager/models/gcp_projects.py b/secrets_manager/models/gcp_projects.py index 4362851..c366c83 100644 --- a/secrets_manager/models/gcp_projects.py +++ b/secrets_manager/models/gcp_projects.py @@ -1,6 +1,5 @@ from google.cloud.resourcemanager_v3.types import Project from pydantic import BaseModel, Field, field_validator -from typing import Dict, Optional class GCPProject(BaseModel): @@ -11,7 +10,7 @@ class GCPProject(BaseModel): name: str = Field( description="The unique resource name of the project (e.g., projects/415104041262)" ) - parent: Optional[str] = Field( + parent: str | None = Field( default=None, description="Reference to parent resource (e.g., organizations/123 or folders/876)", ) @@ -25,7 +24,7 @@ class GCPProject(BaseModel): max_length=30, description="User-assigned display name of the project", ) - labels: Dict[str, str] = Field( + labels: dict[str, str] = Field( default_factory=dict, description="Key-value pairs for project labels" ) @@ -53,7 +52,7 @@ def __str__(self): return f"GCPProject(display_name={self.display_name}, name={self.name}, project_id={self.project_id})" @field_validator("labels") - def validate_labels(cls, v: Dict[str, str]) -> Dict[str, str]: + def validate_labels(cls, v: dict[str, str]) -> dict[str, str]: """Validate label keys and values according to GCP requirements.""" for key, value in v.items(): # Validate key format @@ -63,9 +62,7 @@ def validate_labels(cls, v: Dict[str, str]) -> Dict[str, str]: raise ValueError(f"Label key too long: {key}") # Validate value format - if not value.isalnum() and not all( - c in "-_" for c in value if not c.isalnum() - ): + if not value.isalnum() and not all(c in "-_" for c in value if not c.isalnum()): raise ValueError(f"Invalid label value format: {value}") if len(value) > 63: raise ValueError(f"Label value too long: {value}") diff --git a/secrets_manager/utils/gcp.py b/secrets_manager/utils/gcp.py index 3f6bea4..5fee29c 100644 --- a/secrets_manager/utils/gcp.py +++ b/secrets_manager/utils/gcp.py @@ -1,21 +1,22 @@ import json -from google.cloud import resourcemanager_v3 -from google.cloud import secretmanager +from google.cloud import resourcemanager_v3, secretmanager from google.cloud.secretmanager_v1.services.secret_manager_service.pagers import ( ListSecretsPager, ) from secrets_manager.models.gcp_projects import GCPProject + def list_secrets(gcp_project: GCPProject) -> ListSecretsPager: client = secretmanager.SecretManagerServiceClient() parent = f"projects/{gcp_project.project_id}" return client.list_secrets(request={"parent": parent}) -def get_secret_versions(secret: secretmanager.Secret, show_deleted: bool = False) -> list[ - secretmanager.SecretVersion]: +def get_secret_versions( + secret: secretmanager.Secret, show_deleted: bool = False +) -> list[secretmanager.SecretVersion]: """ Get all versions of a secret from GCP Secret Manager. @@ -31,8 +32,7 @@ def get_secret_versions(secret: secretmanager.Secret, show_deleted: bool = False # List all versions of the secret request = secretmanager.ListSecretVersionsRequest( - parent=parent, - filter="state!=DESTROYED" if not show_deleted else None + parent=parent, filter="state!=DESTROYED" if not show_deleted else None ) versions = [] @@ -41,6 +41,7 @@ def get_secret_versions(secret: secretmanager.Secret, show_deleted: bool = False return versions + def get_secret_version_value(secret_version: secretmanager.SecretVersion) -> dict: """ Get the value of a specific secret version. diff --git a/secrets_manager/utils/helpers.py b/secrets_manager/utils/helpers.py index 047d5d8..9863fc4 100644 --- a/secrets_manager/utils/helpers.py +++ b/secrets_manager/utils/helpers.py @@ -1,5 +1,3 @@ -from textual.markup import escape - def format_error_message(error_message: str, max_length: int | None = None) -> str: """ Format the error message for display. @@ -31,9 +29,7 @@ def sanitize_project_id_search(search_term: str) -> str: # Replace spaces and invalid characters with hyphens # Keep only allowed characters: lowercase letters, numbers, and hyphens sanitized = "".join( - c if c.isalnum() or c == "-" else "-" - for c in sanitized - if c.isalnum() or c in "-_ " + c if c.isalnum() or c == "-" else "-" for c in sanitized if c.isalnum() or c in "-_ " ) # Remove consecutive hyphens @@ -42,8 +38,9 @@ def sanitize_project_id_search(search_term: str) -> str: return sanitized -if __name__ == '__main__': + +if __name__ == "__main__": print(sanitize_project_id_search("")) print(sanitize_project_id_search("-")) print(sanitize_project_id_search("--")) - print(sanitize_project_id_search(" ")) \ No newline at end of file + print(sanitize_project_id_search(" ")) diff --git a/tests/conftest.py b/tests/conftest.py index 2400d56..507e99b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,7 @@ import pytest -from google.cloud.resourcemanager_v3.types import Project from google.cloud import secretmanager +from google.cloud.resourcemanager_v3.types import Project + @pytest.fixture def mock_project_data(): @@ -9,9 +10,10 @@ def mock_project_data(): "project_id": "test-project-123", "display_name": "Test Project", "parent": "organizations/456", - "labels": {"environment": "test", "team": "platform"} + "labels": {"environment": "test", "team": "platform"}, } + @pytest.fixture def mock_gcp_project(mock_project_data): return Project( @@ -19,17 +21,15 @@ def mock_gcp_project(mock_project_data): project_id=mock_project_data["project_id"], display_name=mock_project_data["display_name"], parent=mock_project_data["parent"], - labels=mock_project_data["labels"] + labels=mock_project_data["labels"], ) + @pytest.fixture def mock_secret(): - return secretmanager.Secret( - name="projects/123456789/secrets/test-secret" - ) + return secretmanager.Secret(name="projects/123456789/secrets/test-secret") + @pytest.fixture def mock_secret_version(): - return secretmanager.SecretVersion( - name="projects/123456789/secrets/test-secret/versions/1" - ) \ No newline at end of file + return secretmanager.SecretVersion(name="projects/123456789/secrets/test-secret/versions/1") diff --git a/tests/test_models/test_gcp_projects.py b/tests/test_models/test_gcp_projects.py index a219556..1821132 100644 --- a/tests/test_models/test_gcp_projects.py +++ b/tests/test_models/test_gcp_projects.py @@ -1,6 +1,8 @@ import pytest + from secrets_manager.models.gcp_projects import GCPProject + def test_gcp_project_creation(mock_project_data): project = GCPProject(**mock_project_data) assert project.name == mock_project_data["name"] @@ -8,35 +10,39 @@ def test_gcp_project_creation(mock_project_data): assert project.display_name == mock_project_data["display_name"] assert project.labels == mock_project_data["labels"] + def test_gcp_project_from_api_response(mock_gcp_project): project = GCPProject.from_project_api_response(mock_gcp_project) assert project.name == mock_gcp_project.name assert project.project_id == mock_gcp_project.project_id assert project.display_name == mock_gcp_project.display_name + def test_project_id_validation(): with pytest.raises(ValueError): GCPProject( name="projects/123", project_id="123invalid", # Must start with letter - display_name="Test Project" + display_name="Test Project", ) + def test_display_name_validation(): with pytest.raises(ValueError): GCPProject( name="projects/123", project_id="valid-project", - display_name="abc" # Too short + display_name="abc", # Too short ) + def test_labels_validation(): # Test valid labels project = GCPProject( name="projects/123", project_id="valid-project", display_name="Valid Project", - labels={"env": "test", "team-name": "platform"} + labels={"env": "test", "team-name": "platform"}, ) assert project.labels == {"env": "test", "team-name": "platform"} @@ -46,7 +52,7 @@ def test_labels_validation(): name="projects/123", project_id="valid-project", display_name="Valid Project", - labels={"invalid@key": "value"} + labels={"invalid@key": "value"}, ) # Test invalid label value @@ -55,9 +61,10 @@ def test_labels_validation(): name="projects/123", project_id="valid-project", display_name="Valid Project", - labels={"key": "invalid@value"} + labels={"key": "invalid@value"}, ) + def test_string_representation(mock_project_data): project = GCPProject(**mock_project_data) str_repr = str(project) diff --git a/tests/test_utils/test_gcp.py b/tests/test_utils/test_gcp.py index ea4649b..4f97975 100644 --- a/tests/test_utils/test_gcp.py +++ b/tests/test_utils/test_gcp.py @@ -1,39 +1,39 @@ import json -import pytest from unittest.mock import Mock, patch + +import pytest + +from secrets_manager.models.gcp_projects import GCPProject from secrets_manager.utils.gcp import ( - list_secrets, - get_secret_versions, get_secret_version_value, - search_gcp_projects + get_secret_versions, + list_secrets, + search_gcp_projects, ) -from secrets_manager.models.gcp_projects import GCPProject @pytest.fixture def mock_secret_manager_client(): - with patch('google.cloud.secretmanager.SecretManagerServiceClient') as mock: + with patch("google.cloud.secretmanager.SecretManagerServiceClient") as mock: yield mock @pytest.fixture def mock_projects_client(): - with patch('google.cloud.resourcemanager_v3.ProjectsClient') as mock: + with patch("google.cloud.resourcemanager_v3.ProjectsClient") as mock: yield mock def test_list_secrets(mock_secret_manager_client): mock_project = GCPProject( - name="projects/123", - project_id="test-project", - display_name="Test Project" + name="projects/123", project_id="test-project", display_name="Test Project" ) mock_client = Mock() mock_secret_manager_client.return_value = mock_client mock_client.list_secrets.return_value = [] - result = list_secrets(mock_project) + _ = list_secrets(mock_project) mock_client.list_secrets.assert_called_once_with( request={"parent": f"projects/{mock_project.project_id}"} @@ -46,23 +46,23 @@ def test_get_secret_versions(mock_secret_manager_client, mock_secret): mock_client.list_secret_versions.return_value = [] # Test without deleted versions - result = get_secret_versions(mock_secret) + _ = get_secret_versions(mock_secret) # Verify that list_secret_versions was called once assert mock_client.list_secret_versions.called # Get the actual call arguments - call_args = mock_client.list_secret_versions.call_args[1]['request'] + call_args = mock_client.list_secret_versions.call_args[1]["request"] # Verify the request parameters individually assert call_args.parent == mock_secret.name assert call_args.filter == "state!=DESTROYED" # Test with deleted versions mock_client.list_secret_versions.reset_mock() - result = get_secret_versions(mock_secret, show_deleted=True) + _ = get_secret_versions(mock_secret, show_deleted=True) # Verify the call for show_deleted=True assert mock_client.list_secret_versions.called - call_args = mock_client.list_secret_versions.call_args[1]['request'] + call_args = mock_client.list_secret_versions.call_args[1]["request"] assert call_args.parent == mock_secret.name diff --git a/tests/test_utils/test_helpers.py b/tests/test_utils/test_helpers.py index 6575ef3..e3e03b9 100644 --- a/tests/test_utils/test_helpers.py +++ b/tests/test_utils/test_helpers.py @@ -1,4 +1,3 @@ -import pytest from secrets_manager.utils.helpers import format_error_message, sanitize_project_id_search @@ -107,8 +106,7 @@ def test_preserves_valid_characters(self): ("abc123", "abc123"), ("dev-prod", "dev-prod"), ("test-123-xyz", "test-123-xyz"), - ("abcdefghijklmnopqrstuvwxyz-0123456789", - "abcdefghijklmnopqrstuvwxyz-0123456789"), + ("abcdefghijklmnopqrstuvwxyz-0123456789", "abcdefghijklmnopqrstuvwxyz-0123456789"), ] for input_str, expected in test_cases: assert sanitize_project_id_search(input_str) == expected diff --git a/uv.lock b/uv.lock index 8b54024..2ef03b1 100644 --- a/uv.lock +++ b/uv.lock @@ -60,6 +60,35 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, ] +[[package]] +name = "coverage" +version = "7.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/4f/2251e65033ed2ce1e68f00f91a0294e0f80c80ae8c3ebbe2f12828c4cd53/coverage-7.8.0.tar.gz", hash = "sha256:7a3d62b3b03b4b6fd41a085f3574874cf946cb4604d2b4d3e8dca8cd570ca501", size = 811872 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/21/87e9b97b568e223f3438d93072479c2f36cc9b3f6b9f7094b9d50232acc0/coverage-7.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ac46d0c2dd5820ce93943a501ac5f6548ea81594777ca585bf002aa8854cacd", size = 211708 }, + { url = "https://files.pythonhosted.org/packages/75/be/882d08b28a0d19c9c4c2e8a1c6ebe1f79c9c839eb46d4fca3bd3b34562b9/coverage-7.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:771eb7587a0563ca5bb6f622b9ed7f9d07bd08900f7589b4febff05f469bea00", size = 211981 }, + { url = "https://files.pythonhosted.org/packages/7a/1d/ce99612ebd58082fbe3f8c66f6d8d5694976c76a0d474503fa70633ec77f/coverage-7.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42421e04069fb2cbcbca5a696c4050b84a43b05392679d4068acbe65449b5c64", size = 245495 }, + { url = "https://files.pythonhosted.org/packages/dc/8d/6115abe97df98db6b2bd76aae395fcc941d039a7acd25f741312ced9a78f/coverage-7.8.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:554fec1199d93ab30adaa751db68acec2b41c5602ac944bb19187cb9a41a8067", size = 242538 }, + { url = "https://files.pythonhosted.org/packages/cb/74/2f8cc196643b15bc096d60e073691dadb3dca48418f08bc78dd6e899383e/coverage-7.8.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aaeb00761f985007b38cf463b1d160a14a22c34eb3f6a39d9ad6fc27cb73008", size = 244561 }, + { url = "https://files.pythonhosted.org/packages/22/70/c10c77cd77970ac965734fe3419f2c98665f6e982744a9bfb0e749d298f4/coverage-7.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:581a40c7b94921fffd6457ffe532259813fc68eb2bdda60fa8cc343414ce3733", size = 244633 }, + { url = "https://files.pythonhosted.org/packages/38/5a/4f7569d946a07c952688debee18c2bb9ab24f88027e3d71fd25dbc2f9dca/coverage-7.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f319bae0321bc838e205bf9e5bc28f0a3165f30c203b610f17ab5552cff90323", size = 242712 }, + { url = "https://files.pythonhosted.org/packages/bb/a1/03a43b33f50475a632a91ea8c127f7e35e53786dbe6781c25f19fd5a65f8/coverage-7.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04bfec25a8ef1c5f41f5e7e5c842f6b615599ca8ba8391ec33a9290d9d2db3a3", size = 244000 }, + { url = "https://files.pythonhosted.org/packages/6a/89/ab6c43b1788a3128e4d1b7b54214548dcad75a621f9d277b14d16a80d8a1/coverage-7.8.0-cp313-cp313-win32.whl", hash = "sha256:dd19608788b50eed889e13a5d71d832edc34fc9dfce606f66e8f9f917eef910d", size = 214195 }, + { url = "https://files.pythonhosted.org/packages/12/12/6bf5f9a8b063d116bac536a7fb594fc35cb04981654cccb4bbfea5dcdfa0/coverage-7.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:a9abbccd778d98e9c7e85038e35e91e67f5b520776781d9a1e2ee9d400869487", size = 214998 }, + { url = "https://files.pythonhosted.org/packages/2a/e6/1e9df74ef7a1c983a9c7443dac8aac37a46f1939ae3499424622e72a6f78/coverage-7.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:18c5ae6d061ad5b3e7eef4363fb27a0576012a7447af48be6c75b88494c6cf25", size = 212541 }, + { url = "https://files.pythonhosted.org/packages/04/51/c32174edb7ee49744e2e81c4b1414ac9df3dacfcb5b5f273b7f285ad43f6/coverage-7.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:95aa6ae391a22bbbce1b77ddac846c98c5473de0372ba5c463480043a07bff42", size = 212767 }, + { url = "https://files.pythonhosted.org/packages/e9/8f/f454cbdb5212f13f29d4a7983db69169f1937e869a5142bce983ded52162/coverage-7.8.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e013b07ba1c748dacc2a80e69a46286ff145935f260eb8c72df7185bf048f502", size = 256997 }, + { url = "https://files.pythonhosted.org/packages/e6/74/2bf9e78b321216d6ee90a81e5c22f912fc428442c830c4077b4a071db66f/coverage-7.8.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d766a4f0e5aa1ba056ec3496243150698dc0481902e2b8559314368717be82b1", size = 252708 }, + { url = "https://files.pythonhosted.org/packages/92/4d/50d7eb1e9a6062bee6e2f92e78b0998848a972e9afad349b6cdde6fa9e32/coverage-7.8.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad80e6b4a0c3cb6f10f29ae4c60e991f424e6b14219d46f1e7d442b938ee68a4", size = 255046 }, + { url = "https://files.pythonhosted.org/packages/40/9e/71fb4e7402a07c4198ab44fc564d09d7d0ffca46a9fb7b0a7b929e7641bd/coverage-7.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b87eb6fc9e1bb8f98892a2458781348fa37e6925f35bb6ceb9d4afd54ba36c73", size = 256139 }, + { url = "https://files.pythonhosted.org/packages/49/1a/78d37f7a42b5beff027e807c2843185961fdae7fe23aad5a4837c93f9d25/coverage-7.8.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d1ba00ae33be84066cfbe7361d4e04dec78445b2b88bdb734d0d1cbab916025a", size = 254307 }, + { url = "https://files.pythonhosted.org/packages/58/e9/8fb8e0ff6bef5e170ee19d59ca694f9001b2ec085dc99b4f65c128bb3f9a/coverage-7.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f3c38e4e5ccbdc9198aecc766cedbb134b2d89bf64533973678dfcf07effd883", size = 255116 }, + { url = "https://files.pythonhosted.org/packages/56/b0/d968ecdbe6fe0a863de7169bbe9e8a476868959f3af24981f6a10d2b6924/coverage-7.8.0-cp313-cp313t-win32.whl", hash = "sha256:379fe315e206b14e21db5240f89dc0774bdd3e25c3c58c2c733c99eca96f1ada", size = 214909 }, + { url = "https://files.pythonhosted.org/packages/87/e9/d6b7ef9fecf42dfb418d93544af47c940aa83056c49e6021a564aafbc91f/coverage-7.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2e4b6b87bb0c846a9315e3ab4be2d52fac905100565f4b92f02c445c8799e257", size = 216068 }, + { url = "https://files.pythonhosted.org/packages/59/f1/4da7717f0063a222db253e7121bd6a56f6fb1ba439dcc36659088793347c/coverage-7.8.0-py3-none-any.whl", hash = "sha256:dbf364b4c5e7bae9250528167dfe40219b62e2d573c854d74be213e1e52069f7", size = 203435 }, +] + [[package]] name = "google-api-core" version = "2.24.2" @@ -403,6 +432,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 }, ] +[[package]] +name = "pytest-cov" +version = "6.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/25/69/5f1e57f6c5a39f81411b550027bf72842c4567ff5fd572bed1edc9e4b5d9/pytest_cov-6.1.1.tar.gz", hash = "sha256:46935f7aaefba760e716c2ebfbe1c216240b9592966e7da99ea8292d4d3e2a0a", size = 66857 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/d0/def53b4a790cfb21483016430ed828f64830dd981ebe1089971cd10cab25/pytest_cov-6.1.1-py3-none-any.whl", hash = "sha256:bddf29ed2d0ab6f4df17b4c55b0a657287db8684af9c42ea546b21b1041b3dde", size = 23841 }, +] + [[package]] name = "requests" version = "2.32.3" @@ -482,6 +524,7 @@ dependencies = [ [package.dev-dependencies] dev = [ { name = "pytest" }, + { name = "pytest-cov" }, { name = "ruff" }, ] @@ -496,6 +539,7 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ { name = "pytest", specifier = ">=8.3.5" }, + { name = "pytest-cov", specifier = ">=6.1.1" }, { name = "ruff", specifier = ">=0.11.7" }, ] From dc2963d77ff854302b95095317b63d594f9eea61 Mon Sep 17 00:00:00 2001 From: mikaeld Date: Thu, 1 May 2025 13:36:00 -0400 Subject: [PATCH 2/5] fix uv installation --- .github/workflows/ci.yaml | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6047173..d633048 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -8,28 +8,19 @@ on: jobs: test: + name: python runs-on: ubuntu-latest - strategy: - matrix: - python-version: ["3.13"] steps: - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - cache: 'uv' - - name: Install uv - run: | - pip install uv + uses: astral-sh/setup-uv@v5 - - name: Lint with Ruff - run: | - uv run ruff check . - uv run ruff format --check . + - name: "Set up Python" + uses: actions/setup-python@v5 + with: + python-version-file: "pyproject.toml" - name: Run tests run: | From 049cd2b9c961e02580be98fd572ba9db47cd03fc Mon Sep 17 00:00:00 2001 From: mikaeld Date: Thu, 1 May 2025 13:39:16 -0400 Subject: [PATCH 3/5] fix uv installation --- .github/workflows/ci.yaml | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d633048..417312d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -7,8 +7,30 @@ on: branches: [ main ] jobs: + lint-format: + name: 🧹 Lint and Format + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v5 + + - name: "Set up Python" + uses: actions/setup-python@v5 + with: + python-version-file: "pyproject.toml" + + - name: Lint with Ruff + run: ruff check . + + - name: Format with Ruff + run: ruff format --check . + + test: - name: python + name: 🧪 Test and Coverage runs-on: ubuntu-latest steps: From b2ed687777cfaf3c92f45e98e6232a80245b1a6e Mon Sep 17 00:00:00 2001 From: mikaeld Date: Thu, 1 May 2025 13:40:07 -0400 Subject: [PATCH 4/5] use uv run --- .github/workflows/ci.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 417312d..5a90bb0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -23,11 +23,10 @@ jobs: python-version-file: "pyproject.toml" - name: Lint with Ruff - run: ruff check . + run: uv run ruff check . - name: Format with Ruff - run: ruff format --check . - + run: uv run ruff format --check . test: name: 🧪 Test and Coverage From 5d4f61ebd67333f05d081904f30414ce16227925 Mon Sep 17 00:00:00 2001 From: mikaeld Date: Thu, 1 May 2025 13:47:21 -0400 Subject: [PATCH 5/5] fix readme image --- imgs/tui.png | Bin 21988 -> 20540 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/imgs/tui.png b/imgs/tui.png index 105fb245986f72b625d9bce306be8cfd0aad7612..cd17f0786577c537add1ba21d7601de1030fce06 100644 GIT binary patch literal 20540 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 z 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%0A>!ilJX3+6v>QXC!qm;X&COOAjb*v`)A;I5 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 P00000NkvXXu0mjf18?YO