Coverage for schemas/transformers_validators.py: 36%
64 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 12:56 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 12:56 +0000
1import re
2from typing import Any, Type, Union
4from pydantic import ValidationInfo
6from .overrides import Enum
8# Used in pydantic Field(pattern=...), which compiles with the Rust regex
9# engine: end-of-string is \z there (\Z is unsupported and fails at schema build).
10NAME_PATTERN = r"^[a-zA-Z0-9\-éèàç |&/\\_.#':,@]*\z"
12# Property/column names are interpolated into ClickHouse SQL as identifiers, so
13# they must be restricted to a safe character set (no back-ticks, quotes, parens,
14# etc.) to prevent SQL injection.
15PROPERTY_NAME_PATTERN = r"^[\w$.\- ]+$"
18def transform_email(email: str) -> str:
19 return email.lower().strip() if isinstance(email, str) else email
22def int_to_string(value: int) -> str:
23 return str(value) if isinstance(value, int) else value
26def remove_whitespace(value: str) -> str:
27 return " ".join(value.split()) if isinstance(value, str) else value
30def remove_duplicate_values(value: list) -> list:
31 if value is not None and isinstance(value, list):
32 if len(value) > 0 and (isinstance(value[0], int) or isinstance(value[0], dict)):
33 return value
34 value = list(set(value))
35 return value
38def single_to_list(value: Union[list, Any]) -> list:
39 if value is not None and not isinstance(value, list):
40 value = [value]
41 return value
44def force_is_event(events_enum: list[Type[Enum]]):
45 def fn(value: list):
46 if value is not None and isinstance(value, list):
47 for v in value:
48 if v.get("type") is None:
49 v["isEvent"] = False
50 continue
51 r = False
52 for en in events_enum:
53 if en.has_value(v["type"]) or en.has_value(v["type"].lower()):
54 r = True
55 break
56 v["isEvent"] = r
57 return value
59 return fn
62def check_alphanumeric(v: str, info: ValidationInfo) -> str:
63 if isinstance(v, str): 63 ↛ 66line 63 didn't jump to line 66 because the condition on line 63 was always true
64 is_alphanumeric = v.replace(" ", "").isalnum()
65 assert is_alphanumeric, f"{info.field_name} must be alphanumeric"
66 return v
69def check_account_name(v: str, info: ValidationInfo) -> str:
70 if isinstance(v, str) and len(v) > 0:
71 pattern = r"^[\w\s\-'\.áéíóúàèìòùâêîôûäëïöüãõñçÁÉÍÓÚÀÈÌÒÙÂÊÎÔÛÄËÏÖÜÃÕÑÇ&@]+\Z"
72 is_valid = re.match(pattern, v, re.UNICODE)
73 assert is_valid, (
74 f"{info.field_name} contains invalid characters. Only letters, numbers, spaces, hyphens, apostrophes, and periods are allowed"
75 )
76 return v
79def check_property_name(v: str, info: ValidationInfo = None) -> str:
80 if isinstance(v, str):
81 field_name = getattr(info, "field_name", None) or "name"
82 assert re.match(PROPERTY_NAME_PATTERN, v, re.UNICODE), (
83 f"{field_name} contains invalid characters. Only letters, numbers, "
84 "spaces, and the characters $ . - _ are allowed"
85 )
86 return v
89def check_regex(v: str) -> str:
90 assert v is not None, "Regex is null"
91 assert isinstance(v, str), "Regex value must be a string"
92 assert len(v) > 0, "Regex is empty"
93 is_valid = None
94 try:
95 re.compile(v)
96 except re.error as exc:
97 is_valid = f"Invalid regex: {exc} (at position {exc.pos})"
99 assert is_valid is None, is_valid
100 return v