diff --git a/.github/workflows/auto-merge-on-release.yml b/.github/workflows/auto-merge-on-release.yml index 738401b..a4113c4 100644 --- a/.github/workflows/auto-merge-on-release.yml +++ b/.github/workflows/auto-merge-on-release.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Auto-merge matching PR - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/cache-uv-build.yaml b/.github/workflows/cache-uv-build.yaml index 6412e31..34d8140 100644 --- a/.github/workflows/cache-uv-build.yaml +++ b/.github/workflows/cache-uv-build.yaml @@ -7,7 +7,7 @@ jobs: build-cache: runs-on: ubuntu-latest env: - UV_VERSION: '0.10.6' + UV_VERSION: '0.10.8' PYTHON_VERSION: '3.13' steps: - name: Checkout repository diff --git a/.github/workflows/ci-cd.yaml b/.github/workflows/ci-cd.yaml index b655973..d498850 100644 --- a/.github/workflows/ci-cd.yaml +++ b/.github/workflows/ci-cd.yaml @@ -38,15 +38,14 @@ jobs: release: if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') needs: [test, docker-build-and-image-scan] - uses: ./.github/workflows/release.yaml + uses: milsman2/python-app-template/.github/workflows/release.yaml@main permissions: contents: write secrets: inherit trigger-auto-merge: - needs: release - if: needs.release.result == 'success' - uses: ./.github/workflows/auto-merge-on-release.yml + needs: [release, docker-build-and-image-scan] + uses: milsman2/python-app-template/.github/workflows/auto-merge-on-release.yml@main permissions: contents: write pull-requests: write diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 8b3aacd..23e3418 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -9,7 +9,7 @@ jobs: permissions: contents: read env: - UV_VERSION: '>=0.10.6' + UV_VERSION: '>=0.10.8' PYTHON_VERSION: '3.13' steps: - name: Checkout repository diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 9ab2b90..4ac225a 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -15,7 +15,7 @@ jobs: dist_artifacts_name: dist dist_artifacts_dir: dist lock_file_artifact: uv.lock - UV_VERSION: '>=0.10.6' + UV_VERSION: '>=0.10.8' PYTHON_VERSION: '3.13' GITHUB_ACTIONS_AUTHOR_NAME: github-actions GITHUB_ACTIONS_AUTHOR_EMAIL: actions@users.noreply.github.com diff --git a/.github/workflows/ruff.yaml b/.github/workflows/ruff.yaml index aab58ac..54b6bfc 100644 --- a/.github/workflows/ruff.yaml +++ b/.github/workflows/ruff.yaml @@ -9,7 +9,7 @@ jobs: permissions: contents: read env: - UV_VERSION: '>=0.10.6' + UV_VERSION: '>=0.10.8' PYTHON_VERSION: '3.13' steps: diff --git a/pyproject.toml b/pyproject.toml index 99afe9b..db550b8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ dependencies = [ "black>=26.1.0", "coverage>=7.13.2", "httpx>=0.28.1", - "isort>=7.0.0", + "isort>=8.0.1", "loguru>=0.7.3", "pathlib>=1.0.1", "prometheus-client>=0.24.1", @@ -24,10 +24,10 @@ dependencies = [ ] [project.optional-dependencies] -build = ["uv >= 0.10.6"] +build = ["uv >= 0.10.7"] [build-system] -requires = ["uv_build >= 0.10.6"] +requires = ["uv_build >= 0.10.7"] build-backend = "uv_build" [project.scripts] diff --git a/src/sample_python_app/ui/__init__.py b/src/sample_python_app/ui/__init__.py index a213903..0f6f0e8 100644 --- a/src/sample_python_app/ui/__init__.py +++ b/src/sample_python_app/ui/__init__.py @@ -1,6 +1,6 @@ """UI package for synthwave terminal display and related components.""" from sample_python_app.ui.display import display_astronomical_data -from sample_python_app.ui.synthwave import synthwave_dashboard, synthwave_display +from sample_python_app.ui.synthwave import synthwave_dashboard -__all__ = ["synthwave_display", "synthwave_dashboard", "display_astronomical_data"] +__all__ = ["synthwave_dashboard", "display_astronomical_data"] diff --git a/src/sample_python_app/ui/astro_table.py b/src/sample_python_app/ui/astro_table.py new file mode 100644 index 0000000..8b682a6 --- /dev/null +++ b/src/sample_python_app/ui/astro_table.py @@ -0,0 +1,66 @@ +"""Render an astronomical summary table with figlets and times. + +This module builds a two-column Rich table containing large figlet +art for sunrise/sunset, followed by an "Event | Local Time" header +row and the corresponding event times converted to the app timezone. +""" + +from pyfiglet import Figlet +from rich.align import Align +from rich.console import Group +from rich.table import Table +from rich.text import Text + +from sample_python_app.core import Settings +from sample_python_app.models import AstronomicalData + + +def build_astro_table(astro: AstronomicalData, settings: Settings): + """Build a rich Table for the astronomical data. + + The table renders large figlets first, then a manual header row + "Event | Local Time" immediately below the figlets so the + column labels appear under the art. + """ + astro_table = Table(show_header=False, box=None) + + sunrise_local = astro.sunrise.astimezone(settings.tz) + sun_art = Figlet(font="small", width=60).renderText("SUNRISE") + sun_set_art = Figlet(font="small", width=60).renderText("SUNSET") + sun_text = Text(sun_art, style="bold yellow") + sun_set_text = Text(sun_set_art, style="bold blue") + sunrise_time_art = Figlet(font="mini", width=60).renderText( + sunrise_local.strftime("%I:%M %p") + ) + sunset_time_art = Figlet(font="mini", width=60).renderText( + astro.sunset.astimezone(settings.tz).strftime("%I:%M %p") + ) + sunrise_time_text = Text(sunrise_time_art, style="bold yellow") + sunset_time_text = Text(sunset_time_art, style="bold blue") + + astro_table.add_row( + Align( + Group(Align.center(sun_text), Align.center(sunrise_time_text)), + align="center", + vertical="middle", + ), + Align( + Group(Align.center(sun_set_text), Align.center(sunset_time_text)), + align="center", + vertical="middle", + ), + ) + + astro_table.add_row( + Text("Event", style="bold magenta", justify="center"), + Text("Local Time", style="bold magenta", justify="center"), + ) + + tz = settings.tz + time_fmt = "%I:%M %p %Z" + for name, dt in astro.as_local(tz).items(): + label = name.replace("_", " ").title() + value = dt.strftime(time_fmt) if hasattr(dt, "strftime") else str(dt) + astro_table.add_row(f"[bold #ff6ec7]{label}[/]", f"[bold #00eaff]{value}[/]") + + return astro_table diff --git a/src/sample_python_app/ui/forecast_table.py b/src/sample_python_app/ui/forecast_table.py new file mode 100644 index 0000000..9ac6a48 --- /dev/null +++ b/src/sample_python_app/ui/forecast_table.py @@ -0,0 +1,38 @@ +"""Module for building a rich Table to display forecast data.""" + +from rich.table import Table + +from sample_python_app.core import Settings +from sample_python_app.models.forecast_geojson import ForecastFeature + + +def build_forecast_table(forecast: ForecastFeature, settings: Settings, periods=12): + """Build a rich Table for the hourly forecast data.""" + fc_table = Table(show_header=True, header_style="bold magenta", box=None) + fc_table.add_column("Time", style="bold #00eaff") + fc_table.add_column("T", style="bold #ffdd57") + fc_table.add_column("POP", style="bold #ff6ec7") + fc_table.add_column("Wind", style="bold #00ff9e") + fc_table.add_column("Short", style="bold #ffffff") + + count = 0 + for p in forecast.properties.periods: + if count >= periods: + break + t = p.start_time.astimezone(settings.tz).strftime("%I %p") + temp = ( + f"{p.temperature}°{p.temperature_unit or ''}" + if p.temperature is not None + else "-" + ) + pop = ( + f"{int(p.probability_of_precipitation.value)}%" + if p.probability_of_precipitation + and p.probability_of_precipitation.value is not None + else "-" + ) + wind = p.wind_speed or "-" + short = p.short_forecast or "" + fc_table.add_row(t, temp, pop, wind, short) + count += 1 + return fc_table diff --git a/src/sample_python_app/ui/header_panel.py b/src/sample_python_app/ui/header_panel.py index b79d620..3b3926e 100644 --- a/src/sample_python_app/ui/header_panel.py +++ b/src/sample_python_app/ui/header_panel.py @@ -20,7 +20,6 @@ def build_header_panel( The panel itself remains flexible and does not enforce a fixed height. """ sunrise_local = astro.sunrise.astimezone(settings.tz) - # Render as three stacked lines: SYNTHWAVE, SUNRISE, then the date header_main = Figlet(font="slant", width=80).renderText("SYNTHWAVE") header_main_text = Text(header_main, style="bold magenta") header_sub = Figlet(font="small", width=80).renderText("SUNRISE") @@ -32,10 +31,10 @@ def build_header_panel( Align.center(header_sub_text), Align.center(date_text), ) - # Vertically center the header + date within the panel - return Panel( + header_panel = Panel( Align(content, align="center", vertical="middle"), title="[bold #ff6ec7]Synthwave[/bold #ff6ec7]", border_style="#ff00cc", padding=(0, 1), ) + return header_panel diff --git a/src/sample_python_app/ui/synthwave.py b/src/sample_python_app/ui/synthwave.py index 6056fad..916ba98 100644 --- a/src/sample_python_app/ui/synthwave.py +++ b/src/sample_python_app/ui/synthwave.py @@ -1,117 +1,21 @@ """Synthwave terminal UI for astronomical data display using rich and pyfiglet.""" -from datetime import datetime - -from pyfiglet import Figlet from rich.align import Align -from rich.columns import Columns -from rich.console import Console, Group +from rich.console import Console from rich.layout import Layout from rich.panel import Panel -from rich.table import Table -from rich.text import Text from sample_python_app.core import Settings, setup_logger -from sample_python_app.models import AstronomicalData -from sample_python_app.models.forecast_geojson import ForecastFeature +from sample_python_app.models import AstronomicalData, ForecastFeature +from sample_python_app.ui.astro_table import build_astro_table +from sample_python_app.ui.forecast_table import build_forecast_table from sample_python_app.ui.header_panel import build_header_panel -def synthwave_display(astro: AstronomicalData, settings: Settings): - """Display astronomical data in a synthwave terminal UI.""" - logger = setup_logger(mode="silent") - console = Console() - logger.info("Rendering synthwave terminal UI for astronomical data.") - header = Figlet(font="slant", width=120).renderText("SYNTHWAVE SUNRISE 🌅") - logger.info("Header rendered as figlet art.") - header_text = Text(header) - header_text.stylize("bold magenta") - - sunrise_local = astro.sunrise.astimezone(settings.tz) - sunset_local = astro.sunset.astimezone(settings.tz) - logger.info(f"Local sunrise: {sunrise_local}, Local sunset: {sunset_local}") - date_str = sunrise_local.strftime("%A, %B %d, %Y") - date_art = Figlet(font="mini", width=150).renderText(date_str) - logger.info(f"Date rendered as figlet: {date_str}") - date_text = Text(date_art) - date_text.stylize("bold cyan") - - sun_art = Figlet(font="starwars", width=120).renderText("SUNRISE") - sun_set_art = Figlet(font="starwars", width=120).renderText("SUNSET") - sun_text = Text(sun_art) - sun_text.stylize("bold yellow") - sunrise_time_str = sunrise_local.strftime("%I:%M:%S %p") - logger.info(f"Sunrise time (figlet): {sunrise_time_str}") - sunrise_time_art = Figlet(font="big", width=100).renderText(sunrise_time_str) - sunrise_time_text = Text(sunrise_time_art) - sunrise_time_text.stylize("bold yellow") - - sun_set_text = Text(sun_set_art) - sun_set_text.stylize("bold blue") - sunset_time_str = sunset_local.strftime("%I:%M:%S %p") - logger.info(f"Sunset time (figlet): {sunset_time_str}") - sunset_time_art = Figlet(font="big", width=100).renderText(sunset_time_str) - sunset_time_text = Text(sunset_time_art) - sunset_time_text.stylize("bold blue") - - # Table with color-coded events - astro_table = Table(show_header=True, header_style="bold magenta", box=None) - astro_table.add_column("Event", style="bold #ff00cc") - astro_table.add_column("Local Time", style="bold #00eaff") - tz = settings.tz - time_fmt = "%I:%M:%S %p %Z" - event_colors = { - "sunrise": "#ffe066", - "sunset": "#5dade2", - "civil twilight begin": "#f7cac9", - "civil twilight end": "#92a8d1", - "nautical twilight begin": "#f9d423", - "nautical twilight end": "#6a89cc", - "astronomical twilight begin": "#b388ff", - "astronomical twilight end": "#2e86c1", - } - for name, dt in astro.as_local(tz).items(): - label = name.replace("_", " ").title() - if isinstance(dt, datetime): - value = dt.strftime(time_fmt) - else: - value = str(dt) - color = event_colors.get(label.lower(), "#e17055") - logger.info(f"Event: {label}, Time: {value}, Color: {color}") - astro_table.add_row( - f"[{color}]{label}[/{color}]", f"[{color}]{value}[/{color}]" - ) - - sun_figlet_row = Columns( - [ - Group(Align.center(sun_text), Align.center(sunrise_time_text)), - Group(Align.center(sun_set_text), Align.center(sunset_time_text)), - ], - align="center", - expand=True, - ) - - panel_content = Group( - Align.center(header_text), - Align.center(date_text), - sun_figlet_row, - Align.center(astro_table), - ) - console.print( - Panel( - panel_content, - title="[bold #ff6ec7]Synthwave Astronomical Events[/bold #ff6ec7]", - border_style="#ff00cc", - padding=(1, 2), - ) - ) - - def synthwave_dashboard( astro: AstronomicalData, forecast: ForecastFeature, settings: Settings, - periods: int = 12, ): """Render a compact combined synthwave dashboard. @@ -125,117 +29,19 @@ def synthwave_dashboard( header_height = 16 header_panel = build_header_panel(astro, settings, preferred_height=header_height) - sunrise_local = astro.sunrise.astimezone(settings.tz) - sun_art = Figlet(font="small", width=60).renderText("SUNRISE") - sun_set_art = Figlet(font="small", width=60).renderText("SUNSET") - sun_text = Text(sun_art, style="bold yellow") - sun_set_text = Text(sun_set_art, style="bold blue") - sunrise_time_art = Figlet(font="mini", width=60).renderText( - sunrise_local.strftime("%I:%M %p") - ) - sunset_time_art = Figlet(font="mini", width=60).renderText( - astro.sunset.astimezone(settings.tz).strftime("%I:%M %p") - ) - sunrise_time_text = Text(sunrise_time_art, style="bold yellow") - sunset_time_text = Text(sunset_time_art, style="bold blue") + forecast_table = build_forecast_table(forecast, settings) - fc_table = Table(show_header=True, header_style="bold magenta", box=None) - fc_table.add_column("Time", style="bold #00eaff") - fc_table.add_column("T", style="bold #ffdd57") - fc_table.add_column("POP", style="bold #ff6ec7") - fc_table.add_column("Wind", style="bold #00ff9e") - fc_table.add_column("Short", style="bold #ffffff") - - count = 0 - for p in forecast.properties.periods: - if count >= periods: - break - t = p.start_time.astimezone(settings.tz).strftime("%I %p") - temp = ( - f"{p.temperature}°{p.temperature_unit or ''}" - if p.temperature is not None - else "-" - ) - pop = ( - f"{int(p.probability_of_precipitation.value)}%" - if p.probability_of_precipitation - and p.probability_of_precipitation.value is not None - else "-" - ) - wind = p.wind_speed or "-" - short = p.short_forecast or "" - fc_table.add_row(t, temp, pop, wind, short) - count += 1 - - astro_table = Table(show_header=True, header_style="bold magenta", box=None) - astro_table.add_column("Event", style="bold #ff00cc") - astro_table.add_column("Local Time", style="bold #00eaff") - tz = settings.tz - time_fmt = "%I:%M %p %Z" - for name, dt in astro.as_local(tz).items(): - label = name.replace("_", " ").title() - value = dt.strftime(time_fmt) if hasattr(dt, "strftime") else str(dt) - astro_table.add_row(f"[bold #ff6ec7]{label}[/]", f"[bold #00eaff]{value}[/]") + astro_table = build_astro_table(astro, settings) - left_table = Table(show_header=False, box=None) - left_table.add_column(justify="center") - left_table.add_column(justify="center") - - left_table.add_row( - Align( - Group(Align.center(sun_text), Align.center(sunrise_time_text)), - align="center", - vertical="middle", - ), - Align( - Group(Align.center(sun_set_text), Align.center(sunset_time_text)), - align="center", - vertical="middle", - ), - ) - - left_events = [ - ("Civil Twilight Begin", "civil_twilight_begin"), - ("Nautical Twilight Begin", "nautical_twilight_begin"), - ("Astronomical Twilight Begin", "astronomical_twilight_begin"), - ] - right_events = [ - ("Civil Twilight End", "civil_twilight_end"), - ("Nautical Twilight End", "nautical_twilight_end"), - ("Astronomical Twilight End", "astronomical_twilight_end"), - ] - - times = astro.as_local(tz) - if len(left_events) != len(right_events): - logger.warning( - "left_events and right_events differ in length; zipping to shortest" - ) - n = min(len(left_events), len(right_events)) - for i in range(n): - llabel, lkey = left_events[i] - rlabel, rkey = right_events[i] - lval = times.get(lkey) - rval = times.get(rkey) if rkey else None - ltxt = f"{llabel}\n{lval.strftime('%I:%M %p %Z') if lval else '-'}" - rtxt = ( - f"{rlabel}\n{rval.strftime('%I:%M %p %Z') if rval else '-'}" - if rlabel - else "" - ) - left_table.add_row(Text(ltxt, justify="center"), Text(rtxt, justify="center")) - - # Include both the compact left_table (figlets + paired events) and the - # textual astro_table so tests and users can find plain labels like - # "Sunrise"/"Sunset" in the output. left_panel = Panel( - Group(Align.center(left_table), Align.center(astro_table)), + Align.center(astro_table), title="[bold #ff6ec7]Astronomical[/bold #ff6ec7]", border_style="#ff00cc", padding=(0, 1), ) right_panel = Panel( - Align.center(fc_table, vertical="middle"), + Align.center(forecast_table, vertical="middle"), title="[bold #00ff9e]Hourly Forecast[/bold #00ff9e]", border_style="#00ff9e", padding=(0, 1), @@ -246,18 +52,15 @@ def synthwave_dashboard( Layout(name="header", size=header_height), Layout(name="body", size=20) ) layout["header"].update(header_panel) - # Make hourly forecast panel slightly narrower but not too narrow layout["body"].split_row( Layout(left_panel, ratio=6), Layout(right_panel, ratio=4), ) console.print(layout) - # Also emit a plain-text summary so automated tests (and simple terminals) - # can find labels like "Sunrise"/"Sunset" regardless of rich clipping. - times_plain = astro.as_local(settings.tz) - for name, dt in times_plain.items(): + # Also print a plain-text summary of events so tests that capture + # stdout can assert on labels like "Sunrise" and "Astronomical Twilight Begin". + times = astro.formatted(settings.tz, "%I:%M %p %Z") + for name, val in times.items(): label = name.replace("_", " ").title() - val = dt.strftime("%I:%M %p %Z") if hasattr(dt, "strftime") else str(dt) - # Use Console.print (stdout) so tests capturing stdout can find labels console.print(f"{label}: {val}") diff --git a/uv.lock b/uv.lock index 7cf840b..7ad59d0 100644 --- a/uv.lock +++ b/uv.lock @@ -236,11 +236,11 @@ wheels = [ [[package]] name = "isort" -version = "8.0.0" +version = "8.0.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bf/e3/e72b0b3a85f24cf5fc2cd8e92b996592798f896024c5cdf3709232e6e377/isort-8.0.0.tar.gz", hash = "sha256:fddea59202f231e170e52e71e3510b99c373b6e571b55d9c7b31b679c0fed47c", size = 769482, upload-time = "2026-02-19T16:31:59.716Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ef/7c/ec4ab396d31b3b395e2e999c8f46dec78c5e29209fac49d1f4dace04041d/isort-8.0.1.tar.gz", hash = "sha256:171ac4ff559cdc060bcfff550bc8404a486fee0caab245679c2abe7cb253c78d", size = 769592, upload-time = "2026-02-28T10:08:20.685Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/74/ea/cf3aad99dd12c026e2d6835d559efb6fc50ccfd5b46d42d5fec2608b116a/isort-8.0.0-py3-none-any.whl", hash = "sha256:184916a933041c7cf718787f7e52064f3c06272aff69a5cb4dc46497bd8911d9", size = 89715, upload-time = "2026-02-19T16:31:57.745Z" }, + { url = "https://files.pythonhosted.org/packages/3e/95/c7c34aa53c16353c56d0b802fba48d5f5caa2cdee7958acbcb795c830416/isort-8.0.1-py3-none-any.whl", hash = "sha256:28b89bc70f751b559aeca209e6120393d43fbe2490de0559662be7a9787e3d75", size = 89733, upload-time = "2026-02-28T10:08:19.466Z" }, ] [[package]] @@ -523,11 +523,11 @@ wheels = [ [[package]] name = "python-dotenv" -version = "1.2.1" +version = "1.2.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" } +sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" }, + { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, ] [[package]] @@ -626,7 +626,7 @@ requires-dist = [ { name = "black", specifier = ">=26.1.0" }, { name = "coverage", specifier = ">=7.13.2" }, { name = "httpx", specifier = ">=0.28.1" }, - { name = "isort", specifier = ">=7.0.0" }, + { name = "isort", specifier = ">=8.0.1" }, { name = "loguru", specifier = ">=0.7.3" }, { name = "pathlib", specifier = ">=1.0.1" }, { name = "prometheus-client", specifier = ">=0.24.1" }, @@ -638,7 +638,7 @@ requires-dist = [ { name = "pytest", specifier = ">=9.0.2" }, { name = "rich", specifier = ">=14.3.2" }, { name = "ruff", specifier = ">=0.15.2" }, - { name = "uv", marker = "extra == 'build'", specifier = ">=0.10.6" }, + { name = "uv", marker = "extra == 'build'", specifier = ">=0.10.7" }, ] provides-extras = ["build"] @@ -695,27 +695,27 @@ wheels = [ [[package]] name = "uv" -version = "0.10.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d5/53/7a4274dad70b1d17efb99e36d45fc1b5e4e1e531b43247e518604394c761/uv-0.10.6.tar.gz", hash = "sha256:de86e5e1eb264e74a20fccf56889eea2463edb5296f560958e566647c537b52e", size = 3921763, upload-time = "2026-02-25T00:26:27.066Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4f/f9/faf599c6928dc00d941629260bef157dadb67e8ffb7f4b127b8601f41177/uv-0.10.6-py3-none-linux_armv6l.whl", hash = "sha256:2b46ad78c86d68de6ec13ffaa3a8923467f757574eeaf318e0fce0f63ff77d7a", size = 22412946, upload-time = "2026-02-25T00:26:10.826Z" }, - { url = "https://files.pythonhosted.org/packages/c4/8f/82dd6aa8acd2e1b1ba12fd49210bd19843383538e0e63e8d7a23a7d39d93/uv-0.10.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a1d9873eb26cbef9138f8c52525bc3fd63be2d0695344cdcf84f0dc2838a6844", size = 21524262, upload-time = "2026-02-25T00:27:09.318Z" }, - { url = "https://files.pythonhosted.org/packages/3b/48/5767af19db6f21176e43dfde46ea04e33c49ba245ac2634e83db15d23c8f/uv-0.10.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5a62cdf5ba356dcc792b960e744d67056b0e6d778ce7381e1d78182357bd82e8", size = 20184248, upload-time = "2026-02-25T00:26:20.281Z" }, - { url = "https://files.pythonhosted.org/packages/27/1b/13c2fcdb776ae78b5c22eb2d34931bb3ef9bd71b9578b8fa7af8dd7c11c4/uv-0.10.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:b70a04d51e2239b3aee0e4d4ed9af18c910360155953017cecded5c529588e65", size = 22049300, upload-time = "2026-02-25T00:26:07.039Z" }, - { url = "https://files.pythonhosted.org/packages/6f/43/348e2c378b3733eba15f6144b35a8c84af5c884232d6bbed29e256f74b6f/uv-0.10.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:2b622059a1ae287f8b995dcb6f5548de83b89b745ff112801abbf09e25fd8fa9", size = 22030505, upload-time = "2026-02-25T00:26:46.171Z" }, - { url = "https://files.pythonhosted.org/packages/a5/3f/dcec580099bc52f73036bfb09acb42616660733de1cc3f6c92287d2c7f3e/uv-0.10.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f43db1aa80776386646453c07d5590e1ae621f031a2afe6efba90f89c34c628c", size = 22041360, upload-time = "2026-02-25T00:26:53.725Z" }, - { url = "https://files.pythonhosted.org/packages/2c/96/f70abe813557d317998806517bb53b3caa5114591766db56ae9cc142ff39/uv-0.10.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4ca8a26694ba7d0ae902f11054734805741f2b080fe8397401b80c99264edab6", size = 23309916, upload-time = "2026-02-25T00:27:12.99Z" }, - { url = "https://files.pythonhosted.org/packages/db/1d/d8b955937dd0153b48fdcfd5ff70210d26e4b407188e976df620572534fd/uv-0.10.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f2cddae800d14159a9ccb4ff161648b0b0d1b31690d9c17076ec00f538c52ac", size = 24191174, upload-time = "2026-02-25T00:26:30.051Z" }, - { url = "https://files.pythonhosted.org/packages/c2/3d/3d0669d65bf4a270420d70ca0670917ce5c25c976c8b0acd52465852509b/uv-0.10.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:153fcf5375c988b2161bf3a6a7d9cc907d6bbe38f3cb16276da01b2dae4df72c", size = 23320328, upload-time = "2026-02-25T00:26:23.82Z" }, - { url = "https://files.pythonhosted.org/packages/85/f2/f2ccc2196fd6cf1321c2e8751a96afabcbc9509b184c671ece3e804effda/uv-0.10.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f27f2d135d4533f88537ecd254c72dfd25311d912da8649d15804284d70adb93", size = 23229798, upload-time = "2026-02-25T00:26:50.12Z" }, - { url = "https://files.pythonhosted.org/packages/2d/b9/1008266a041e8a55430a92aef8ecc58aaaa7eb7107a26cf4f7c127d14363/uv-0.10.6-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:dd993ec2bf5303a170946342955509559763cf8dcfe334ec7bb9f115a0f86021", size = 22143661, upload-time = "2026-02-25T00:26:42.507Z" }, - { url = "https://files.pythonhosted.org/packages/93/e4/1f8de7da5f844b4c9eafa616e262749cd4e3d9c685190b7967c4681869da/uv-0.10.6-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:8529e4d4aac40b4e7588177321cb332cc3309d36d7cc482470a1f6cfe7a7e14a", size = 22888045, upload-time = "2026-02-25T00:26:15.935Z" }, - { url = "https://files.pythonhosted.org/packages/e2/2b/03b840dd0101dc69ef6e83ceb2e2970e4b4f118291266cf3332a4b64092c/uv-0.10.6-py3-none-musllinux_1_1_i686.whl", hash = "sha256:ed9e16453a5f73ee058c566392885f445d00534dc9e754e10ab9f50f05eb27a5", size = 22549404, upload-time = "2026-02-25T00:27:05.333Z" }, - { url = "https://files.pythonhosted.org/packages/4c/4e/1ee4d4301874136a4b3bbd9eeba88da39f4bafa6f633b62aef77d8195c56/uv-0.10.6-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:33e5362039bfa91599df0b7487854440ffef1386ac681ec392d9748177fb1d43", size = 23426872, upload-time = "2026-02-25T00:26:35.01Z" }, - { url = "https://files.pythonhosted.org/packages/d3/e3/e000030118ff1a82ecfc6bd5af70949821edac739975a027994f5b17258f/uv-0.10.6-py3-none-win32.whl", hash = "sha256:fa7c504a1e16713b845d457421b07dd9c40f40d911ffca6897f97388de49df5a", size = 21501863, upload-time = "2026-02-25T00:26:57.182Z" }, - { url = "https://files.pythonhosted.org/packages/1c/cc/dd88c9f20c054ef0aea84ad1dd9f8b547463824857e4376463a948983bed/uv-0.10.6-py3-none-win_amd64.whl", hash = "sha256:ecded4d21834b21002bc6e9a2628d21f5c8417fd77a5db14250f1101bcb69dac", size = 23981891, upload-time = "2026-02-25T00:26:38.773Z" }, - { url = "https://files.pythonhosted.org/packages/cf/06/ca117002cd64f6701359253d8566ec7a0edcf61715b4969f07ee41d06f61/uv-0.10.6-py3-none-win_arm64.whl", hash = "sha256:4b5688625fc48565418c56a5cd6c8c32020dbb7c6fb7d10864c2d2c93c508302", size = 22339889, upload-time = "2026-02-25T00:27:00.818Z" }, +version = "0.10.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/e7/600a90d4662dbd8414c1f6b709c8c79075d37d2044f72b94acbfaf29baad/uv-0.10.8.tar.gz", hash = "sha256:4b23242b5224c7eaea481ce6c6dbc210f0eafb447cf60211633980947cd23de4", size = 3936600, upload-time = "2026-03-03T21:35:22.386Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/6c/8ef256575242d5f3869c5a445ffd4363b91a89acb34a3e043bec2ad5a1be/uv-0.10.8-py3-none-linux_armv6l.whl", hash = "sha256:d214c82c7c14dd23f9aeb609d03070b8ea2b2f0cf249c9321cbbb5375a17e5df", size = 22461003, upload-time = "2026-03-03T21:35:20.093Z" }, + { url = "https://files.pythonhosted.org/packages/c9/fb/fd0656a92e6b9c4f92ddba7dcd76bd87469be500755125e06fea853dc212/uv-0.10.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d1315c3901c5859aec2c5b4a17da4c5410d17f6890890f9f1a31f25aa0fa9ace", size = 21549446, upload-time = "2026-03-03T21:35:58.203Z" }, + { url = "https://files.pythonhosted.org/packages/64/b9/1a4105df3afe7af99791f5b00fb037d85b2e3aaa1227e95878538d51ecf3/uv-0.10.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a253e5d2cae9e02654de31918b610dfc8f1f16a33f34046603757820bc45ee1b", size = 20222180, upload-time = "2026-03-03T21:35:46.984Z" }, + { url = "https://files.pythonhosted.org/packages/c5/72/6e98e0f8b3fe80cb881c36492dca6d932fbb05f956dfdccbdb8ebe4ceff4/uv-0.10.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:57a24e15fd9dd4a36bcec2ccbe4b26d2a172c109e954a8940f5e8a8b965dae74", size = 22064813, upload-time = "2026-03-03T21:35:17.108Z" }, + { url = "https://files.pythonhosted.org/packages/71/b6/737da8577f4b1799f7024f6cd98fffcac77076a1b078b277cffc84946e96/uv-0.10.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:675dc659195f9b9811ef5534eb3f16459fc88e109aefacbc91c07751b5b9715a", size = 22064861, upload-time = "2026-03-03T21:35:25.067Z" }, + { url = "https://files.pythonhosted.org/packages/7e/21/464ee3cd81f44345953cb26dd49870811f7647f3074f7651775cadb2158b/uv-0.10.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:18d2968b0a50111c2fc6b782f7c63ded4f461c44efab537f552cf565f9aaae25", size = 22054515, upload-time = "2026-03-03T21:35:44.572Z" }, + { url = "https://files.pythonhosted.org/packages/11/2c/1c592d7b843ffa999502116b0dc573732b40cb37061a4acc741dcdb181da/uv-0.10.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ed3c7ebb6f757cddedb56dec3d7c745e5ea7310b11e12ae1c28f1e8172e7bbf", size = 23433992, upload-time = "2026-03-03T21:35:36.886Z" }, + { url = "https://files.pythonhosted.org/packages/f1/e2/2b716f0613746138294598668bbe65295a8da3d8fa104a756dec6284bf3c/uv-0.10.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ffaf115501e33be0d4f13cb5b7c2b46b031d4c679a6109e24a7edfb719c44c6c", size = 24257250, upload-time = "2026-03-03T21:35:49.954Z" }, + { url = "https://files.pythonhosted.org/packages/3e/4d/0165e82cd1117cd6f8a7d9a2122c23cc091f7cf738aa4a2a54579420a08f/uv-0.10.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0209ee8cb573e113ff4a760360f28448f9ebcdcf9c91ca49e872821de5d2d054", size = 23338918, upload-time = "2026-03-03T21:35:33.795Z" }, + { url = "https://files.pythonhosted.org/packages/20/74/652129a25145732482bb0020602507f52d9a5ca0e1a40ddd6deb27402333/uv-0.10.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11dc790f732dc5fee61f0f6bd998fc2e9c200df1082245604ac091c32c23a523", size = 23259370, upload-time = "2026-03-03T21:35:39.478Z" }, + { url = "https://files.pythonhosted.org/packages/19/c5/6e5923d6c9e3b50dc8542647bea692b7c227a9489f59ddff4fdfb20d8459/uv-0.10.8-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:e26f8c35684face38db814d452dd1a2181152dbf7f7b2de1f547e6ba0c378d67", size = 22174747, upload-time = "2026-03-03T21:35:42.081Z" }, + { url = "https://files.pythonhosted.org/packages/92/cd/eee9e1883888327d07f51e7595ed5952e0bca2dc79d1c03b8a6e4309553e/uv-0.10.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:385add107d40c43dc00ca8c1a21ecf43101f846f8339eb7026bf6c9f6df7760d", size = 22893359, upload-time = "2026-03-03T21:35:30.802Z" }, + { url = "https://files.pythonhosted.org/packages/bf/36/407a22917e55ce5cc2e7af956e3b9d91648a96558858acef84e3c50d5ca8/uv-0.10.8-py3-none-musllinux_1_1_i686.whl", hash = "sha256:24e8eb28c4f05acb38e60fefe2a2b15f4283a3849ce580bf2a62aca0a13123b3", size = 22637451, upload-time = "2026-03-03T21:35:55.677Z" }, + { url = "https://files.pythonhosted.org/packages/21/d5/dabef9914e1ff27ad95e4b1daf59cd97c80e26a44c04c2870bcca7c83fc0/uv-0.10.8-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:73a8c1a1fceac73cd983dcc0a64f4f94f5fd1e5428681a5a76132574264504fb", size = 23480991, upload-time = "2026-03-03T21:35:52.809Z" }, + { url = "https://files.pythonhosted.org/packages/2f/c0/1a4a45a9246f087e9446d0d804a436f6ee0befeaef731b04d1b2802d9d8f/uv-0.10.8-py3-none-win32.whl", hash = "sha256:9f344fdb34938ce35e9211a1b866adfa0c7f043967652ed1431917514aeec062", size = 21579030, upload-time = "2026-03-03T21:35:28.176Z" }, + { url = "https://files.pythonhosted.org/packages/a4/2b/b29510efa1e6f409db105dbdafbd942ca3a2b638bef682ff2e5b9f6e4021/uv-0.10.8-py3-none-win_amd64.whl", hash = "sha256:1e63015284ed28c2112717256c328513215fb966a57c5870788eac2e8f949f28", size = 23944828, upload-time = "2026-03-03T21:36:00.763Z" }, + { url = "https://files.pythonhosted.org/packages/3f/9e/b5a11b0523171c0103c4fed54da76685a765ad4d3215e8220facfd24aed9/uv-0.10.8-py3-none-win_arm64.whl", hash = "sha256:a80284f46b6f2e0b3d03eb7c2d43e17139a4ec313e8b9f56a71efafc996804cb", size = 22322224, upload-time = "2026-03-03T21:35:14.148Z" }, ] [[package]]