Files
py-healthchecks.io-fork/src/healthchecks_io/schemas/checks.py
Mariatta 7fa3e2ff1a Bump to pydantic 2.8.0 (#702)
* deps: bump pydantic from 1.10.15 to 2.8.0

Bumps [pydantic](https://github.com/pydantic/pydantic) from 1.10.15 to 2.8.0.
- [Release notes](https://github.com/pydantic/pydantic/releases)
- [Changelog](https://github.com/pydantic/pydantic/blob/main/HISTORY.md)
- [Commits](https://github.com/pydantic/pydantic/compare/v1.10.15...v2.8.0)

---
updated-dependencies:
- dependency-name: pydantic
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update dependencies
run the bump-pydantic tool.

* Migrate the validate_uuid using field_validator.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-03 16:20:29 -05:00

254 lines
9.6 KiB
Python

"""Schemas for checks.
https://healthchecks.io/docs/api/
"""
from datetime import datetime
from pathlib import PurePath
from typing import Any
from typing import Dict
from typing import List
from typing import Optional
from typing import Union
from urllib.parse import urlparse
import pytz
from croniter import croniter
from pydantic import field_validator, BaseModel, ValidationInfo
from pydantic import Field
class Check(BaseModel):
"""Schema for a check object, either from a readonly api request or a rw api request."""
unique_key: Optional[str] = None
name: str
slug: str
tags: Optional[str] = None
desc: Optional[str] = None
grace: int
n_pings: int
status: str
last_ping: Optional[datetime] = None
next_ping: Optional[datetime] = None
manual_resume: bool
methods: Optional[str] = None
# healthchecks.io's api doesn't return a scheme so we cant use Pydantic AnyUrl here
ping_url: Optional[str] = None
update_url: Optional[str] = None
pause_url: Optional[str] = None
channels: Optional[str] = None
timeout: Optional[int] = None
uuid: Optional[str] = Field(default=None, validate_default=True)
@field_validator("uuid")
@classmethod
def validate_uuid(cls, value: Optional[str], info: ValidationInfo) -> Optional[str]: # noqa: B902
"""Tries to set the uuid from the ping_url.
Will return none if a read only token is used because it cannot retrieve the UUID of a check
"""
if value is None and info.data.get("ping_url", None) is not None:
# url is like healthchecks.io/ping/8f57b84b-86c2-4546-8923-03f83d27604a, so we want just the
# UUID off the end
# Parse the url, grab the path and then just get the name using pathlib
path = PurePath(str(urlparse(info.data.get("ping_url")).path))
return path.name
return value
@classmethod
def from_api_result(cls, check_dict: Dict[str, Any]) -> "Check":
"""Converts a dictionary from the healthchecks api into an Check object."""
return cls(**check_dict)
class CheckCreate(BaseModel):
"""Pydantic object for creating a check."""
name: Optional[str] = Field("", description="Name of the check")
tags: Optional[str] = Field("", description="String separated list of tags to apply")
desc: Optional[str] = Field("", description="Description of the check")
timeout: Optional[int] = Field(
86400,
description="The expected period of this check in seconds.",
gte=60,
lte=31536000,
)
grace: Optional[int] = Field(
3600,
description="The grace period for this check in seconds.",
gte=60,
lte=31536000,
)
schedule: Optional[str] = Field(
None,
description="A cron expression defining this check's schedule. "
"If you specify both timeout and schedule parameters, "
"Healthchecks.io will create a Cron check and ignore the "
"timeout value.",
)
tz: Optional[str] = Field(
"UTC",
description="Server's timezone. This setting only has an effect " "in combination with the schedule parameter.",
)
manual_resume: Optional[bool] = Field(
False,
description="Controls whether a paused check automatically resumes "
"when pinged (the default) or not. If set to false, a paused "
"check will leave the paused state when it receives a ping. "
"If set to true, a paused check will ignore pings and stay "
"paused until you manually resume it from the web dashboard.",
)
methods: Optional[str] = Field(
"",
description="Specifies the allowed HTTP methods for making "
"ping requests. Must be one of the two values: an empty "
"string or POST. Set this field to an empty string to "
"allow HEAD, GET, and POST requests. Set this field to "
"POST to allow only POST requests.",
)
channels: Optional[str] = Field(
None,
description="By default, this API call assigns no integrations"
"to the newly created check. By default, this API call "
"assigns no integrations to the newly created check. "
"To assign specific integrations, use a comma-separated list "
"of integration UUIDs.",
)
unique: Optional[List[Optional[str]]] = Field(
[],
description="Enables upsert functionality. Before creating a check, "
"Healthchecks.io looks for existing checks, filtered by fields listed "
"in unique. If Healthchecks.io does not find a matching check, it "
"creates a new check and returns it with the HTTP status code 201 "
"If Healthchecks.io finds a matching check, it updates the existing "
"check and returns it with HTTP status code 200. The accepted values "
"for the unique field are name, tags, timeout, and grace.",
)
@field_validator("schedule")
@classmethod
def validate_schedule(cls, value: str) -> str:
"""Validates that the schedule is a valid cron expression."""
if not croniter.is_valid(value):
raise ValueError("Schedule is not a valid cron expression")
return value
@field_validator("tz")
@classmethod
def validate_tz(cls, value: str) -> str:
"""Validates that the timezone is a valid timezone string."""
if value not in pytz.all_timezones:
raise ValueError("Tz is not a valid timezone")
return value
@field_validator("methods")
@classmethod
def validate_methods(cls, value: str) -> str:
"""Validate that methods."""
if value not in ("", "POST"):
raise ValueError("Methods is invalid, it should be either an empty string or POST")
return value
@field_validator("unique")
@classmethod
def validate_unique(cls, value: List[Optional[str]]) -> List[Optional[str]]:
"""Validate unique list."""
for unique in value:
if unique not in ("name", "tags", "timeout", "grace"):
raise ValueError(
"Unique is not valid. Unique can only be name, tags, timeout, and grace or an empty list"
)
return value
class CheckUpdate(CheckCreate):
"""Pydantic object for updating a check."""
name: Optional[str] = Field(None, description="Name of the check")
tags: Optional[str] = Field(None, description="String separated list of tags to apply")
timeout: Optional[int] = Field(
None,
description="The expected period of this check in seconds.",
gte=60,
lte=31536000,
)
grace: Optional[int] = Field(
None,
description="The grace period for this check in seconds.",
gte=60,
lte=31536000,
)
schedule: Optional[str] = Field(
None,
description="A cron expression defining this check's schedule. "
"If you specify both timeout and schedule parameters, "
"Healthchecks.io will create a Cron check and ignore the "
"timeout value.",
)
tz: Optional[str] = Field(
None,
description="Server's timezone. This setting only has an effect " "in combination with the schedule parameter.",
)
manual_resume: Optional[bool] = Field(
None,
description="Controls whether a paused check automatically resumes "
"when pinged (the default) or not. If set to false, a paused "
"check will leave the paused state when it receives a ping. "
"If set to true, a paused check will ignore pings and stay "
"paused until you manually resume it from the web dashboard.",
)
methods: Optional[str] = Field(
None,
description="Specifies the allowed HTTP methods for making "
"ping requests. Must be one of the two values: an empty "
"string or POST. Set this field to an empty string to "
"allow HEAD, GET, and POST requests. Set this field to "
"POST to allow only POST requests.",
)
channels: Optional[str] = Field(
None,
description="By default, this API call assigns no integrations"
"to the newly created check. By default, this API call "
"assigns no integrations to the newly created check. "
"To assign specific integrations, use a comma-separated list "
"of integration UUIDs.",
)
unique: Optional[List[Optional[str]]] = Field(
None,
description="Enables upsert functionality. Before creating a check, "
"Healthchecks.io looks for existing checks, filtered by fields listed "
"in unique. If Healthchecks.io does not find a matching check, it "
"creates a new check and returns it with the HTTP status code 201 "
"If Healthchecks.io finds a matching check, it updates the existing "
"check and returns it with HTTP status code 200. The accepted values "
"for the unique field are name, tags, timeout, and grace.",
)
class CheckPings(BaseModel):
"""A Pydantic schema for a check's Pings."""
type: str
date: datetime
number_of_pings: int
scheme: str
remote_addr: str
method: str
user_agent: str
duration: Optional[float] = None
@classmethod
def from_api_result(cls, ping_dict: Dict[str, Union[str, int, datetime]]) -> "CheckPings":
"""Converts a dictionary from the healthchecks api into a CheckPings object."""
ping_dict["number_of_pings"] = ping_dict["n"]
ping_dict["user_agent"] = ping_dict["ua"]
return cls(**ping_dict)
class CheckStatuses(BaseModel):
"""A Pydantic schema for a check's Statuses."""
timestamp: datetime
up: int