diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index a14427f2..6d79bda5 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -19,12 +19,20 @@ jobs: - name: Exit if not on master branch if: github.ref_name != 'master' run: exit -1 + + - name: Install uv + uses: astral-sh/setup-uv@v2 + + - name: Set up Python 3.12 + run: uv python install 3.12 - name: Install build dependencies - run: python -m pip install --upgrade build + run: | + uv venv + uv pip install --upgrade build - name: Build source distribution - run: python -m build + run: uv run python -m build - uses: actions/upload-artifact@v4 with: @@ -43,15 +51,16 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Install uv + uses: astral-sh/setup-uv@v2 + - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} + run: uv python install ${{ matrix.python-version }} - name: Run PyInstaller with Tox run: | - python -m ensurepip --upgrade - python -m pip install tox tox-uv tox-gh-actions + uv venv + uv pip install tox-uv tox-gh-actions tox - name: Rename binary diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 36879ae8..cb1de9c6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,5 +1,6 @@ on: [push, pull_request] + name: CI jobs: test: @@ -11,28 +12,22 @@ jobs: python-version: - '3.11' - '3.12' + env: + # Disable colors and formatting in Rich console output + TERM: dumb steps: - name: Checkout - uses: actions/checkout@v3 -# - name: Cache pip -# uses: actions/cache@v3 -# with: -# path: ~/.cache/pip -# key: v1-pip-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('requirements-*.txt') }} -# restore-keys: | -# v1-pip-${{ runner.os }}-${{ matrix.python-version }} -# v1-pip-${{ runner.os }} -# v1-pip- - - name: Setup Python - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} + uses: actions/checkout@v4 + - name: Install uv + uses: astral-sh/setup-uv@v2 + - name: Set up Python ${{ matrix.python-version }} + run: uv python install ${{ matrix.python-version }} - name: Install mreg-cli run: | - python -m ensurepip --upgrade - pip install -e . + uv venv + uv pip install -e .[dev] - name: Test and compare api calls - run: ci/run_testsuite_and_record.sh + run: uv run ci/run_testsuite_and_record.sh tox: name: tox @@ -45,15 +40,15 @@ jobs: - "3.12" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + - name: Install uv + uses: astral-sh/setup-uv@v2 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} + run: uv python install ${{ matrix.python-version }} - name: Install dependencies run: | - python -m ensurepip --upgrade - python -m pip install tox tox-uv tox-gh-actions + uv venv + uv pip install tox-uv tox-gh-actions - name: Test with tox - run: tox r + run: uv run tox r \ No newline at end of file diff --git a/ci/diff.py b/ci/diff.py index bb13a6f8..2582810b 100644 --- a/ci/diff.py +++ b/ci/diff.py @@ -1,105 +1,339 @@ +from __future__ import annotations + +import argparse import difflib import json import re import sys -from typing import Any, Dict, List +import urllib.parse +from enum import StrEnum +from typing import Any, Generator, Iterable, NamedTuple +from rich import box +from rich.console import Console, Group +from rich.markup import escape +from rich.panel import Panel +from rich.prompt import Prompt -def replace_timestamps(obj: Any) -> Any: - """Recursively replace timestamp values in a JSON object. +console = Console(soft_wrap=True, highlight=False) +"""Stdout console used to print diffs.""" + +err_console = Console(stderr=True, highlight=False) +"""Stderr console used to print messages and errors.""" + +timestamp_pattern = re.compile( + r"\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:[+-]\d{2}:\d{2})?|\d{4}-\d{2}-\d{2}" +) +datetime_str_pattern = re.compile( + r"\b[A-Za-z]{3}\s[A-Za-z]{3}\s+([0-2]?[0-9]|3[0-1])\s([0-1]?[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])\s[0-9]{4}\b" +) +serial_pattern = re.compile(r"\b[sS]erial:\s+\d+") +ipv4_pattern = re.compile(r"((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}") +ipv6_pattern = re.compile(r"\b([0-9a-fA-F]{1,4}::?){1,7}[0-9a-fA-F]{1,4}\b") +mac_pattern = re.compile(r"\b([0-9a-f]{2}:){5}[0-9a-f]{2}\b") + +# Pattern matching strings starting with `"url": "/api/v1/` and ending with `"` +api_v1_pattern = re.compile(r'"url":\s*"/api/v1/.*?"') +# Pattern matching URLs where the final component is a number +# Defines 4 capture groups to be able to replace the number with a placeholder. +# Only matches the number if it is preceded by a `/` or `=` +# Does not match patterns containing `` and `` after `/api/v1/`. +api_v1_pattern_with_number = re.compile( + r'("url":\s*"/api/v1/(?!.*?<(?:IPv6|IPv4)>).*?)([/=])(\d+)(")' +) + + +class DiffError(Exception): + """Base class for diff errors.""" + + +class CommandCountError(DiffError): + """Exception raised when the number of commands in the two files is different.""" + + def __init__(self, expected: int, result: int) -> None: # noqa: D107 + self.expected = expected + self.result = result + super().__init__( + f"Expected {expected} commands, got {result} commands. Diff must be resolved manually." + ) + + +DIFF_COLORS = {"-": "red", "+": "green"} + + +def fmt_line(line: str) -> str: + """Format a single diff line with color.""" + line = escape(line) + if line and (color := DIFF_COLORS.get(line[0], None)): + return f"[{color}]{line}[/]" + return line - :param obj: A JSON object (dict, list, or primitive type). - :returns: A new object with timestamps replaced. - """ - timestamp_pattern = re.compile( - r"\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:[+-]\d{2}:\d{2})?|\d{4}-\d{2}-\d{2}" - ) - if isinstance(obj, dict): - return {k: replace_timestamps(v) for k, v in obj.items()} - elif isinstance(obj, list): - return [replace_timestamps(elem) for elem in obj] - elif isinstance(obj, str): - return timestamp_pattern.sub("