From df8b0bc9830f879adc6a6f59bad06ef8b2a5cf42 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 22:32:37 +0000 Subject: [PATCH] chore: bump mypy from 1.19.1 to 1.20.0 Bumps [mypy](https://github.com/python/mypy) from 1.19.1 to 1.20.0. - [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md) - [Commits](https://github.com/python/mypy/compare/v1.19.1...v1.20.0) --- updated-dependencies: - dependency-name: mypy dependency-version: 1.20.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/auto-merge.yml | 2 +- .github/workflows/build-test.yml | 2 +- .github/workflows/bump-version.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/preview-bump-version.yml | 2 +- .github/workflows/publish.yml | 2 +- .github/workflows/test.yml | 2 +- eval/evaluator.py | 2 +- src/lightman_ai/ai/base/agent.py | 3 +- src/lightman_ai/main.py | 10 ++--- src/lightman_ai/sources/bleeping_computer.py | 28 +++++-------- src/lightman_ai/sources/exceptions.py | 8 ---- src/lightman_ai/sources/the_hacker_news.py | 23 +++++------ tests/ai/base/test_agent.py | 4 +- tests/ai/gemini/test_agent.py | 2 +- tests/ai/openai/test_agent.py | 2 +- tests/sources/test_bleeping_computer.py | 13 +------ tests/sources/test_the_hacker_news.py | 11 +----- tests/test_cli.py | 38 +----------------- tests/test_main.py | 28 +------------ uv.lock | 41 ++++++++++++-------- 21 files changed, 66 insertions(+), 161 deletions(-) diff --git a/.github/workflows/auto-merge.yml b/.github/workflows/auto-merge.yml index 30e6093..0c3cb59 100644 --- a/.github/workflows/auto-merge.yml +++ b/.github/workflows/auto-merge.yml @@ -12,7 +12,7 @@ jobs: steps: - name: Dependabot metadata id: metadata - uses: dependabot/fetch-metadata@ffa630c65fa7e0ecfa0625b5ceda64399aea1b36 # v3.0.0 + uses: dependabot/fetch-metadata@21025c705c08248db411dc16f3619e6b5f9ea21a # v2.5.0 with: github-token: "${{ secrets.GITHUB_TOKEN }}" - name: Enable auto-merge for Dependabot PRs diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 4aa6270..af0c8a6 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -20,7 +20,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install uv - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 - name: Build the package run: uv build --python $PYTHON_VERSION diff --git a/.github/workflows/bump-version.yml b/.github/workflows/bump-version.yml index b26e236..01d8ad3 100644 --- a/.github/workflows/bump-version.yml +++ b/.github/workflows/bump-version.yml @@ -12,7 +12,7 @@ jobs: name: "Bump version and create changelog with commitizen" steps: - - uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 + - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 id: app-token with: app-id: ${{ vars.ELEMENTSINTERACTIVE_BOT_APP_ID }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 843c16d..31508ad 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -16,7 +16,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install uv - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 - name: Install the project run: uv sync --locked --group lint --group test --all-extras --python 3.13 diff --git a/.github/workflows/preview-bump-version.yml b/.github/workflows/preview-bump-version.yml index e3fbf4d..29dd43d 100644 --- a/.github/workflows/preview-bump-version.yml +++ b/.github/workflows/preview-bump-version.yml @@ -12,7 +12,7 @@ jobs: name: "Preview next version" steps: - - uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 + - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 id: app-token with: app-id: ${{ vars.ELEMENTSINTERACTIVE_BOT_APP_ID }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 988a87c..3b0ec45 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -21,7 +21,7 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install uv - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 - name: Publish to pypi run: | uv build --python 3.13 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5338045..ca2d1c5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,7 +16,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install uv - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 - name: Install the project run: uv sync --locked --group test --all-extras --python 3.13 diff --git a/eval/evaluator.py b/eval/evaluator.py index a75c2e0..f54d8a5 100644 --- a/eval/evaluator.py +++ b/eval/evaluator.py @@ -25,7 +25,7 @@ async def evaluate( ) -> None: agent_class = get_agent_class_from_agent_name(agent) classified_articles = await Classifier( - agent=agent_class(prompt, model=model), + agent=agent_class(len(sources) > 1 if sources else False, prompt, model=model), score=score_threshold, relevant_articles=relevant_articles, non_relevant_articles=non_relevant_articles, diff --git a/src/lightman_ai/ai/base/agent.py b/src/lightman_ai/ai/base/agent.py index 920d13d..9506808 100644 --- a/src/lightman_ai/ai/base/agent.py +++ b/src/lightman_ai/ai/base/agent.py @@ -16,6 +16,7 @@ class BaseAgent(ABC): def __init__( self, + multiple_sources: bool, system_prompt: str, model: str | None = None, logger: logging.Logger | None = None, @@ -26,7 +27,7 @@ def __init__( model=agent_model, output_type=SelectedArticlesList, system_prompt=system_prompt, - instructions=MERGE_ARTICLES_FROM_DIFFERENT_SOURCES, + instructions=MERGE_ARTICLES_FROM_DIFFERENT_SOURCES if multiple_sources else None, ) self.logger = logger or logging.getLogger("lightman") self.logger.info("Selected %s's %s model", self, selected_model) diff --git a/src/lightman_ai/main.py b/src/lightman_ai/main.py index 1205287..4bd45d2 100644 --- a/src/lightman_ai/main.py +++ b/src/lightman_ai/main.py @@ -77,15 +77,13 @@ async def lightman( if not sources: raise NoSourcesError - tasks = [_get_articles_from_source(source, start_date) for source in sources] - articles_lists = await asyncio.gather(*tasks) - articles = ArticlesList() - for articles_list in articles_lists: - articles += articles_list + for source in sources: + articles += await _get_articles_from_source(source, start_date) + multiple_sources = len(sources) > 1 agent_class = get_agent_class_from_agent_name(agent) - agent_instance = agent_class(prompt, model, logger=logger) + agent_instance = agent_class(multiple_sources, prompt, model, logger=logger) classified_articles = await _classify_articles( articles=articles, diff --git a/src/lightman_ai/sources/bleeping_computer.py b/src/lightman_ai/sources/bleeping_computer.py index bcbef98..5f2d53b 100644 --- a/src/lightman_ai/sources/bleeping_computer.py +++ b/src/lightman_ai/sources/bleeping_computer.py @@ -8,12 +8,7 @@ from httpx import AsyncClient from lightman_ai.article.models import Article, ArticlesList from lightman_ai.sources.base import BaseSource -from lightman_ai.sources.exceptions import ( - IncompleteArticleFromSourceError, - MalformedSourceResponseError, - NoArticlesError, - SourceError, -) +from lightman_ai.sources.exceptions import IncompleteArticleFromSourceError, MalformedSourceResponseError from pydantic import ValidationError logger = logging.getLogger("lightman") @@ -29,19 +24,14 @@ class BleepingComputerSource(BaseSource): @override async def get_articles(self, date: datetime | None = None) -> ArticlesList: """Return the articles that are present in BleepingComputer feed.""" - try: - logger.info("Downloading articles from %s", BLEEPING_COMPUTER_URL) - feed = await self.get_feed() - articles = self._xml_to_list_of_articles(feed) - logger.info("Articles properly downloaded and parsed.") - if not articles: - raise NoArticlesError - if date: - return ArticlesList.get_articles_from_date_onwards(articles=articles, start_date=date) - else: - return ArticlesList(articles=articles) - except Exception as e: - raise SourceError("Could not download articles from BleepingComputer") from e + logger.info("Downloading articles from %s", BLEEPING_COMPUTER_URL) + feed = await self.get_feed() + articles = self._xml_to_list_of_articles(feed) + logger.info("Articles properly downloaded and parsed.") + if date: + return ArticlesList.get_articles_from_date_onwards(articles=articles, start_date=date) + else: + return ArticlesList(articles=articles) async def get_feed(self) -> str: """Retrieve the BleepingComputer RSS Feed.""" diff --git a/src/lightman_ai/sources/exceptions.py b/src/lightman_ai/sources/exceptions.py index 87222ee..0709012 100644 --- a/src/lightman_ai/sources/exceptions.py +++ b/src/lightman_ai/sources/exceptions.py @@ -8,11 +8,3 @@ class MalformedSourceResponseError(BaseSourceError): class IncompleteArticleFromSourceError(MalformedSourceResponseError): """Exception for when all the mandatory fields could not be retrieved from an article.""" - - -class SourceError(BaseSourceError): - """Exception for when something went wrong while downloading or parsing the articles from source.""" - - -class NoArticlesError(BaseSourceError): - """Exception for when no articles where found after the download was successful.""" diff --git a/src/lightman_ai/sources/the_hacker_news.py b/src/lightman_ai/sources/the_hacker_news.py index a9b4fc9..a0b224d 100644 --- a/src/lightman_ai/sources/the_hacker_news.py +++ b/src/lightman_ai/sources/the_hacker_news.py @@ -8,7 +8,7 @@ from httpx import AsyncClient from lightman_ai.article.models import Article, ArticlesList from lightman_ai.sources.base import BaseSource -from lightman_ai.sources.exceptions import MalformedSourceResponseError, NoArticlesError, SourceError +from lightman_ai.sources.exceptions import MalformedSourceResponseError from pydantic import ValidationError logger = logging.getLogger("lightman") @@ -25,19 +25,14 @@ class TheHackerNewsSource(BaseSource): @override async def get_articles(self, date: datetime | None = None) -> ArticlesList: """Return the articles that are present in THN feed.""" - try: - logger.info("Downloading articles from %s", THN_URL) - feed = await self.get_feed() - articles = self._xml_to_list_of_articles(feed) - if not articles: - raise NoArticlesError - logger.info("Articles properly downloaded and parsed.") - if date: - return ArticlesList.get_articles_from_date_onwards(articles=articles, start_date=date) - else: - return ArticlesList(articles=articles) - except Exception as e: - raise SourceError("Could not download articles from THN source") from e + logger.info("Downloading articles from %s", THN_URL) + feed = await self.get_feed() + articles = self._xml_to_list_of_articles(feed) + logger.info("Articles properly downloaded and parsed.") + if date: + return ArticlesList.get_articles_from_date_onwards(articles=articles, start_date=date) + else: + return ArticlesList(articles=articles) async def get_feed(self) -> str: """Retrieve the TheHackerNews' RSS Feed.""" diff --git a/tests/ai/base/test_agent.py b/tests/ai/base/test_agent.py index 1e1c7b6..a3ea481 100644 --- a/tests/ai/base/test_agent.py +++ b/tests/ai/base/test_agent.py @@ -19,7 +19,7 @@ class TestBaseAgent: @patch("lightman_ai.ai.base.agent.Agent") async def test__get_prompt_result(self, m_agent: Mock, test_prompt: str, thn_news: ArticlesList) -> None: """Check that we receive an instance of `SelectedArticlesList` when running the method.""" - agent = FakeAgent(test_prompt) + agent = FakeAgent(False, test_prompt) with patch("tests.ai.base.test_agent.FakeAgent.run_prompt") as m_run_prompt: await agent.run_prompt(str(thn_news)) @@ -32,7 +32,7 @@ async def test__get_prompt_result(self, m_agent: Mock, test_prompt: str, thn_new @patch("lightman_ai.ai.base.agent.Agent") async def test_agent_is_intantiated_with_model_when_set(self, m_agent: Mock, test_prompt: str) -> None: - agent = FakeAgent(test_prompt, model="my model") + agent = FakeAgent(False, test_prompt, model="my model") await agent.run_prompt("") assert m_agent.call_count == 1 diff --git a/tests/ai/gemini/test_agent.py b/tests/ai/gemini/test_agent.py index affaad1..e460e79 100644 --- a/tests/ai/gemini/test_agent.py +++ b/tests/ai/gemini/test_agent.py @@ -8,7 +8,7 @@ class TestGeminiAgent: - agent = GeminiAgent(system_prompt="Test system prompt") + agent = GeminiAgent(False, system_prompt="Test system prompt") async def test__run_prompt(self, test_prompt: str) -> None: """Test that we can run a prompt and receive a SelectedArticlesList.""" diff --git a/tests/ai/openai/test_agent.py b/tests/ai/openai/test_agent.py index 1cb364e..7d6e772 100644 --- a/tests/ai/openai/test_agent.py +++ b/tests/ai/openai/test_agent.py @@ -5,7 +5,7 @@ class TestAgent: - agent = OpenAIAgent(system_prompt="Test system prompt") + agent = OpenAIAgent(False, system_prompt="Test system prompt") async def test__run_prompt(self, test_prompt: str) -> None: """Test that we can run a prompt and receive a SelectedArticlesList.""" diff --git a/tests/sources/test_bleeping_computer.py b/tests/sources/test_bleeping_computer.py index 977b360..329e521 100644 --- a/tests/sources/test_bleeping_computer.py +++ b/tests/sources/test_bleeping_computer.py @@ -1,10 +1,9 @@ from datetime import UTC, datetime -from unittest.mock import patch import pytest from lightman_ai.article.models import ArticlesList from lightman_ai.sources.bleeping_computer import BleepingComputerSource -from lightman_ai.sources.exceptions import MalformedSourceResponseError, SourceError +from lightman_ai.sources.exceptions import MalformedSourceResponseError from tests.conftest import patch_httpx_client_get @@ -101,13 +100,3 @@ def test_xml_to_list_of_articles_validation_error(self) -> None: """ # no title assert not BleepingComputerSource()._xml_to_list_of_articles(xml) - - async def test_get_articles_raises_when_no_articles_in_feed(self, bc_xml: str) -> None: - with ( - patch_httpx_client_get(bc_xml), - patch( - "lightman_ai.sources.bleeping_computer.BleepingComputerSource._xml_to_list_of_articles", return_value=[] - ), - pytest.raises(SourceError), - ): - await BleepingComputerSource().get_articles() diff --git a/tests/sources/test_the_hacker_news.py b/tests/sources/test_the_hacker_news.py index c94bf1c..51523e5 100644 --- a/tests/sources/test_the_hacker_news.py +++ b/tests/sources/test_the_hacker_news.py @@ -1,9 +1,8 @@ from datetime import UTC, datetime -from unittest.mock import patch import pytest from lightman_ai.article.models import ArticlesList -from lightman_ai.sources.exceptions import MalformedSourceResponseError, SourceError +from lightman_ai.sources.exceptions import MalformedSourceResponseError from lightman_ai.sources.the_hacker_news import TheHackerNewsSource from tests.conftest import patch_httpx_client_get @@ -127,11 +126,3 @@ def test_xml_to_list_of_articles_cleans_description(self) -> None: assert len(articles) == 1 assert articles[0].description == "Test description with spaces" - - async def test_get_articles_raises_when_no_articles_in_feed(self, thn_xml: str) -> None: - with ( - patch_httpx_client_get(thn_xml), - patch("lightman_ai.sources.the_hacker_news.TheHackerNewsSource._xml_to_list_of_articles", return_value=[]), - pytest.raises(SourceError), - ): - await TheHackerNewsSource().get_articles() diff --git a/tests/test_cli.py b/tests/test_cli.py index 76a5c01..50af887 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,6 +1,6 @@ import json from datetime import datetime -from unittest.mock import AsyncMock, Mock, call, patch +from unittest.mock import Mock, call, patch from zoneinfo import ZoneInfo import pytest @@ -9,7 +9,6 @@ from lightman_ai import cli from lightman_ai.article.models import PrimarySelectedArticle from lightman_ai.core.config import FileConfig, PromptConfig -from lightman_ai.sources.utils import SOURCE_CHOICES from tests.conftest import patch_config_file @@ -409,38 +408,3 @@ def test_regular_output_with_no_articles( assert result.exit_code == 0 assert "No relevant articles found." in result.output - - @patch("lightman_ai.cli.load_dotenv") - @patch("lightman_ai.cli.FileConfig.get_config_from_file") - @patch("lightman_ai.cli.PromptConfig.get_config_from_file") - def test_exit_code_is_not_zero_if_a_source_fails(self, m_prompt: Mock, m_config: Mock, m_load_dotenv: Mock) -> None: - """Test that exit code is 0 when all sources fail and no articles are found. - - Proves that: - - Sources attempted to retrieve news (httpx was called for each source) - - The AI agent was never invoked (no point classifying zero articles) - """ - runner = CliRunner() - m_prompt.return_value = PromptConfig({"eval": "eval prompt"}) - m_config.return_value = FileConfig() - - with ( - patch("httpx.AsyncClient.get") as mock_get, - patch("pydantic_ai.Agent.run", new_callable=AsyncMock) as mock_agent_run, - patch_config_file(), - ): - mock_get.side_effect = Exception("Network error") - result = runner.invoke( - cli.run, - [ - "--agent", - "openai", - "--prompt", - "eval", - "--dry-run", - ], - ) - - assert result.exit_code != 0 - assert mock_get.call_count == len(SOURCE_CHOICES) - assert mock_agent_run.call_count == 0 diff --git a/tests/test_main.py b/tests/test_main.py index f65b42f..7a735a8 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -12,7 +12,6 @@ from lightman_ai.core.sentry import configure_sentry from lightman_ai.exceptions import NoSourcesError from lightman_ai.main import _create_service_desk_issues, _get_articles_from_source, lightman -from lightman_ai.sources.exceptions import SourceError from lightman_ai.sources.utils import SOURCE_CHOICES from tests.conftest import patch_httpx_client_get, patch_multiple_responses from tests.utils import patch_agent, patch_get_articles_from_xml @@ -117,7 +116,7 @@ async def test_lightman_and_service_desk_publish(self, test_prompt: str, thn_xml assert relevant_article_1.title in called_titles assert relevant_article_2.title in called_titles - async def test_lightman_no_publish_if_dry_run(self, test_prompt: str, thn_xml: str, bc_xml: str) -> None: + async def test_lightman_no_publish_if_dry_run(self, test_prompt: str, thn_xml: str) -> None: now = datetime.now(UTC) relevant_article_1 = PrimarySelectedArticle( title="article 2", link="https://article2.com", why_is_relevant="a", relevance_score=8, published_at=now @@ -130,7 +129,7 @@ async def test_lightman_no_publish_if_dry_run(self, test_prompt: str, thn_xml: s ) agent_response = SelectedArticlesList(articles=[relevant_article_1, relevant_article_2, not_relevant_article]) with ( - patch_multiple_responses([thn_xml, bc_xml]), + patch_httpx_client_get(thn_xml), patch_agent(agent_response), patch("lightman_ai.main.ServiceDeskIntegration.from_env") as mock_service_desk_env, ): @@ -163,29 +162,6 @@ async def test_lightman_raises_error_when_sources_is_none(self) -> None: dry_run=True, ) - async def test_lightman_fails_when_one_source_raises_exception(self, test_prompt: str, thn_xml: str) -> None: - """Test that execution fails when one source raises an exception during download.""" - with ( - patch("httpx.AsyncClient.get") as mock_get, - patch("pydantic_ai.Agent.run", new_callable=AsyncMock) as mock_agent_run, - ): - mock_get.side_effect = [ - Mock(text=thn_xml, **{"raise_for_status.return_value": None}), - Exception("Network error: Connection timeout"), - ] - - with pytest.raises(SourceError): - await lightman( - agent="openai", - prompt=test_prompt, - sources=SOURCE_CHOICES, - score_threshold=8, - dry_run=True, - ) - - assert mock_get.call_count == len(SOURCE_CHOICES) - mock_agent_run.assert_not_called() - class TestCreateServiceDeskIssues: """Tests for the _create_service_desk_issues function.""" diff --git a/uv.lock b/uv.lock index aa8d165..ce59a98 100644 --- a/uv.lock +++ b/uv.lock @@ -1110,7 +1110,7 @@ wheels = [ [[package]] name = "mypy" -version = "1.19.1" +version = "1.20.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, @@ -1118,21 +1118,30 @@ dependencies = [ { name = "pathspec" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f5/db/4efed9504bc01309ab9c2da7e352cc223569f05478012b5d9ece38fd44d2/mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba", size = 3582404, upload-time = "2025-12-15T05:03:48.42Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/de/9f/a6abae693f7a0c697dbb435aac52e958dc8da44e92e08ba88d2e42326176/mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250", size = 13201927, upload-time = "2025-12-15T05:02:29.138Z" }, - { url = "https://files.pythonhosted.org/packages/9a/a4/45c35ccf6e1c65afc23a069f50e2c66f46bd3798cbe0d680c12d12935caa/mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b", size = 12206730, upload-time = "2025-12-15T05:03:01.325Z" }, - { url = "https://files.pythonhosted.org/packages/05/bb/cdcf89678e26b187650512620eec8368fded4cfd99cfcb431e4cdfd19dec/mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e", size = 12724581, upload-time = "2025-12-15T05:03:20.087Z" }, - { url = "https://files.pythonhosted.org/packages/d1/32/dd260d52babf67bad8e6770f8e1102021877ce0edea106e72df5626bb0ec/mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef", size = 13616252, upload-time = "2025-12-15T05:02:49.036Z" }, - { url = "https://files.pythonhosted.org/packages/71/d0/5e60a9d2e3bd48432ae2b454b7ef2b62a960ab51292b1eda2a95edd78198/mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75", size = 13840848, upload-time = "2025-12-15T05:02:55.95Z" }, - { url = "https://files.pythonhosted.org/packages/98/76/d32051fa65ecf6cc8c6610956473abdc9b4c43301107476ac03559507843/mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd", size = 10135510, upload-time = "2025-12-15T05:02:58.438Z" }, - { url = "https://files.pythonhosted.org/packages/de/eb/b83e75f4c820c4247a58580ef86fcd35165028f191e7e1ba57128c52782d/mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1", size = 13199744, upload-time = "2025-12-15T05:03:30.823Z" }, - { url = "https://files.pythonhosted.org/packages/94/28/52785ab7bfa165f87fcbb61547a93f98bb20e7f82f90f165a1f69bce7b3d/mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718", size = 12215815, upload-time = "2025-12-15T05:02:42.323Z" }, - { url = "https://files.pythonhosted.org/packages/0a/c6/bdd60774a0dbfb05122e3e925f2e9e846c009e479dcec4821dad881f5b52/mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b", size = 12740047, upload-time = "2025-12-15T05:03:33.168Z" }, - { url = "https://files.pythonhosted.org/packages/32/2a/66ba933fe6c76bd40d1fe916a83f04fed253152f451a877520b3c4a5e41e/mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045", size = 13601998, upload-time = "2025-12-15T05:03:13.056Z" }, - { url = "https://files.pythonhosted.org/packages/e3/da/5055c63e377c5c2418760411fd6a63ee2b96cf95397259038756c042574f/mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957", size = 13807476, upload-time = "2025-12-15T05:03:17.977Z" }, - { url = "https://files.pythonhosted.org/packages/cd/09/4ebd873390a063176f06b0dbf1f7783dd87bd120eae7727fa4ae4179b685/mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f", size = 10281872, upload-time = "2025-12-15T05:03:05.549Z" }, - { url = "https://files.pythonhosted.org/packages/8d/f4/4ce9a05ce5ded1de3ec1c1d96cf9f9504a04e54ce0ed55cfa38619a32b8d/mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247", size = 2471239, upload-time = "2025-12-15T05:03:07.248Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/f8/5c/b0089fe7fef0a994ae5ee07029ced0526082c6cfaaa4c10d40a10e33b097/mypy-1.20.0.tar.gz", hash = "sha256:eb96c84efcc33f0b5e0e04beacf00129dd963b67226b01c00b9dfc8affb464c3", size = 3815028, upload-time = "2026-03-31T16:55:14.959Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d6/a7/f64ea7bd592fa431cb597418b6dec4a47f7d0c36325fec7ac67bc8402b94/mypy-1.20.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b20c8b0fd5877abdf402e79a3af987053de07e6fb208c18df6659f708b535134", size = 14485344, upload-time = "2026-03-31T16:49:16.78Z" }, + { url = "https://files.pythonhosted.org/packages/bb/72/8927d84cfc90c6abea6e96663576e2e417589347eb538749a464c4c218a0/mypy-1.20.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:367e5c993ba34d5054d11937d0485ad6dfc60ba760fa326c01090fc256adf15c", size = 13327400, upload-time = "2026-03-31T16:53:08.02Z" }, + { url = "https://files.pythonhosted.org/packages/ab/4a/11ab99f9afa41aa350178d24a7d2da17043228ea10f6456523f64b5a6cf6/mypy-1.20.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f799d9db89fc00446f03281f84a221e50018fc40113a3ba9864b132895619ebe", size = 13706384, upload-time = "2026-03-31T16:52:28.577Z" }, + { url = "https://files.pythonhosted.org/packages/42/79/694ca73979cfb3535ebfe78733844cd5aff2e63304f59bf90585110d975a/mypy-1.20.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:555658c611099455b2da507582ea20d2043dfdfe7f5ad0add472b1c6238b433f", size = 14700378, upload-time = "2026-03-31T16:48:45.527Z" }, + { url = "https://files.pythonhosted.org/packages/84/24/a022ccab3a46e3d2cdf2e0e260648633640eb396c7e75d5a42818a8d3971/mypy-1.20.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:efe8d70949c3023698c3fca1e94527e7e790a361ab8116f90d11221421cd8726", size = 14932170, upload-time = "2026-03-31T16:49:36.038Z" }, + { url = "https://files.pythonhosted.org/packages/d8/9b/549228d88f574d04117e736f55958bd4908f980f9f5700a07aeb85df005b/mypy-1.20.0-cp313-cp313-win_amd64.whl", hash = "sha256:f49590891d2c2f8a9de15614e32e459a794bcba84693c2394291a2038bbaaa69", size = 10888526, upload-time = "2026-03-31T16:50:59.827Z" }, + { url = "https://files.pythonhosted.org/packages/91/17/15095c0e54a8bc04d22d4ff06b2139d5f142c2e87520b4e39010c4862771/mypy-1.20.0-cp313-cp313-win_arm64.whl", hash = "sha256:76a70bf840495729be47510856b978f1b0ec7d08f257ca38c9d932720bf6b43e", size = 9816456, upload-time = "2026-03-31T16:49:59.537Z" }, + { url = "https://files.pythonhosted.org/packages/4e/0e/6ca4a84cbed9e62384bc0b2974c90395ece5ed672393e553996501625fc5/mypy-1.20.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:0f42dfaab7ec1baff3b383ad7af562ab0de573c5f6edb44b2dab016082b89948", size = 14483331, upload-time = "2026-03-31T16:52:57.999Z" }, + { url = "https://files.pythonhosted.org/packages/7d/c5/5fe9d8a729dd9605064691816243ae6c49fde0bd28f6e5e17f6a24203c43/mypy-1.20.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:31b5dbb55293c1bd27c0fc813a0d2bb5ceef9d65ac5afa2e58f829dab7921fd5", size = 13342047, upload-time = "2026-03-31T16:54:21.555Z" }, + { url = "https://files.pythonhosted.org/packages/4c/33/e18bcfa338ca4e6b2771c85d4c5203e627d0c69d9de5c1a2cf2ba13320ba/mypy-1.20.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49d11c6f573a5a08f77fad13faff2139f6d0730ebed2cfa9b3d2702671dd7188", size = 13719585, upload-time = "2026-03-31T16:51:53.89Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8d/93491ff7b79419edc7eabf95cb3b3f7490e2e574b2855c7c7e7394ff933f/mypy-1.20.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d3243c406773185144527f83be0e0aefc7bf4601b0b2b956665608bf7c98a83", size = 14685075, upload-time = "2026-03-31T16:54:04.464Z" }, + { url = "https://files.pythonhosted.org/packages/b5/9d/d924b38a4923f8d164bf2b4ec98bf13beaf6e10a5348b4b137eadae40a6e/mypy-1.20.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a79c1eba7ac4209f2d850f0edd0a2f8bba88cbfdfefe6fb76a19e9d4fe5e71a2", size = 14919141, upload-time = "2026-03-31T16:54:51.785Z" }, + { url = "https://files.pythonhosted.org/packages/59/98/1da9977016678c0b99d43afe52ed00bb3c1a0c4c995d3e6acca1a6ebb9b4/mypy-1.20.0-cp314-cp314-win_amd64.whl", hash = "sha256:00e047c74d3ec6e71a2eb88e9ea551a2edb90c21f993aefa9e0d2a898e0bb732", size = 11050925, upload-time = "2026-03-31T16:51:30.758Z" }, + { url = "https://files.pythonhosted.org/packages/5e/e3/ba0b7a3143e49a9c4f5967dde6ea4bf8e0b10ecbbcca69af84027160ee89/mypy-1.20.0-cp314-cp314-win_arm64.whl", hash = "sha256:931a7630bba591593dcf6e97224a21ff80fb357e7982628d25e3c618e7f598ef", size = 10001089, upload-time = "2026-03-31T16:49:43.632Z" }, + { url = "https://files.pythonhosted.org/packages/12/28/e617e67b3be9d213cda7277913269c874eb26472489f95d09d89765ce2d8/mypy-1.20.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:26c8b52627b6552f47ff11adb4e1509605f094e29815323e487fc0053ebe93d1", size = 15534710, upload-time = "2026-03-31T16:52:12.506Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0c/3b5f2d3e45dc7169b811adce8451679d9430399d03b168f9b0489f43adaa/mypy-1.20.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:39362cdb4ba5f916e7976fccecaab1ba3a83e35f60fa68b64e9a70e221bb2436", size = 14393013, upload-time = "2026-03-31T16:54:41.186Z" }, + { url = "https://files.pythonhosted.org/packages/a3/49/edc8b0aa145cc09c1c74f7ce2858eead9329931dcbbb26e2ad40906daa4e/mypy-1.20.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:34506397dbf40c15dc567635d18a21d33827e9ab29014fb83d292a8f4f8953b6", size = 15047240, upload-time = "2026-03-31T16:54:31.955Z" }, + { url = "https://files.pythonhosted.org/packages/42/37/a946bb416e37a57fa752b3100fd5ede0e28df94f92366d1716555d47c454/mypy-1.20.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:555493c44a4f5a1b58d611a43333e71a9981c6dbe26270377b6f8174126a0526", size = 15858565, upload-time = "2026-03-31T16:53:36.997Z" }, + { url = "https://files.pythonhosted.org/packages/2f/99/7690b5b5b552db1bd4ff362e4c0eb3107b98d680835e65823fbe888c8b78/mypy-1.20.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2721f0ce49cb74a38f00c50da67cb7d36317b5eda38877a49614dc018e91c787", size = 16087874, upload-time = "2026-03-31T16:52:48.313Z" }, + { url = "https://files.pythonhosted.org/packages/aa/76/53e893a498138066acd28192b77495c9357e5a58cc4be753182846b43315/mypy-1.20.0-cp314-cp314t-win_amd64.whl", hash = "sha256:47781555a7aa5fedcc2d16bcd72e0dc83eb272c10dd657f9fb3f9cc08e2e6abb", size = 12572380, upload-time = "2026-03-31T16:49:52.454Z" }, + { url = "https://files.pythonhosted.org/packages/76/9c/6dbdae21f01b7aacddc2c0bbf3c5557aa547827fdf271770fe1e521e7093/mypy-1.20.0-cp314-cp314t-win_arm64.whl", hash = "sha256:c70380fe5d64010f79fb863b9081c7004dd65225d2277333c219d93a10dad4dd", size = 10381174, upload-time = "2026-03-31T16:51:20.179Z" }, + { url = "https://files.pythonhosted.org/packages/21/66/4d734961ce167f0fd8380769b3b7c06dbdd6ff54c2190f3f2ecd22528158/mypy-1.20.0-py3-none-any.whl", hash = "sha256:a6e0641147cbfa7e4e94efdb95c2dab1aff8cfc159ded13e07f308ddccc8c48e", size = 2636365, upload-time = "2026-03-31T16:51:44.911Z" }, ] [[package]]