From 5c519364cb658b0649b45f884550262a7bf72d14 Mon Sep 17 00:00:00 2001 From: Ali Karbassi Date: Thu, 24 Jul 2025 23:08:25 -0500 Subject: [PATCH 1/4] spec --- .kiro/specs/black-to-ruff-migration/design.md | 153 --------- .../black-to-ruff-migration/requirements.md | 52 --- .kiro/specs/black-to-ruff-migration/tasks.md | 66 ---- .kiro/specs/python-modernization/design.md | 324 ++++++++++++++++++ .../python-modernization/requirements.md | 73 ++++ .kiro/specs/python-modernization/tasks.md | 134 ++++++++ 6 files changed, 531 insertions(+), 271 deletions(-) delete mode 100644 .kiro/specs/black-to-ruff-migration/design.md delete mode 100644 .kiro/specs/black-to-ruff-migration/requirements.md delete mode 100644 .kiro/specs/black-to-ruff-migration/tasks.md create mode 100644 .kiro/specs/python-modernization/design.md create mode 100644 .kiro/specs/python-modernization/requirements.md create mode 100644 .kiro/specs/python-modernization/tasks.md diff --git a/.kiro/specs/black-to-ruff-migration/design.md b/.kiro/specs/black-to-ruff-migration/design.md deleted file mode 100644 index b4b2a9e5..00000000 --- a/.kiro/specs/black-to-ruff-migration/design.md +++ /dev/null @@ -1,153 +0,0 @@ -# Design Document - -## Overview - -This design outlines the migration from Black and isort to Ruff for the We All Code Django project. Ruff is a fast Python linter and formatter written in Rust that can replace both Black (formatting) and isort (import sorting) with a single tool. The migration will maintain existing code style preferences while consolidating tooling and improving performance. - -## Architecture - -### Tool Replacement Strategy - -The migration follows a direct replacement approach: - -- **Black** → **Ruff formatter** (maintains Black-compatible formatting) -- **isort** → **Ruff import sorting** (maintains isort-compatible import organization) - -### Configuration Approach - -Ruff will be configured in `pyproject.toml` using the `[tool.ruff]` section with subsections for: - -- General settings (line length, target Python version, exclusions) -- Formatter settings (`[tool.ruff.format]`) -- Import sorting settings (`[tool.ruff.isort]`) -- Linting rules (`[tool.ruff.lint]`) - -## Components and Interfaces - -### Configuration Files - -#### pyproject.toml Updates - -**Rationale**: Centralizing all tool configuration in pyproject.toml follows Python packaging standards and simplifies maintenance. - -- Remove `[tool.black]` section -- Remove `[tool.isort]` section -- Add comprehensive `[tool.ruff]` configuration -- Add Ruff as a development dependency in the "# Development & Debugging" section alongside django-debug-toolbar -- Remove Black and isort from dependencies if present - -#### Pre-commit Configuration Updates - -**Rationale**: Maintaining pre-commit integration ensures code quality checks remain automated and consistent across the development team. - -- Replace Black hook (currently using psf/black rev 23.3.0) with Ruff formatter hook -- Replace isort hook (currently using pycqa/isort rev 5.12.0) with Ruff import sorting hook -- Maintain existing exclusion patterns for migrations and .vscode folders -- Keep all other pre-commit hooks unchanged (trailing-whitespace, end-of-file-fixer, etc.) - -#### Documentation Updates - -**Rationale**: Comprehensive documentation updates ensure all team members and new contributors understand the current tooling and maintain consistency across the project. - -- Update `.kiro/steering/tech.md` Code Quality Tools section to reference Ruff instead of Black and isort -- Update `.kiro/steering/structure.md` Development Conventions section to reference Ruff formatting -- Check and update `README.md` if it contains references to Black or isort -- Update any developer setup instructions to include Ruff-specific commands -- Ensure all documentation maintains consistency with Ruff usage - -### Ruff Configuration Sections - -Based on the current Black and isort configuration, Ruff will be configured as follows: - -#### Core Settings - -```toml -[tool.ruff] -target-version = "py311" -exclude = [migrations, build artifacts, etc.] -``` - -#### Import Sorting Settings - -**Rationale**: Django-aware import sorting maintains the existing project's import organization patterns while leveraging Ruff's performance benefits. This ensures proper separation of Django imports from other third-party libraries, maintaining the project's existing import organization standards. - - - -## Data Models - -No data models are affected by this migration as it only changes development tooling configuration. - -## Error Handling - -### Migration Validation - -- Verify Ruff produces equivalent formatting to Black on existing codebase -- Ensure import sorting maintains Django-aware section organization -- Test pre-commit hooks function correctly with new configuration - -### Rollback Strategy - -- Keep backup of original Black/isort configuration -- Document steps to revert if issues are discovered -- Maintain git history for easy rollback - -## Testing Strategy - -### Configuration Testing - -1. **Format Consistency Test**: Run Ruff formatter on existing codebase and verify minimal changes -2. **Import Sorting Test**: Verify Ruff import sorting maintains Django section organization -3. **Pre-commit Integration Test**: Test pre-commit hooks with Ruff configuration -4. **Exclusion Pattern Test**: Verify migrations and other excluded files are not processed - -### Validation Steps - -**Rationale**: These validation steps ensure the migration maintains code quality and formatting consistency while verifying all requirements are met. - -1. Install Ruff and configure in pyproject.toml -2. Run `docker compose run --rm app uv run ruff format --check .` on codebase to verify compatibility (respects pyproject.toml settings) -3. Run `docker compose run --rm app uv run ruff check --select I .` to test import sorting (respects pyproject.toml settings) -4. Test pre-commit hooks in containerized environment using `docker compose run --rm app pre-commit run --all-files` -5. Compare output with existing Black/isort formatting to ensure consistency -6. Verify uv commands work correctly with Ruff (addresses Requirement 4.4) -7. Confirm migrations are properly excluded from formatting and linting - -### Performance Verification - -- Measure formatting speed improvement with Ruff vs Black+isort -- Verify pre-commit hook execution time improvement - -## Implementation Considerations - -### Dependency Management - -**Rationale**: Proper dependency management ensures Ruff is available in all development environments and follows the project's existing organizational patterns. - -- Ruff will be added to the "# Development & Debugging" section in pyproject.toml dependencies (addresses Requirement 4.1, 4.3) -- Black and isort configurations will be removed from pyproject.toml (addresses Requirement 4.2) -- uv will handle Ruff installation and version management (addresses Requirement 4.4) -- Ruff will be placed appropriately within the development tools section to maintain logical grouping - -### Backward Compatibility - -- Ruff's Black-compatible formatter ensures existing code style is maintained -- Django-aware import sorting preserves current import organization -- Line length and exclusion patterns remain unchanged - -### Team Adoption - -- Developers will need to update their local pre-commit hooks -- IDE integrations may need to be updated to use Ruff instead of Black -- Documentation will guide developers through the transition - -## Requirements Traceability - -This design addresses all requirements from the requirements document: - -**Requirement 1 (Tool Replacement)**: Addressed through pyproject.toml configuration sections that replace Black and isort with Ruff while maintaining migration exclusions, and Django-aware import sorting. - -**Requirement 2 (Pre-commit Integration)**: Addressed through pre-commit configuration updates that replace Black and isort hooks with Ruff equivalents while maintaining existing exclusion patterns. - -**Requirement 3 (Documentation Updates)**: Addressed through systematic updates to steering documents, README, and developer setup instructions to reflect Ruff usage consistently. - -**Requirement 4 (Dependency Management)**: Addressed through adding Ruff to development dependencies and removing Black/isort configurations, with uv handling installation and version management. diff --git a/.kiro/specs/black-to-ruff-migration/requirements.md b/.kiro/specs/black-to-ruff-migration/requirements.md deleted file mode 100644 index 4602d76e..00000000 --- a/.kiro/specs/black-to-ruff-migration/requirements.md +++ /dev/null @@ -1,52 +0,0 @@ -# Requirements Document - -## Introduction - -This feature involves migrating the We All Code Django project from using Black (code formatter) and isort (import sorter) to Ruff, which is a faster, all-in-one Python linter and formatter that can replace both tools. Ruff provides the same formatting capabilities as Black while also offering linting and import sorting functionality in a single, faster tool. - -## Requirements - -### Requirement 1: Tool Replacement - -**User Story:** As a developer, I want to use Ruff instead of Black and isort, so that I have faster code formatting and linting with a single tool. - -#### Acceptance Criteria - -1. WHEN the project is configured THEN Ruff SHALL replace Black as the code formatter -2. WHEN the project is configured THEN Ruff SHALL replace isort for import sorting -3. WHEN Ruff is configured THEN it SHALL exclude migrations from formatting (same as current Black config) -4. WHEN Ruff is configured THEN it SHALL maintain Django-aware import sorting sections with proper separation of Django imports from other third-party libraries - -### Requirement 2: Pre-commit Integration - -**User Story:** As a developer, I want the pre-commit hooks updated to use Ruff, so that code quality checks run automatically before commits. - -#### Acceptance Criteria - -1. WHEN pre-commit hooks are updated THEN they SHALL use Ruff instead of Black and isort -2. WHEN pre-commit runs THEN it SHALL format code using Ruff -3. WHEN pre-commit runs THEN it SHALL sort imports using Ruff -4. WHEN pre-commit runs THEN it SHALL maintain the same exclusion patterns as before - -### Requirement 3: Documentation Updates - -**User Story:** As a developer, I want all project documentation updated to reflect the Ruff migration, so that new contributors understand the current tooling and existing developers have accurate reference materials. - -#### Acceptance Criteria - -1. WHEN documentation is updated THEN .kiro/steering/tech.md SHALL reference Ruff instead of Black and isort in the Code Quality Tools section -2. WHEN documentation is updated THEN .kiro/steering/structure.md SHALL reference Ruff formatting conventions instead of Black -3. WHEN documentation is updated THEN README.md SHALL be updated if it contains references to Black or isort -4. WHEN documentation is updated THEN any developer setup instructions SHALL include Ruff-specific commands -5. WHEN documentation is updated THEN all references to code formatting tools SHALL be consistent with Ruff usage - -### Requirement 4: Dependency Management - -**User Story:** As a developer, I want Ruff to be added as a project dependency, so that it's available in the development environment. - -#### Acceptance Criteria - -1. WHEN dependencies are updated THEN Ruff SHALL be added to pyproject.toml -2. WHEN dependencies are updated THEN Black and isort SHALL be removed from dependencies (if present) -3. WHEN Ruff is added THEN it SHALL be in the appropriate dependency group for development tools -4. WHEN the configuration is complete THEN Ruff SHALL be usable via uv commands diff --git a/.kiro/specs/black-to-ruff-migration/tasks.md b/.kiro/specs/black-to-ruff-migration/tasks.md deleted file mode 100644 index fdc00915..00000000 --- a/.kiro/specs/black-to-ruff-migration/tasks.md +++ /dev/null @@ -1,66 +0,0 @@ -# Implementation Plan - -- [x] 1. Configure Ruff in pyproject.toml - - - Remove existing [tool.black] and [tool.isort] configuration sections - - Add comprehensive [tool.ruff] configuration with general settings (target-version = "py311", exclude migrations) - - Add Ruff as a development dependency in the "# Development & Debugging" section alongside django-debug-toolbar - - Add [tool.ruff.lint] section with comprehensive linting rules - - _Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 4.1, 4.2, 4.3, 4.4_ - -- [x] 2. Update pre-commit configuration - - - Replace Black hook (psf/black) with Ruff formatter hook (charliermarsh/ruff-pre-commit) - - Replace isort hook (pycqa/isort) with Ruff import sorting hook using ruff-check - - Maintain existing exclusion patterns for migrations and .vscode folders - - Keep all other pre-commit hooks unchanged (trailing-whitespace, end-of-file-fixer, etc.) - - _Requirements: 2.1, 2.2, 2.3, 2.4_ - -- [x] 3. Update documentation files -- [x] 3.1 Update .kiro/steering/tech.md - - - Replace Black and isort references with Ruff in Code Quality Tools section - - Update tool descriptions to reflect Ruff's combined formatting and linting capabilities - - _Requirements: 3.1, 3.6_ - -- [x] 3.2 Update .kiro/steering/structure.md - - - Replace Black formatter references with Ruff in Development Conventions section - - Update code style documentation to reference Ruff instead of Black and isort - - Maintain Django-aware import sorting documentation - - _Requirements: 3.2, 3.6_ - -- [x] 3.3 Check and update README.md if needed - - - Search for any references to Black or isort in README.md (none found) - - Update developer setup instructions to include Ruff-specific commands if present (none needed) - - Ensure consistency with Ruff usage throughout documentation - - _Requirements: 3.3, 3.4, 3.5_ - -- [-] 4. Enhance Ruff configuration for complete migration -- [ ] 4.1 Complete pyproject.toml Ruff configuration - - - Add exclude patterns for migrations and other directories - - Add [tool.ruff.isort] section with Django-aware import sorting (known-django, section-order, combine-as-imports) - - _Requirements: 1.1, 1.2, 1.3, 1.4, 1.5_ - -- [x] 5. Test Ruff configuration -- [x] 5.1 Validate formatting compatibility - - - Run `docker compose run --rm app uv run ruff format --check .` on existing codebase to verify minimal changes - - Compare Ruff output with current formatting to ensure consistency - - Verify migrations are excluded from formatting (configured in pyproject.toml) - - _Requirements: 1.1, 1.3, 1.4_ - -- [x] 5.2 Validate import sorting - - - Run `docker compose run --rm app uv run ruff check --select I .` to test import sorting functionality - - Verify Django-aware section organization is maintained - - Test that import sorting follows isort-compatible behavior using `docker compose run --rm app uv run ruff check --select I --fix .` - - _Requirements: 1.2, 1.5_ - -- [x] 5.3 Test pre-commit integration - - Run `docker compose run --rm app pre-commit run --all-files` to test pre-commit hooks in containerized environment - - Test that Ruff hooks execute correctly and maintain exclusion patterns - - Verify pre-commit performance improvement with Ruff using `docker compose run --rm app pre-commit run ruff-format ruff-check` - - _Requirements: 2.1, 2.2, 2.3, 2.4_ diff --git a/.kiro/specs/python-modernization/design.md b/.kiro/specs/python-modernization/design.md new file mode 100644 index 00000000..b4f66da8 --- /dev/null +++ b/.kiro/specs/python-modernization/design.md @@ -0,0 +1,324 @@ +# Design Document + +## Overview + +This design outlines the systematic approach to modernizing the We All Code Django application from Python 3.11 to Python 3.13, incorporating modern language features and replacing custom utility functions with native Python equivalents. The modernization will be performed incrementally to ensure stability and maintainability. + +## Architecture + +### Python Version Migration Strategy + +The migration follows a layered approach: + +1. **Infrastructure Layer**: Update Docker, uv configuration, and deployment scripts +2. **Dependency Layer**: Verify and update all Python dependencies for 3.13 compatibility +3. **Code Layer**: Modernize Python syntax and patterns throughout the codebase +4. **Testing Layer**: Ensure all tests pass and add new tests for modernized code + +### Compatibility Matrix + +| Component | Current Version | Target Version | Compatibility Status | +| ---------- | --------------- | -------------- | ------------------------------- | +| Python | 3.11.9 | 3.13.x | ✅ Supported | +| Django | 5.2.x | 5.2.x | ✅ Python 3.13 compatible | +| PostgreSQL | 16 | 16 | ✅ No changes needed | +| Docker | Current | Current | ✅ Python 3.13 images available | + +## Components and Interfaces + +### 1. Configuration Updates + +#### Docker Configuration + +- **Dockerfile**: Update base image from `python:3.11.9` to `python:3.13` +- **docker-compose.yml**: No changes required (inherits from Dockerfile) +- **.python-version**: Update from `3.11` to `3.13` + +#### Package Configuration + +- **pyproject.toml**: Update `requires-python` constraint to `>=3.13,<3.14` +- **uv.lock**: Regenerate lock file for Python 3.13 compatibility + +### 2. Code Modernization + +#### Type Annotations Enhancement + +```python +# Before (Python 3.11 style) +from typing import List, Dict, Optional, Union + +def process_items(items: List[str], config: Optional[Dict[str, str]] = None) -> Union[str, None]: + pass + +# After (Python 3.13 style) +def process_items(items: list[str], config: dict[str, str] | None = None) -> str | None: + pass +``` + +#### Utility Function Modernization + +The `coderdojochi/util.py` file will be updated to replace custom implementations with native Python functions: + +```python +# Current custom batches function - REMOVE ENTIRELY +def batches(items, batch_size): + for start_index in range(0, len(items), batch_size): + yield items[start_index : start_index + batch_size] + +# Replace with native itertools.batched (Python 3.12+) +from itertools import batched + +# Usage changes from: +for recipients_batch in batches(recipients, batch_size): + # process batch + +# To: +for recipients_batch in batched(recipients, batch_size): + # process batch +``` + +#### Native Function Replacements + +**Available Native Replacements:** + +- `batches()` → `itertools.batched()` (Python 3.12+) +- Custom list chunking → `itertools.batched()` +- Manual enumeration loops → `enumerate()` +- Index-based iteration → `zip()` or `itertools.zip_longest()` +- Custom grouping → `itertools.groupby()` +- Manual filtering → `filter()` with lambda or comprehensions +- Custom mapping → `map()` or comprehensions +- String joining patterns → `str.join()` +- File path operations → `pathlib.Path` methods + +#### Email Function Enhancement + +The email utility function will be modernized with: + +- Proper type annotations +- Modern union syntax +- Enhanced error handling +- Better parameter validation + +### 3. Model Layer Updates + +#### Modern Property Decorators + +Update model properties to use modern Python features: + +```python +# Enhanced with type hints and modern patterns +@property +def end_date(self) -> datetime: + """Calculate session end date based on course duration.""" + return (self.old_end_date + if self.old_end_date + else self.start_date + self.course.duration) +``` + +#### String Representation + +Ensure all `__str__` methods use f-strings consistently: + +```python +def __str__(self) -> str: + date = formats.date_format(self.start_date, "SHORT_DATETIME_FORMAT") + return f"{self.course.title} | {date}" +``` + +### 4. Comprehensive Native Function Audit + +#### Code Pattern Analysis + +A systematic review will identify custom implementations that can be replaced: + +**String Operations:** + +- Custom string formatting → f-strings and `str.format_map()` +- Manual string joining → `str.join()` +- Custom padding/alignment → `str.center()`, `str.ljust()`, `str.rjust()` + +**Collection Operations:** + +- Manual list flattening → `itertools.chain.from_iterable()` +- Custom unique filtering → `dict.fromkeys()` or `set()` +- Manual sorting with custom logic → `sorted()` with `key` parameter +- Custom min/max finding → `min()`, `max()` with `key` parameter + +**File and Path Operations:** + +- `os.path` usage → `pathlib.Path` methods +- Manual file reading loops → `Path.read_text()`, `Path.read_bytes()` +- Custom directory traversal → `Path.glob()`, `Path.rglob()` + +**Data Processing:** + +- Custom aggregation → `statistics` module functions +- Manual counting → `collections.Counter` +- Custom caching → `functools.lru_cache`, `functools.cache` +- Manual memoization → `functools.cached_property` + +**Validation and Type Checking:** + +- Custom type validation → `isinstance()` with union types +- Manual None checking → walrus operator and modern patterns +- Custom default handling → `getattr()` with defaults + +#### Search Strategy + +Use automated tools to find replacement opportunities: + +```bash +# Find custom implementations that might have native equivalents +ruff check --select FURB # flake8-refurb for modernization suggestions +ruff check --select UP # pyupgrade for syntax modernization +``` + +## Data Models + +### Type System Integration + +The modernization will introduce consistent type annotations across: + +- **Model Fields**: Add type hints to custom model methods +- **View Methods**: Enhance view methods with proper return type annotations +- **Utility Functions**: Complete type annotation coverage +- **Form Handling**: Type-safe form processing + +### Generic Types Usage + +Replace typing module imports with built-in generics: + +- `List[T]` → `list[T]` +- `Dict[K, V]` → `dict[K, V]` +- `Tuple[T, ...]` → `tuple[T, ...]` +- `Optional[T]` → `T | None` +- `Union[A, B]` → `A | B` + +## Error Handling + +### Modern Exception Patterns + +Implement enhanced exception handling using Python 3.13 features: + +```python +# Enhanced exception handling with context +try: + msg.send() +except Exception as e: + logger.error("Email sending failed", extra={ + "recipient_count": len(recipients_batch), + "template": template_name, + "error": str(e) + }) + raise EmailDeliveryError("Failed to send email batch") from e +``` + +### Validation Improvements + +Use modern validation patterns: + +- Leverage `match` statements where appropriate +- Use walrus operator for assignment expressions +- Implement better type checking with `isinstance()` improvements + +## Testing Strategy + +### Compatibility Testing + +1. **Unit Tests**: Ensure all existing tests pass on Python 3.13 +2. **Integration Tests**: Verify Django application functionality +3. **Performance Tests**: Benchmark improvements from Python 3.13 +4. **Regression Tests**: Confirm no functionality is broken + +### New Test Coverage + +Add tests for: + +- Type annotation correctness +- Modernized utility functions +- Error handling improvements +- Performance characteristics + +### Test Environment Setup + +```bash +# Test matrix for different Python versions +python3.11 -m pytest # Baseline +python3.13 -m pytest # Target version +``` + +## Performance Considerations + +### Expected Improvements + +Python 3.13 provides several performance benefits: + +- **Faster startup time**: Improved import system +- **Better memory usage**: Enhanced garbage collection +- **Optimized operations**: Faster string operations and comprehensions + +### Benchmarking Plan + +Measure performance impact on: + +- Application startup time +- Email batch processing +- Database query performance +- Template rendering speed + +## Security Enhancements + +### Modern Security Patterns + +- Use `secrets` module for cryptographic operations +- Leverage improved SSL/TLS handling in Python 3.13 +- Implement better input validation with modern type checking + +## Migration Path + +### Phase 1: Infrastructure + +1. Update Docker configuration +2. Update Python version files +3. Regenerate dependency locks +4. Test basic application startup + +### Phase 2: Core Utilities + +1. Modernize `util.py` functions +2. Add comprehensive type annotations +3. Update error handling patterns +4. Test email functionality + +### Phase 3: Models and Views + +1. Add type hints to model methods +2. Modernize view implementations +3. Update template context handling +4. Test user-facing functionality + +### Phase 4: Testing and Validation + +1. Run comprehensive test suite +2. Performance benchmarking +3. Security validation +4. Documentation updates + +## Rollback Strategy + +### Contingency Plan + +If issues arise during migration: + +1. **Quick Rollback**: Revert Docker image to Python 3.11.9 +2. **Dependency Issues**: Use previous uv.lock file +3. **Code Issues**: Git revert specific commits +4. **Testing**: Maintain parallel testing environments + +### Risk Mitigation + +- Maintain separate branches for each migration phase +- Use feature flags for gradual rollout +- Implement comprehensive monitoring +- Prepare rollback procedures for each component diff --git a/.kiro/specs/python-modernization/requirements.md b/.kiro/specs/python-modernization/requirements.md new file mode 100644 index 00000000..0bd73c7a --- /dev/null +++ b/.kiro/specs/python-modernization/requirements.md @@ -0,0 +1,73 @@ +# Requirements Document + +## Introduction + +This feature involves updating the We All Code Django application to use the latest version of Python (3.13) and modernizing the codebase to leverage native Python functions and modern language features. The goal is to improve performance, maintainability, and take advantage of the latest Python capabilities while maintaining full compatibility with the existing Django application. + +## Requirements + +### Requirement 1 + +**User Story:** As a developer, I want to use the latest Python version (3.13) so that I can benefit from performance improvements, security updates, and new language features. + +#### Acceptance Criteria + +1. WHEN the application is deployed THEN it SHALL use Python 3.13.x as the runtime +2. WHEN dependencies are installed THEN they SHALL be compatible with Python 3.13 +3. WHEN the Docker container is built THEN it SHALL use Python 3.13 base image +4. WHEN the development environment is set up THEN it SHALL use Python 3.13 with uv package manager + +### Requirement 2 + +**User Story:** As a developer, I want to use modern Python type annotations so that I can improve code clarity and catch type-related errors early. + +#### Acceptance Criteria + +1. WHEN utility functions are defined THEN they SHALL include proper type annotations using modern Python syntax +2. WHEN function parameters are defined THEN they SHALL use built-in generic types (list, dict, tuple) instead of typing module equivalents where possible +3. WHEN return types are specified THEN they SHALL use the most appropriate modern type annotation syntax +4. WHEN optional parameters are used THEN they SHALL use the modern union syntax (X | None) instead of Optional[X] + +### Requirement 3 + +**User Story:** As a developer, I want to replace custom utility functions with native Python equivalents so that the code is more maintainable and performant. + +#### Acceptance Criteria + +1. WHEN batching operations are needed THEN the system SHALL use modern Python itertools or built-in functions instead of custom implementations +2. WHEN string operations are performed THEN they SHALL use f-strings and modern string methods +3. WHEN collection operations are needed THEN they SHALL use built-in functions like enumerate, zip, and comprehensions appropriately +4. WHEN file operations are performed THEN they SHALL use pathlib instead of os.path where appropriate + +### Requirement 4 + +**User Story:** As a developer, I want the Docker configuration updated to use Python 3.13 so that the containerized environment matches the latest Python version. + +#### Acceptance Criteria + +1. WHEN the Dockerfile is built THEN it SHALL use python:3.13 as the base image +2. WHEN the .python-version file is read THEN it SHALL specify 3.13 +3. WHEN pyproject.toml is parsed THEN it SHALL require Python >=3.13,<3.14 +4. WHEN uv is used THEN it SHALL properly resolve dependencies for Python 3.13 + +### Requirement 5 + +**User Story:** As a developer, I want all dependencies to be compatible with Python 3.13 so that the application runs without compatibility issues. + +#### Acceptance Criteria + +1. WHEN dependencies are resolved THEN they SHALL all support Python 3.13 +2. WHEN the application starts THEN there SHALL be no deprecation warnings related to Python version compatibility +3. WHEN tests are run THEN they SHALL pass on Python 3.13 +4. WHEN production deployment occurs THEN it SHALL use Python 3.13 without issues + +### Requirement 6 + +**User Story:** As a developer, I want to modernize code patterns to use current Python best practices so that the codebase is more readable and maintainable. + +#### Acceptance Criteria + +1. WHEN exception handling is implemented THEN it SHALL use modern exception chaining and context managers where appropriate +2. WHEN data structures are manipulated THEN they SHALL use modern Python idioms like walrus operator where beneficial +3. WHEN async operations are needed THEN they SHALL use modern async/await patterns +4. WHEN configuration is handled THEN it SHALL use modern approaches like dataclasses or Pydantic where appropriate diff --git a/.kiro/specs/python-modernization/tasks.md b/.kiro/specs/python-modernization/tasks.md new file mode 100644 index 00000000..187c1e9d --- /dev/null +++ b/.kiro/specs/python-modernization/tasks.md @@ -0,0 +1,134 @@ +# Implementation Plan + +- [ ] 1. Update Python version configuration files + + - Update `.python-version` from `3.11` to `3.13` + - Update `pyproject.toml` requires-python constraint to `>=3.13,<3.14` + - Update `Dockerfile` base image from `python:3.11.9` to `python:3.13` + - _Requirements: 1.1, 4.1, 4.2, 4.3_ + +- [ ] 2. Regenerate dependency lock file for Python 3.13 + + - Run `docker compose run --rm app uv lock --upgrade` to regenerate uv.lock for Python 3.13 + - Test that all dependencies resolve correctly with `docker compose run --rm app uv sync --frozen` + - Verify no compatibility issues with existing dependencies + - _Requirements: 5.1, 5.2_ + +- [ ] 3. Replace custom batches function with native itertools.batched + + - Remove the custom `batches()` function from `coderdojochi/util.py` + - Replace all usage of `batches()` with `itertools.batched()` in the email function + - Add import for `itertools.batched` at the top of the file + - Test email batch processing functionality + - _Requirements: 3.1, 6.4_ + +- [ ] 4. Add comprehensive type annotations to util.py + + - Add type annotations to the `email()` function parameters and return type + - Use modern union syntax (`|` instead of `Union`) for optional parameters + - Replace `typing.List` with built-in `list` type + - Add proper type hints for all function parameters + - _Requirements: 2.1, 2.2, 2.3, 2.4_ + +- [ ] 5. Modernize email function parameter validation + + - Replace manual type checking with modern `isinstance()` patterns + - Use walrus operator where beneficial for assignment expressions + - Implement better error messages with f-string formatting + - Add type-safe parameter validation + - _Requirements: 3.3, 6.1, 6.2_ + +- [ ] 6. Update model methods with type annotations + + - Add return type annotations to all `@property` methods in `Session` model + - Add type hints to `__str__` methods across all models + - Use modern union syntax for optional return types + - Ensure all model methods have proper type annotations + - _Requirements: 2.1, 2.2, 2.3_ + +- [ ] 7. Modernize view classes with type annotations + + - Add type annotations to view methods in `SessionDetailView` + - Update context data methods with proper return types + - Use modern type hints for Django model and queryset types + - Add type annotations to all view helper methods + - _Requirements: 2.1, 2.2_ + +- [ ] 8. Replace custom string operations with native functions + + - Audit codebase for custom string formatting and replace with f-strings + - Replace manual string joining with `str.join()` where applicable + - Update any custom padding/alignment with native string methods + - Ensure consistent f-string usage throughout the codebase + - _Requirements: 3.2, 6.4_ + +- [ ] 9. Replace custom collection operations with native functions + + - Find and replace custom list flattening with `itertools.chain.from_iterable()` + - Replace custom unique filtering with `dict.fromkeys()` or `set()` + - Update manual sorting with `sorted()` and proper `key` parameters + - Replace custom min/max finding with native `min()`/`max()` functions + - _Requirements: 3.3, 6.4_ + +- [ ] 10. Update file operations to use pathlib + + - Replace `os.path` usage with `pathlib.Path` methods where found + - Update file reading operations to use `Path.read_text()` where applicable + - Replace custom directory traversal with `Path.glob()` methods + - Ensure consistent pathlib usage across the codebase + - _Requirements: 3.4, 6.4_ + +- [ ] 11. Enhance error handling with modern patterns + + - Update exception handling in email function to use exception chaining + - Implement better error context with structured logging + - Add proper exception types for better error handling + - Use modern exception patterns throughout the codebase + - _Requirements: 6.1, 6.3_ + +- [ ] 12. Run automated modernization tools + + - Execute `docker compose run --rm app ruff check --select UP --fix` to apply pyupgrade modernizations + - Run `docker compose run --rm app ruff check --select FURB --fix` for refurb modernization suggestions + - Apply any additional modernization suggestions from ruff + - Verify all automated changes are correct + - _Requirements: 6.4_ + +- [ ] 13. Update imports to use built-in generic types + + - Replace `from typing import List, Dict, Tuple, Optional, Union` with built-in types + - Update all type annotations to use `list`, `dict`, `tuple` instead of typing equivalents + - Replace `Optional[T]` with `T | None` syntax + - Replace `Union[A, B]` with `A | B` syntax + - _Requirements: 2.2, 2.4_ + +- [ ] 14. Test application startup and basic functionality + + - Build Docker container with Python 3.13 + - Test application startup without errors + - Verify database connections work correctly + - Test basic page rendering and functionality + - _Requirements: 1.4, 5.3, 5.4_ + +- [ ] 15. Test email functionality with modernized code + + - Test email sending with new itertools.batched implementation + - Verify batch processing works correctly + - Test error handling in email function + - Confirm all email templates render properly + - _Requirements: 3.1, 5.3_ + +- [ ] 16. Run comprehensive test suite + + - Execute all existing unit tests on Python 3.13 + - Run integration tests to verify functionality + - Test Django admin interface functionality + - Verify all user-facing features work correctly + - _Requirements: 5.3, 5.4_ + +- [ ] 17. Update development documentation + - Update README.md with Python 3.13 requirements + - Update development setup instructions + - Document any new dependencies or requirements + - Update Docker development instructions + - _Requirements: 1.4, 4.4_ From dff21e0ad1301e107dfd229cd56b2f0bd971b048 Mon Sep 17 00:00:00 2001 From: Ali Karbassi Date: Fri, 25 Jul 2025 21:13:39 -0500 Subject: [PATCH 2/4] Update Python version from 3.11 to 3.13 - Update .python-version to 3.13 - Update pyproject.toml requires-python to >=3.13,<3.14 - Update Dockerfile base image to python:3.13 - Update Ruff target-version to py313 --- .kiro/specs/python-modernization/tasks.md | 2 +- .python-version | 2 +- Dockerfile | 2 +- pyproject.toml | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.kiro/specs/python-modernization/tasks.md b/.kiro/specs/python-modernization/tasks.md index 187c1e9d..11dbbcc7 100644 --- a/.kiro/specs/python-modernization/tasks.md +++ b/.kiro/specs/python-modernization/tasks.md @@ -1,6 +1,6 @@ # Implementation Plan -- [ ] 1. Update Python version configuration files +- [x] 1. Update Python version configuration files - Update `.python-version` from `3.11` to `3.13` - Update `pyproject.toml` requires-python constraint to `>=3.13,<3.14` diff --git a/.python-version b/.python-version index 2c073331..24ee5b1b 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.11 +3.13 diff --git a/Dockerfile b/Dockerfile index 5b494668..eb3c3035 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.11.9 +FROM python:3.13 ARG DJANGO_ENV diff --git a/pyproject.toml b/pyproject.toml index f9d7235e..e477655c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ version = "0.0.1" license = {text = "MIT"} readme = "README.md" authors = [{name = "Ali Karbassi", email = "ali@weallcode.org"}] -requires-python = ">=3.11,<3.12" +requires-python = ">=3.13,<3.14" dependencies = [ # Core Framework @@ -78,7 +78,7 @@ Homepage = "https://github.com/weallcode/website" [tool.ruff] # Use Ruff's recommended defaults -target-version = "py311" +target-version = "py313" # Exclude a variety of commonly ignored directories. extend-exclude = [ "*/migrations/*.py", From a5d409b773bbffa1cfb25b180dc4e0b13a7d37ee Mon Sep 17 00:00:00 2001 From: Ali Karbassi Date: Fri, 25 Jul 2025 21:48:06 -0500 Subject: [PATCH 3/4] refactor: replace custom batches function with native itertools.batched - Remove custom batches() implementation from coderdojochi/util.py - Use native itertools.batched() for email recipient batching - Modernize code to leverage Python 3.12+ standard library features Addresses requirements 3.1 and 6.4 from python-modernization spec --- .kiro/specs/python-modernization/tasks.md | 4 +- coderdojochi/util.py | 19 +-- uv.lock | 171 ++++++++++------------ 3 files changed, 84 insertions(+), 110 deletions(-) diff --git a/.kiro/specs/python-modernization/tasks.md b/.kiro/specs/python-modernization/tasks.md index 11dbbcc7..25231a35 100644 --- a/.kiro/specs/python-modernization/tasks.md +++ b/.kiro/specs/python-modernization/tasks.md @@ -7,14 +7,14 @@ - Update `Dockerfile` base image from `python:3.11.9` to `python:3.13` - _Requirements: 1.1, 4.1, 4.2, 4.3_ -- [ ] 2. Regenerate dependency lock file for Python 3.13 +- [x] 2. Regenerate dependency lock file for Python 3.13 - Run `docker compose run --rm app uv lock --upgrade` to regenerate uv.lock for Python 3.13 - Test that all dependencies resolve correctly with `docker compose run --rm app uv sync --frozen` - Verify no compatibility issues with existing dependencies - _Requirements: 5.1, 5.2_ -- [ ] 3. Replace custom batches function with native itertools.batched +- [x] 3. Replace custom batches function with native itertools.batched - Remove the custom `batches()` function from `coderdojochi/util.py` - Replace all usage of `batches()` with `itertools.batched()` in the email function diff --git a/coderdojochi/util.py b/coderdojochi/util.py index d9b2ab0b..0d6bb2f4 100644 --- a/coderdojochi/util.py +++ b/coderdojochi/util.py @@ -1,4 +1,5 @@ import logging +from itertools import batched from anymail.message import AnymailMessage from django.conf import settings @@ -66,7 +67,7 @@ def email( "group_id": unsub_group_id, } - for recipients_batch in batches(recipients, batch_size): + for recipients_batch in batched(recipients, batch_size): msg = AnymailMessage( subject=subject, body=body, @@ -107,20 +108,4 @@ def email( user.save() -def batches(items, batch_size): - """ - Split a list into smaller batches of a specified size. - Args: - items (list): The list of items to be split into batches - batch_size (int): The maximum number of items per batch - - Yields: - list: A batch containing up to batch_size items from the original list - - Example: - >>> list(batches([1, 2, 3, 4, 5], 2)) - [[1, 2], [3, 4], [5]] - """ - for start_index in range(0, len(items), batch_size): - yield items[start_index : start_index + batch_size] diff --git a/uv.lock b/uv.lock index 834ce08e..21ee5f82 100644 --- a/uv.lock +++ b/uv.lock @@ -1,6 +1,6 @@ version = 1 revision = 2 -requires-python = "==3.11.*" +requires-python = "==3.13.*" [[package]] name = "arrow" @@ -26,30 +26,30 @@ wheels = [ [[package]] name = "boto3" -version = "1.39.13" +version = "1.39.14" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, { name = "jmespath" }, { name = "s3transfer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b1/cc/5ebce7eeba468c0bf5092c94684039e8484ca00329e62f0627662c930959/boto3-1.39.13.tar.gz", hash = "sha256:ace50ccfc4caba235b020e7d36f0191aa399771cb6fe6e34b4359b671aab1a4b", size = 111862, upload-time = "2025-07-24T19:18:27.416Z" } +sdist = { url = "https://files.pythonhosted.org/packages/66/8f/acc7d434730e0c931ece4b46c983bf5afb7ae63abb545b535f0eda538476/boto3-1.39.14.tar.gz", hash = "sha256:fabb16360a93b449d5241006485bcc761c26694e75ac01009f4459f114acc06e", size = 111844, upload-time = "2025-07-25T19:25:26.943Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/08/bf/a6a73de65e3347305fc0aed129c63897e047042f55152414295a930700d6/boto3-1.39.13-py3-none-any.whl", hash = "sha256:8e62c5724dc06a1934fde155a2eb48cb851cc17ad0b5142da9eb9e46fe0355d3", size = 139899, upload-time = "2025-07-24T19:18:25.388Z" }, + { url = "https://files.pythonhosted.org/packages/93/9a/01ea17a58a27b7f4a7a6f6e2f4d4191b9e92362b77b6d58689f2d7eccb99/boto3-1.39.14-py3-none-any.whl", hash = "sha256:82c6868cad18c3bd4170915e9525f9af5f83e9779c528417f8863629558fc2d0", size = 139898, upload-time = "2025-07-25T19:25:25.506Z" }, ] [[package]] name = "botocore" -version = "1.39.13" +version = "1.39.14" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jmespath" }, { name = "python-dateutil" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/dd/39/eb875fff1c1d3299da660ed6cb00dd9b313e831ef7d18cb3c0f346a39578/botocore-1.39.13.tar.gz", hash = "sha256:ee8053f34e425a40843daccfa78820d6891f0d4f85fc647ab98f9ba28c36f9e7", size = 14222597, upload-time = "2025-07-24T19:18:16.023Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/ca/8994676a67f0a9d39a0844124f196c4dedc2fbca370c839f61246c1fea6d/botocore-1.39.14.tar.gz", hash = "sha256:7fc44d4ad13b524e5d8a6296785776ef5898ac026ff74df9b35313831d507926", size = 14226110, upload-time = "2025-07-25T19:25:17.751Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ac/ce/8bb5626b0313c3f203215124f432d04ef46de611a9377b0791386e7355b2/botocore-1.39.13-py3-none-any.whl", hash = "sha256:6318ae28984d05aaabe92160446d37a2c498951b34a4d5431bc1ec7eb0376417", size = 13882999, upload-time = "2025-07-24T19:18:11.103Z" }, + { url = "https://files.pythonhosted.org/packages/08/c0/6b8200686c1f9ea44ab0daab0223b04799d60ccf882b9d7f770fbb40571e/botocore-1.39.14-py3-none-any.whl", hash = "sha256:4ed551c77194167b7e8063f33059bc2f9b2ead0ed4ee33dc7857273648ed4349", size = 13888318, upload-time = "2025-07-25T19:25:13.386Z" }, ] [[package]] @@ -70,18 +70,17 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload-time = "2024-09-04T20:45:21.852Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264, upload-time = "2024-09-04T20:43:51.124Z" }, - { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651, upload-time = "2024-09-04T20:43:52.872Z" }, - { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259, upload-time = "2024-09-04T20:43:56.123Z" }, - { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200, upload-time = "2024-09-04T20:43:57.891Z" }, - { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235, upload-time = "2024-09-04T20:44:00.18Z" }, - { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721, upload-time = "2024-09-04T20:44:01.585Z" }, - { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242, upload-time = "2024-09-04T20:44:03.467Z" }, - { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999, upload-time = "2024-09-04T20:44:05.023Z" }, - { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242, upload-time = "2024-09-04T20:44:06.444Z" }, - { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604, upload-time = "2024-09-04T20:44:08.206Z" }, - { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727, upload-time = "2024-09-04T20:44:09.481Z" }, - { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400, upload-time = "2024-09-04T20:44:10.873Z" }, + { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989, upload-time = "2024-09-04T20:44:28.956Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802, upload-time = "2024-09-04T20:44:30.289Z" }, + { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792, upload-time = "2024-09-04T20:44:32.01Z" }, + { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893, upload-time = "2024-09-04T20:44:33.606Z" }, + { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810, upload-time = "2024-09-04T20:44:35.191Z" }, + { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200, upload-time = "2024-09-04T20:44:36.743Z" }, + { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447, upload-time = "2024-09-04T20:44:38.492Z" }, + { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358, upload-time = "2024-09-04T20:44:40.046Z" }, + { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469, upload-time = "2024-09-04T20:44:41.616Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475, upload-time = "2024-09-04T20:44:43.733Z" }, + { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009, upload-time = "2024-09-04T20:44:45.309Z" }, ] [[package]] @@ -99,19 +98,19 @@ version = "3.4.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/05/85/4c40d00dcc6284a1c1ad5de5e0996b06f39d8232f1031cd23c2f5c07ee86/charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", size = 198794, upload-time = "2025-05-02T08:32:11.945Z" }, - { url = "https://files.pythonhosted.org/packages/41/d9/7a6c0b9db952598e97e93cbdfcb91bacd89b9b88c7c983250a77c008703c/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", size = 142846, upload-time = "2025-05-02T08:32:13.946Z" }, - { url = "https://files.pythonhosted.org/packages/66/82/a37989cda2ace7e37f36c1a8ed16c58cf48965a79c2142713244bf945c89/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", size = 153350, upload-time = "2025-05-02T08:32:15.873Z" }, - { url = "https://files.pythonhosted.org/packages/df/68/a576b31b694d07b53807269d05ec3f6f1093e9545e8607121995ba7a8313/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", size = 145657, upload-time = "2025-05-02T08:32:17.283Z" }, - { url = "https://files.pythonhosted.org/packages/92/9b/ad67f03d74554bed3aefd56fe836e1623a50780f7c998d00ca128924a499/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f", size = 147260, upload-time = "2025-05-02T08:32:18.807Z" }, - { url = "https://files.pythonhosted.org/packages/a6/e6/8aebae25e328160b20e31a7e9929b1578bbdc7f42e66f46595a432f8539e/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", size = 149164, upload-time = "2025-05-02T08:32:20.333Z" }, - { url = "https://files.pythonhosted.org/packages/8b/f2/b3c2f07dbcc248805f10e67a0262c93308cfa149a4cd3d1fe01f593e5fd2/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", size = 144571, upload-time = "2025-05-02T08:32:21.86Z" }, - { url = "https://files.pythonhosted.org/packages/60/5b/c3f3a94bc345bc211622ea59b4bed9ae63c00920e2e8f11824aa5708e8b7/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", size = 151952, upload-time = "2025-05-02T08:32:23.434Z" }, - { url = "https://files.pythonhosted.org/packages/e2/4d/ff460c8b474122334c2fa394a3f99a04cf11c646da895f81402ae54f5c42/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", size = 155959, upload-time = "2025-05-02T08:32:24.993Z" }, - { url = "https://files.pythonhosted.org/packages/a2/2b/b964c6a2fda88611a1fe3d4c400d39c66a42d6c169c924818c848f922415/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", size = 153030, upload-time = "2025-05-02T08:32:26.435Z" }, - { url = "https://files.pythonhosted.org/packages/59/2e/d3b9811db26a5ebf444bc0fa4f4be5aa6d76fc6e1c0fd537b16c14e849b6/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", size = 148015, upload-time = "2025-05-02T08:32:28.376Z" }, - { url = "https://files.pythonhosted.org/packages/90/07/c5fd7c11eafd561bb51220d600a788f1c8d77c5eef37ee49454cc5c35575/charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", size = 98106, upload-time = "2025-05-02T08:32:30.281Z" }, - { url = "https://files.pythonhosted.org/packages/a8/05/5e33dbef7e2f773d672b6d79f10ec633d4a71cd96db6673625838a4fd532/charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", size = 105402, upload-time = "2025-05-02T08:32:32.191Z" }, + { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload-time = "2025-05-02T08:32:56.363Z" }, + { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435, upload-time = "2025-05-02T08:32:58.551Z" }, + { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653, upload-time = "2025-05-02T08:33:00.342Z" }, + { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231, upload-time = "2025-05-02T08:33:02.081Z" }, + { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243, upload-time = "2025-05-02T08:33:04.063Z" }, + { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442, upload-time = "2025-05-02T08:33:06.418Z" }, + { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147, upload-time = "2025-05-02T08:33:08.183Z" }, + { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057, upload-time = "2025-05-02T08:33:09.986Z" }, + { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454, upload-time = "2025-05-02T08:33:11.814Z" }, + { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174, upload-time = "2025-05-02T08:33:13.707Z" }, + { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload-time = "2025-05-02T08:33:15.458Z" }, + { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload-time = "2025-05-02T08:33:17.06Z" }, + { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload-time = "2025-05-02T08:33:18.753Z" }, { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" }, ] @@ -148,12 +147,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f2/2e/e2dbd629481b499b14516eed933f3276eb3239f7cee2dcfa4ee6b44d4711/cryptography-45.0.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7259038202a47fdecee7e62e0fd0b0738b6daa335354396c6ddebdbe1206af2a", size = 4554189, upload-time = "2025-07-02T13:05:46.045Z" }, { url = "https://files.pythonhosted.org/packages/f8/ea/a78a0c38f4c8736287b71c2ea3799d173d5ce778c7d6e3c163a95a05ad2a/cryptography-45.0.5-cp37-abi3-win32.whl", hash = "sha256:1e1da5accc0c750056c556a93c3e9cb828970206c68867712ca5805e46dc806f", size = 2911769, upload-time = "2025-07-02T13:05:48.329Z" }, { url = "https://files.pythonhosted.org/packages/79/b3/28ac139109d9005ad3f6b6f8976ffede6706a6478e21c889ce36c840918e/cryptography-45.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:90cb0a7bb35959f37e23303b7eed0a32280510030daba3f7fdfbb65defde6a97", size = 3390016, upload-time = "2025-07-02T13:05:50.811Z" }, - { url = "https://files.pythonhosted.org/packages/c0/71/9bdbcfd58d6ff5084687fe722c58ac718ebedbc98b9f8f93781354e6d286/cryptography-45.0.5-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8c4a6ff8a30e9e3d38ac0539e9a9e02540ab3f827a3394f8852432f6b0ea152e", size = 3587878, upload-time = "2025-07-02T13:06:06.339Z" }, - { url = "https://files.pythonhosted.org/packages/f0/63/83516cfb87f4a8756eaa4203f93b283fda23d210fc14e1e594bd5f20edb6/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:bd4c45986472694e5121084c6ebbd112aa919a25e783b87eb95953c9573906d6", size = 4152447, upload-time = "2025-07-02T13:06:08.345Z" }, - { url = "https://files.pythonhosted.org/packages/22/11/d2823d2a5a0bd5802b3565437add16f5c8ce1f0778bf3822f89ad2740a38/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:982518cd64c54fcada9d7e5cf28eabd3ee76bd03ab18e08a48cad7e8b6f31b18", size = 4386778, upload-time = "2025-07-02T13:06:10.263Z" }, - { url = "https://files.pythonhosted.org/packages/5f/38/6bf177ca6bce4fe14704ab3e93627c5b0ca05242261a2e43ef3168472540/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:12e55281d993a793b0e883066f590c1ae1e802e3acb67f8b442e721e475e6463", size = 4151627, upload-time = "2025-07-02T13:06:13.097Z" }, - { 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" }, - { url = "https://files.pythonhosted.org/packages/f6/34/31a1604c9a9ade0fdab61eb48570e09a796f4d9836121266447b0eaf7feb/cryptography-45.0.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:e357286c1b76403dd384d938f93c46b2b058ed4dfcdce64a770f0537ed3feb6f", size = 3331106, upload-time = "2025-07-02T13:06:18.058Z" }, ] [[package]] @@ -228,16 +221,16 @@ socialaccount = [ [[package]] name = "django-anymail" -version = "13.0" +version = "13.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "django" }, { name = "requests" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e3/8f/c5c8e0c952797247c763edfd02346a9f989489d54a3debd717ecb0abb55a/django_anymail-13.0.tar.gz", hash = "sha256:87f42d9ff12a9a029d5e88edaaf62a4b880aa9a6a7ef937042b7e96579e6db07", size = 94298, upload-time = "2025-04-03T21:04:44.958Z" } +sdist = { url = "https://files.pythonhosted.org/packages/08/c4/db9dc7d869575b3c086ac778250341638aa1370984aff17de050b6ba38d1/django_anymail-13.0.1.tar.gz", hash = "sha256:fd93b36c167eb968bd68a351218c29ae23a68682e67340ae8554478cf27f9d56", size = 94611, upload-time = "2025-07-25T17:40:17.77Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/32/c9691a478ca0460131c2f0bfadbb0e20cd316588aa24f809aebdaa7a79b2/django_anymail-13.0-py3-none-any.whl", hash = "sha256:6da4465eff18f679955f74332501a3a4299e34079015d91e1fce9c049d784d6c", size = 130285, upload-time = "2025-04-03T21:04:43.136Z" }, + { url = "https://files.pythonhosted.org/packages/c9/4d/461912648ebd16d1c5c42eba3f8320671daa3a3913a8bcb0e27c0ba675e8/django_anymail-13.0.1-py3-none-any.whl", hash = "sha256:f2c20c9f240d93789aa8da2ee54d701c6b0345d101133d65d67e9355a914af81", size = 130712, upload-time = "2025-07-25T17:40:16.396Z" }, ] [[package]] @@ -580,24 +573,31 @@ version = "11.3.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523", size = 47113069, upload-time = "2025-07-01T09:16:30.666Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/db/26/77f8ed17ca4ffd60e1dcd220a6ec6d71210ba398cfa33a13a1cd614c5613/pillow-11.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1cd110edf822773368b396281a2293aeb91c90a2db00d78ea43e7e861631b722", size = 5316531, upload-time = "2025-07-01T09:13:59.203Z" }, - { url = "https://files.pythonhosted.org/packages/cb/39/ee475903197ce709322a17a866892efb560f57900d9af2e55f86db51b0a5/pillow-11.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c412fddd1b77a75aa904615ebaa6001f169b26fd467b4be93aded278266b288", size = 4686560, upload-time = "2025-07-01T09:14:01.101Z" }, - { url = "https://files.pythonhosted.org/packages/d5/90/442068a160fd179938ba55ec8c97050a612426fae5ec0a764e345839f76d/pillow-11.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1aa4de119a0ecac0a34a9c8bde33f34022e2e8f99104e47a3ca392fd60e37d", size = 5870978, upload-time = "2025-07-03T13:09:55.638Z" }, - { url = "https://files.pythonhosted.org/packages/13/92/dcdd147ab02daf405387f0218dcf792dc6dd5b14d2573d40b4caeef01059/pillow-11.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:91da1d88226663594e3f6b4b8c3c8d85bd504117d043740a8e0ec449087cc494", size = 7641168, upload-time = "2025-07-03T13:10:00.37Z" }, - { url = "https://files.pythonhosted.org/packages/6e/db/839d6ba7fd38b51af641aa904e2960e7a5644d60ec754c046b7d2aee00e5/pillow-11.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:643f189248837533073c405ec2f0bb250ba54598cf80e8c1e043381a60632f58", size = 5973053, upload-time = "2025-07-01T09:14:04.491Z" }, - { url = "https://files.pythonhosted.org/packages/f2/2f/d7675ecae6c43e9f12aa8d58b6012683b20b6edfbdac7abcb4e6af7a3784/pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:106064daa23a745510dabce1d84f29137a37224831d88eb4ce94bb187b1d7e5f", size = 6640273, upload-time = "2025-07-01T09:14:06.235Z" }, - { url = "https://files.pythonhosted.org/packages/45/ad/931694675ede172e15b2ff03c8144a0ddaea1d87adb72bb07655eaffb654/pillow-11.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd8ff254faf15591e724dc7c4ddb6bf4793efcbe13802a4ae3e863cd300b493e", size = 6082043, upload-time = "2025-07-01T09:14:07.978Z" }, - { url = "https://files.pythonhosted.org/packages/3a/04/ba8f2b11fc80d2dd462d7abec16351b45ec99cbbaea4387648a44190351a/pillow-11.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:932c754c2d51ad2b2271fd01c3d121daaa35e27efae2a616f77bf164bc0b3e94", size = 6715516, upload-time = "2025-07-01T09:14:10.233Z" }, - { url = "https://files.pythonhosted.org/packages/48/59/8cd06d7f3944cc7d892e8533c56b0acb68399f640786313275faec1e3b6f/pillow-11.3.0-cp311-cp311-win32.whl", hash = "sha256:b4b8f3efc8d530a1544e5962bd6b403d5f7fe8b9e08227c6b255f98ad82b4ba0", size = 6274768, upload-time = "2025-07-01T09:14:11.921Z" }, - { url = "https://files.pythonhosted.org/packages/f1/cc/29c0f5d64ab8eae20f3232da8f8571660aa0ab4b8f1331da5c2f5f9a938e/pillow-11.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:1a992e86b0dd7aeb1f053cd506508c0999d710a8f07b4c791c63843fc6a807ac", size = 6986055, upload-time = "2025-07-01T09:14:13.623Z" }, - { url = "https://files.pythonhosted.org/packages/c6/df/90bd886fabd544c25addd63e5ca6932c86f2b701d5da6c7839387a076b4a/pillow-11.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:30807c931ff7c095620fe04448e2c2fc673fcbb1ffe2a7da3fb39613489b1ddd", size = 2423079, upload-time = "2025-07-01T09:14:15.268Z" }, - { url = "https://files.pythonhosted.org/packages/9e/e3/6fa84033758276fb31da12e5fb66ad747ae83b93c67af17f8c6ff4cc8f34/pillow-11.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7c8ec7a017ad1bd562f93dbd8505763e688d388cde6e4a010ae1486916e713e6", size = 5270566, upload-time = "2025-07-01T09:16:19.801Z" }, - { url = "https://files.pythonhosted.org/packages/5b/ee/e8d2e1ab4892970b561e1ba96cbd59c0d28cf66737fc44abb2aec3795a4e/pillow-11.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9ab6ae226de48019caa8074894544af5b53a117ccb9d3b3dcb2871464c829438", size = 4654618, upload-time = "2025-07-01T09:16:21.818Z" }, - { url = "https://files.pythonhosted.org/packages/f2/6d/17f80f4e1f0761f02160fc433abd4109fa1548dcfdca46cfdadaf9efa565/pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe27fb049cdcca11f11a7bfda64043c37b30e6b91f10cb5bab275806c32f6ab3", size = 4874248, upload-time = "2025-07-03T13:11:20.738Z" }, - { url = "https://files.pythonhosted.org/packages/de/5f/c22340acd61cef960130585bbe2120e2fd8434c214802f07e8c03596b17e/pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:465b9e8844e3c3519a983d58b80be3f668e2a7a5db97f2784e7079fbc9f9822c", size = 6583963, upload-time = "2025-07-03T13:11:26.283Z" }, - { url = "https://files.pythonhosted.org/packages/31/5e/03966aedfbfcbb4d5f8aa042452d3361f325b963ebbadddac05b122e47dd/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5418b53c0d59b3824d05e029669efa023bbef0f3e92e75ec8428f3799487f361", size = 4957170, upload-time = "2025-07-01T09:16:23.762Z" }, - { url = "https://files.pythonhosted.org/packages/cc/2d/e082982aacc927fc2cab48e1e731bdb1643a1406acace8bed0900a61464e/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:504b6f59505f08ae014f724b6207ff6222662aab5cc9542577fb084ed0676ac7", size = 5581505, upload-time = "2025-07-01T09:16:25.593Z" }, - { url = "https://files.pythonhosted.org/packages/34/e7/ae39f538fd6844e982063c3a5e4598b8ced43b9633baa3a85ef33af8c05c/pillow-11.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c84d689db21a1c397d001aa08241044aa2069e7587b398c8cc63020390b1c1b8", size = 6984598, upload-time = "2025-07-01T09:16:27.732Z" }, + { url = "https://files.pythonhosted.org/packages/1e/93/0952f2ed8db3a5a4c7a11f91965d6184ebc8cd7cbb7941a260d5f018cd2d/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd", size = 2128328, upload-time = "2025-07-01T09:14:35.276Z" }, + { url = "https://files.pythonhosted.org/packages/4b/e8/100c3d114b1a0bf4042f27e0f87d2f25e857e838034e98ca98fe7b8c0a9c/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8", size = 2170652, upload-time = "2025-07-01T09:14:37.203Z" }, + { url = "https://files.pythonhosted.org/packages/aa/86/3f758a28a6e381758545f7cdb4942e1cb79abd271bea932998fc0db93cb6/pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f", size = 2227443, upload-time = "2025-07-01T09:14:39.344Z" }, + { url = "https://files.pythonhosted.org/packages/01/f4/91d5b3ffa718df2f53b0dc109877993e511f4fd055d7e9508682e8aba092/pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c", size = 5278474, upload-time = "2025-07-01T09:14:41.843Z" }, + { url = "https://files.pythonhosted.org/packages/f9/0e/37d7d3eca6c879fbd9dba21268427dffda1ab00d4eb05b32923d4fbe3b12/pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd", size = 4686038, upload-time = "2025-07-01T09:14:44.008Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b0/3426e5c7f6565e752d81221af9d3676fdbb4f352317ceafd42899aaf5d8a/pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e", size = 5864407, upload-time = "2025-07-03T13:10:15.628Z" }, + { url = "https://files.pythonhosted.org/packages/fc/c1/c6c423134229f2a221ee53f838d4be9d82bab86f7e2f8e75e47b6bf6cd77/pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1", size = 7639094, upload-time = "2025-07-03T13:10:21.857Z" }, + { url = "https://files.pythonhosted.org/packages/ba/c9/09e6746630fe6372c67c648ff9deae52a2bc20897d51fa293571977ceb5d/pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805", size = 5973503, upload-time = "2025-07-01T09:14:45.698Z" }, + { url = "https://files.pythonhosted.org/packages/d5/1c/a2a29649c0b1983d3ef57ee87a66487fdeb45132df66ab30dd37f7dbe162/pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8", size = 6642574, upload-time = "2025-07-01T09:14:47.415Z" }, + { url = "https://files.pythonhosted.org/packages/36/de/d5cc31cc4b055b6c6fd990e3e7f0f8aaf36229a2698501bcb0cdf67c7146/pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2", size = 6084060, upload-time = "2025-07-01T09:14:49.636Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ea/502d938cbaeec836ac28a9b730193716f0114c41325db428e6b280513f09/pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b", size = 6721407, upload-time = "2025-07-01T09:14:51.962Z" }, + { url = "https://files.pythonhosted.org/packages/45/9c/9c5e2a73f125f6cbc59cc7087c8f2d649a7ae453f83bd0362ff7c9e2aee2/pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3", size = 6273841, upload-time = "2025-07-01T09:14:54.142Z" }, + { url = "https://files.pythonhosted.org/packages/23/85/397c73524e0cd212067e0c969aa245b01d50183439550d24d9f55781b776/pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51", size = 6978450, upload-time = "2025-07-01T09:14:56.436Z" }, + { url = "https://files.pythonhosted.org/packages/17/d2/622f4547f69cd173955194b78e4d19ca4935a1b0f03a302d655c9f6aae65/pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580", size = 2423055, upload-time = "2025-07-01T09:14:58.072Z" }, + { url = "https://files.pythonhosted.org/packages/dd/80/a8a2ac21dda2e82480852978416cfacd439a4b490a501a288ecf4fe2532d/pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e", size = 5281110, upload-time = "2025-07-01T09:14:59.79Z" }, + { url = "https://files.pythonhosted.org/packages/44/d6/b79754ca790f315918732e18f82a8146d33bcd7f4494380457ea89eb883d/pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d", size = 4689547, upload-time = "2025-07-01T09:15:01.648Z" }, + { url = "https://files.pythonhosted.org/packages/49/20/716b8717d331150cb00f7fdd78169c01e8e0c219732a78b0e59b6bdb2fd6/pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced", size = 5901554, upload-time = "2025-07-03T13:10:27.018Z" }, + { url = "https://files.pythonhosted.org/packages/74/cf/a9f3a2514a65bb071075063a96f0a5cf949c2f2fce683c15ccc83b1c1cab/pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c", size = 7669132, upload-time = "2025-07-03T13:10:33.01Z" }, + { url = "https://files.pythonhosted.org/packages/98/3c/da78805cbdbee9cb43efe8261dd7cc0b4b93f2ac79b676c03159e9db2187/pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8", size = 6005001, upload-time = "2025-07-01T09:15:03.365Z" }, + { url = "https://files.pythonhosted.org/packages/6c/fa/ce044b91faecf30e635321351bba32bab5a7e034c60187fe9698191aef4f/pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59", size = 6668814, upload-time = "2025-07-01T09:15:05.655Z" }, + { url = "https://files.pythonhosted.org/packages/7b/51/90f9291406d09bf93686434f9183aba27b831c10c87746ff49f127ee80cb/pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe", size = 6113124, upload-time = "2025-07-01T09:15:07.358Z" }, + { url = "https://files.pythonhosted.org/packages/cd/5a/6fec59b1dfb619234f7636d4157d11fb4e196caeee220232a8d2ec48488d/pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c", size = 6747186, upload-time = "2025-07-01T09:15:09.317Z" }, + { url = "https://files.pythonhosted.org/packages/49/6b/00187a044f98255225f172de653941e61da37104a9ea60e4f6887717e2b5/pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788", size = 6277546, upload-time = "2025-07-01T09:15:11.311Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5c/6caaba7e261c0d75bab23be79f1d06b5ad2a2ae49f028ccec801b0e853d6/pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31", size = 6985102, upload-time = "2025-07-01T09:15:13.164Z" }, + { url = "https://files.pythonhosted.org/packages/f3/7e/b623008460c09a0cb38263c93b828c666493caee2eb34ff67f778b87e58c/pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e", size = 2424803, upload-time = "2025-07-01T09:15:15.695Z" }, ] [[package]] @@ -630,7 +630,6 @@ name = "psycopg" version = "3.2.9" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions" }, { name = "tzdata", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/27/4a/93a6ab570a8d1a4ad171a1f4256e205ce48d828781312c0bbaff36380ecb/psycopg-3.2.9.tar.gz", hash = "sha256:2fbb46fcd17bc81f993f28c47f1ebea38d66ae97cc2dbc3cad73b37cefbff700", size = 158122, upload-time = "2025-05-13T16:11:15.533Z" } @@ -648,17 +647,17 @@ name = "psycopg-binary" version = "3.2.9" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b6/84/259ea58aca48e03c3c793b4ccfe39ed63db7b8081ef784d039330d9eed96/psycopg_binary-3.2.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2504e9fd94eabe545d20cddcc2ff0da86ee55d76329e1ab92ecfcc6c0a8156c4", size = 4040785, upload-time = "2025-05-13T16:07:07.569Z" }, - { url = "https://files.pythonhosted.org/packages/25/22/ce58ffda2b7e36e45042b4d67f1bbd4dd2ccf4cfd2649696685c61046475/psycopg_binary-3.2.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:093a0c079dd6228a7f3c3d82b906b41964eaa062a9a8c19f45ab4984bf4e872b", size = 4087601, upload-time = "2025-05-13T16:07:11.75Z" }, - { url = "https://files.pythonhosted.org/packages/c6/4f/b043e85268650c245025e80039b79663d8986f857bc3d3a72b1de67f3550/psycopg_binary-3.2.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:387c87b51d72442708e7a853e7e7642717e704d59571da2f3b29e748be58c78a", size = 4676524, upload-time = "2025-05-13T16:07:17.038Z" }, - { url = "https://files.pythonhosted.org/packages/da/29/7afbfbd3740ea52fda488db190ef2ef2a9ff7379b85501a2142fb9f7dd56/psycopg_binary-3.2.9-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d9ac10a2ebe93a102a326415b330fff7512f01a9401406896e78a81d75d6eddc", size = 4495671, upload-time = "2025-05-13T16:07:21.709Z" }, - { url = "https://files.pythonhosted.org/packages/ea/eb/df69112d18a938cbb74efa1573082248437fa663ba66baf2cdba8a95a2d0/psycopg_binary-3.2.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:72fdbda5b4c2a6a72320857ef503a6589f56d46821592d4377c8c8604810342b", size = 4768132, upload-time = "2025-05-13T16:07:25.818Z" }, - { url = "https://files.pythonhosted.org/packages/76/fe/4803b20220c04f508f50afee9169268553f46d6eed99640a08c8c1e76409/psycopg_binary-3.2.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f34e88940833d46108f949fdc1fcfb74d6b5ae076550cd67ab59ef47555dba95", size = 4458394, upload-time = "2025-05-13T16:07:29.148Z" }, - { url = "https://files.pythonhosted.org/packages/0f/0f/5ecc64607ef6f62b04e610b7837b1a802ca6f7cb7211339f5d166d55f1dd/psycopg_binary-3.2.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a3e0f89fe35cb03ff1646ab663dabf496477bab2a072315192dbaa6928862891", size = 3776879, upload-time = "2025-05-13T16:07:32.503Z" }, - { url = "https://files.pythonhosted.org/packages/c8/d8/1c3d6e99b7db67946d0eac2cd15d10a79aa7b1e3222ce4aa8e7df72027f5/psycopg_binary-3.2.9-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6afb3e62f2a3456f2180a4eef6b03177788df7ce938036ff7f09b696d418d186", size = 3333329, upload-time = "2025-05-13T16:07:35.555Z" }, - { url = "https://files.pythonhosted.org/packages/d7/02/a4e82099816559f558ccaf2b6945097973624dc58d5d1c91eb1e54e5a8e9/psycopg_binary-3.2.9-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:cc19ed5c7afca3f6b298bfc35a6baa27adb2019670d15c32d0bb8f780f7d560d", size = 3435683, upload-time = "2025-05-13T16:07:37.863Z" }, - { url = "https://files.pythonhosted.org/packages/91/e4/f27055290d58e8818bed8a297162a096ef7f8ecdf01d98772d4b02af46c4/psycopg_binary-3.2.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bc75f63653ce4ec764c8f8c8b0ad9423e23021e1c34a84eb5f4ecac8538a4a4a", size = 3497124, upload-time = "2025-05-13T16:07:40.567Z" }, - { url = "https://files.pythonhosted.org/packages/67/3d/17ed07579625529534605eeaeba34f0536754a5667dbf20ea2624fc80614/psycopg_binary-3.2.9-cp311-cp311-win_amd64.whl", hash = "sha256:3db3ba3c470801e94836ad78bf11fd5fab22e71b0c77343a1ee95d693879937a", size = 2939520, upload-time = "2025-05-13T16:07:45.467Z" }, + { url = "https://files.pythonhosted.org/packages/28/0b/f61ff4e9f23396aca674ed4d5c9a5b7323738021d5d72d36d8b865b3deaf/psycopg_binary-3.2.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:98bbe35b5ad24a782c7bf267596638d78aa0e87abc7837bdac5b2a2ab954179e", size = 4017127, upload-time = "2025-05-13T16:08:21.391Z" }, + { url = "https://files.pythonhosted.org/packages/bc/00/7e181fb1179fbfc24493738b61efd0453d4b70a0c4b12728e2b82db355fd/psycopg_binary-3.2.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:72691a1615ebb42da8b636c5ca9f2b71f266be9e172f66209a361c175b7842c5", size = 4080322, upload-time = "2025-05-13T16:08:24.049Z" }, + { url = "https://files.pythonhosted.org/packages/58/fd/94fc267c1d1392c4211e54ccb943be96ea4032e761573cf1047951887494/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25ab464bfba8c401f5536d5aa95f0ca1dd8257b5202eede04019b4415f491351", size = 4655097, upload-time = "2025-05-13T16:08:27.376Z" }, + { url = "https://files.pythonhosted.org/packages/41/17/31b3acf43de0b2ba83eac5878ff0dea5a608ca2a5c5dd48067999503a9de/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e8aeefebe752f46e3c4b769e53f1d4ad71208fe1150975ef7662c22cca80fab", size = 4482114, upload-time = "2025-05-13T16:08:30.781Z" }, + { url = "https://files.pythonhosted.org/packages/85/78/b4d75e5fd5a85e17f2beb977abbba3389d11a4536b116205846b0e1cf744/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7e4e4dd177a8665c9ce86bc9caae2ab3aa9360b7ce7ec01827ea1baea9ff748", size = 4737693, upload-time = "2025-05-13T16:08:34.625Z" }, + { url = "https://files.pythonhosted.org/packages/3b/95/7325a8550e3388b00b5e54f4ced5e7346b531eb4573bf054c3dbbfdc14fe/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fc2915949e5c1ea27a851f7a472a7da7d0a40d679f0a31e42f1022f3c562e87", size = 4437423, upload-time = "2025-05-13T16:08:37.444Z" }, + { url = "https://files.pythonhosted.org/packages/1a/db/cef77d08e59910d483df4ee6da8af51c03bb597f500f1fe818f0f3b925d3/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a1fa38a4687b14f517f049477178093c39c2a10fdcced21116f47c017516498f", size = 3758667, upload-time = "2025-05-13T16:08:40.116Z" }, + { url = "https://files.pythonhosted.org/packages/95/3e/252fcbffb47189aa84d723b54682e1bb6d05c8875fa50ce1ada914ae6e28/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5be8292d07a3ab828dc95b5ee6b69ca0a5b2e579a577b39671f4f5b47116dfd2", size = 3320576, upload-time = "2025-05-13T16:08:43.243Z" }, + { url = "https://files.pythonhosted.org/packages/1c/cd/9b5583936515d085a1bec32b45289ceb53b80d9ce1cea0fef4c782dc41a7/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:778588ca9897b6c6bab39b0d3034efff4c5438f5e3bd52fda3914175498202f9", size = 3411439, upload-time = "2025-05-13T16:08:47.321Z" }, + { url = "https://files.pythonhosted.org/packages/45/6b/6f1164ea1634c87956cdb6db759e0b8c5827f989ee3cdff0f5c70e8331f2/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f0d5b3af045a187aedbd7ed5fc513bd933a97aaff78e61c3745b330792c4345b", size = 3477477, upload-time = "2025-05-13T16:08:51.166Z" }, + { url = "https://files.pythonhosted.org/packages/7b/1d/bf54cfec79377929da600c16114f0da77a5f1670f45e0c3af9fcd36879bc/psycopg_binary-3.2.9-cp313-cp313-win_amd64.whl", hash = "sha256:2290bc146a1b6a9730350f695e8b670e1d1feb8446597bed0bbe7c3c30e0abcb", size = 2928009, upload-time = "2025-05-13T16:08:53.67Z" }, ] [[package]] @@ -667,8 +666,7 @@ version = "2.9.10" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/62/51/2007ea29e605957a17ac6357115d0c1a1b60c8c984951c19419b3474cdfd/psycopg2-2.9.10.tar.gz", hash = "sha256:12ec0b40b0273f95296233e8750441339298e6a572f7039da5b260e3c8b60e11", size = 385672, upload-time = "2024-10-16T11:24:54.832Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/a2/c51ca3e667c34e7852157b665e3d49418e68182081060231d514dd823225/psycopg2-2.9.10-cp311-cp311-win32.whl", hash = "sha256:47c4f9875125344f4c2b870e41b6aad585901318068acd01de93f3677a6522c2", size = 1024538, upload-time = "2024-10-16T11:18:33.48Z" }, - { url = "https://files.pythonhosted.org/packages/33/39/5a9a229bb5414abeb86e33b8fc8143ab0aecce5a7f698a53e31367d30caa/psycopg2-2.9.10-cp311-cp311-win_amd64.whl", hash = "sha256:0435034157049f6846e95103bd8f5a668788dd913a7c30162ca9503fdf542cb4", size = 1163736, upload-time = "2024-10-16T11:18:36.616Z" }, + { url = "https://files.pythonhosted.org/packages/ae/49/a6cfc94a9c483b1fa401fbcb23aca7892f60c7269c5ffa2ac408364f80dc/psycopg2-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:91fd603a2155da8d0cfcdbf8ab24a2d54bca72795b90d2a3ed2b6da8d979dee2", size = 2569060, upload-time = "2025-01-04T20:09:15.28Z" }, ] [[package]] @@ -712,15 +710,15 @@ 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/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/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" }, ] [[package]] @@ -824,15 +822,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/72/52/43e70a8e57fefb172c22a21000b03ebcc15e47e97f5cb8495b9c2832efb4/types_python_dateutil-2.9.0.20250708-py3-none-any.whl", hash = "sha256:4d6d0cc1cc4d24a2dc3816024e502564094497b713f7befda4d5bc7a8e3fd21f", size = 17724, upload-time = "2025-07-08T03:14:02.593Z" }, ] -[[package]] -name = "typing-extensions" -version = "4.14.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" }, -] - [[package]] name = "tzdata" version = "2025.2" From 4a8c6d1869202b2bca4dddb715492d44c728867e Mon Sep 17 00:00:00 2001 From: Ali Karbassi Date: Fri, 25 Jul 2025 21:57:03 -0500 Subject: [PATCH 4/4] feat: add comprehensive type annotations to util.py email function - Add type hints to all 12 function parameters and return type - Use modern union syntax (|) instead of Union for optional parameters - Replace typing.List/Dict with built-in list/dict types - Fix mutable default arguments by using None and proper initialization - Improve code maintainability and IDE support for type checking Addresses requirements 2.1, 2.2, 2.3, 2.4 from python-modernization spec --- .kiro/specs/python-modernization/tasks.md | 2 +- coderdojochi/util.py | 37 +++++++++++++++-------- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/.kiro/specs/python-modernization/tasks.md b/.kiro/specs/python-modernization/tasks.md index 25231a35..b225229f 100644 --- a/.kiro/specs/python-modernization/tasks.md +++ b/.kiro/specs/python-modernization/tasks.md @@ -22,7 +22,7 @@ - Test email batch processing functionality - _Requirements: 3.1, 6.4_ -- [ ] 4. Add comprehensive type annotations to util.py +- [x] 4. Add comprehensive type annotations to util.py - Add type annotations to the `email()` function parameters and return type - Use modern union syntax (`|` instead of `Union`) for optional parameters diff --git a/coderdojochi/util.py b/coderdojochi/util.py index 0d6bb2f4..038beabb 100644 --- a/coderdojochi/util.py +++ b/coderdojochi/util.py @@ -1,5 +1,6 @@ import logging from itertools import batched +from typing import Any from anymail.message import AnymailMessage from django.conf import settings @@ -13,19 +14,29 @@ def email( - subject, - template_name, - attachments=[], - batch_size=500, - bcc=None, - merge_data={}, - merge_global_data={}, - mixed_subtype=None, - preheader=None, - recipients=[], - reply_to=None, - unsub_group_id=None, -): + subject: str, + template_name: str, + attachments: list[Any] | None = None, + batch_size: int = 500, + bcc: list[str] | None | bool = None, + merge_data: dict[str, Any] | None = None, + merge_global_data: dict[str, Any] | None = None, + mixed_subtype: str | None = None, + preheader: str | None = None, + recipients: list[str] | None = None, + reply_to: str | None = None, + unsub_group_id: int | None = None, +) -> None: + # Handle mutable default arguments + if attachments is None: + attachments = [] + if merge_data is None: + merge_data = {} + if merge_global_data is None: + merge_global_data = {} + if recipients is None: + recipients = [] + if not (subject and template_name and recipients): raise ValueError("Missing required parameters: 'subject', 'template_name', and 'recipients' are all required.")