From a60f5274b267160b2ea247530b34ffe6370daae1 Mon Sep 17 00:00:00 2001 From: Rene Luria Date: Mon, 11 Aug 2025 14:49:17 +0200 Subject: [PATCH] feat: Add Ruff linting, pre-commit hooks, and build scripts - Configure Ruff for linting and formatting with pre-commit hooks - Add Makefile with convenient commands for development workflow - Create build and upload scripts for Gitea package registry - Update README with documentation for new features - Fix code quality issues identified by Ruff - Add development dependencies (ruff, pre-commit) to pyproject.toml - Update Python version requirement to >=3.9 - Add template for Gitea PyPI configuration - Bump version to 0.3.0 - All tests passing and code properly formatted --- .gitignore | 2 +- .gitlab-ci.yml | 4 +- .pre-commit-config.yaml | 20 ++ GITEA_PYPI_CONFIG_TEMPLATE.txt | 16 ++ Makefile | 48 +++++ README.md | 122 ++++++++++++- UV_USAGE.md | 2 +- paroles_net_scraper/__init__.py | 2 +- paroles_net_scraper/cli.py | 4 +- paroles_net_scraper/paroles_net_scraper.py | 141 +++++++++------ pyproject.toml | 81 ++++++++- scripts/__init__.py | 1 + scripts/build_and_upload.py | 85 +++++++++ scripts/build_and_upload.sh | 68 +++++++ tests/test_paroles_net_scraper.py | 53 ++++-- uv.lock | 201 +++++++++++++++++---- 16 files changed, 725 insertions(+), 125 deletions(-) create mode 100644 .pre-commit-config.yaml create mode 100644 GITEA_PYPI_CONFIG_TEMPLATE.txt create mode 100644 Makefile create mode 100644 scripts/__init__.py create mode 100644 scripts/build_and_upload.py create mode 100755 scripts/build_and_upload.sh diff --git a/.gitignore b/.gitignore index 4c4c952..2a7fede 100644 --- a/.gitignore +++ b/.gitignore @@ -154,4 +154,4 @@ cython_debug/ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ \ No newline at end of file +#.idea/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4b25c49..c34d0a3 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -30,7 +30,7 @@ before_script: - uv sync --dev # Run tests - uv run pytest tests/ -v - + test:python-3.11: extends: .test_base image: python:3.11-slim @@ -70,4 +70,4 @@ deploy-package: only: - tags dependencies: - - build-package \ No newline at end of file + - build-package diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..e5d0a46 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,20 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + + - repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.12.8 + hooks: + # Run the linter. + - id: ruff + args: [--fix, --exit-non-zero-on-fix] + # Run the formatter. + - id: ruff-format diff --git a/GITEA_PYPI_CONFIG_TEMPLATE.txt b/GITEA_PYPI_CONFIG_TEMPLATE.txt new file mode 100644 index 0000000..1e25cf4 --- /dev/null +++ b/GITEA_PYPI_CONFIG_TEMPLATE.txt @@ -0,0 +1,16 @@ +# Gitea PyPI Configuration Template +# Save this as ~/.pypirc and update with your credentials + +[distutils] +index-servers = gitea + +[gitea] +repository = https://gitea.parano.ch/api/packages/herel/pypi +username = herel +password = YOUR_GITEA_PERSONAL_ACCESS_TOKEN + +# To create a personal access token in Gitea: +# 1. Go to your Gitea profile settings +# 2. Click on "Applications" +# 3. Generate a new token with package registry permissions +# 4. Copy the token and paste it above as the password diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2f63c39 --- /dev/null +++ b/Makefile @@ -0,0 +1,48 @@ +# Makefile for building and uploading paroles-net-scraper package + +.PHONY: build upload clean help lint format check test install-dev + +# Default target +help: + @echo "Available targets:" + @echo " build - Build the package" + @echo " upload - Build and upload package to Gitea" + @echo " clean - Clean build artifacts" + @echo " lint - Lint Python code with Ruff" + @echo " format - Format Python code with Ruff" + @echo " check - Check Python code with Ruff" + @echo " test - Run tests" + @echo " install-dev - Install development dependencies" + @echo " help - Show this help message" + +# Build the package +build: + uv build + +# Upload package to Gitea +upload: + ./scripts/build_and_upload.sh + +# Clean build artifacts +clean: + rm -rf dist/ *.egg-info/ + +# Install development dependencies +install-dev: + uv pip install -e .[test] + +# Run tests +test: + uv run pytest tests/ -v + +# Lint Python code with Ruff +lint: + ruff check . --fix + +# Format Python code with Ruff +format: + ruff format . + +# Check Python code with Ruff +check: + ruff check . diff --git a/README.md b/README.md index aa589ec..f6b1656 100644 --- a/README.md +++ b/README.md @@ -67,11 +67,55 @@ The project includes a comprehensive test suite using pytest. To run the tests: pytest tests/ -v ``` -Or if you're using the virtual environment: +Or if you're using uv: ```bash -source .venv/bin/activate -pytest tests/ -v +uv run pytest tests/ -v +``` + +## Code Quality + +This project uses Ruff for linting and formatting, and pre-commit hooks to ensure code quality. + +### Linting and Formatting + +- **Ruff** is used for both linting and formatting +- **pre-commit** hooks are installed to automatically check and format code before committing + +To manually run linting: +```bash +ruff check . +``` + +To automatically fix linting issues: +```bash +ruff check . --fix +``` + +To format code: +```bash +ruff format . +``` + +### Pre-commit Hooks + +Pre-commit hooks are automatically run when you commit changes. To manually run all pre-commit hooks: + +```bash +pre-commit run --all-files +``` + +### Development Dependencies + +To install development dependencies including Ruff and pre-commit: + +```bash +uv pip install -e .[dev] +``` + +Or using Make: +```bash +make install-dev ``` ## CI/CD @@ -88,6 +132,76 @@ To use the GitLab CI pipeline: 2. Ensure your GitLab runner is configured 3. Set up PyPI credentials as CI/CD variables if you want to deploy +## Building and Uploading to Gitea Package Registry + +This project includes scripts to build and upload packages to Gitea's PyPI package registry. + +### Prerequisites + +1. Create a personal access token in Gitea with package registry permissions +2. Configure `~/.pypirc` with your Gitea credentials: + +```ini +[distutils] +index-servers = gitea + +[gitea] +repository = https://gitea.parano.ch/api/packages/herel/pypi +username = herel +password = YOUR_GITEA_PERSONAL_ACCESS_TOKEN +``` + +### Using the Build and Upload Scripts + +#### Option 1: Using the shell script + +```bash +./scripts/build_and_upload.sh +``` + +#### Option 2: Using the Python script + +```bash +uv run scripts/build_and_upload.py +``` + +#### Option 3: Using Make + +```bash +make upload +``` + +#### Option 4: Using the installed command + +After installing the package in development mode: +```bash +uv pip install -e . +build-and-upload +``` + +### Manual Build and Upload + +If you prefer to build and upload manually: + +1. Build the package: + ```bash + uv build + ``` + +2. Upload to Gitea: + ```bash + twine upload --repository gitea dist/* + ``` + +### Installing from Gitea PyPI + +To install a package from Gitea's registry: +```bash +pip install --index-url https://YOUR_GITEA_TOKEN@gitea.parano.ch/api/packages/herel/pypi/simple --no-deps paroles-net-scraper +``` + +Note: Replace `YOUR_GITEA_TOKEN` with your actual personal access token. + ## How it works The package constructs a URL based on the artist name and song title, then scrapes the paroles.net website to extract the lyrics. It uses BeautifulSoup to parse the HTML and extract only the relevant text content while filtering out advertisements and other unwanted content. @@ -102,4 +216,4 @@ This package is for educational purposes only. Please respect the terms of servi - requests - beautifulsoup4 - pytest (for running tests) -- uv (for dependency management and packaging) \ No newline at end of file +- uv (for dependency management and packaging) diff --git a/UV_USAGE.md b/UV_USAGE.md index 5b5dc2d..df10746 100644 --- a/UV_USAGE.md +++ b/UV_USAGE.md @@ -65,4 +65,4 @@ To update dependencies: ```bash uv lock --upgrade -``` \ No newline at end of file +``` diff --git a/paroles_net_scraper/__init__.py b/paroles_net_scraper/__init__.py index 556d408..76b7812 100644 --- a/paroles_net_scraper/__init__.py +++ b/paroles_net_scraper/__init__.py @@ -6,4 +6,4 @@ from .paroles_net_scraper import get_song_lyrics, search_song __version__ = "0.1.0" __author__ = "Rene Luria" -__all__ = ["get_song_lyrics", "search_song"] \ No newline at end of file +__all__ = ["get_song_lyrics", "search_song"] diff --git a/paroles_net_scraper/cli.py b/paroles_net_scraper/cli.py index de70f8c..b574671 100644 --- a/paroles_net_scraper/cli.py +++ b/paroles_net_scraper/cli.py @@ -3,8 +3,8 @@ Command line interface for paroles.net scraper """ -import sys import os +import sys # Add the package directory to the path sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) @@ -12,4 +12,4 @@ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from paroles_net_scraper.paroles_net_scraper import main if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/paroles_net_scraper/paroles_net_scraper.py b/paroles_net_scraper/paroles_net_scraper.py index 2614a91..012186d 100644 --- a/paroles_net_scraper/paroles_net_scraper.py +++ b/paroles_net_scraper/paroles_net_scraper.py @@ -3,83 +3,95 @@ Module to fetch song lyrics from paroles.net """ -import requests -from bs4 import BeautifulSoup import argparse import re +import requests +from bs4 import BeautifulSoup + def get_song_lyrics(artist, song_title): """ Fetch song lyrics from paroles.net - + Args: artist (str): Name of the artist song_title (str): Title of the song - + Returns: str: Song lyrics or error message """ # Format the URL # Convert artist and song to lowercase and replace spaces with hyphens - formatted_artist = artist.lower().replace(' ', '-').replace('$', 's').replace('&', 'and') - formatted_song = song_title.lower().replace(' ', '-').replace('\'', '').replace('"', '') - + formatted_artist = ( + artist.lower().replace(" ", "-").replace("$", "s").replace("&", "and") + ) + formatted_song = ( + song_title.lower().replace(" ", "-").replace("'", "").replace('"', "") + ) + url = f"https://www.paroles.net/{formatted_artist}/paroles-{formatted_song}" - + try: # Set headers to mimic a browser request headers = { - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' + "User-Agent": ( + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " + "(KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" + ) } - + # Send GET request response = requests.get(url, headers=headers) response.raise_for_status() # Raise exception for bad status codes - + # Parse HTML content - soup = BeautifulSoup(response.content, 'html.parser') - + soup = BeautifulSoup(response.content, "html.parser") + # Find the lyrics container # Looking for the div with class 'song-text' - lyrics_div = soup.find('div', class_='song-text') - + lyrics_div = soup.find("div", class_="song-text") + if not lyrics_div: return "Lyrics not found on the page" - + # Extract text content # Get all text from the div but preserve line breaks lyrics_parts = [] for element in lyrics_div.descendants: - if element.name == 'br': - lyrics_parts.append('\n') - elif element.string and element.string.strip(): - # Skip the heading that repeats the song info - if 'Paroles de la chanson' not in element.string: - lyrics_parts.append(element.string) - + if element.name == "br": + lyrics_parts.append("\n") + elif ( + element.string + and element.string.strip() + and "Paroles de la chanson" not in element.string + ): + lyrics_parts.append(element.string) + # Join the parts and clean up - lyrics = ''.join(lyrics_parts).strip() - + lyrics = "".join(lyrics_parts).strip() + # Clean up extra whitespace while preserving verse structure - lines = lyrics.split('\n') + lines = lyrics.split("\n") cleaned_lines = [] for line in lines: stripped_line = line.strip() # Skip empty lines and ad content - if stripped_line and not re.match(r'^(Content_\d+|.*Advertisement.*|\d+\s*)$', stripped_line): + if stripped_line and not re.match( + r"^(Content_\d+|.*Advertisement.*|\d+\s*)$", stripped_line + ): # Also remove inline ad markers - cleaned_line = re.sub(r'^Content_\d+\s*', '', stripped_line) + cleaned_line = re.sub(r"^Content_\d+\s*", "", stripped_line) if cleaned_line: # Only add non-empty lines cleaned_lines.append(cleaned_line) - - lyrics = '\n'.join(cleaned_lines).strip() - + + lyrics = "\n".join(cleaned_lines).strip() + if not lyrics: return "Could not extract lyrics from the page" - + return lyrics - + except requests.exceptions.RequestException as e: return f"Error fetching lyrics: {str(e)}" except Exception as e: @@ -89,48 +101,65 @@ def get_song_lyrics(artist, song_title): def search_song(artist, song_title): """ Search for a song on paroles.net and return the first result - + Args: artist (str): Name of the artist song_title (str): Title of the song - + Returns: str: URL of the first search result or error message """ # Format search URL search_query = f"{artist} {song_title}" - search_url = f"https://www.paroles.net/recherche?q={requests.utils.quote(search_query)}" - + search_url = ( + f"https://www.paroles.net/recherche?q={requests.utils.quote(search_query)}" + ) + try: headers = { - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' + "User-Agent": ( + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " + "(KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" + ) } - + response = requests.get(search_url, headers=headers) response.raise_for_status() - - soup = BeautifulSoup(response.content, 'html.parser') - + + soup = BeautifulSoup(response.content, "html.parser") + # Find the first search result link - first_result = soup.find('a', href=lambda x: x and '/paroles-' in x) - + first_result = soup.find("a", href=lambda x: x and "/paroles-" in x) + if first_result: return f"https://www.paroles.net{first_result['href']}" else: return "No search results found" - + except requests.exceptions.RequestException as e: return f"Error searching for song: {str(e)}" def main(): - parser = argparse.ArgumentParser(description='Fetch song lyrics from paroles.net') - parser.add_argument('query', help='Artist and song in format "ARTIST - SONG TITLE" or separate artist and song arguments') - parser.add_argument('song', nargs='?', help='Song title (optional if using ARTIST - SONG format)') - parser.add_argument('--search', action='store_true', help='Use search functionality instead of direct URL construction') - + parser = argparse.ArgumentParser(description="Fetch song lyrics from paroles.net") + parser.add_argument( + "query", + help=( + "Artist and song in format 'ARTIST - SONG TITLE' or " + "separate artist and song arguments" + ), + ) + parser.add_argument( + "song", nargs="?", help="Song title (optional if using ARTIST - SONG format)" + ) + parser.add_argument( + "--search", + action="store_true", + help="Use search functionality instead of direct URL construction", + ) + args = parser.parse_args() - + # Handle both input formats: # 1. Single argument: "ARTIST - SONG TITLE" # 2. Two arguments: ARTIST SONG_TITLE @@ -139,17 +168,19 @@ def main(): if " - " in args.query: artist, song = args.query.split(" - ", 1) else: - print("Error: Please provide artist and song in format 'ARTIST - SONG TITLE'") + print( + "Error: Please provide artist and song in format 'ARTIST - SONG TITLE'" + ) return else: # Two argument format: artist and song provided separately artist = args.query song = args.song - + # Strip any leading/trailing whitespace artist = artist.strip() song = song.strip() - + if args.search: # First search for the song to get the correct URL search_result = search_song(artist, song) @@ -167,4 +198,4 @@ def main(): if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/pyproject.toml b/pyproject.toml index 6e75010..dcdffd7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,9 +1,9 @@ [project] name = "paroles-net-scraper" -version = "0.2.0" +version = "0.3.0" description = "A Python package to fetch song lyrics from paroles.net" authors = [ - {name = "Rene Luria", email = "rene.luria@infomaniak.com"} + {name = "Rene Luria", email = "rene@luria.ch"} ] readme = "README.md" license = {text = "MIT"} @@ -11,19 +11,24 @@ dependencies = [ "requests>=2.25.1", "beautifulsoup4>=4.9.3" ] -requires-python = ">=3.7" +requires-python = ">=3.9" [project.optional-dependencies] test = [ "pytest>=6.0" ] +dev = [ + "ruff>=0.12.8", + "pre-commit>=4.3.0" +] [project.scripts] paroles-scraper = "paroles_net_scraper.cli:main" +build-and-upload = "scripts.build_and_upload:main" [project.urls] -Homepage = "https://github.com/yourusername/paroles-net-scraper" -Repository = "https://github.com/yourusername/paroles-net-scraper" +Homepage = "https://gitea.parano.ch/herel/paroles-net-scraper" +Repository = "https://gitea.parano.ch/herel/paroles-net-scraper" [build-system] requires = ["setuptools>=45", "wheel"] @@ -31,7 +36,69 @@ build-backend = "setuptools.build_meta" [tool.setuptools.packages.find] where = ["."] -include = ["paroles_net_scraper*"] +include = ["paroles_net_scraper*", "scripts*"] [tool.setuptools.package-data] -paroles_net_scraper = ["py.typed"] \ No newline at end of file +paroles_net_scraper = ["py.typed"] + +[tool.ruff] +# Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default. +lint.select = ["E", "F", "I", "UP", "B", "SIM", "PL"] +lint.extend-ignore = [ + "D203", # one-blank-line-before-class (incompatible with D211) + "D212", # multi-line-summary-first-line (incompatible with D213) +] + +# Allow autofix for all enabled rules (when `--fix`) is provided. +lint.fixable = ["ALL"] +lint.unfixable = [] + +# Exclude a variety of commonly ignored directories. +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".hg", + ".mypy_cache", + ".nox", + ".pants.d", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "venv", +] + +# Same as Black. +line-length = 88 + +# Allow unused variables when underscore-prefixed. +lint.dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +# Assume Python 3.7 +target-version = "py37" + +[tool.ruff.lint.mccabe] +# Unlike Flake8, default to a complexity level of 10. +max-complexity = 10 + +[tool.ruff.format] +# Like Black, use double quotes for strings. +quote-style = "double" + +# Like Black, indent with spaces, rather than tabs. +indent-style = "space" + +# Like Black, respect magic trailing commas. +skip-magic-trailing-comma = false + +# Like Black, automatically detect the appropriate line ending. +line-ending = "auto" diff --git a/scripts/__init__.py b/scripts/__init__.py new file mode 100644 index 0000000..d085fe4 --- /dev/null +++ b/scripts/__init__.py @@ -0,0 +1 @@ +# This file makes the scripts directory a Python package diff --git a/scripts/build_and_upload.py b/scripts/build_and_upload.py new file mode 100644 index 0000000..dd49142 --- /dev/null +++ b/scripts/build_and_upload.py @@ -0,0 +1,85 @@ +""" +Build and upload script for paroles-net-scraper package +""" + +import importlib.util +import os +import shutil +import subprocess +import sys +from pathlib import Path + + +def run_command(cmd): + """Run a command and handle errors.""" + print(f"Running: {' '.join(cmd)}") + try: + subprocess.run(cmd, check=True) + except subprocess.CalledProcessError as e: + print(f"Error running command: {e}") + sys.exit(1) + + +def main(): + """Main entry point for the build and upload script.""" + # Get project root directory + project_root = Path(__file__).parent.parent.absolute() + os.chdir(project_root) + + # Check if we're in the right directory + if not (project_root / "pyproject.toml").exists(): + print("Error: pyproject.toml not found.") + sys.exit(1) + + # Install twine if not already installed + if importlib.util.find_spec("twine") is None: + print("Installing twine...") + run_command(["uv", "pip", "install", "twine"]) + + # Clean previous builds + print("Cleaning previous builds...") + dist_dir = project_root / "dist" + if dist_dir.exists(): + shutil.rmtree(dist_dir) + + # Build the package + print("Building package...") + run_command(["uv", "build"]) + + # Check if build was successful + if not dist_dir.exists() or not any(dist_dir.iterdir()): + print("Error: Build failed or no files created in dist/") + sys.exit(1) + + print("Build successful. Files created:") + for f in dist_dir.iterdir(): + print(f" {f.name}") + + # Check if .pypirc exists + pypirc_path = Path.home() / ".pypirc" + if not pypirc_path.exists(): + print( + "Warning: ~/.pypirc not found. Please create it with your " + "Gitea credentials:" + ) + print("") + print("[distutils]") + print("index-servers = gitea") + print("") + print("[gitea]") + print("repository = https://gitea.parano.ch/api/packages/herel/pypi") + print("username = herel") + print("password = YOUR_GITEA_TOKEN") + print("") + print("Replace YOUR_GITEA_TOKEN with a personal access token from Gitea.") + sys.exit(1) + + # Upload to Gitea PyPI registry + print("Uploading to Gitea PyPI registry...") + run_command(["twine", "upload", "--repository", "gitea", "dist/*"]) + + print("Package uploaded successfully to Gitea!") + + +if __name__ == "__main__": + main() diff --git a/scripts/build_and_upload.sh b/scripts/build_and_upload.sh new file mode 100755 index 0000000..1df6aee --- /dev/null +++ b/scripts/build_and_upload.sh @@ -0,0 +1,68 @@ +#!/bin/bash + +# Script to build and upload Python package to Gitea PyPI registry +# Usage: ./scripts/build_and_upload.sh + +set -e # Exit on any error + +echo "=== Building and Uploading Package to Gitea ===" + +# Create scripts directory if it doesn't exist +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" +cd "$PROJECT_ROOT" + +# Check if we're in the right directory +if [[ ! -f "pyproject.toml" ]]; then + echo "Error: pyproject.toml not found. Please run this script from the project root." + exit 1 +fi + +# Install twine if not already installed +if ! command -v twine &> /dev/null; then + echo "Installing twine..." + uv pip install twine +fi + +# Clean previous builds +echo "Cleaning previous builds..." +rm -rf dist/ + +# Build the package +echo "Building package..." +uv build + +# Check if build was successful +if [[ ! -d "dist" ]] || [[ -z "$(ls -A dist)" ]]; then + echo "Error: Build failed or no files created in dist/" + exit 1 +fi + +echo "Build successful. Files created:" +ls -la dist/ + +# Upload to Gitea PyPI registry +echo "Uploading to Gitea PyPI registry..." +echo "Note: You need to have ~/.pypirc configured with Gitea credentials" +echo "See: https://docs.gitea.com/usage/packages/pypi" + +# Check if .pypirc exists +if [[ ! -f "$HOME/.pypirc" ]]; then + echo "Warning: ~/.pypirc not found. Please create it with your Gitea credentials:" + echo "" + echo "[distutils]" + echo "index-servers = gitea" + echo "" + echo "[gitea]" + echo "repository = https://gitea.parano.ch/api/packages/herel/pypi" + echo "username = herel" + echo "password = YOUR_GITEA_TOKEN" + echo "" + echo "Replace YOUR_GITEA_TOKEN with a personal access token from Gitea." + exit 1 +fi + +# Upload using twine +twine upload --repository gitea dist/* + +echo "Package uploaded successfully to Gitea!" diff --git a/tests/test_paroles_net_scraper.py b/tests/test_paroles_net_scraper.py index cf153b8..670c31c 100644 --- a/tests/test_paroles_net_scraper.py +++ b/tests/test_paroles_net_scraper.py @@ -2,13 +2,14 @@ Test suite for paroles_net_scraper package """ -import sys import os +import sys +from unittest.mock import Mock, patch + import pytest -from unittest.mock import patch, Mock # Add the parent directory to the path so we can import the scraper -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) # Import from the package from paroles_net_scraper import get_song_lyrics @@ -31,14 +32,17 @@ def test_get_song_lyrics_success(): """ - + # Mock response object mock_response = Mock() mock_response.content = mock_html mock_response.raise_for_status.return_value = None - + # Mock BeautifulSoup parsing - with patch('paroles_net_scraper.paroles_net_scraper.requests.get', return_value=mock_response): + with patch( + "paroles_net_scraper.paroles_net_scraper.requests.get", + return_value=mock_response, + ): lyrics = get_song_lyrics("Test Artist", "Test Song") assert "This is the first line of the song" in lyrics assert "This is the second line of the song" in lyrics @@ -59,20 +63,26 @@ def test_get_song_lyrics_not_found(): """ - + # Mock response object mock_response = Mock() mock_response.content = mock_html mock_response.raise_for_status.return_value = None - - with patch('paroles_net_scraper.paroles_net_scraper.requests.get', return_value=mock_response): + + with patch( + "paroles_net_scraper.paroles_net_scraper.requests.get", + return_value=mock_response, + ): lyrics = get_song_lyrics("Non Existent", "Non Existent Song") assert lyrics == "Lyrics not found on the page" def test_get_song_lyrics_request_exception(): """Test handling of request exceptions""" - with patch('paroles_net_scraper.paroles_net_scraper.requests.get', side_effect=Exception("Network error")): + with patch( + "paroles_net_scraper.paroles_net_scraper.requests.get", + side_effect=Exception("Network error"), + ): lyrics = get_song_lyrics("Test Artist", "Test Song") assert "Error parsing lyrics" in lyrics @@ -81,19 +91,28 @@ def test_url_formatting(): """Test URL formatting with special characters""" # This test will check that the URL is properly formatted # We'll test this by checking the requests.get call arguments - + mock_response = Mock() mock_response.content = "
" mock_response.raise_for_status.return_value = None - - with patch('paroles_net_scraper.paroles_net_scraper.requests.get', return_value=mock_response) as mock_get: + + with patch( + "paroles_net_scraper.paroles_net_scraper.requests.get", + return_value=mock_response, + ) as mock_get: # Test with artist and song containing spaces and special characters get_song_lyrics("Ed Sheeran", "Shape of You") expected_url = "https://www.paroles.net/ed-sheeran/paroles-shape-of-you" - mock_get.assert_called_once_with(expected_url, headers={ - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' - }) + mock_get.assert_called_once_with( + expected_url, + headers={ + "User-Agent": ( + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " + "(KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" + ) + }, + ) if __name__ == "__main__": - pytest.main([__file__]) \ No newline at end of file + pytest.main([__file__]) diff --git a/uv.lock b/uv.lock index ec68de4..cac9f50 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -requires-python = ">=3.7" +requires-python = ">=3.9" [[package]] name = "beautifulsoup4" @@ -23,6 +23,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216 }, ] +[[package]] +name = "cfgv" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249 }, +] + [[package]] name = "charset-normalizer" version = "3.4.3" @@ -84,17 +93,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/72/2a/aff5dd112b2f14bcc3462c312dce5445806bfc8ab3a7328555da95330e4b/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", size = 152224 }, { url = "https://files.pythonhosted.org/packages/b7/8c/9839225320046ed279c6e839d51f028342eb77c91c89b8ef2549f951f3ec/charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", size = 100086 }, { url = "https://files.pythonhosted.org/packages/ee/7a/36fbcf646e41f710ce0a563c1c9a343c6edf9be80786edeb15b6f62e17db/charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", size = 107400 }, - { url = "https://files.pythonhosted.org/packages/22/82/63a45bfc36f73efe46731a3a71cb84e2112f7e0b049507025ce477f0f052/charset_normalizer-3.4.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0f2be7e0cf7754b9a30eb01f4295cc3d4358a479843b31f328afd210e2c7598c", size = 198805 }, - { url = "https://files.pythonhosted.org/packages/0c/52/8b0c6c3e53f7e546a5e49b9edb876f379725914e1130297f3b423c7b71c5/charset_normalizer-3.4.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c60e092517a73c632ec38e290eba714e9627abe9d301c8c8a12ec32c314a2a4b", size = 142862 }, - { url = "https://files.pythonhosted.org/packages/59/c0/a74f3bd167d311365e7973990243f32c35e7a94e45103125275b9e6c479f/charset_normalizer-3.4.3-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:252098c8c7a873e17dd696ed98bbe91dbacd571da4b87df3736768efa7a792e4", size = 155104 }, - { url = "https://files.pythonhosted.org/packages/1a/79/ae516e678d6e32df2e7e740a7be51dc80b700e2697cb70054a0f1ac2c955/charset_normalizer-3.4.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3653fad4fe3ed447a596ae8638b437f827234f01a8cd801842e43f3d0a6b281b", size = 152598 }, - { url = "https://files.pythonhosted.org/packages/00/bd/ef9c88464b126fa176f4ef4a317ad9b6f4d30b2cffbc43386062367c3e2c/charset_normalizer-3.4.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8999f965f922ae054125286faf9f11bc6932184b93011d138925a1773830bbe9", size = 147391 }, - { url = "https://files.pythonhosted.org/packages/7a/03/cbb6fac9d3e57f7e07ce062712ee80d80a5ab46614684078461917426279/charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d95bfb53c211b57198bb91c46dd5a2d8018b3af446583aab40074bf7988401cb", size = 145037 }, - { url = "https://files.pythonhosted.org/packages/64/d1/f9d141c893ef5d4243bc75c130e95af8fd4bc355beff06e9b1e941daad6e/charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:5b413b0b1bfd94dbf4023ad6945889f374cd24e3f62de58d6bb102c4d9ae534a", size = 156425 }, - { url = "https://files.pythonhosted.org/packages/c5/35/9c99739250742375167bc1b1319cd1cec2bf67438a70d84b2e1ec4c9daa3/charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:b5e3b2d152e74e100a9e9573837aba24aab611d39428ded46f4e4022ea7d1942", size = 153734 }, - { url = "https://files.pythonhosted.org/packages/50/10/c117806094d2c956ba88958dab680574019abc0c02bcf57b32287afca544/charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a2d08ac246bb48479170408d6c19f6385fa743e7157d716e144cad849b2dd94b", size = 148551 }, - { url = "https://files.pythonhosted.org/packages/61/c5/dc3ba772489c453621ffc27e8978a98fe7e41a93e787e5e5bde797f1dddb/charset_normalizer-3.4.3-cp38-cp38-win32.whl", hash = "sha256:ec557499516fc90fd374bf2e32349a2887a876fbf162c160e3c01b6849eaf557", size = 98459 }, - { url = "https://files.pythonhosted.org/packages/05/35/bb59b1cd012d7196fc81c2f5879113971efc226a63812c9cf7f89fe97c40/charset_normalizer-3.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:5d8d01eac18c423815ed4f4a2ec3b439d654e55ee4ad610e153cf02faf67ea40", size = 105887 }, { url = "https://files.pythonhosted.org/packages/c2/ca/9a0983dd5c8e9733565cf3db4df2b0a2e9a82659fd8aa2a868ac6e4a991f/charset_normalizer-3.4.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05", size = 207520 }, { url = "https://files.pythonhosted.org/packages/39/c6/99271dc37243a4f925b09090493fb96c9333d7992c6187f5cfe5312008d2/charset_normalizer-3.4.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e", size = 147307 }, { url = "https://files.pythonhosted.org/packages/e4/69/132eab043356bba06eb333cc2cc60c6340857d0a2e4ca6dc2b51312886b3/charset_normalizer-3.4.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99", size = 160448 }, @@ -118,6 +116,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, ] +[[package]] +name = "distlib" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047 }, +] + [[package]] name = "exceptiongroup" version = "1.3.0" @@ -130,6 +137,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674 }, ] +[[package]] +name = "filelock" +version = "3.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215 }, +] + +[[package]] +name = "identify" +version = "2.6.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/ca/ffbabe3635bb839aa36b3a893c91a9b0d368cb4d8073e03a12896970af82/identify-2.6.13.tar.gz", hash = "sha256:da8d6c828e773620e13bfa86ea601c5a5310ba4bcd65edf378198b56a1f9fb32", size = 99243 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/ce/461b60a3ee109518c055953729bf9ed089a04db895d47e95444071dcdef2/identify-2.6.13-py2.py3-none-any.whl", hash = "sha256:60381139b3ae39447482ecc406944190f690d4a2997f2584062089848361b33b", size = 99153 }, +] + [[package]] name = "idna" version = "3.10" @@ -139,19 +164,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, ] -[[package]] -name = "importlib-metadata" -version = "6.7.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.8'" }, - { name = "zipp" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a3/82/f6e29c8d5c098b6be61460371c2c5591f4a335923639edec43b3830650a4/importlib_metadata-6.7.0.tar.gz", hash = "sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4", size = 53569 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ff/94/64287b38c7de4c90683630338cf28f129decbba0a44f0c6db35a873c73c4/importlib_metadata-6.7.0-py3-none-any.whl", hash = "sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5", size = 22934 }, -] - [[package]] name = "iniconfig" version = "2.0.0" @@ -161,6 +173,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, ] +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, +] + [[package]] name = "packaging" version = "24.0" @@ -172,7 +193,7 @@ wheels = [ [[package]] name = "paroles-net-scraper" -version = "0.1.0" +version = "0.2.0" source = { editable = "." } dependencies = [ { name = "beautifulsoup4" }, @@ -180,6 +201,10 @@ dependencies = [ ] [package.optional-dependencies] +dev = [ + { name = "pre-commit" }, + { name = "ruff" }, +] test = [ { name = "pytest" }, ] @@ -187,22 +212,46 @@ test = [ [package.metadata] requires-dist = [ { name = "beautifulsoup4", specifier = ">=4.9.3" }, + { name = "pre-commit", marker = "extra == 'dev'", specifier = ">=4.3.0" }, { name = "pytest", marker = "extra == 'test'", specifier = ">=6.0" }, { name = "requests", specifier = ">=2.25.1" }, + { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.12.8" }, +] + +[[package]] +name = "platformdirs" +version = "4.3.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567 }, ] [[package]] name = "pluggy" version = "1.2.0" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "importlib-metadata", marker = "python_full_version < '3.8'" }, -] sdist = { url = "https://files.pythonhosted.org/packages/8a/42/8f2833655a29c4e9cb52ee8a2be04ceac61bcff4a680fb338cbd3d1e322d/pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3", size = 61613 } wheels = [ { url = "https://files.pythonhosted.org/packages/51/32/4a79112b8b87b21450b066e102d6608907f4c885ed7b04c3fdb085d4d6ae/pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849", size = 17695 }, ] +[[package]] +name = "pre-commit" +version = "4.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ff/29/7cf5bbc236333876e4b41f56e06857a87937ce4bf91e117a6991a2dbb02a/pre_commit-4.3.0.tar.gz", hash = "sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16", size = 193792 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl", hash = "sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8", size = 220965 }, +] + [[package]] name = "pytest" version = "7.4.4" @@ -210,7 +259,6 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, - { name = "importlib-metadata", marker = "python_full_version < '3.8'" }, { name = "iniconfig" }, { name = "packaging" }, { name = "pluggy" }, @@ -221,6 +269,59 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/51/ff/f6e8b8f39e08547faece4bd80f89d5a8de68a38b2d179cc1c4490ffa3286/pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8", size = 325287 }, ] +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199 }, + { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758 }, + { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463 }, + { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280 }, + { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239 }, + { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802 }, + { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527 }, + { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052 }, + { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774 }, + { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 }, + { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 }, + { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 }, + { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 }, + { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 }, + { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 }, + { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 }, + { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 }, + { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 }, + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, + { url = "https://files.pythonhosted.org/packages/65/d8/b7a1db13636d7fb7d4ff431593c510c8b8fca920ade06ca8ef20015493c5/PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", size = 184777 }, + { url = "https://files.pythonhosted.org/packages/0a/02/6ec546cd45143fdf9840b2c6be8d875116a64076218b61d68e12548e5839/PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", size = 172318 }, + { url = "https://files.pythonhosted.org/packages/0e/9a/8cc68be846c972bda34f6c2a93abb644fb2476f4dcc924d52175786932c9/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", size = 720891 }, + { url = "https://files.pythonhosted.org/packages/e9/6c/6e1b7f40181bc4805e2e07f4abc10a88ce4648e7e95ff1abe4ae4014a9b2/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", size = 722614 }, + { url = "https://files.pythonhosted.org/packages/3d/32/e7bd8535d22ea2874cef6a81021ba019474ace0d13a4819c2a4bce79bd6a/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", size = 737360 }, + { url = "https://files.pythonhosted.org/packages/d7/12/7322c1e30b9be969670b672573d45479edef72c9a0deac3bb2868f5d7469/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", size = 699006 }, + { url = "https://files.pythonhosted.org/packages/82/72/04fcad41ca56491995076630c3ec1e834be241664c0c09a64c9a2589b507/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", size = 723577 }, + { url = "https://files.pythonhosted.org/packages/ed/5e/46168b1f2757f1fcd442bc3029cd8767d88a98c9c05770d8b420948743bb/PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", size = 144593 }, + { url = "https://files.pythonhosted.org/packages/19/87/5124b1c1f2412bb95c59ec481eaf936cd32f0fe2a7b16b97b81c4c017a6a/PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", size = 162312 }, +] + [[package]] name = "requests" version = "2.31.0" @@ -236,6 +337,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/70/8e/0e2d847013cb52cd35b38c009bb167a1a26b2ce6cd6965bf26b47bc0bf44/requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", size = 62574 }, ] +[[package]] +name = "ruff" +version = "0.12.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/da/5bd7565be729e86e1442dad2c9a364ceeff82227c2dece7c29697a9795eb/ruff-0.12.8.tar.gz", hash = "sha256:4cb3a45525176e1009b2b64126acf5f9444ea59066262791febf55e40493a033", size = 5242373 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/1e/c843bfa8ad1114fab3eb2b78235dda76acd66384c663a4e0415ecc13aa1e/ruff-0.12.8-py3-none-linux_armv6l.whl", hash = "sha256:63cb5a5e933fc913e5823a0dfdc3c99add73f52d139d6cd5cc8639d0e0465513", size = 11675315 }, + { url = "https://files.pythonhosted.org/packages/24/ee/af6e5c2a8ca3a81676d5480a1025494fd104b8896266502bb4de2a0e8388/ruff-0.12.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9a9bbe28f9f551accf84a24c366c1aa8774d6748438b47174f8e8565ab9dedbc", size = 12456653 }, + { url = "https://files.pythonhosted.org/packages/99/9d/e91f84dfe3866fa648c10512904991ecc326fd0b66578b324ee6ecb8f725/ruff-0.12.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2fae54e752a3150f7ee0e09bce2e133caf10ce9d971510a9b925392dc98d2fec", size = 11659690 }, + { url = "https://files.pythonhosted.org/packages/fe/ac/a363d25ec53040408ebdd4efcee929d48547665858ede0505d1d8041b2e5/ruff-0.12.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0acbcf01206df963d9331b5838fb31f3b44fa979ee7fa368b9b9057d89f4a53", size = 11896923 }, + { url = "https://files.pythonhosted.org/packages/58/9f/ea356cd87c395f6ade9bb81365bd909ff60860975ca1bc39f0e59de3da37/ruff-0.12.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ae3e7504666ad4c62f9ac8eedb52a93f9ebdeb34742b8b71cd3cccd24912719f", size = 11477612 }, + { url = "https://files.pythonhosted.org/packages/1a/46/92e8fa3c9dcfd49175225c09053916cb97bb7204f9f899c2f2baca69e450/ruff-0.12.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb82efb5d35d07497813a1c5647867390a7d83304562607f3579602fa3d7d46f", size = 13182745 }, + { url = "https://files.pythonhosted.org/packages/5e/c4/f2176a310f26e6160deaf661ef60db6c3bb62b7a35e57ae28f27a09a7d63/ruff-0.12.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:dbea798fc0065ad0b84a2947b0aff4233f0cb30f226f00a2c5850ca4393de609", size = 14206885 }, + { url = "https://files.pythonhosted.org/packages/87/9d/98e162f3eeeb6689acbedbae5050b4b3220754554526c50c292b611d3a63/ruff-0.12.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:49ebcaccc2bdad86fd51b7864e3d808aad404aab8df33d469b6e65584656263a", size = 13639381 }, + { url = "https://files.pythonhosted.org/packages/81/4e/1b7478b072fcde5161b48f64774d6edd59d6d198e4ba8918d9f4702b8043/ruff-0.12.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ac9c570634b98c71c88cb17badd90f13fc076a472ba6ef1d113d8ed3df109fb", size = 12613271 }, + { url = "https://files.pythonhosted.org/packages/e8/67/0c3c9179a3ad19791ef1b8f7138aa27d4578c78700551c60d9260b2c660d/ruff-0.12.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:560e0cd641e45591a3e42cb50ef61ce07162b9c233786663fdce2d8557d99818", size = 12847783 }, + { url = "https://files.pythonhosted.org/packages/4e/2a/0b6ac3dd045acf8aa229b12c9c17bb35508191b71a14904baf99573a21bd/ruff-0.12.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:71c83121512e7743fba5a8848c261dcc454cafb3ef2934a43f1b7a4eb5a447ea", size = 11702672 }, + { url = "https://files.pythonhosted.org/packages/9d/ee/f9fdc9f341b0430110de8b39a6ee5fa68c5706dc7c0aa940817947d6937e/ruff-0.12.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:de4429ef2ba091ecddedd300f4c3f24bca875d3d8b23340728c3cb0da81072c3", size = 11440626 }, + { url = "https://files.pythonhosted.org/packages/89/fb/b3aa2d482d05f44e4d197d1de5e3863feb13067b22c571b9561085c999dc/ruff-0.12.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a2cab5f60d5b65b50fba39a8950c8746df1627d54ba1197f970763917184b161", size = 12462162 }, + { url = "https://files.pythonhosted.org/packages/18/9f/5c5d93e1d00d854d5013c96e1a92c33b703a0332707a7cdbd0a4880a84fb/ruff-0.12.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:45c32487e14f60b88aad6be9fd5da5093dbefb0e3e1224131cb1d441d7cb7d46", size = 12913212 }, + { url = "https://files.pythonhosted.org/packages/71/13/ab9120add1c0e4604c71bfc2e4ef7d63bebece0cfe617013da289539cef8/ruff-0.12.8-py3-none-win32.whl", hash = "sha256:daf3475060a617fd5bc80638aeaf2f5937f10af3ec44464e280a9d2218e720d3", size = 11694382 }, + { url = "https://files.pythonhosted.org/packages/f6/dc/a2873b7c5001c62f46266685863bee2888caf469d1edac84bf3242074be2/ruff-0.12.8-py3-none-win_amd64.whl", hash = "sha256:7209531f1a1fcfbe8e46bcd7ab30e2f43604d8ba1c49029bb420b103d0b5f76e", size = 12740482 }, + { url = "https://files.pythonhosted.org/packages/cb/5c/799a1efb8b5abab56e8a9f2a0b72d12bd64bb55815e9476c7d0a2887d2f7/ruff-0.12.8-py3-none-win_arm64.whl", hash = "sha256:c90e1a334683ce41b0e7a04f41790c429bf5073b62c1ae701c9dc5b3d14f0749", size = 11884718 }, +] + [[package]] name = "soupsieve" version = "2.4.1" @@ -273,10 +399,15 @@ wheels = [ ] [[package]] -name = "zipp" -version = "3.15.0" +name = "virtualenv" +version = "20.33.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/00/27/f0ac6b846684cecce1ee93d32450c45ab607f65c2e0255f0092032d91f07/zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b", size = 18454 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5b/fa/c9e82bbe1af6266adf08afb563905eb87cab83fde00a0a08963510621047/zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556", size = 6758 }, +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8b/60/4f20960df6c7b363a18a55ab034c8f2bcd5d9770d1f94f9370ec104c1855/virtualenv-20.33.1.tar.gz", hash = "sha256:1b44478d9e261b3fb8baa5e74a0ca3bc0e05f21aa36167bf9cbf850e542765b8", size = 6082160 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/ff/ded57ac5ff40a09e6e198550bab075d780941e0b0f83cbeabd087c59383a/virtualenv-20.33.1-py3-none-any.whl", hash = "sha256:07c19bc66c11acab6a5958b815cbcee30891cd1c2ccf53785a28651a0d8d8a67", size = 6060362 }, ]