schon/evibes/utils/renderers.py
Egor fureunoir Gorbunov 160b35a591 Features: (1) Update camelize function to separately handle lists and tuples for improved clarity;
Fixes: (1) Correct development server URL in DRF settings from `http://api.localhost:8000/` to `http://localhost:8000/`;

Extra: (1) Minor alignment adjustments in `renderers.py` for better readability.
2025-12-18 19:36:05 +03:00

118 lines
3.7 KiB
Python

import re
from typing import Any, MutableMapping
from django.utils.module_loading import import_string
from drf_orjson_renderer.renderers import ORJSONRenderer
from evibes.settings.base import MIDDLEWARE
from evibes.settings.drf import JSON_UNDERSCOREIZE
camelize_re = re.compile(r"[a-z0-9]?_[a-z0-9]")
def underscore_to_camel(match):
group = match.group()
if len(group) == 3:
return group[0] + group[2].upper()
else:
return group[1].upper()
def _camelize_key(key: str) -> str:
if not isinstance(key, str) or not key:
return key
if "_" not in key:
return key
parts = key.split("_")
first = parts[0]
rest = [p.capitalize() if p else "" for p in parts[1:]]
return first + "".join(rest)
def camelize(obj: Any) -> Any:
if isinstance(obj, dict):
return {
(_camelize_key(k) if isinstance(k, str) else k): camelize(v)
for k, v in obj.items()
}
if isinstance(obj, list):
return [camelize(v) for v in obj]
if isinstance(obj, tuple):
return tuple(camelize(v) for v in obj)
return obj
def camelize_serializer_fields(result, generator, request, public):
ignore_fields = JSON_UNDERSCOREIZE.get("ignore_fields") or ()
ignore_keys = JSON_UNDERSCOREIZE.get("ignore_keys") or ()
def has_middleware_installed():
try:
from djangorestframework_camel_case.middleware import CamelCaseMiddleWare
except ImportError:
return False
return any(
isinstance(m, type) and issubclass(m, CamelCaseMiddleWare)
for m in map(import_string, MIDDLEWARE)
)
def camelize_str(key: str) -> str:
new_key = re.sub(camelize_re, underscore_to_camel, key) if "_" in key else key
if key in ignore_keys or new_key in ignore_keys:
return key
return new_key
def camelize_component(
schema: MutableMapping, name: str | None = None
) -> MutableMapping:
if name is not None and (
name in ignore_fields or camelize_str(name) in ignore_fields
):
return schema
elif schema.get("type") == "object":
if "properties" in schema:
schema["properties"] = {
camelize_str(field_name): camelize_component(
field_schema, field_name
)
for field_name, field_schema in schema["properties"].items()
}
if "required" in schema:
schema["required"] = [
camelize_str(field) for field in schema["required"]
]
elif schema.get("type") == "array" and isinstance(
schema["items"], MutableMapping
):
camelize_component(schema["items"])
return schema
for (_, component_type), component in generator.registry._components.items():
if component_type == "schemas":
camelize_component(component.schema)
if has_middleware_installed():
for url_schema in result["paths"].values():
for method_schema in url_schema.values():
for parameter in method_schema.get("parameters", []):
parameter["name"] = camelize_str(parameter["name"])
return result
class CamelCaseRenderer(ORJSONRenderer):
def render(
self, data: Any, media_type: str | None = None, renderer_context: Any = None
) -> bytes:
if data is None:
return b""
ctx = renderer_context or {}
camelize_disabled = ctx.get("camelize", True) is False
payload = data if camelize_disabled else camelize(data)
return super().render(payload, media_type=media_type, renderer_context=ctx)
__all__ = ["CamelCaseRenderer", "camelize"]