Skip to content

[FEATURE] Implement PATCH method for partial player updates #461

@nanotaboada

Description

@nanotaboada

Problem

Currently, the API only supports PUT operations for updating player data, which requires sending the entire player object even when only one or two fields need to be modified. This is inefficient and doesn't follow REST best practices for partial resource updates. Users need a way to update specific player fields without having to provide all player data.

Proposed Solution

Implement HTTP PATCH method endpoints to allow partial updates of player resources. The API should accept a PlayerPartialUpdateRequest model and update only the fields provided in the request, leaving other fields unchanged.

Important constraint: Partial updates should allow modifying team-related data (position, squad number, team, league) but NOT personal details (firstName, middleName, lastName, dateOfBirth). Personal information should only be modified via PUT (full update).

Endpoint to implement:

  • PATCH /players/squadnumber/{squad_number} - Update specific fields of a player by squad number

Note: We use squad number (natural identifier) instead of internal ID, as squad numbers are unique and user-facing.

Suggested Approach

1. Route Definition (routes/player_route.py)

  • Add @api_router.patch("/players/squadnumber/{squad_number}") endpoint
  • Include cache invalidation (call await simple_memory_cache.clear(CACHE_KEY))
  • Add appropriate status codes (200 for success, 404 for not found, 400 for validation errors, 409 for conflicts)

2. Service Layer (services/player_service.py)

  • Create async def patch_player_by_squad_number(async_session: AsyncSession, squad_number: int, update_data: dict) -> Optional[Player]
  • If updating squad_number field itself, validate new squad_number doesn't create duplicates
  • Handle partial data using exclude_unset=True in Pydantic model serialization

3. Model Definition (models/player_model.py)

  • Refactor existing PlayerModel class to support Request/Response pattern with data transformations

  • Create PlayerResponse class (for GET operations) with transformed properties to demonstrate data mapping:

    class PlayerResponse(BaseModel):
        full_name: str = Field(alias="fullName")  # Computed from firstName + middleName + lastName
        birth: str  # Formatted date string from dateOfBirth
        dorsal: int  # Maps from squadNumber (different naming)
        position: str
        club: str  # Maps from team (different naming)
        league: str
        starting11: str  # Transformed from bool to "Yes"/"No" string
    
        @field_validator('full_name', mode='before')
        @classmethod
        def compute_full_name(cls, v, info):
            # Compute from firstName, middleName, lastName in entity
            pass
  • Create PlayerCreateRequest class (for POST - all fields except id)

  • Create PlayerUpdateRequest class (for PUT - full replacement, all fields except id)

  • Create PlayerPartialUpdateRequest class with only team-related fields as Optional:

    • squad_number: Optional[int]
    • position: Optional[str]
    • abbr_position: Optional[str]
    • team: Optional[str]
    • league: Optional[str]
    • starting11: Optional[bool]
    • Excludes: firstName, middleName, lastName, dateOfBirth (personal details)
  • Implement mapper function or use Pydantic @computed_field to transform entity to response:

    • Concatenate firstName, middleName, lastName → fullName
    • Format dateOfBirth → birth (ISO date string)
    • Rename squadNumber → dorsal
    • Rename team → club
    • Convert starting11 bool → "Yes"/"No" string
  • All models maintain camelCase aliasing with to_camel function

  • Add appropriate validators for fields that require validation (e.g., squad number uniqueness)

4. Testing (tests/test_main.py)

  • Test partial updates (single field, multiple fields)
  • Test squad number uniqueness validation during PATCH
  • Test 404 response for non-existent player
  • Test cache invalidation after PATCH operations
  • Use test data from player_stub.py

5. Code Style

  • Format with Black: black .
  • Lint with flake8: flake8
  • Follow async/await patterns consistently
  • Add proper type hints for all function parameters and return values

Acceptance Criteria

  • Models refactored to use Request/Response pattern with data transformations:
    • PlayerResponse for GET operations with computed/transformed fields (fullName, birth, dorsal, club, starting11 as string)
    • Pydantic computed fields or mapper function transforms entity to response format
    • PlayerCreateRequest for POST operations
    • PlayerUpdateRequest for PUT operations
    • PlayerPartialUpdateRequest for PATCH operations (excludes personal details)
  • Data mapping demonstrates Pydantic transformation capabilities
  • PATCH endpoint /players/squadnumber/{squad_number} successfully updates team-related player fields
  • Personal details (names, date of birth) cannot be modified via PATCH
  • Only fields provided in request body are updated (other fields remain unchanged)
  • Squad number uniqueness is validated when updating to a new squad number
  • Appropriate HTTP status codes returned (200 OK, 404 Not Found, 400 Bad Request, 409 Conflict)
  • Cache is invalidated after successful PATCH operations
  • All tests pass with pytest -v
  • Code coverage remains above 80% target
  • Code formatted with Black and passes flake8 linting
  • API documentation reflects new model names and PATCH endpoint in /docs

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestplanningEnables automatic issue planning with CodeRabbitpriority mediumPlanned enhancement. Queue for upcoming work.pythonPull requests that update Python code

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions