From 0cdb201d6de13f41aba913dd30eaf2405a1b16db Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 5 Aug 2025 00:31:45 +0000 Subject: [PATCH 1/5] Add performance monitoring docs and remove multi-intent routing example Co-authored-by: stephenc211 --- docs/concepts/nodes-and-actions.md | 76 +++++ docs/development/documentation-management.md | 2 +- docs/development/index.md | 1 + docs/development/performance-monitoring.md | 290 +++++++++++++++++++ docs/development/testing.md | 4 +- docs/examples/index.md | 1 - docs/examples/multi-intent-routing.md | 51 ---- docs/index.md | 1 - mkdocs.yml | 1 + 9 files changed, 371 insertions(+), 56 deletions(-) create mode 100644 docs/development/performance-monitoring.md delete mode 100644 docs/examples/multi-intent-routing.md diff --git a/docs/concepts/nodes-and-actions.md b/docs/concepts/nodes-and-actions.md index 092be4e..a05c4d7 100644 --- a/docs/concepts/nodes-and-actions.md +++ b/docs/concepts/nodes-and-actions.md @@ -44,6 +44,82 @@ weather_action = action( - **action_func** - Function to execute - **param_schema** - Parameter type definitions +#### Argument Extraction + +Actions automatically extract parameters from user input using the argument extraction system: + +- **RuleBasedArgumentExtractor** - Uses pattern matching and rules for fast extraction +- **LLMArgumentExtractor** - Uses LLM for intelligent parameter extraction +- **Automatic Selection** - Intent Kit chooses the best extractor based on your configuration + +```python +from intent_kit import action + +# Rule-based extraction (fast, deterministic) +greet_action = action( + name="greet", + description="Greet the user", + action_func=lambda name: f"Hello {name}!", + param_schema={"name": str}, + argument_extractor="rule" # Use rule-based extraction +) + +# LLM-based extraction (intelligent, flexible) +weather_action = action( + name="weather", + description="Get weather information", + action_func=lambda city: f"Weather in {city} is sunny", + param_schema={"city": str}, + argument_extractor="llm" # Use LLM extraction +) +``` + +#### Error Handling Strategies + +Actions support pluggable error handling strategies for robust execution: + +```python +from intent_kit import action + +# Retry on failure +retry_action = action( + name="retry_example", + description="Example with retry strategy", + action_func=lambda x: x / 0, # Will fail + param_schema={"x": float}, + remediation_strategy="retry_on_fail", + remediation_config={ + "max_attempts": 3, + "base_delay": 1.0 + } +) + +# Fallback to another action +fallback_action = action( + name="fallback_example", + description="Example with fallback strategy", + action_func=lambda x: x / 0, # Will fail + param_schema={"x": float}, + remediation_strategy="fallback_to_another_node", + remediation_config={ + "fallback_name": "safe_calculation" + } +) + +# Self-reflection for parameter correction +reflect_action = action( + name="reflect_example", + description="Example with self-reflection", + action_func=lambda name: f"Hello {name}!", + param_schema={"name": str}, + remediation_strategy="self_reflect", + remediation_config={ + "max_reflections": 2, + "llm_config": {"provider": "openai", "model": "gpt-3.5-turbo"} + } +) +``` + ### Classifier Nodes Classifier nodes route input to appropriate child nodes based on classification logic. diff --git a/docs/development/documentation-management.md b/docs/development/documentation-management.md index 7ba9e54..01f3ccc 100644 --- a/docs/development/documentation-management.md +++ b/docs/development/documentation-management.md @@ -96,7 +96,7 @@ The documentation has been updated to use consistent terminology: - **Actions** instead of "handlers" - Functions that execute and produce outputs - **Classifiers** - Nodes that route input to appropriate actions -- **Splitters** - Nodes that handle multiple nodes in single input +- **Classifiers** - Nodes that route input to appropriate actions ## Navigation Structure diff --git a/docs/development/index.md b/docs/development/index.md index 88dac88..2c3cd12 100644 --- a/docs/development/index.md +++ b/docs/development/index.md @@ -8,5 +8,6 @@ Welcome to the Development section of the Intent Kit documentation. Here you'll - [Testing](testing.md): Unit tests and integration testing. - [Evaluation](evaluation.md): Performance evaluation and benchmarking. - [Debugging](debugging.md): Debugging tools and techniques. +- [Performance Monitoring](performance-monitoring.md): Performance tracking and reporting. For additional information, see the [project README on GitHub](https://github.com/Stephen-Collins-tech/intent-kit#readme) or explore other sections of the documentation. diff --git a/docs/development/performance-monitoring.md b/docs/development/performance-monitoring.md new file mode 100644 index 0000000..3ed6fe1 --- /dev/null +++ b/docs/development/performance-monitoring.md @@ -0,0 +1,290 @@ +# Performance Monitoring + +Intent Kit v0.5.0 introduces comprehensive performance monitoring capabilities to help you track and optimize your AI workflows. + +## Overview + +Performance monitoring in Intent Kit includes: + +- **PerfUtil** - Utility for measuring execution time +- **ReportUtil** - Generate detailed performance reports +- **Token Usage Tracking** - Real-time token consumption and cost calculation +- **Execution Tracing** - Detailed logs of decision paths and performance + +## PerfUtil + +The `PerfUtil` class provides flexible timing utilities for measuring code execution time. + +### Basic Usage + +```python +from intent_kit.utils.perf_util import PerfUtil + +# Manual timing +perf = PerfUtil("my task", auto_print=False) +perf.start() +# ... your code here ... +perf.stop() +print(perf.format()) # "my task: 1.234 seconds elapsed" +``` + +### Context Manager Usage + +```python +from intent_kit.utils.perf_util import PerfUtil + +# Automatic timing with context manager +with PerfUtil("my task") as perf: + # ... your code here ... + # Automatically prints timing on exit +``` + +### Collecting Multiple Timings + +```python +from intent_kit.utils.perf_util import PerfUtil + +timings = [] + +# Collect multiple timings +with PerfUtil.collect("task1", timings): + # ... code for task1 ... + +with PerfUtil.collect("task2", timings): + # ... code for task2 ... + +# Generate summary table +PerfUtil.report_table(timings, "My Performance Summary") +``` + +## ReportUtil + +The `ReportUtil` class generates comprehensive performance reports for your intent graphs. + +### Basic Performance Report + +```python +from intent_kit.utils.report_utils import ReportUtil +from intent_kit.utils.perf_util import PerfUtil + +# Your graph and test inputs +graph = IntentGraphBuilder().root(classifier).build() +test_inputs = ["Hello Alice", "What's 2 + 3?", "Weather in NYC"] + +results = [] +timings = [] + +# Run tests with timing +with PerfUtil("full test run") as perf: + for test_input in test_inputs: + with PerfUtil.collect(test_input, timings): + result = graph.route(test_input) + results.append(result) + +# Generate report +report = ReportUtil.format_execution_results( + results=results, + llm_config=llm_config, + perf_info=perf.format(), + timings=timings, +) + +print(report) +``` + +### Report Features + +The generated report includes: + +- **Execution Summary** - Total time, average time per request +- **Individual Results** - Each input/output with timing +- **Token Usage** - Token consumption and estimated costs +- **Performance Breakdown** - Detailed timing for each step +- **Error Analysis** - Any failures or issues encountered + +## Token Usage Tracking + +Intent Kit automatically tracks token usage across all LLM operations. + +### Cost Calculation + +```python +from intent_kit.utils.report_utils import ReportUtil + +# Get cost information from results +for result in results: + if result.token_usage: + print(f"Input tokens: {result.token_usage.input_tokens}") + print(f"Output tokens: {result.token_usage.output_tokens}") + print(f"Estimated cost: ${result.token_usage.estimated_cost:.4f}") +``` + +### Provider-Specific Tracking + +Different AI providers have different pricing models: + +- **OpenAI** - Per-token pricing with model-specific rates +- **Anthropic** - Per-token pricing with Claude model rates +- **Google AI** - Per-token pricing with Gemini model rates +- **Ollama** - Local models typically have no token costs + +## Execution Tracing + +Enable detailed execution tracing to understand performance bottlenecks. + +### Enable Tracing + +```python +from intent_kit import IntentGraphBuilder + +graph = ( + IntentGraphBuilder() + .with_json(graph_config) + .with_functions(function_registry) + .with_context_trace(True) # Enable detailed tracing + .with_debug_context(True) # Enable debug information + .build() +) +``` + +### Trace Information + +Tracing provides: + +- **Node Execution Times** - How long each classifier and action takes +- **Decision Paths** - Which nodes were visited and why +- **Parameter Extraction** - Time spent extracting parameters +- **LLM Calls** - Individual LLM request timing and token usage +- **Error Details** - Detailed error information with timing + +## Performance Best Practices + +### 1. Use Context Managers + +```python +# Good: Automatic timing and cleanup +with PerfUtil("my operation") as perf: + result = graph.route(input_text) + +# Good: Collect multiple timings +timings = [] +with PerfUtil.collect("operation", timings): + result = graph.route(input_text) +``` + +### 2. Monitor Token Usage + +```python +# Check token usage for cost optimization +for result in results: + if result.token_usage: + total_cost += result.token_usage.estimated_cost + print(f"Total cost so far: ${total_cost:.4f}") +``` + +### 3. Profile Your Workflows + +```python +# Profile different parts of your workflow +with PerfUtil.collect("classification", timings): + # Classifier execution + +with PerfUtil.collect("parameter_extraction", timings): + # Parameter extraction + +with PerfUtil.collect("action_execution", timings): + # Action execution +``` + +### 4. Generate Regular Reports + +```python +# Generate performance reports for monitoring +report = ReportUtil.format_execution_results( + results=results, + llm_config=llm_config, + perf_info=perf.format(), + timings=timings, +) + +# Save reports for historical analysis +with open(f"performance_report_{date}.md", "w") as f: + f.write(report) +``` + +## Integration with Evaluation + +Performance monitoring integrates with the evaluation framework: + +```python +from intent_kit.evals import run_eval, load_dataset + +# Load test dataset +dataset = load_dataset("tests/my_tests.yaml") + +# Run evaluation with performance tracking +results = run_eval(dataset, graph) + +# Generate comprehensive report +report = ReportUtil.format_evaluation_results( + results=results, + dataset=dataset, + llm_config=llm_config, + include_performance=True +) +``` + +This provides both accuracy metrics and performance data in a single report. + +## Example: Complete Performance Monitoring + +```python +from intent_kit import IntentGraphBuilder +from intent_kit.utils.perf_util import PerfUtil +from intent_kit.utils.report_utils import ReportUtil + +# Build your graph +graph = IntentGraphBuilder().root(classifier).build() + +# Test inputs +test_inputs = [ + "Hello Alice", + "What's 15 plus 7?", + "Weather in San Francisco", + "Help me", + "Multiply 8 and 3", +] + +results = [] +timings = [] + +# Run tests with comprehensive monitoring +with PerfUtil("full test suite") as perf: + for test_input in test_inputs: + with PerfUtil.collect(test_input, timings): + result = graph.route(test_input) + results.append(result) + +# Generate comprehensive report +report = ReportUtil.format_execution_results( + results=results, + llm_config=llm_config, + perf_info=perf.format(), + timings=timings, +) + +print("Performance Report:") +print(report) + +# Save for historical analysis +with open("performance_report.md", "w") as f: + f.write(report) +``` + +This comprehensive monitoring approach helps you: + +- **Optimize Performance** - Identify bottlenecks and slow operations +- **Control Costs** - Monitor token usage and estimated costs +- **Debug Issues** - Trace execution paths and identify problems +- **Track Improvements** - Compare performance over time +- **Validate Changes** - Ensure updates don't degrade performance \ No newline at end of file diff --git a/docs/development/testing.md b/docs/development/testing.md index d1262ab..778c4eb 100644 --- a/docs/development/testing.md +++ b/docs/development/testing.md @@ -29,14 +29,14 @@ pytest -v ## Test Categories ### Unit Tests -- Node functionality (actions, classifiers, splitters) +- Node functionality (actions, classifiers) - Graph building and routing - Context management - Parameter extraction and validation ### Integration Tests - Complete workflow execution -- Multi-intent routing +- Single intent routing - Error handling and recovery - LLM integration diff --git a/docs/examples/index.md b/docs/examples/index.md index d0b1d13..a843771 100644 --- a/docs/examples/index.md +++ b/docs/examples/index.md @@ -6,6 +6,5 @@ Explore working examples of Intent Kit in action. These guides show how to build - [Calculator Bot](calculator-bot.md): Simple math operations - [Context-Aware Chatbot](context-aware-chatbot.md): Remembering conversations -- [Multi-Intent Routing](multi-intent-routing.md): Handling complex requests Check back for more examples as the documentation grows! diff --git a/docs/examples/multi-intent-routing.md b/docs/examples/multi-intent-routing.md deleted file mode 100644 index 0905726..0000000 --- a/docs/examples/multi-intent-routing.md +++ /dev/null @@ -1,51 +0,0 @@ -# Multi-Intent Routing Example - -The following shows how intent-kit can handle _multiple_ nodes in a single user utterance using a splitter node. - -```python -from intent_kit import IntentGraphBuilder, action, rule_splitter_node - -# Actions for individual nodes - -def greet(name: str) -> str: - return f"Hello {name}!" - -def weather(city: str) -> str: - return f"The weather in {city} is sunny." - -hello_action = action( - name="greet", - description="Greet the user", - action_func=greet, - param_schema={"name": str}, -) - -weather_action = action( - name="weather", - description="Get weather information", - action_func=weather, - param_schema={"city": str}, -) - -# Splitter routes parts of the sentence to different actions -splitter = rule_splitter_node( - name="multi_split", - children=[hello_action, weather_action], -) - -graph = IntentGraphBuilder().root(splitter).build() - -result = graph.route("Hello Alice and what's the weather in Paris?") -print(result.output) -``` - -The `rule_splitter_node` looks for keywords ("hello", "weather", etc.) and breaks the user input into sub-phrases routed to the appropriate actions. The final `result.output` aggregates the outputs from each intent, e.g.: - -``` -{ - "greet": "Hello Alice!", - "weather": "The weather in Paris is sunny." -} -``` - -For a more robust version that uses LLM-based splitting, see `examples/multi_intent_demo.py`. diff --git a/docs/index.md b/docs/index.md index e965b36..aabce20 100644 --- a/docs/index.md +++ b/docs/index.md @@ -15,7 +15,6 @@ Get up and running in minutes with our [Quickstart Guide](quickstart.md). ### Examples - [Calculator Bot](examples/calculator-bot.md) - Simple math operations - [Context-Aware Chatbot](examples/context-aware-chatbot.md) - Remembering conversations -- [Multi-Intent Routing](examples/multi-intent-routing.md) - Handling complex requests ### Development - [Building](development/building.md) - How to build the package diff --git a/mkdocs.yml b/mkdocs.yml index 4c3a103..fa2613a 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -48,4 +48,5 @@ nav: - Testing: development/testing.md - Evaluation: development/evaluation.md - Debugging: development/debugging.md + - Performance Monitoring: development/performance-monitoring.md - Documentation Management: development/documentation-management.md From 7c68c95e193f846cca52c44fd1f51cd4a3e023f1 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 5 Aug 2025 00:52:17 +0000 Subject: [PATCH 2/5] Add security audit workflow and pre-commit hook for dependency checks Co-authored-by: stephenc211 --- .github/workflows/ci.yml | 18 +++++++++++- .pre-commit-config.yaml | 5 ++++ README.md | 6 ++++ pyproject.toml | 2 ++ scripts/security.py | 62 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 scripts/security.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fee42a2..a1693b3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,9 +67,25 @@ jobs: - name: Evaluations (Mock Mode) run: uv run python -m intent_kit.evals.run_all_evals --quiet --mock + security: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Install uv + uses: astral-sh/setup-uv@v6 + - name: Sync dependencies + run: uv sync --group dev + - name: Install pip-audit + run: uv add --group dev pip-audit + - name: Security audit + run: uv run pip-audit --local + build: runs-on: ubuntu-latest - needs: [test, eval] + needs: [test, eval, security] steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3ed1434..139ef9a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -46,6 +46,11 @@ repos: language: system files: .codecov.yml pass_filenames: false + - id: security + name: Security audit + entry: uv run security + language: system + pass_filenames: false - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.5.0 hooks: diff --git a/README.md b/README.md index 027fa4d..f5c904b 100644 --- a/README.md +++ b/README.md @@ -260,6 +260,11 @@ This means you can deploy with confidence, knowing your AI workflows work reliab - Catch regressions automatically - Validate reliability before deployment +### **Security** +- Automated security audits with pip-audit +- Vulnerability scanning in CI/CD pipeline +- Dependency security monitoring + --- ## Common Use Cases @@ -342,6 +347,7 @@ uv run pytest # Run tests uv run lint # Lint code uv run black --check . # Format check uv run typecheck # Type checking +uv run security # Security audit uv build # Build package ``` diff --git a/pyproject.toml b/pyproject.toml index 3bc96cb..76e36b8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,6 +62,7 @@ typecheck = "scripts.typecheck:main" examples = "scripts.examples:run_all" example = "scripts.examples:run_single" list-examples = "scripts.examples:list_examples" +security = "scripts.security:main" [tool.setuptools.packages.find] where = ["."] @@ -92,4 +93,5 @@ dev = [ "tqdm", "twine>=5.1.0", "types-pyyaml>=6.0.12.20250516", + "pip-audit>=2.9.0", ] diff --git a/scripts/security.py b/scripts/security.py new file mode 100644 index 0000000..3910814 --- /dev/null +++ b/scripts/security.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +""" +Security audit script for Intent Kit. + +This script runs pip-audit to check for known vulnerabilities in dependencies. +""" + +import subprocess +import sys +from pathlib import Path + + +def run_pip_audit(): + """Run pip-audit and return the result.""" + try: + # Try to find pip-audit in common locations + pip_audit_paths = [ + "pip-audit", + str(Path.home() / ".local/bin/pip-audit"), + "/usr/local/bin/pip-audit", + ] + + for path in pip_audit_paths: + try: + result = subprocess.run( + [path, "--local"], + capture_output=True, + text=True, + check=True + ) + return True, result.stdout + except (subprocess.CalledProcessError, FileNotFoundError): + continue + + return False, "pip-audit not found. Install it with: pip install pip-audit" + except Exception as e: + return False, f"Error running pip-audit: {e}" + + +def main(): + """Main function to run security audit.""" + print("🔒 Running security audit...") + print("=" * 50) + + success, output = run_pip_audit() + + if success: + print("✅ Security audit passed!") + print("No known vulnerabilities found.") + print("\nAudit output:") + print(output) + return 0 + else: + print("❌ Security audit failed!") + print("Vulnerabilities found or audit failed to run.") + print("\nError output:") + print(output) + return 1 + + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file From fd3834463d6487dc6300436958c644b526b35731 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 5 Aug 2025 01:27:45 +0000 Subject: [PATCH 3/5] Add auto-amend hook to stage reformatted files automatically Co-authored-by: stephenc211 --- .pre-commit-config.yaml | 5 +++ pyproject.toml | 1 + scripts/auto_amend.py | 83 ++++++++++++++++++++++++++++++++++++ scripts/post_commit_amend.py | 0 4 files changed, 89 insertions(+) create mode 100644 scripts/auto_amend.py create mode 100644 scripts/post_commit_amend.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 139ef9a..e6e569e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -51,6 +51,11 @@ repos: entry: uv run security language: system pass_filenames: false + - id: auto-amend + name: Auto-amend commit with reformatted files + entry: uv run auto-amend + language: system + pass_filenames: false - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.5.0 hooks: diff --git a/pyproject.toml b/pyproject.toml index 76e36b8..ea944bc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,6 +63,7 @@ examples = "scripts.examples:run_all" example = "scripts.examples:run_single" list-examples = "scripts.examples:list_examples" security = "scripts.security:main" +auto-amend = "scripts.auto_amend:main" [tool.setuptools.packages.find] where = ["."] diff --git a/scripts/auto_amend.py b/scripts/auto_amend.py new file mode 100644 index 0000000..82ddde4 --- /dev/null +++ b/scripts/auto_amend.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +""" +Auto-amend script for pre-commit hooks. + +This script automatically stages any files that were reformatted by previous hooks. +It runs after formatting tools (black, ruff) to ensure reformatted files are included in the commit. +""" + +import subprocess +import sys +from pathlib import Path + + +def get_staged_files(): + """Get list of files that are currently staged for commit.""" + try: + result = subprocess.run( + ["git", "diff", "--cached", "--name-only"], + capture_output=True, + text=True, + check=True + ) + return result.stdout.strip().split('\n') if result.stdout.strip() else [] + except subprocess.CalledProcessError: + return [] + + +def get_modified_files(): + """Get list of files that have been modified (including by formatters).""" + try: + result = subprocess.run( + ["git", "diff", "--name-only"], + capture_output=True, + text=True, + check=True + ) + return result.stdout.strip().split('\n') if result.stdout.strip() else [] + except subprocess.CalledProcessError: + return [] + + +def stage_files(files): + """Stage the specified files.""" + if not files: + return True + + try: + subprocess.run( + ["git", "add"] + files, + check=True + ) + return True + except subprocess.CalledProcessError: + return False + + +def main(): + """Main function to auto-stage reformatted files.""" + print("🔄 Auto-staging reformatted files...") + + # Get files that were modified by formatters + modified_files = get_modified_files() + + if not modified_files: + print("✅ No files were reformatted") + return 0 + + print(f"📝 Found {len(modified_files)} reformatted files:") + for file in modified_files: + print(f" - {file}") + + # Stage the reformatted files + if not stage_files(modified_files): + print("❌ Failed to stage reformatted files") + return 1 + + print("✅ Successfully staged reformatted files") + print("💡 These files will be included in your commit") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/scripts/post_commit_amend.py b/scripts/post_commit_amend.py new file mode 100644 index 0000000..e69de29 From 6157b345862fa15cfa2130a4a7d2c22954c02781 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 5 Aug 2025 01:31:00 +0000 Subject: [PATCH 4/5] Configure Black to automatically fix formatting issues Co-authored-by: stephenc211 --- .github/workflows/ci.yml | 2 +- .pre-commit-config.yaml | 1 + README.md | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a1693b3..680a24a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: - name: Lint (Ruff) run: uv run lint - name: Black - run: uv run black --check . + run: uv run black --fix . - name: Typecheck (mypy) run: uv run typecheck diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e6e569e..47cc212 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,6 +8,7 @@ repos: rev: 24.4.2 hooks: - id: black + args: [--fix] - repo: local hooks: - id: mypy diff --git a/README.md b/README.md index f5c904b..d15fa79 100644 --- a/README.md +++ b/README.md @@ -345,7 +345,7 @@ uv run pre-commit install ```bash uv run pytest # Run tests uv run lint # Lint code -uv run black --check . # Format check +uv run black --fix . # Format and fix code uv run typecheck # Type checking uv run security # Security audit uv build # Build package From a11ed1930f55abafcf6be2b861d2069bd240771a Mon Sep 17 00:00:00 2001 From: Stephen Collins Date: Mon, 4 Aug 2025 20:55:06 -0500 Subject: [PATCH 5/5] update pre-commit, CI/CD --- .github/workflows/ci.yml | 4 +- .pre-commit-config.yaml | 1 - scripts/auto_amend.py | 31 +++--- scripts/lint.py | 55 +++++++++-- scripts/security.py | 15 ++- uv.lock | 205 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 271 insertions(+), 40 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 680a24a..c3c150a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,10 +20,8 @@ jobs: uses: astral-sh/setup-uv@v6 - name: Sync dependencies run: uv sync --group dev - - name: Lint (Ruff) + - name: Lint run: uv run lint - - name: Black - run: uv run black --fix . - name: Typecheck (mypy) run: uv run typecheck diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 47cc212..e6e569e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,7 +8,6 @@ repos: rev: 24.4.2 hooks: - id: black - args: [--fix] - repo: local hooks: - id: mypy diff --git a/scripts/auto_amend.py b/scripts/auto_amend.py index 82ddde4..9ddf0d1 100644 --- a/scripts/auto_amend.py +++ b/scripts/auto_amend.py @@ -8,7 +8,6 @@ import subprocess import sys -from pathlib import Path def get_staged_files(): @@ -18,9 +17,9 @@ def get_staged_files(): ["git", "diff", "--cached", "--name-only"], capture_output=True, text=True, - check=True + check=True, ) - return result.stdout.strip().split('\n') if result.stdout.strip() else [] + return result.stdout.strip().split("\n") if result.stdout.strip() else [] except subprocess.CalledProcessError: return [] @@ -29,12 +28,9 @@ def get_modified_files(): """Get list of files that have been modified (including by formatters).""" try: result = subprocess.run( - ["git", "diff", "--name-only"], - capture_output=True, - text=True, - check=True + ["git", "diff", "--name-only"], capture_output=True, text=True, check=True ) - return result.stdout.strip().split('\n') if result.stdout.strip() else [] + return result.stdout.strip().split("\n") if result.stdout.strip() else [] except subprocess.CalledProcessError: return [] @@ -43,12 +39,9 @@ def stage_files(files): """Stage the specified files.""" if not files: return True - + try: - subprocess.run( - ["git", "add"] + files, - check=True - ) + subprocess.run(["git", "add"] + files, check=True) return True except subprocess.CalledProcessError: return False @@ -57,27 +50,27 @@ def stage_files(files): def main(): """Main function to auto-stage reformatted files.""" print("🔄 Auto-staging reformatted files...") - + # Get files that were modified by formatters modified_files = get_modified_files() - + if not modified_files: print("✅ No files were reformatted") return 0 - + print(f"📝 Found {len(modified_files)} reformatted files:") for file in modified_files: print(f" - {file}") - + # Stage the reformatted files if not stage_files(modified_files): print("❌ Failed to stage reformatted files") return 1 - + print("✅ Successfully staged reformatted files") print("💡 These files will be included in your commit") return 0 if __name__ == "__main__": - sys.exit(main()) \ No newline at end of file + sys.exit(main()) diff --git a/scripts/lint.py b/scripts/lint.py index 9359ce4..ae74982 100644 --- a/scripts/lint.py +++ b/scripts/lint.py @@ -1,17 +1,56 @@ #!/usr/bin/env python3 -"""Wrapper script for ruff linting.""" +"""Stepwise lint/format wrapper.""" + import sys import subprocess +def run_step(cmd, desc): + print(f"==> {desc}") + result = subprocess.run(cmd, capture_output=True, text=True) + if result.stdout: + print(result.stdout, end="") + if result.stderr: + print(result.stderr, end="", file=sys.stderr) + return result.returncode + + def main(): - """Run ruff check with the current directory.""" - try: - cmd = [sys.executable, "-m", "ruff", "check", "."] + sys.argv[1:] - result = subprocess.run(cmd, capture_output=False, text=True) - sys.exit(result.returncode) - except KeyboardInterrupt: - sys.exit(1) + # Parse command line arguments + fix_mode = "--fix" in sys.argv + # Remove --fix from argv to avoid passing it to subprocesses + args = [arg for arg in sys.argv[1:] if arg != "--fix"] + + if fix_mode: + steps = [ + { + "cmd": [sys.executable, "-m", "ruff", "check", "--fix", "."] + args, + "desc": "ruff check --fix", + }, + { + "cmd": [sys.executable, "-m", "black", "."] + args, + "desc": "black format", + }, + ] + else: + steps = [ + { + "cmd": [sys.executable, "-m", "ruff", "check", "."] + args, + "desc": "ruff check", + }, + { + "cmd": [sys.executable, "-m", "black", "--check", "."] + args, + "desc": "black check", + }, + ] + + failed = False + for step in steps: + code = run_step(step["cmd"], step["desc"]) + if code != 0: + failed = True + + sys.exit(1 if failed else 0) if __name__ == "__main__": diff --git a/scripts/security.py b/scripts/security.py index 3910814..46fa3c1 100644 --- a/scripts/security.py +++ b/scripts/security.py @@ -19,19 +19,16 @@ def run_pip_audit(): str(Path.home() / ".local/bin/pip-audit"), "/usr/local/bin/pip-audit", ] - + for path in pip_audit_paths: try: result = subprocess.run( - [path, "--local"], - capture_output=True, - text=True, - check=True + [path, "--local"], capture_output=True, text=True, check=True ) return True, result.stdout except (subprocess.CalledProcessError, FileNotFoundError): continue - + return False, "pip-audit not found. Install it with: pip install pip-audit" except Exception as e: return False, f"Error running pip-audit: {e}" @@ -41,9 +38,9 @@ def main(): """Main function to run security audit.""" print("🔒 Running security audit...") print("=" * 50) - + success, output = run_pip_audit() - + if success: print("✅ Security audit passed!") print("No known vulnerabilities found.") @@ -59,4 +56,4 @@ def main(): if __name__ == "__main__": - sys.exit(main()) \ No newline at end of file + sys.exit(main()) diff --git a/uv.lock b/uv.lock index 9143fd0..dbf726b 100644 --- a/uv.lock +++ b/uv.lock @@ -127,6 +127,15 @@ jupyter = [ { name = "tokenize-rt" }, ] +[[package]] +name = "boolean-py" +version = "5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/cf/85379f13b76f3a69bca86b60237978af17d6aa0bc5998978c3b8cf05abb2/boolean_py-5.0.tar.gz", hash = "sha256:60cbc4bad079753721d32649545505362c754e121570ada4658b852a3a318d95", size = 37047, upload-time = "2025-04-03T10:39:49.734Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/ca/78d423b324b8d77900030fa59c4aa9054261ef0925631cd2501dd015b7b7/boolean_py-5.0-py3-none-any.whl", hash = "sha256:ef28a70bd43115208441b53a045d1549e2f0ec6e3d08a9d142cbc41c1938e8d9", size = 26577, upload-time = "2025-04-03T10:39:48.449Z" }, +] + [[package]] name = "build" version = "1.2.2.post1" @@ -141,6 +150,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/84/c2/80633736cd183ee4a62107413def345f7e6e3c01563dbca1417363cf957e/build-1.2.2.post1-py3-none-any.whl", hash = "sha256:1d61c0887fa860c01971625baae8bdd338e517b836a2f70dd1f7aa3a6b2fc5b5", size = 22950, upload-time = "2024-10-06T17:22:23.299Z" }, ] +[[package]] +name = "cachecontrol" +version = "0.14.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "msgpack" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/3a/0cbeb04ea57d2493f3ec5a069a117ab467f85e4a10017c6d854ddcbff104/cachecontrol-0.14.3.tar.gz", hash = "sha256:73e7efec4b06b20d9267b441c1f733664f989fb8688391b670ca812d70795d11", size = 28985, upload-time = "2025-04-30T16:45:06.135Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/4c/800b0607b00b3fd20f1087f80ab53d6b4d005515b0f773e4831e37cfa83f/cachecontrol-0.14.3-py3-none-any.whl", hash = "sha256:b35e44a3113f17d2a31c1e6b27b9de6d4405f84ae51baa8c1d3cc5b633010cae", size = 21802, upload-time = "2025-04-30T16:45:03.863Z" }, +] + +[package.optional-dependencies] +filecache = [ + { name = "filelock" }, +] + [[package]] name = "cachetools" version = "5.5.2" @@ -386,6 +413,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/38/6a/69fc67e5266bff68a91bcb81dff8fb0aba4d79a78521a08812048913e16f/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:5aa1e32983d4443e310f726ee4b071ab7569f58eedfdd65e9675484a4eb67bd1", size = 4385593, upload-time = "2025-07-02T13:06:15.689Z" }, ] +[[package]] +name = "cyclonedx-python-lib" +version = "9.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "license-expression" }, + { name = "packageurl-python" }, + { name = "py-serializable" }, + { name = "sortedcontainers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/fc/abaad5482f7b59c9a0a9d8f354ce4ce23346d582a0d85730b559562bbeb4/cyclonedx_python_lib-9.1.0.tar.gz", hash = "sha256:86935f2c88a7b47a529b93c724dbd3e903bc573f6f8bd977628a7ca1b5dadea1", size = 1048735, upload-time = "2025-02-27T17:23:40.367Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/53/f1/f3be2e9820a2c26fa77622223e91f9c504e1581830930d477e06146073f4/cyclonedx_python_lib-9.1.0-py3-none-any.whl", hash = "sha256:55693fca8edaecc3363b24af14e82cc6e659eb1e8353e58b587c42652ce0fb52", size = 374968, upload-time = "2025-02-27T17:23:37.766Z" }, +] + [[package]] name = "debugpy" version = "1.8.15" @@ -416,6 +458,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, ] +[[package]] +name = "defusedxml" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" }, +] + [[package]] name = "distlib" version = "0.4.0" @@ -647,6 +698,7 @@ dev = [ { name = "mkdocs-material" }, { name = "mkdocstrings", extra = ["python"] }, { name = "mypy" }, + { name = "pip-audit" }, { name = "pre-commit" }, { name = "pytest" }, { name = "pytest-cov" }, @@ -686,6 +738,7 @@ dev = [ { name = "mkdocs-material", specifier = ">=9.5.17" }, { name = "mkdocstrings", extras = ["python"], specifier = ">=0.24.0" }, { name = "mypy", specifier = ">=1.10.0" }, + { name = "pip-audit", specifier = ">=2.9.0" }, { name = "pre-commit", specifier = ">=3.6.0" }, { name = "pytest", specifier = ">=8.4.1" }, { name = "pytest-cov", specifier = ">=5.0" }, @@ -932,6 +985,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d3/32/da7f44bcb1105d3e88a0b74ebdca50c59121d2ddf71c9e34ba47df7f3a56/keyring-25.6.0-py3-none-any.whl", hash = "sha256:552a3f7af126ece7ed5c89753650eec89c7eaae8617d0aa4d9ad2b75111266bd", size = 39085, upload-time = "2024-12-25T15:26:44.377Z" }, ] +[[package]] +name = "license-expression" +version = "30.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "boolean-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/71/d89bb0e71b1415453980fd32315f2a037aad9f7f70f695c7cec7035feb13/license_expression-30.4.4.tar.gz", hash = "sha256:73448f0aacd8d0808895bdc4b2c8e01a8d67646e4188f887375398c761f340fd", size = 186402, upload-time = "2025-07-22T11:13:32.17Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/40/791891d4c0c4dab4c5e187c17261cedc26285fd41541577f900470a45a4d/license_expression-30.4.4-py3-none-any.whl", hash = "sha256:421788fdcadb41f049d2dc934ce666626265aeccefddd25e162a26f23bcbf8a4", size = 120615, upload-time = "2025-07-22T11:13:31.217Z" }, +] + [[package]] name = "markdown" version = "3.8.2" @@ -1171,6 +1236,44 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2b/9f/7ba6f94fc1e9ac3d2b853fdff3035fb2fa5afbed898c4a72b8a020610594/more_itertools-10.7.0-py3-none-any.whl", hash = "sha256:d43980384673cb07d2f7d2d918c616b30c659c089ee23953f601d6609c67510e", size = 65278, upload-time = "2025-04-22T14:17:40.49Z" }, ] +[[package]] +name = "msgpack" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/45/b1/ea4f68038a18c77c9467400d166d74c4ffa536f34761f7983a104357e614/msgpack-1.1.1.tar.gz", hash = "sha256:77b79ce34a2bdab2594f490c8e80dd62a02d650b91a75159a63ec413b8d104cd", size = 173555, upload-time = "2025-06-13T06:52:51.324Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/83/97f24bf9848af23fe2ba04380388216defc49a8af6da0c28cc636d722502/msgpack-1.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:71ef05c1726884e44f8b1d1773604ab5d4d17729d8491403a705e649116c9558", size = 82728, upload-time = "2025-06-13T06:51:50.68Z" }, + { url = "https://files.pythonhosted.org/packages/aa/7f/2eaa388267a78401f6e182662b08a588ef4f3de6f0eab1ec09736a7aaa2b/msgpack-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:36043272c6aede309d29d56851f8841ba907a1a3d04435e43e8a19928e243c1d", size = 79279, upload-time = "2025-06-13T06:51:51.72Z" }, + { url = "https://files.pythonhosted.org/packages/f8/46/31eb60f4452c96161e4dfd26dbca562b4ec68c72e4ad07d9566d7ea35e8a/msgpack-1.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a32747b1b39c3ac27d0670122b57e6e57f28eefb725e0b625618d1b59bf9d1e0", size = 423859, upload-time = "2025-06-13T06:51:52.749Z" }, + { url = "https://files.pythonhosted.org/packages/45/16/a20fa8c32825cc7ae8457fab45670c7a8996d7746ce80ce41cc51e3b2bd7/msgpack-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a8b10fdb84a43e50d38057b06901ec9da52baac6983d3f709d8507f3889d43f", size = 429975, upload-time = "2025-06-13T06:51:53.97Z" }, + { url = "https://files.pythonhosted.org/packages/86/ea/6c958e07692367feeb1a1594d35e22b62f7f476f3c568b002a5ea09d443d/msgpack-1.1.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba0c325c3f485dc54ec298d8b024e134acf07c10d494ffa24373bea729acf704", size = 413528, upload-time = "2025-06-13T06:51:55.507Z" }, + { url = "https://files.pythonhosted.org/packages/75/05/ac84063c5dae79722bda9f68b878dc31fc3059adb8633c79f1e82c2cd946/msgpack-1.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:88daaf7d146e48ec71212ce21109b66e06a98e5e44dca47d853cbfe171d6c8d2", size = 413338, upload-time = "2025-06-13T06:51:57.023Z" }, + { url = "https://files.pythonhosted.org/packages/69/e8/fe86b082c781d3e1c09ca0f4dacd457ede60a13119b6ce939efe2ea77b76/msgpack-1.1.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d8b55ea20dc59b181d3f47103f113e6f28a5e1c89fd5b67b9140edb442ab67f2", size = 422658, upload-time = "2025-06-13T06:51:58.419Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2b/bafc9924df52d8f3bb7c00d24e57be477f4d0f967c0a31ef5e2225e035c7/msgpack-1.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4a28e8072ae9779f20427af07f53bbb8b4aa81151054e882aee333b158da8752", size = 427124, upload-time = "2025-06-13T06:51:59.969Z" }, + { url = "https://files.pythonhosted.org/packages/a2/3b/1f717e17e53e0ed0b68fa59e9188f3f610c79d7151f0e52ff3cd8eb6b2dc/msgpack-1.1.1-cp311-cp311-win32.whl", hash = "sha256:7da8831f9a0fdb526621ba09a281fadc58ea12701bc709e7b8cbc362feabc295", size = 65016, upload-time = "2025-06-13T06:52:01.294Z" }, + { url = "https://files.pythonhosted.org/packages/48/45/9d1780768d3b249accecc5a38c725eb1e203d44a191f7b7ff1941f7df60c/msgpack-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:5fd1b58e1431008a57247d6e7cc4faa41c3607e8e7d4aaf81f7c29ea013cb458", size = 72267, upload-time = "2025-06-13T06:52:02.568Z" }, + { url = "https://files.pythonhosted.org/packages/e3/26/389b9c593eda2b8551b2e7126ad3a06af6f9b44274eb3a4f054d48ff7e47/msgpack-1.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ae497b11f4c21558d95de9f64fff7053544f4d1a17731c866143ed6bb4591238", size = 82359, upload-time = "2025-06-13T06:52:03.909Z" }, + { url = "https://files.pythonhosted.org/packages/ab/65/7d1de38c8a22cf8b1551469159d4b6cf49be2126adc2482de50976084d78/msgpack-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:33be9ab121df9b6b461ff91baac6f2731f83d9b27ed948c5b9d1978ae28bf157", size = 79172, upload-time = "2025-06-13T06:52:05.246Z" }, + { url = "https://files.pythonhosted.org/packages/0f/bd/cacf208b64d9577a62c74b677e1ada005caa9b69a05a599889d6fc2ab20a/msgpack-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f64ae8fe7ffba251fecb8408540c34ee9df1c26674c50c4544d72dbf792e5ce", size = 425013, upload-time = "2025-06-13T06:52:06.341Z" }, + { url = "https://files.pythonhosted.org/packages/4d/ec/fd869e2567cc9c01278a736cfd1697941ba0d4b81a43e0aa2e8d71dab208/msgpack-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a494554874691720ba5891c9b0b39474ba43ffb1aaf32a5dac874effb1619e1a", size = 426905, upload-time = "2025-06-13T06:52:07.501Z" }, + { url = "https://files.pythonhosted.org/packages/55/2a/35860f33229075bce803a5593d046d8b489d7ba2fc85701e714fc1aaf898/msgpack-1.1.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb643284ab0ed26f6957d969fe0dd8bb17beb567beb8998140b5e38a90974f6c", size = 407336, upload-time = "2025-06-13T06:52:09.047Z" }, + { url = "https://files.pythonhosted.org/packages/8c/16/69ed8f3ada150bf92745fb4921bd621fd2cdf5a42e25eb50bcc57a5328f0/msgpack-1.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d275a9e3c81b1093c060c3837e580c37f47c51eca031f7b5fb76f7b8470f5f9b", size = 409485, upload-time = "2025-06-13T06:52:10.382Z" }, + { url = "https://files.pythonhosted.org/packages/c6/b6/0c398039e4c6d0b2e37c61d7e0e9d13439f91f780686deb8ee64ecf1ae71/msgpack-1.1.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4fd6b577e4541676e0cc9ddc1709d25014d3ad9a66caa19962c4f5de30fc09ef", size = 412182, upload-time = "2025-06-13T06:52:11.644Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d0/0cf4a6ecb9bc960d624c93effaeaae75cbf00b3bc4a54f35c8507273cda1/msgpack-1.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb29aaa613c0a1c40d1af111abf025f1732cab333f96f285d6a93b934738a68a", size = 419883, upload-time = "2025-06-13T06:52:12.806Z" }, + { url = "https://files.pythonhosted.org/packages/62/83/9697c211720fa71a2dfb632cad6196a8af3abea56eece220fde4674dc44b/msgpack-1.1.1-cp312-cp312-win32.whl", hash = "sha256:870b9a626280c86cff9c576ec0d9cbcc54a1e5ebda9cd26dab12baf41fee218c", size = 65406, upload-time = "2025-06-13T06:52:14.271Z" }, + { url = "https://files.pythonhosted.org/packages/c0/23/0abb886e80eab08f5e8c485d6f13924028602829f63b8f5fa25a06636628/msgpack-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:5692095123007180dca3e788bb4c399cc26626da51629a31d40207cb262e67f4", size = 72558, upload-time = "2025-06-13T06:52:15.252Z" }, + { url = "https://files.pythonhosted.org/packages/a1/38/561f01cf3577430b59b340b51329803d3a5bf6a45864a55f4ef308ac11e3/msgpack-1.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3765afa6bd4832fc11c3749be4ba4b69a0e8d7b728f78e68120a157a4c5d41f0", size = 81677, upload-time = "2025-06-13T06:52:16.64Z" }, + { url = "https://files.pythonhosted.org/packages/09/48/54a89579ea36b6ae0ee001cba8c61f776451fad3c9306cd80f5b5c55be87/msgpack-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8ddb2bcfd1a8b9e431c8d6f4f7db0773084e107730ecf3472f1dfe9ad583f3d9", size = 78603, upload-time = "2025-06-13T06:52:17.843Z" }, + { url = "https://files.pythonhosted.org/packages/a0/60/daba2699b308e95ae792cdc2ef092a38eb5ee422f9d2fbd4101526d8a210/msgpack-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:196a736f0526a03653d829d7d4c5500a97eea3648aebfd4b6743875f28aa2af8", size = 420504, upload-time = "2025-06-13T06:52:18.982Z" }, + { url = "https://files.pythonhosted.org/packages/20/22/2ebae7ae43cd8f2debc35c631172ddf14e2a87ffcc04cf43ff9df9fff0d3/msgpack-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d592d06e3cc2f537ceeeb23d38799c6ad83255289bb84c2e5792e5a8dea268a", size = 423749, upload-time = "2025-06-13T06:52:20.211Z" }, + { url = "https://files.pythonhosted.org/packages/40/1b/54c08dd5452427e1179a40b4b607e37e2664bca1c790c60c442c8e972e47/msgpack-1.1.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4df2311b0ce24f06ba253fda361f938dfecd7b961576f9be3f3fbd60e87130ac", size = 404458, upload-time = "2025-06-13T06:52:21.429Z" }, + { url = "https://files.pythonhosted.org/packages/2e/60/6bb17e9ffb080616a51f09928fdd5cac1353c9becc6c4a8abd4e57269a16/msgpack-1.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e4141c5a32b5e37905b5940aacbc59739f036930367d7acce7a64e4dec1f5e0b", size = 405976, upload-time = "2025-06-13T06:52:22.995Z" }, + { url = "https://files.pythonhosted.org/packages/ee/97/88983e266572e8707c1f4b99c8fd04f9eb97b43f2db40e3172d87d8642db/msgpack-1.1.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b1ce7f41670c5a69e1389420436f41385b1aa2504c3b0c30620764b15dded2e7", size = 408607, upload-time = "2025-06-13T06:52:24.152Z" }, + { url = "https://files.pythonhosted.org/packages/bc/66/36c78af2efaffcc15a5a61ae0df53a1d025f2680122e2a9eb8442fed3ae4/msgpack-1.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4147151acabb9caed4e474c3344181e91ff7a388b888f1e19ea04f7e73dc7ad5", size = 424172, upload-time = "2025-06-13T06:52:25.704Z" }, + { url = "https://files.pythonhosted.org/packages/8c/87/a75eb622b555708fe0427fab96056d39d4c9892b0c784b3a721088c7ee37/msgpack-1.1.1-cp313-cp313-win32.whl", hash = "sha256:500e85823a27d6d9bba1d057c871b4210c1dd6fb01fbb764e37e4e8847376323", size = 65347, upload-time = "2025-06-13T06:52:26.846Z" }, + { url = "https://files.pythonhosted.org/packages/ca/91/7dc28d5e2a11a5ad804cf2b7f7a5fcb1eb5a4966d66a5d2b41aee6376543/msgpack-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:6d489fba546295983abd142812bda76b57e33d0b9f5d5b71c09a583285506f69", size = 72341, upload-time = "2025-06-13T06:52:27.835Z" }, +] + [[package]] name = "mypy" version = "1.17.0" @@ -1295,6 +1398,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8a/91/1f1cf577f745e956b276a8b1d3d76fa7a6ee0c2b05db3b001b900f2c71db/openai-1.97.0-py3-none-any.whl", hash = "sha256:a1c24d96f4609f3f7f51c9e1c2606d97cc6e334833438659cfd687e9c972c610", size = 764953, upload-time = "2025-07-16T16:37:33.135Z" }, ] +[[package]] +name = "packageurl-python" +version = "0.17.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/37/0f/66c682b6d6844247a5ba04e1be51ee782d1a921ebffc8fa0b3f4d520d885/packageurl_python-0.17.3.tar.gz", hash = "sha256:719995f0c7f706890277ba57ec95afcaa9696c836a7675770a1279b01a41f7be", size = 43004, upload-time = "2025-08-01T03:24:35.307Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/39/71ca0df9154070661a967edc2a518d612e94782cfe502ab09f180efc224c/packageurl_python-0.17.3-py3-none-any.whl", hash = "sha256:f51b5aab570159f07258c8e998e9972ff3bf060da16b7334a42bd9f9737777d9", size = 29942, upload-time = "2025-08-01T03:24:33.131Z" }, +] + [[package]] name = "packaging" version = "25.0" @@ -1343,6 +1455,60 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" }, ] +[[package]] +name = "pip" +version = "25.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/16/650289cd3f43d5a2fadfd98c68bd1e1e7f2550a1a5326768cddfbcedb2c5/pip-25.2.tar.gz", hash = "sha256:578283f006390f85bb6282dffb876454593d637f5d1be494b5202ce4877e71f2", size = 1840021, upload-time = "2025-07-30T21:50:15.401Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/3f/945ef7ab14dc4f9d7f40288d2df998d1837ee0888ec3659c813487572faa/pip-25.2-py3-none-any.whl", hash = "sha256:6d67a2b4e7f14d8b31b8b52648866fa717f45a1eb70e83002f4331d07e953717", size = 1752557, upload-time = "2025-07-30T21:50:13.323Z" }, +] + +[[package]] +name = "pip-api" +version = "0.0.34" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pip" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/f1/ee85f8c7e82bccf90a3c7aad22863cc6e20057860a1361083cd2adacb92e/pip_api-0.0.34.tar.gz", hash = "sha256:9b75e958f14c5a2614bae415f2adf7eeb54d50a2cfbe7e24fd4826471bac3625", size = 123017, upload-time = "2024-07-09T20:32:30.641Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/f7/ebf5003e1065fd00b4cbef53bf0a65c3d3e1b599b676d5383ccb7a8b88ba/pip_api-0.0.34-py3-none-any.whl", hash = "sha256:8b2d7d7c37f2447373aa2cf8b1f60a2f2b27a84e1e9e0294a3f6ef10eb3ba6bb", size = 120369, upload-time = "2024-07-09T20:32:29.099Z" }, +] + +[[package]] +name = "pip-audit" +version = "2.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachecontrol", extra = ["filecache"] }, + { name = "cyclonedx-python-lib" }, + { name = "packaging" }, + { name = "pip-api" }, + { name = "pip-requirements-parser" }, + { name = "platformdirs" }, + { name = "requests" }, + { name = "rich" }, + { name = "toml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cc/7f/28fad19a9806f796f13192ab6974c07c4a04d9cbb8e30dd895c3c11ce7ee/pip_audit-2.9.0.tar.gz", hash = "sha256:0b998410b58339d7a231e5aa004326a294e4c7c6295289cdc9d5e1ef07b1f44d", size = 52089, upload-time = "2025-04-07T16:45:23.679Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/9e/f4dfd9d3dadb6d6dc9406f1111062f871e2e248ed7b584cca6020baf2ac1/pip_audit-2.9.0-py3-none-any.whl", hash = "sha256:348b16e60895749a0839875d7cc27ebd692e1584ebe5d5cb145941c8e25a80bd", size = 58634, upload-time = "2025-04-07T16:45:22.056Z" }, +] + +[[package]] +name = "pip-requirements-parser" +version = "32.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "pyparsing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/2a/63b574101850e7f7b306ddbdb02cb294380d37948140eecd468fae392b54/pip-requirements-parser-32.0.1.tar.gz", hash = "sha256:b4fa3a7a0be38243123cf9d1f3518da10c51bdb165a2b2985566247f9155a7d3", size = 209359, upload-time = "2022-12-21T15:25:22.732Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/d0/d04f1d1e064ac901439699ee097f58688caadea42498ec9c4b4ad2ef84ab/pip_requirements_parser-32.0.1-py3-none-any.whl", hash = "sha256:4659bc2a667783e7a15d190f6fccf8b2486685b6dba4c19c3876314769c57526", size = 35648, upload-time = "2022-12-21T15:25:21.046Z" }, +] + [[package]] name = "platformdirs" version = "4.3.8" @@ -1422,6 +1588,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, ] +[[package]] +name = "py-serializable" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "defusedxml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/73/21/d250cfca8ff30c2e5a7447bc13861541126ce9bd4426cd5d0c9f08b5547d/py_serializable-2.1.0.tar.gz", hash = "sha256:9d5db56154a867a9b897c0163b33a793c804c80cee984116d02d49e4578fc103", size = 52368, upload-time = "2025-07-21T09:56:48.07Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/bf/7595e817906a29453ba4d99394e781b6fabe55d21f3c15d240f85dd06bb1/py_serializable-2.1.0-py3-none-any.whl", hash = "sha256:b56d5d686b5a03ba4f4db5e769dc32336e142fc3bd4d68a8c25579ebb0a67304", size = 23045, upload-time = "2025-07-21T09:56:46.848Z" }, +] + [[package]] name = "pyasn1" version = "0.6.1" @@ -1554,6 +1732,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/98/d4/10bb14004d3c792811e05e21b5e5dcae805aacb739bd12a0540967b99592/pymdown_extensions-10.16-py3-none-any.whl", hash = "sha256:f5dd064a4db588cb2d95229fc4ee63a1b16cc8b4d0e6145c0899ed8723da1df2", size = 266143, upload-time = "2025-06-21T17:56:35.356Z" }, ] +[[package]] +name = "pyparsing" +version = "3.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/22/f1129e69d94ffff626bdb5c835506b3a5b4f3d070f17ea295e12c2c6f60f/pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be", size = 1088608, upload-time = "2025-03-25T05:01:28.114Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/e7/df2285f3d08fee213f2d041540fa4fc9ca6c2d44cf36d3a035bf2a8d2bcc/pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf", size = 111120, upload-time = "2025-03-25T05:01:24.908Z" }, +] + [[package]] name = "pyproject-hooks" version = "1.2.0" @@ -1865,6 +2052,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, ] +[[package]] +name = "sortedcontainers" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" }, +] + [[package]] name = "stack-data" version = "0.6.3" @@ -1897,6 +2093,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl", hash = "sha256:a152bf4f249c847a66497a4a95f63376ed68ac6abf092a2f7cfb29d044ecff44", size = 6004, upload-time = "2025-05-23T23:47:58.812Z" }, ] +[[package]] +name = "toml" +version = "0.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253, upload-time = "2020-11-01T01:40:22.204Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" }, +] + [[package]] name = "tomli" version = "2.2.1"