From 9f6d7bb4ac3b7b0a74c01c7faf3271a6c856df92 Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Sun, 22 Feb 2026 16:53:56 -0700 Subject: [PATCH] Rename package from noaa-tides to mcnoaa-tides Distribution name, import package, entry point script, MCP config, and all internal references updated. Git tracks the directory rename so file history is preserved. --- .mcp.json | 4 +- README.md | 20 ++--- pyproject.toml | 8 +- src/{noaa_tides => mcnoaa_tides}/__init__.py | 2 +- .../charts/__init__.py | 6 +- .../charts/conditions.py | 10 +-- .../charts/tides.py | 2 +- src/{noaa_tides => mcnoaa_tides}/client.py | 4 +- src/{noaa_tides => mcnoaa_tides}/models.py | 0 src/{noaa_tides => mcnoaa_tides}/prompts.py | 0 src/{noaa_tides => mcnoaa_tides}/resources.py | 2 +- src/{noaa_tides => mcnoaa_tides}/server.py | 10 +-- .../tools/__init__.py | 0 .../tools/charts.py | 16 ++-- .../tools/conditions.py | 2 +- .../tools/meteorological.py | 2 +- .../tools/stations.py | 2 +- .../tools/tides.py | 2 +- tests/conftest.py | 10 +-- tests/test_charts.py | 4 +- uv.lock | 76 +++++++++---------- 21 files changed, 91 insertions(+), 91 deletions(-) rename src/{noaa_tides => mcnoaa_tides}/__init__.py (75%) rename src/{noaa_tides => mcnoaa_tides}/charts/__init__.py (82%) rename src/{noaa_tides => mcnoaa_tides}/charts/conditions.py (97%) rename src/{noaa_tides => mcnoaa_tides}/charts/tides.py (98%) rename src/{noaa_tides => mcnoaa_tides}/client.py (98%) rename src/{noaa_tides => mcnoaa_tides}/models.py (100%) rename src/{noaa_tides => mcnoaa_tides}/prompts.py (100%) rename src/{noaa_tides => mcnoaa_tides}/resources.py (97%) rename src/{noaa_tides => mcnoaa_tides}/server.py (84%) rename src/{noaa_tides => mcnoaa_tides}/tools/__init__.py (100%) rename src/{noaa_tides => mcnoaa_tides}/tools/charts.py (92%) rename src/{noaa_tides => mcnoaa_tides}/tools/conditions.py (98%) rename src/{noaa_tides => mcnoaa_tides}/tools/meteorological.py (97%) rename src/{noaa_tides => mcnoaa_tides}/tools/stations.py (98%) rename src/{noaa_tides => mcnoaa_tides}/tools/tides.py (98%) diff --git a/.mcp.json b/.mcp.json index 698bef9..ed75f8a 100644 --- a/.mcp.json +++ b/.mcp.json @@ -1,8 +1,8 @@ { "mcpServers": { - "noaa-tides": { + "mcnoaa-tides": { "command": "uv", - "args": ["run", "--directory", "/home/rpm/claude/mat/noaa-tides", "noaa-tides"] + "args": ["run", "--directory", "/home/rpm/claude/mat/noaa-tides", "mcnoaa-tides"] } } } diff --git a/README.md b/README.md index 1dfefbd..610332f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# noaa-tides +# mcnoaa-tides MCP server for [NOAA CO-OPS Tides and Currents](https://tidesandcurrents.noaa.gov/). Exposes tide predictions, observed water levels, and meteorological data from ~301 U.S. coastal stations as tools, resources, and prompts via [FastMCP 3.0](https://gofastmcp.com/). @@ -8,16 +8,16 @@ Built for marine planning — fishing trips, boating, crabbing, safety checks. ```bash # Run directly (no install needed) -uvx noaa-tides +uvx mcnoaa-tides # Or add to Claude Code -claude mcp add noaa-tides -- uvx noaa-tides +claude mcp add mcnoaa-tides -- uvx mcnoaa-tides # With visualization support (charts) -uv pip install noaa-tides[viz] +uv pip install mcnoaa-tides[viz] # Local development -uv run noaa-tides +uv run mcnoaa-tides ``` ## Tools @@ -192,7 +192,7 @@ Parameters: - `include_observed` — overlay actual readings (default true) - `format` — `"png"` (inline image) or `"html"` (interactive file) -Requires `noaa-tides[viz]` — install with `uv pip install noaa-tides[viz]`. +Requires `mcnoaa-tides[viz]` — install with `uv pip install mcnoaa-tides[viz]`. ### `visualize_conditions` — Multi-panel conditions dashboard @@ -208,7 +208,7 @@ Parameters: - `hours` — data window (default 24) - `format` — `"png"` (inline image) or `"html"` (interactive file) -Requires `noaa-tides[viz]`. +Requires `mcnoaa-tides[viz]`. ## Resources @@ -253,7 +253,7 @@ marine_safety_check(station_id="9447130") ## Development ```bash -git clone && cd noaa-tides +git clone && cd mcnoaa-tides uv sync --dev # Run tests (mock client, no network) @@ -263,12 +263,12 @@ uv run pytest tests/ -v uv run ruff check src/ # Start server locally -uv run noaa-tides +uv run mcnoaa-tides # Headless test with Claude claude -p "Search for tide stations in Rhode Island" \ --mcp-config .mcp.json \ - --allowedTools "mcp__noaa-tides__*" + --allowedTools "mcp__mcnoaa-tides__*" ``` ## Data source diff --git a/pyproject.toml b/pyproject.toml index 8bfda3d..6531ba0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [project] -name = "noaa-tides" +name = "mcnoaa-tides" version = "2026.02.21" description = "FastMCP server for NOAA CO-OPS Tides and Currents API" authors = [{ name = "Ryan Malloy", email = "ryan@supported.systems" }] @@ -12,14 +12,14 @@ license = "MIT" viz = ["matplotlib>=3.8", "plotly>=5.18"] [project.scripts] -noaa-tides = "noaa_tides.server:main" +mcnoaa-tides = "mcnoaa_tides.server:main" [build-system] requires = ["hatchling"] build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] -packages = ["src/noaa_tides"] +packages = ["src/mcnoaa_tides"] [tool.ruff] target-version = "py312" @@ -32,4 +32,4 @@ select = ["E", "F", "I", "W"] asyncio_mode = "auto" [dependency-groups] -dev = ["pytest>=8", "pytest-asyncio>=0.24", "ruff>=0.9", "noaa-tides[viz]"] +dev = ["pytest>=8", "pytest-asyncio>=0.24", "ruff>=0.9", "mcnoaa-tides[viz]"] diff --git a/src/noaa_tides/__init__.py b/src/mcnoaa_tides/__init__.py similarity index 75% rename from src/noaa_tides/__init__.py rename to src/mcnoaa_tides/__init__.py index b6e9fe5..1cb78f4 100644 --- a/src/noaa_tides/__init__.py +++ b/src/mcnoaa_tides/__init__.py @@ -1,6 +1,6 @@ from importlib.metadata import PackageNotFoundError, version try: - __version__ = version("noaa-tides") + __version__ = version("mcnoaa-tides") except PackageNotFoundError: __version__ = "0.0.0-dev" diff --git a/src/noaa_tides/charts/__init__.py b/src/mcnoaa_tides/charts/__init__.py similarity index 82% rename from src/noaa_tides/charts/__init__.py rename to src/mcnoaa_tides/charts/__init__.py index 6b356df..cef5e59 100644 --- a/src/noaa_tides/charts/__init__.py +++ b/src/mcnoaa_tides/charts/__init__.py @@ -1,6 +1,6 @@ """Chart rendering for NOAA tide and conditions data. -Optional dependency — requires noaa-tides[viz] to be installed. +Optional dependency — requires mcnoaa-tides[viz] to be installed. """ # Marine color palette — shared across all chart renderers @@ -21,7 +21,7 @@ def check_deps(format: str) -> None: except ImportError: raise ValueError( "PNG charts require matplotlib. Install with: " - "uv pip install noaa-tides[viz]" + "uv pip install mcnoaa-tides[viz]" ) elif format == "html": try: @@ -29,5 +29,5 @@ def check_deps(format: str) -> None: except ImportError: raise ValueError( "HTML charts require plotly. Install with: " - "uv pip install noaa-tides[viz]" + "uv pip install mcnoaa-tides[viz]" ) diff --git a/src/noaa_tides/charts/conditions.py b/src/mcnoaa_tides/charts/conditions.py similarity index 97% rename from src/noaa_tides/charts/conditions.py rename to src/mcnoaa_tides/charts/conditions.py index f37c2c1..f9f9c82 100644 --- a/src/noaa_tides/charts/conditions.py +++ b/src/mcnoaa_tides/charts/conditions.py @@ -3,7 +3,7 @@ import io from datetime import datetime -from noaa_tides.charts import BG_COLOR, CORAL, GRID_COLOR, OCEAN_BLUE, SAND, SLATE, TEAL +from mcnoaa_tides.charts import BG_COLOR, CORAL, GRID_COLOR, OCEAN_BLUE, SAND, SLATE, TEAL def _parse_time_series(data: list[dict], value_key: str = "v") -> tuple[list, list]: @@ -105,7 +105,7 @@ def render_conditions_png(snapshot: dict, station_name: str = "") -> bytes: if has_predictions: preds = snapshot["predictions"].get("predictions", []) if preds: - from noaa_tides.charts.tides import _parse_predictions + from mcnoaa_tides.charts.tides import _parse_predictions p_times, p_values, markers = _parse_predictions(preds) ax.plot( @@ -122,7 +122,7 @@ def render_conditions_png(snapshot: dict, station_name: str = "") -> bytes: if has_water_level: obs_data = snapshot["water_level"].get("data", []) if obs_data: - from noaa_tides.charts.tides import _parse_observed + from mcnoaa_tides.charts.tides import _parse_observed o_times, o_values = _parse_observed(obs_data) if o_times: @@ -255,7 +255,7 @@ def render_conditions_html(snapshot: dict, station_name: str = "") -> str: if has_predictions: preds = snapshot["predictions"].get("predictions", []) if preds: - from noaa_tides.charts.tides import _parse_predictions + from mcnoaa_tides.charts.tides import _parse_predictions p_times, p_values, markers = _parse_predictions(preds) fig.add_trace( @@ -290,7 +290,7 @@ def render_conditions_html(snapshot: dict, station_name: str = "") -> str: if has_water_level: obs_data = snapshot["water_level"].get("data", []) if obs_data: - from noaa_tides.charts.tides import _parse_observed + from mcnoaa_tides.charts.tides import _parse_observed o_times, o_values = _parse_observed(obs_data) if o_times: diff --git a/src/noaa_tides/charts/tides.py b/src/mcnoaa_tides/charts/tides.py similarity index 98% rename from src/noaa_tides/charts/tides.py rename to src/mcnoaa_tides/charts/tides.py index 574df44..5f3fbe2 100644 --- a/src/noaa_tides/charts/tides.py +++ b/src/mcnoaa_tides/charts/tides.py @@ -3,7 +3,7 @@ import io from datetime import datetime -from noaa_tides.charts import BG_COLOR, CORAL, GRID_COLOR, OCEAN_BLUE, SAND, SLATE, TEAL +from mcnoaa_tides.charts import BG_COLOR, CORAL, GRID_COLOR, OCEAN_BLUE, SAND, SLATE, TEAL def _parse_predictions(predictions: list[dict]) -> tuple[list[datetime], list[float], list[dict]]: diff --git a/src/noaa_tides/client.py b/src/mcnoaa_tides/client.py similarity index 98% rename from src/noaa_tides/client.py rename to src/mcnoaa_tides/client.py index b300123..039eb91 100644 --- a/src/noaa_tides/client.py +++ b/src/mcnoaa_tides/client.py @@ -7,7 +7,7 @@ import time import httpx -from noaa_tides.models import Station +from mcnoaa_tides.models import Station DATA_URL = "https://api.tidesandcurrents.noaa.gov/api/prod/datagetter" META_URL = "https://api.tidesandcurrents.noaa.gov/mdapi/prod/webapi" @@ -139,7 +139,7 @@ class NOAAClient: "units": units, "time_zone": time_zone, "format": "json", - "application": "noaa-tides-mcp", + "application": "mcnoaa-tides-mcp", } if begin_date: params["begin_date"] = begin_date diff --git a/src/noaa_tides/models.py b/src/mcnoaa_tides/models.py similarity index 100% rename from src/noaa_tides/models.py rename to src/mcnoaa_tides/models.py diff --git a/src/noaa_tides/prompts.py b/src/mcnoaa_tides/prompts.py similarity index 100% rename from src/noaa_tides/prompts.py rename to src/mcnoaa_tides/prompts.py diff --git a/src/noaa_tides/resources.py b/src/mcnoaa_tides/resources.py similarity index 97% rename from src/noaa_tides/resources.py rename to src/mcnoaa_tides/resources.py index cdeaa4c..9789b60 100644 --- a/src/noaa_tides/resources.py +++ b/src/mcnoaa_tides/resources.py @@ -4,7 +4,7 @@ import json from fastmcp import Context, FastMCP -from noaa_tides.client import NOAAClient +from mcnoaa_tides.client import NOAAClient def register(mcp: FastMCP) -> None: diff --git a/src/noaa_tides/server.py b/src/mcnoaa_tides/server.py similarity index 84% rename from src/noaa_tides/server.py rename to src/mcnoaa_tides/server.py index 155b09b..de870cd 100644 --- a/src/noaa_tides/server.py +++ b/src/mcnoaa_tides/server.py @@ -5,9 +5,9 @@ from contextlib import asynccontextmanager from fastmcp import FastMCP -from noaa_tides import __version__, prompts, resources -from noaa_tides.client import NOAAClient -from noaa_tides.tools import charts, conditions, meteorological, stations, tides +from mcnoaa_tides import __version__, prompts, resources +from mcnoaa_tides.client import NOAAClient +from mcnoaa_tides.tools import charts, conditions, meteorological, stations, tides @asynccontextmanager @@ -34,7 +34,7 @@ async def lifespan(server: FastMCP): mcp = FastMCP( - "noaa-tides", + "mcnoaa-tides", instructions=( "NOAA Tides & Currents data server. " "Provides tide predictions, observed water levels, and meteorological data " @@ -57,5 +57,5 @@ prompts.register(mcp) def main(): - print(f"noaa-tides v{__version__}", file=sys.stderr) + print(f"mcnoaa-tides v{__version__}", file=sys.stderr) mcp.run() diff --git a/src/noaa_tides/tools/__init__.py b/src/mcnoaa_tides/tools/__init__.py similarity index 100% rename from src/noaa_tides/tools/__init__.py rename to src/mcnoaa_tides/tools/__init__.py diff --git a/src/noaa_tides/tools/charts.py b/src/mcnoaa_tides/tools/charts.py similarity index 92% rename from src/noaa_tides/tools/charts.py rename to src/mcnoaa_tides/tools/charts.py index 5398df3..064e347 100644 --- a/src/noaa_tides/tools/charts.py +++ b/src/mcnoaa_tides/tools/charts.py @@ -8,8 +8,8 @@ from typing import Literal from fastmcp import Context, FastMCP from fastmcp.utilities.types import Image -from noaa_tides.charts import check_deps -from noaa_tides.client import NOAAClient +from mcnoaa_tides.charts import check_deps +from mcnoaa_tides.client import NOAAClient def register(mcp: FastMCP) -> None: @@ -30,7 +30,7 @@ def register(mcp: FastMCP) -> None: PNG format returns an inline image. HTML format saves an interactive chart to artifacts/charts/ and returns the file path. - Requires noaa-tides[viz] to be installed. + Requires mcnoaa-tides[viz] to be installed. """ check_deps(format) noaa: NOAAClient = ctx.lifespan_context["noaa_client"] @@ -81,12 +81,12 @@ def register(mcp: FastMCP) -> None: pass if format == "png": - from noaa_tides.charts.tides import render_tide_chart_png + from mcnoaa_tides.charts.tides import render_tide_chart_png png_bytes = render_tide_chart_png(predictions, observed, station_name) return Image(data=png_bytes, format="image/png") else: - from noaa_tides.charts.tides import render_tide_chart_html + from mcnoaa_tides.charts.tides import render_tide_chart_html html = render_tide_chart_html(predictions, observed, station_name) path = _save_html(html, station_id, "tides") @@ -112,7 +112,7 @@ def register(mcp: FastMCP) -> None: PNG format returns an inline image. HTML format saves an interactive chart to artifacts/charts/ and returns the file path. - Requires noaa-tides[viz] to be installed. + Requires mcnoaa-tides[viz] to be installed. """ check_deps(format) noaa: NOAAClient = ctx.lifespan_context["noaa_client"] @@ -159,12 +159,12 @@ def register(mcp: FastMCP) -> None: pass if format == "png": - from noaa_tides.charts.conditions import render_conditions_png + from mcnoaa_tides.charts.conditions import render_conditions_png png_bytes = render_conditions_png(snapshot, station_name) return Image(data=png_bytes, format="image/png") else: - from noaa_tides.charts.conditions import render_conditions_html + from mcnoaa_tides.charts.conditions import render_conditions_html html = render_conditions_html(snapshot, station_name) path = _save_html(html, station_id, "conditions") diff --git a/src/noaa_tides/tools/conditions.py b/src/mcnoaa_tides/tools/conditions.py similarity index 98% rename from src/noaa_tides/tools/conditions.py rename to src/mcnoaa_tides/tools/conditions.py index cef12a4..dd09659 100644 --- a/src/noaa_tides/tools/conditions.py +++ b/src/mcnoaa_tides/tools/conditions.py @@ -5,7 +5,7 @@ from datetime import datetime, timezone from fastmcp import Context, FastMCP -from noaa_tides.client import NOAAClient +from mcnoaa_tides.client import NOAAClient def register(mcp: FastMCP) -> None: diff --git a/src/noaa_tides/tools/meteorological.py b/src/mcnoaa_tides/tools/meteorological.py similarity index 97% rename from src/noaa_tides/tools/meteorological.py rename to src/mcnoaa_tides/tools/meteorological.py index 85cab37..56b93ab 100644 --- a/src/noaa_tides/tools/meteorological.py +++ b/src/mcnoaa_tides/tools/meteorological.py @@ -4,7 +4,7 @@ from typing import Literal from fastmcp import Context, FastMCP -from noaa_tides.client import NOAAClient +from mcnoaa_tides.client import NOAAClient MetProduct = Literal[ "air_temperature", diff --git a/src/noaa_tides/tools/stations.py b/src/mcnoaa_tides/tools/stations.py similarity index 98% rename from src/noaa_tides/tools/stations.py rename to src/mcnoaa_tides/tools/stations.py index 57f220c..9baa430 100644 --- a/src/noaa_tides/tools/stations.py +++ b/src/mcnoaa_tides/tools/stations.py @@ -2,7 +2,7 @@ from fastmcp import Context, FastMCP -from noaa_tides.client import NOAAClient +from mcnoaa_tides.client import NOAAClient def register(mcp: FastMCP) -> None: diff --git a/src/noaa_tides/tools/tides.py b/src/mcnoaa_tides/tools/tides.py similarity index 98% rename from src/noaa_tides/tools/tides.py rename to src/mcnoaa_tides/tools/tides.py index 644e4da..9e424bc 100644 --- a/src/noaa_tides/tools/tides.py +++ b/src/mcnoaa_tides/tools/tides.py @@ -2,7 +2,7 @@ from fastmcp import Context, FastMCP -from noaa_tides.client import NOAAClient +from mcnoaa_tides.client import NOAAClient def register(mcp: FastMCP) -> None: diff --git a/tests/conftest.py b/tests/conftest.py index 7542093..bcd74d1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,9 +8,9 @@ from fastmcp import Client, FastMCP from fastmcp.client.transports import StreamableHttpTransport from fastmcp.utilities.tests import run_server_async -from noaa_tides import prompts, resources -from noaa_tides.client import NOAAClient -from noaa_tides.tools import charts, conditions, meteorological, stations, tides +from mcnoaa_tides import prompts, resources +from mcnoaa_tides.client import NOAAClient +from mcnoaa_tides.tools import charts, conditions, meteorological, stations, tides # Realistic station fixtures MOCK_STATIONS_RAW = [ @@ -98,7 +98,7 @@ MOCK_METADATA = { def _build_mock_client() -> NOAAClient: """Build a NOAAClient with mocked HTTP but real search/nearest logic.""" - from noaa_tides.models import Station + from mcnoaa_tides.models import Station client = NOAAClient() client._stations = [Station(**s) for s in MOCK_STATIONS_RAW] @@ -135,7 +135,7 @@ async def _test_lifespan(server: FastMCP): def _build_test_server() -> FastMCP: - mcp = FastMCP("noaa-tides-test", lifespan=_test_lifespan) + mcp = FastMCP("mcnoaa-tides-test", lifespan=_test_lifespan) stations.register(mcp) tides.register(mcp) meteorological.register(mcp) diff --git a/tests/test_charts.py b/tests/test_charts.py index 8b70a19..c3ca1f8 100644 --- a/tests/test_charts.py +++ b/tests/test_charts.py @@ -3,8 +3,8 @@ import pytest from fastmcp import Client -from noaa_tides.charts.conditions import render_conditions_html, render_conditions_png -from noaa_tides.charts.tides import ( +from mcnoaa_tides.charts.conditions import render_conditions_html, render_conditions_png +from mcnoaa_tides.charts.tides import ( _parse_observed, _parse_predictions, render_tide_chart_html, diff --git a/uv.lock b/uv.lock index 4b12809..5240a78 100644 --- a/uv.lock +++ b/uv.lock @@ -767,6 +767,44 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5d/49/d651878698a0b67f23aa28e17f45a6d6dd3d3f933fa29087fa4ce5947b5a/matplotlib-3.10.8-cp314-cp314t-win_arm64.whl", hash = "sha256:113bb52413ea508ce954a02c10ffd0d565f9c3bc7f2eddc27dfe1731e71c7b5f", size = 8192560, upload-time = "2025-12-10T22:56:38.008Z" }, ] +[[package]] +name = "mcnoaa-tides" +version = "2026.2.21" +source = { editable = "." } +dependencies = [ + { name = "fastmcp" }, +] + +[package.optional-dependencies] +viz = [ + { name = "matplotlib" }, + { name = "plotly" }, +] + +[package.dev-dependencies] +dev = [ + { name = "mcnoaa-tides", extra = ["viz"] }, + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "fastmcp", specifier = ">=3.0.1" }, + { name = "matplotlib", marker = "extra == 'viz'", specifier = ">=3.8" }, + { name = "plotly", marker = "extra == 'viz'", specifier = ">=5.18" }, +] +provides-extras = ["viz"] + +[package.metadata.requires-dev] +dev = [ + { name = "mcnoaa-tides", extras = ["viz"] }, + { name = "pytest", specifier = ">=8" }, + { name = "pytest-asyncio", specifier = ">=0.24" }, + { name = "ruff", specifier = ">=0.9" }, +] + [[package]] name = "mcp" version = "1.26.0" @@ -819,44 +857,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/03/cc/7cb74758e6df95e0c4e1253f203b6dd7f348bf2f29cf89e9210a2416d535/narwhals-2.16.0-py3-none-any.whl", hash = "sha256:846f1fd7093ac69d63526e50732033e86c30ea0026a44d9b23991010c7d1485d", size = 443951, upload-time = "2026-02-02T10:30:58.635Z" }, ] -[[package]] -name = "noaa-tides" -version = "2026.2.21" -source = { editable = "." } -dependencies = [ - { name = "fastmcp" }, -] - -[package.optional-dependencies] -viz = [ - { name = "matplotlib" }, - { name = "plotly" }, -] - -[package.dev-dependencies] -dev = [ - { name = "noaa-tides", extra = ["viz"] }, - { name = "pytest" }, - { name = "pytest-asyncio" }, - { name = "ruff" }, -] - -[package.metadata] -requires-dist = [ - { name = "fastmcp", specifier = ">=3.0.1" }, - { name = "matplotlib", marker = "extra == 'viz'", specifier = ">=3.8" }, - { name = "plotly", marker = "extra == 'viz'", specifier = ">=5.18" }, -] -provides-extras = ["viz"] - -[package.metadata.requires-dev] -dev = [ - { name = "noaa-tides", extras = ["viz"] }, - { name = "pytest", specifier = ">=8" }, - { name = "pytest-asyncio", specifier = ">=0.24" }, - { name = "ruff", specifier = ">=0.9" }, -] - [[package]] name = "numpy" version = "2.4.2"