Coverage for src / links / schemas.py: 97%

29 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-05-10 22:03 +0200

1from datetime import datetime, timedelta, timezone 

2from pydantic import BaseModel, HttpUrl, Field, field_validator 

3from typing import Optional 

4 

5 

6class LinkBase(BaseModel): 

7 original_url: HttpUrl = Field(example="https://vkvideo.ru") 

8 custom_alias: Optional[str] = Field( 

9 None, 

10 min_length=4, 

11 max_length=16, 

12 pattern="^[a-zA-Z0-9_-]+$", 

13 example="my-link" 

14 ) 

15 expire_at: Optional[datetime] = Field( 

16 example=f"{(datetime.now(timezone.utc) + timedelta(days=30)).isoformat(timespec='minutes')}" 

17 ) 

18 

19 @field_validator('expire_at') 

20 def validate_expire_at(cls, value): 

21 if value and value < datetime.now(value.tzinfo): 

22 raise ValueError("Expiration date must be in the future") 

23 return value.replace(second=0, microsecond=0) if value else None 

24 

25 

26class LinkCreate(LinkBase): 

27 @field_validator('original_url') 

28 def ensure_scheme(cls, url): 

29 if not str(url).startswith(('http://', 'https://')): 

30 return f"http://{url}" 

31 return url 

32 

33 

34class LinkResponse(LinkBase): 

35 short_id: str = Field(example="abc123") 

36 created_at: datetime = Field(example=datetime.now(timezone.utc).isoformat()) 

37 

38 

39class LinkStatsResponse(LinkResponse): 

40 click_count: int = Field(example=42) 

41 

42 

43class LinkUpdate(BaseModel): 

44 original_url: Optional[HttpUrl] = None 

45 expire_at: Optional[datetime] = None 

46 

47 @field_validator('expire_at') 

48 def round_expire_at(cls, value): 

49 return value.replace(second=0, microsecond=0) if value else None