74 lines
2.2 KiB
Python
74 lines
2.2 KiB
Python
"""
|
|
Case conversion utilities for API payloads.
|
|
"""
|
|
import re
|
|
from typing import Any, Dict, List
|
|
|
|
|
|
_FIRST_CAP_RE = re.compile(r"(.)([A-Z][a-z]+)")
|
|
_ALL_CAP_RE = re.compile(r"([a-z0-9])([A-Z])")
|
|
|
|
_SPECIAL_ALIASES = {
|
|
"avatar_url": "avatar",
|
|
"avatarUrl": "avatar",
|
|
}
|
|
|
|
|
|
def to_snake(name: str) -> str:
|
|
"""Convert camelCase/PascalCase to snake_case."""
|
|
if not isinstance(name, str) or not name:
|
|
return name
|
|
if name.lower() == name:
|
|
return name
|
|
s1 = _FIRST_CAP_RE.sub(r"\1_\2", name)
|
|
s2 = _ALL_CAP_RE.sub(r"\1_\2", s1)
|
|
return s2.lower()
|
|
|
|
|
|
def to_camel(name: str) -> str:
|
|
"""Convert snake_case to camelCase."""
|
|
if not isinstance(name, str) or not name:
|
|
return name
|
|
if "_" not in name:
|
|
return name
|
|
parts = name.split("_")
|
|
if not parts:
|
|
return name
|
|
first = parts[0]
|
|
rest = "".join(part.capitalize() if part else "_" for part in parts[1:])
|
|
return first + rest
|
|
|
|
|
|
def convert_keys_to_snake(data: Any) -> Any:
|
|
"""Recursively convert dict keys to snake_case."""
|
|
if isinstance(data, list):
|
|
return [convert_keys_to_snake(item) for item in data]
|
|
if isinstance(data, dict):
|
|
converted: Dict[Any, Any] = {}
|
|
for key, value in data.items():
|
|
new_key = to_snake(key) if isinstance(key, str) else key
|
|
converted[new_key] = convert_keys_to_snake(value)
|
|
return converted
|
|
return data
|
|
|
|
|
|
def add_camelcase_aliases(data: Any) -> Any:
|
|
"""Recursively add camelCase aliases for snake_case keys."""
|
|
if isinstance(data, list):
|
|
return [add_camelcase_aliases(item) for item in data]
|
|
if isinstance(data, dict):
|
|
result: Dict[Any, Any] = {}
|
|
for key, value in data.items():
|
|
result[key] = add_camelcase_aliases(value)
|
|
# Add camelCase aliases without overriding existing keys
|
|
for key, value in list(result.items()):
|
|
if isinstance(key, str):
|
|
camel_key = to_camel(key)
|
|
if camel_key != key and camel_key not in result:
|
|
result[camel_key] = value
|
|
alias_key = _SPECIAL_ALIASES.get(key)
|
|
if alias_key and alias_key not in result:
|
|
result[alias_key] = value
|
|
return result
|
|
return data
|