diff --git a/.github/workflows/bandit.yml b/.github/workflows/bandit.yml index 9c44763..635ec0c 100644 --- a/.github/workflows/bandit.yml +++ b/.github/workflows/bandit.yml @@ -46,7 +46,7 @@ jobs: # comma-separated list of paths (glob patterns supported) to exclude from scan (note that these are in addition to the excluded paths provided in the config file) (default: .svn,CVS,.bzr,.hg,.git,__pycache__,.tox,.eggs,*.egg) # excluded_paths: # optional, default is DEFAULT # comma-separated list of test IDs to skip - # skips: # optional, default is DEFAULT + skips: B101 # skip assert_used warnings in test files # path to a .bandit file that supplies command line arguments # ini_path: # optional, default is DEFAULT diff --git a/README.md b/README.md index 80dbbc3..7e4a63a 100644 --- a/README.md +++ b/README.md @@ -168,10 +168,49 @@ This project uses [pytest](https://pytest.org/) for testing. To run the tests: ``` The test suite includes: + - Unit tests for calendar parsing and analysis functions - Mock calendar data to ensure consistent test results - Validation of output format and statistics calculations +### Security Scanning + +This project uses [Bandit](https://bandit.readthedocs.io/) for security vulnerability scanning. Bandit is automatically installed as a development dependency. + +1. Make sure you have the development dependencies installed: + + ```bash + uv sync --group dev + ``` + +2. Run security scan on the main application: + + ```bash + bandit -r calendar_analyzer.py + ``` + +3. Run security scan on test files (skipping assert warnings): + + ```bash + bandit -r tests/ --skip B101 + ``` + +4. Run security scan on all Python files: + + ```bash + bandit -r . --exclude tests/ + ``` + +The project's GitHub Actions workflow automatically runs Bandit security scans on all code. The configuration in `pyproject.toml` skips B101 (assert_used) warnings for test files since assertions are expected and appropriate in tests. + +**Security Features:** + +- No hardcoded secrets or credentials +- Secure temporary file handling +- Input validation for date formats +- Safe file operations with proper error handling +- No external network requests (local processing only) + ### Dependency Management This project uses `uv` for fast and reliable dependency management with `pyproject.toml`. Dependencies are organized into: diff --git a/cspell.json b/cspell.json index 7cedc70..c7d3f60 100644 --- a/cspell.json +++ b/cspell.json @@ -2,16 +2,17 @@ "version": "0.2", "language": "en", "words": [ + "abirismyname", "astral", "calendars", "capsys", "cspell", "datetime", "dateutil", - "dtstart", "DTEND", - "gettz", + "dtstart", "Getchell", + "gettz", "ical", "icalendar", "icbu", @@ -20,13 +21,15 @@ "pylint", "pyproject", "pytest", + "SARIF", "sdist", + "shundor", "sqlite", "sqlitedb", "timezone", "uv", - "venv", "VCALENDAR", + "venv", "VEVENT" ], "ignorePaths": [ diff --git a/pyproject.toml b/pyproject.toml index b35ee15..c2f2c3b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,8 +26,13 @@ dev = [ "isort>=6.0.1", "pytest>=8.2.1", "pytest-cov>=6.2.1", + "bandit>=1.8.5", ] +[tool.bandit] +# Skip assert_used (B101) warnings in test files since assertions are expected in tests +skips = ["B101"] + [build-system] requires = ["hatchling"] build-backend = "hatchling.build" diff --git a/tests/test_calendar_analyzer.py b/tests/test_calendar_analyzer.py index 017adb1..fd03982 100644 --- a/tests/test_calendar_analyzer.py +++ b/tests/test_calendar_analyzer.py @@ -1,16 +1,59 @@ -# test_calendar_analyzer.py +"""Tests for calendar_analyzer module.""" + +# Standard library imports +import os import tempfile import textwrap -import pytest -import os -from datetime import datetime -from unittest.mock import patch +from datetime import datetime, timedelta from pathlib import Path +from unittest.mock import patch + +# Third-party imports +import pytest from dateutil import tz + +# Local imports import calendar_analyzer +def create_temp_ics_file(content, suffix=".ics"): + """Helper function to create a temporary ICS file with specified content. + + Args: + content (str): The ICS content to write to the file + suffix (str): File suffix (default: ".ics") + + Returns: + str: Path to the created temporary file + + Note: + The caller is responsible for cleaning up the file using os.unlink() + """ + with tempfile.NamedTemporaryFile(suffix=suffix, mode="w+", delete=False) as tmp: + tmp.write(content) + tmp.flush() + return tmp.name + + +def create_temp_dummy_file(suffix=".ics"): + """Helper function to create a temporary dummy file path. + + Args: + suffix (str): File suffix (default: ".ics") + + Returns: + str: Path to the created temporary file + + Note: + The caller is responsible for cleaning up the file using os.unlink() + """ + with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as dummy_file: + dummy_path = dummy_file.name + return dummy_path + + def test_analyze_mock_ics(monkeypatch, capsys): + """Test analyzing a mock ICS calendar file with sample events.""" # Step 1: Create a mock ICS calendar file ics_content = textwrap.dedent(""" BEGIN:VCALENDAR @@ -28,35 +71,36 @@ def test_analyze_mock_ics(monkeypatch, capsys): END:VCALENDAR """) - with tempfile.NamedTemporaryFile(suffix=".ics", mode="w+", delete=False) as tmp: - tmp.write(ics_content) - tmp.flush() + tmp_path = create_temp_ics_file(ics_content) - # Step 2: Patch arguments to simulate CLI input - monkeypatch.setattr("sys.argv", [ - "calendar_analyzer.py", - "--calendar", tmp.name, - "--start-date", "2023-06-30", - "--end-date", "2023-07-03", - "--titles", "10" - ]) + # Step 2: Patch arguments to simulate CLI input + monkeypatch.setattr("sys.argv", [ + "calendar_analyzer.py", + "--calendar", tmp_path, + "--start-date", "2023-06-30", + "--end-date", "2023-07-03", + "--titles", "10" + ]) - # Step 3: Run the script - calendar_analyzer.main() + # Step 3: Run the script + calendar_analyzer.main() - # Step 4: Capture and validate output - out = capsys.readouterr().out - assert "Test Meeting" in out - assert "Project Sync" in out - assert "Total Meetings: 2" in out - assert "Total Meeting Hours: 3.0" in out + # Step 4: Capture and validate output + out = capsys.readouterr().out + assert "Test Meeting" in out + assert "Project Sync" in out + assert "Total Meetings: 2" in out + assert "Total Meeting Hours: 3.0" in out def test_invalid_start_date_format(monkeypatch, capsys): """Test that invalid start date format causes system exit.""" + # Create a temporary dummy file path (secure alternative to mktemp) + dummy_path = create_temp_dummy_file() + monkeypatch.setattr("sys.argv", [ "calendar_analyzer.py", - "--calendar", "/tmp/dummy.ics", + "--calendar", dummy_path, "--start-date", "invalid-date" ]) @@ -70,9 +114,12 @@ def test_invalid_start_date_format(monkeypatch, capsys): def test_invalid_end_date_format(monkeypatch, capsys): """Test that invalid end date format causes system exit.""" + # Create a temporary dummy file path (secure alternative to mktemp) + dummy_path = create_temp_dummy_file() + monkeypatch.setattr("sys.argv", [ "calendar_analyzer.py", - "--calendar", "/tmp/dummy.ics", + "--calendar", dummy_path, "--end-date", "2023/01/01" ]) @@ -86,9 +133,12 @@ def test_invalid_end_date_format(monkeypatch, capsys): def test_end_date_before_start_date(monkeypatch, capsys): """Test that end date before start date causes system exit.""" + # Create a temporary dummy file path (secure alternative to mktemp) + dummy_path = create_temp_dummy_file() + monkeypatch.setattr("sys.argv", [ "calendar_analyzer.py", - "--calendar", "/tmp/dummy.ics", + "--calendar", dummy_path, "--start-date", "2023-07-01", "--end-date", "2023-06-30" ]) @@ -117,30 +167,33 @@ def test_valid_date_formats(monkeypatch, capsys): END:VCALENDAR """) - with tempfile.NamedTemporaryFile(suffix=".ics", mode="w+", delete=False) as tmp: - tmp.write(ics_content) - tmp.flush() + tmp_path = create_temp_ics_file(ics_content) - monkeypatch.setattr("sys.argv", [ - "calendar_analyzer.py", - "--calendar", tmp.name, - "--start-date", "2023-06-30", - "--end-date", "2023-07-31" - ]) + monkeypatch.setattr("sys.argv", [ + "calendar_analyzer.py", + "--calendar", tmp_path, + "--start-date", "2023-06-30", + "--end-date", "2023-07-31" + ]) - # Should not raise SystemExit - calendar_analyzer.main() + # Should not raise SystemExit + calendar_analyzer.main() - out = capsys.readouterr().out - assert "Test Meeting" in out + out = capsys.readouterr().out + assert "Test Meeting" in out def test_edge_case_dates(monkeypatch, capsys): """Test edge case date formats.""" # Test leap year date + # Create a temporary dummy file path that doesn't exist + dummy_path = create_temp_dummy_file() + # Remove the file to make it nonexistent (for this test) + os.unlink(dummy_path) + monkeypatch.setattr("sys.argv", [ "calendar_analyzer.py", - "--calendar", "/tmp/dummy.ics", + "--calendar", dummy_path, "--start-date", "2024-02-29" # Valid leap year date ]) @@ -148,9 +201,13 @@ def test_edge_case_dates(monkeypatch, capsys): calendar_analyzer.main() # Test invalid leap year date + dummy_path2 = create_temp_dummy_file() + # Remove this file too since we want to test date validation, not file reading + os.unlink(dummy_path2) + monkeypatch.setattr("sys.argv", [ "calendar_analyzer.py", - "--calendar", "/tmp/dummy.ics", + "--calendar", dummy_path2, "--start-date", "2023-02-29" # Invalid - 2023 is not a leap year ]) @@ -211,35 +268,33 @@ def test_file_output_functionality(monkeypatch, capsys): END:VCALENDAR """) - with tempfile.NamedTemporaryFile(suffix=".ics", mode="w+", delete=False) as tmp_ics: - tmp_ics.write(ics_content) - tmp_ics.flush() + tmp_ics_path = create_temp_ics_file(ics_content) - with tempfile.NamedTemporaryFile(mode="w+", delete=False) as tmp_output: - output_path = tmp_output.name + with tempfile.NamedTemporaryFile(mode="w+", delete=False) as tmp_output: + output_path = tmp_output.name - monkeypatch.setattr("sys.argv", [ - "calendar_analyzer.py", - "--calendar", tmp_ics.name, - "--start-date", "2023-06-30", - "--end-date", "2023-07-03", - "--output", output_path - ]) + monkeypatch.setattr("sys.argv", [ + "calendar_analyzer.py", + "--calendar", tmp_ics_path, + "--start-date", "2023-06-30", + "--end-date", "2023-07-03", + "--output", output_path + ]) - calendar_analyzer.main() + calendar_analyzer.main() - # Check that file was created and contains expected content - with open(output_path, 'r') as f: - content = f.read() - assert "Test Meeting" in content - assert "Calendar Analysis Summary" in content + # Check that file was created and contains expected content + with open(output_path, 'r', encoding='utf-8') as f: + content = f.read() + assert "Test Meeting" in content + assert "Calendar Analysis Summary" in content - out = capsys.readouterr().out - assert f"Analysis saved to: {output_path}" in out + out = capsys.readouterr().out + assert f"Analysis saved to: {output_path}" in out - # Clean up - os.unlink(tmp_ics.name) - os.unlink(output_path) + # Clean up + os.unlink(tmp_ics_path) + os.unlink(output_path) def test_file_output_error(monkeypatch, capsys): @@ -255,27 +310,25 @@ def test_file_output_error(monkeypatch, capsys): END:VCALENDAR """) - with tempfile.NamedTemporaryFile(suffix=".ics", mode="w+", delete=False) as tmp: - tmp.write(ics_content) - tmp.flush() + tmp_path = create_temp_ics_file(ics_content) - monkeypatch.setattr("sys.argv", [ - "calendar_analyzer.py", - "--calendar", tmp.name, - "--start-date", "2023-06-30", - "--end-date", "2023-07-03", - "--output", "/invalid/path/output.txt" # Invalid path - ]) + monkeypatch.setattr("sys.argv", [ + "calendar_analyzer.py", + "--calendar", tmp_path, + "--start-date", "2023-06-30", + "--end-date", "2023-07-03", + "--output", "/invalid/path/output.txt" # Invalid path + ]) - with pytest.raises(SystemExit) as exc_info: - calendar_analyzer.main() + with pytest.raises(SystemExit) as exc_info: + calendar_analyzer.main() - assert exc_info.value.code == 1 - out = capsys.readouterr().out - assert "Error saving to file:" in out + assert exc_info.value.code == 1 + out = capsys.readouterr().out + assert "Error saving to file:" in out - # Clean up - os.unlink(tmp.name) + # Clean up + os.unlink(tmp_path) def test_calendar_file_read_error(monkeypatch, capsys): @@ -311,24 +364,21 @@ def test_analyze_calendar_with_different_duration_formats(): END:VCALENDAR """) - with tempfile.NamedTemporaryFile(suffix=".ics", mode="w+", delete=False) as tmp: - tmp.write(ics_content) - tmp.flush() + tmp_path = create_temp_ics_file(ics_content) - from pathlib import Path - meetings, stats = calendar_analyzer.analyze_calendar( - Path(tmp.name), - datetime(2023, 6, 30, tzinfo=calendar_analyzer.PACIFIC), - datetime(2023, 7, 2, tzinfo=calendar_analyzer.PACIFIC) - ) + _, stats = calendar_analyzer.analyze_calendar( + Path(tmp_path), + datetime(2023, 6, 30, tzinfo=calendar_analyzer.PACIFIC), + datetime(2023, 7, 2, tzinfo=calendar_analyzer.PACIFIC) + ) - # Should have 2 meetings - assert stats['total_meetings'] == 2 - # First meeting should have some duration, second defaults to 1 hour - assert stats['total_hours'] >= 2.0 + # Should have 2 meetings + assert stats['total_meetings'] == 2 + # First meeting should have some duration, second defaults to 1 hour + assert stats['total_hours'] >= 2.0 - # Clean up - os.unlink(tmp.name) + # Clean up + os.unlink(tmp_path) def test_generate_summary_with_long_titles(): @@ -383,31 +433,26 @@ def test_analyze_calendar_date_filtering(): END:VCALENDAR """) - with tempfile.NamedTemporaryFile(suffix=".ics", mode="w+", delete=False) as tmp: - tmp.write(ics_content) - tmp.flush() + tmp_path = create_temp_ics_file(ics_content) - from pathlib import Path - meetings, stats = calendar_analyzer.analyze_calendar( - Path(tmp.name), - datetime(2023, 6, 30, tzinfo=calendar_analyzer.PACIFIC), - datetime(2023, 7, 5, tzinfo=calendar_analyzer.PACIFIC) - ) + meetings, stats = calendar_analyzer.analyze_calendar( + Path(tmp_path), + datetime(2023, 6, 30, tzinfo=calendar_analyzer.PACIFIC), + datetime(2023, 7, 5, tzinfo=calendar_analyzer.PACIFIC) + ) - # Should only have the meeting in range - assert stats['total_meetings'] == 1 - assert meetings[0]['summary'] == 'In Range' + # Should only have the meeting in range + assert stats['total_meetings'] == 1 + assert meetings[0]['summary'] == 'In Range' - # Clean up - os.unlink(tmp.name) + # Clean up + os.unlink(tmp_path) def test_get_calendar_path_with_specified_file(capsys): """Test get_calendar_path when a specific file is provided.""" # Create a temporary file - with tempfile.NamedTemporaryFile(suffix=".ics", delete=False) as tmp: - tmp.write(b"test content") - tmp_path = tmp.name + tmp_path = create_temp_ics_file("test content") try: result = calendar_analyzer.get_calendar_path(tmp_path) @@ -443,7 +488,10 @@ def test_get_calendar_path_with_directory(capsys): def test_get_calendar_path_nonexistent_file(capsys): """Test get_calendar_path with a nonexistent file.""" - nonexistent_path = "/tmp/nonexistent_calendar.ics" + # Use a more secure temporary path that doesn't exist + nonexistent_path = create_temp_dummy_file("_nonexistent.ics") + # Remove the file to make it nonexistent but keep the secure path + os.unlink(nonexistent_path) result = calendar_analyzer.get_calendar_path(nonexistent_path) @@ -485,8 +533,7 @@ def test_get_calendar_path_auto_discovery_with_files(mock_home, capsys): new_calendar.write_text("new calendar content") # Make old_calendar older by changing its modification time - import time - old_time = time.time() - 3600 # 1 hour ago + old_time = os.path.getmtime(new_calendar) - 3600 # 1 hour ago os.utime(old_calendar, (old_time, old_time)) result = calendar_analyzer.get_calendar_path() @@ -722,28 +769,25 @@ def test_analyze_calendar_icbu_directory_listing_error(capsys): assert "Error listing directory contents: Permission denied" in out -def test_analyze_calendar_with_malformed_ics(capsys): +def test_analyze_calendar_with_malformed_ics(): """Test analyze_calendar with malformed ICS content.""" malformed_ics = "This is not valid ICS content" - with tempfile.NamedTemporaryFile(suffix=".ics", mode="w+", delete=False) as tmp: - tmp.write(malformed_ics) - tmp.flush() + tmp_path = create_temp_ics_file(malformed_ics) - # The icalendar library will raise a ValueError, which gets caught as an exception - # but not specifically OSError, so it might not trigger our exception handler - # Let's test that it raises some kind of exception - with pytest.raises((SystemExit, ValueError)): - calendar_analyzer.analyze_calendar(Path(tmp.name)) + # The icalendar library will raise a ValueError, which gets caught as an exception + # but not specifically OSError, so it might not trigger our exception handler + # Let's test that it raises some kind of exception + with pytest.raises((SystemExit, ValueError)): + calendar_analyzer.analyze_calendar(Path(tmp_path)) - # Clean up - os.unlink(tmp.name) + # Clean up + os.unlink(tmp_path) def test_analyze_calendar_default_date_range(): """Test analyze_calendar with default date ranges (no start/end specified).""" # Use a recent date that would be within the default 365-day range - from datetime import datetime, timedelta recent_date = datetime.now() - timedelta(days=30) # 30 days ago recent_date_str = recent_date.strftime('%Y%m%dT%H%M%SZ') @@ -758,22 +802,20 @@ def test_analyze_calendar_default_date_range(): END:VCALENDAR """) - with tempfile.NamedTemporaryFile(suffix=".ics", mode="w+", delete=False) as tmp: - tmp.write(ics_content) - tmp.flush() + tmp_path = create_temp_ics_file(ics_content) - # Test with default date range (past 365 days) - meetings, stats = calendar_analyzer.analyze_calendar(Path(tmp.name)) + # Test with default date range (past 365 days) + meetings, stats = calendar_analyzer.analyze_calendar(Path(tmp_path)) - # Should process the calendar and find the recent meeting - assert isinstance(meetings, list) - assert isinstance(stats, dict) - assert stats['total_meetings'] == 1 - assert stats['total_hours'] == 1.0 - assert meetings[0]['summary'] == 'Recent Meeting' + # Should process the calendar and find the recent meeting + assert isinstance(meetings, list) + assert isinstance(stats, dict) + assert stats['total_meetings'] == 1 + assert stats['total_hours'] == 1.0 + assert meetings[0]['summary'] == 'Recent Meeting' - # Clean up - os.unlink(tmp.name) + # Clean up + os.unlink(tmp_path) def test_analyze_calendar_with_non_datetime_events(): @@ -794,22 +836,20 @@ def test_analyze_calendar_with_non_datetime_events(): END:VCALENDAR """) - with tempfile.NamedTemporaryFile(suffix=".ics", mode="w+", delete=False) as tmp: - tmp.write(ics_content) - tmp.flush() + tmp_path = create_temp_ics_file(ics_content) - meetings, stats = calendar_analyzer.analyze_calendar( - Path(tmp.name), - datetime(2023, 6, 30, tzinfo=calendar_analyzer.PACIFIC), - datetime(2023, 7, 2, tzinfo=calendar_analyzer.PACIFIC) - ) + meetings, stats = calendar_analyzer.analyze_calendar( + Path(tmp_path), + datetime(2023, 6, 30, tzinfo=calendar_analyzer.PACIFIC), + datetime(2023, 7, 2, tzinfo=calendar_analyzer.PACIFIC) + ) - # Should only process the datetime event, not the all-day event - assert stats['total_meetings'] == 1 - assert meetings[0]['summary'] == 'Timed Event' + # Should only process the datetime event, not the all-day event + assert stats['total_meetings'] == 1 + assert meetings[0]['summary'] == 'Timed Event' - # Clean up - os.unlink(tmp.name) + # Clean up + os.unlink(tmp_path) def test_analyze_calendar_duration_parsing_edge_cases(): @@ -836,24 +876,22 @@ def test_analyze_calendar_duration_parsing_edge_cases(): END:VCALENDAR """) - with tempfile.NamedTemporaryFile(suffix=".ics", mode="w+", delete=False) as tmp: - tmp.write(ics_content) - tmp.flush() + tmp_path = create_temp_ics_file(ics_content) - meetings, stats = calendar_analyzer.analyze_calendar( - Path(tmp.name), - datetime(2023, 6, 30, tzinfo=calendar_analyzer.PACIFIC), - datetime(2023, 7, 2, tzinfo=calendar_analyzer.PACIFIC) - ) + meetings, stats = calendar_analyzer.analyze_calendar( + Path(tmp_path), + datetime(2023, 6, 30, tzinfo=calendar_analyzer.PACIFIC), + datetime(2023, 7, 2, tzinfo=calendar_analyzer.PACIFIC) + ) - # Should process all events, with fallback durations where needed - assert stats['total_meetings'] == 3 + # Should process all events, with fallback durations where needed + assert stats['total_meetings'] == 3 - # Find each meeting and check duration handling - meeting_summaries = [m['summary'] for m in meetings] - assert '30 Minute Meeting' in meeting_summaries - assert 'All Day Event with Duration' in meeting_summaries - assert 'Invalid Duration Meeting' in meeting_summaries + # Find each meeting and check duration handling + meeting_summaries = [m['summary'] for m in meetings] + assert '30 Minute Meeting' in meeting_summaries + assert 'All Day Event with Duration' in meeting_summaries + assert 'Invalid Duration Meeting' in meeting_summaries - # Clean up - os.unlink(tmp.name) + # Clean up + os.unlink(tmp_path) diff --git a/uv.lock b/uv.lock index 7796e2d..54edd53 100644 --- a/uv.lock +++ b/uv.lock @@ -20,6 +20,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/15/58/5260205b9968c20b6457ed82f48f9e3d6edf2f1f95103161798b73aeccf0/astroid-3.3.10-py3-none-any.whl", hash = "sha256:104fb9cb9b27ea95e847a94c003be03a9e039334a8ebca5ee27dafaf5c5711eb", size = 275388, upload-time = "2025-05-10T13:33:08.391Z" }, ] +[[package]] +name = "bandit" +version = "1.8.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "pyyaml" }, + { name = "rich" }, + { name = "stevedore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4e/01/b2ce2f54db060ed7b25960892b275ad8238ca15f5a8821b09f8e7f75870d/bandit-1.8.5.tar.gz", hash = "sha256:db812e9c39b8868c0fed5278b77fffbbaba828b4891bc80e34b9c50373201cfd", size = 4237566, upload-time = "2025-06-17T01:43:36.697Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/b0/5c8976e61944f91904d4fd33bdbe55248138bfbd1a6092753b1b0fb7abbc/bandit-1.8.5-py3-none-any.whl", hash = "sha256:cb2e57524e99e33ced48833c6cc9c12ac78ae970bb6a450a83c4b506ecc1e2f9", size = 131759, upload-time = "2025-06-17T01:43:35.045Z" }, +] + [[package]] name = "calendar-analyzer" version = "0.1.0" @@ -32,6 +47,7 @@ dependencies = [ [package.dev-dependencies] dev = [ + { name = "bandit" }, { name = "isort" }, { name = "pylint" }, { name = "pytest" }, @@ -47,6 +63,7 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ + { name = "bandit", specifier = ">=1.8.5" }, { name = "isort", specifier = ">=6.0.1" }, { name = "pylint", specifier = ">=3.3.7" }, { name = "pytest", specifier = ">=8.2.1" }, @@ -193,6 +210,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c1/11/114d0a5f4dabbdcedc1125dee0888514c3c3b16d3e9facad87ed96fad97c/isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615", size = 94186, upload-time = "2025-02-26T21:13:14.911Z" }, ] +[[package]] +name = "markdown-it-py" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, +] + [[package]] name = "mccabe" version = "0.7.0" @@ -202,6 +231,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350, upload-time = "2022-01-24T01:14:49.62Z" }, ] +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + [[package]] name = "numpy" version = "2.0.2" @@ -450,6 +488,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/db/84/5ffd2c447c02db56326f5c19a235a747fae727e4842cc20e1ddd28f990f6/pandas-2.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:b198687ca9c8529662213538a9bb1e60fa0bf0f6af89292eb68fea28743fcd5a", size = 11104735, upload-time = "2025-06-06T00:02:21.088Z" }, ] +[[package]] +name = "pbr" +version = "6.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/01/d2/510cc0d218e753ba62a1bc1434651db3cd797a9716a0a66cc714cb4f0935/pbr-6.1.1.tar.gz", hash = "sha256:93ea72ce6989eb2eed99d0f75721474f69ad88128afdef5ac377eb797c4bf76b", size = 125702, upload-time = "2025-02-04T14:28:06.514Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/ac/684d71315abc7b1214d59304e23a982472967f6bf4bde5a98f1503f648dc/pbr-6.1.1-py2.py3-none-any.whl", hash = "sha256:38d4daea5d9fa63b3f626131b9d34947fd0c8be9b05a29276870580050a25a76", size = 108997, upload-time = "2025-02-04T14:28:03.168Z" }, +] + [[package]] name = "platformdirs" version = "4.3.8" @@ -550,6 +600,82 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, ] +[[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, upload-time = "2024-08-06T20:33:50.674Z" } +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, upload-time = "2024-08-06T20:31:40.178Z" }, + { 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, upload-time = "2024-08-06T20:31:42.173Z" }, + { 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, upload-time = "2024-08-06T20:31:44.263Z" }, + { 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, upload-time = "2024-08-06T20:31:50.199Z" }, + { 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, upload-time = "2024-08-06T20:31:52.292Z" }, + { 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, upload-time = "2024-08-06T20:31:53.836Z" }, + { 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, upload-time = "2024-08-06T20:31:55.565Z" }, + { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052, upload-time = "2024-08-06T20:31:56.914Z" }, + { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774, upload-time = "2024-08-06T20:31:58.304Z" }, + { 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, upload-time = "2024-08-06T20:32:03.408Z" }, + { 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, upload-time = "2024-08-06T20:32:04.926Z" }, + { 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, upload-time = "2024-08-06T20:32:06.459Z" }, + { 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, upload-time = "2024-08-06T20:32:08.338Z" }, + { 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, upload-time = "2024-08-06T20:32:14.124Z" }, + { 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, upload-time = "2024-08-06T20:32:16.17Z" }, + { 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, upload-time = "2024-08-06T20:32:18.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850, upload-time = "2024-08-06T20:32:19.889Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980, upload-time = "2024-08-06T20:32:21.273Z" }, + { 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, upload-time = "2024-08-06T20:32:25.131Z" }, + { 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, upload-time = "2024-08-06T20:32:26.511Z" }, + { 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, upload-time = "2024-08-06T20:32:28.363Z" }, + { 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, upload-time = "2024-08-06T20:32:30.058Z" }, + { 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, upload-time = "2024-08-06T20:32:31.881Z" }, + { 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, upload-time = "2024-08-06T20:32:37.083Z" }, + { 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, upload-time = "2024-08-06T20:32:38.898Z" }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" }, + { 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, upload-time = "2024-08-06T20:32:43.4Z" }, + { 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, upload-time = "2024-08-06T20:32:44.801Z" }, + { 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, upload-time = "2024-08-06T20:32:46.432Z" }, + { 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, upload-time = "2024-08-06T20:32:51.188Z" }, + { 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, upload-time = "2024-08-06T20:32:53.019Z" }, + { 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, upload-time = "2024-08-06T20:32:54.708Z" }, + { 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, upload-time = "2024-08-06T20:32:56.985Z" }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, + { 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, upload-time = "2024-08-06T20:33:25.896Z" }, + { 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, upload-time = "2024-08-06T20:33:27.212Z" }, + { 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, upload-time = "2024-08-06T20:33:28.974Z" }, + { 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, upload-time = "2024-08-06T20:33:34.157Z" }, + { 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, upload-time = "2024-08-06T20:33:35.84Z" }, + { 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, upload-time = "2024-08-06T20:33:37.501Z" }, + { 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, upload-time = "2024-08-06T20:33:39.389Z" }, + { url = "https://files.pythonhosted.org/packages/ed/5e/46168b1f2757f1fcd442bc3029cd8767d88a98c9c05770d8b420948743bb/PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", size = 144593, upload-time = "2024-08-06T20:33:46.63Z" }, + { url = "https://files.pythonhosted.org/packages/19/87/5124b1c1f2412bb95c59ec481eaf936cd32f0fe2a7b16b97b81c4c017a6a/PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", size = 162312, upload-time = "2024-08-06T20:33:49.073Z" }, +] + +[[package]] +name = "rich" +version = "14.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078, upload-time = "2025-03-30T14:15:14.23Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229, upload-time = "2025-03-30T14:15:12.283Z" }, +] + +[[package]] +name = "setuptools" +version = "80.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, +] + [[package]] name = "six" version = "1.17.0" @@ -559,6 +685,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, ] +[[package]] +name = "stevedore" +version = "5.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pbr" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/3f/13cacea96900bbd31bb05c6b74135f85d15564fc583802be56976c940470/stevedore-5.4.1.tar.gz", hash = "sha256:3135b5ae50fe12816ef291baff420acb727fcd356106e3e9cbfa9e5985cd6f4b", size = 513858, upload-time = "2025-02-20T14:03:57.285Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/45/8c4ebc0c460e6ec38e62ab245ad3c7fc10b210116cea7c16d61602aa9558/stevedore-5.4.1-py3-none-any.whl", hash = "sha256:d10a31c7b86cba16c1f6e8d15416955fc797052351a56af15e608ad20811fcfe", size = 49533, upload-time = "2025-02-20T14:03:55.849Z" }, +] + [[package]] name = "tomli" version = "2.2.1"