Skip to content

Instantly share code, notes, and snippets.

@tanbro
Last active June 16, 2023 03:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tanbro/06eb6de43a432b7b0132b6b55b8a22fc to your computer and use it in GitHub Desktop.
Save tanbro/06eb6de43a432b7b0132b6b55b8a22fc to your computer and use it in GitHub Desktop.
Change dictionary's key naming style
"""
Change dictionary's key naming style
"""
from typing import (
Any,
Callable,
Iterable,
Mapping,
MutableMapping,
MutableSequence,
Union,
)
import stringcase
__all__ = ["case_convert", "to_camel", "to_pascal", "to_snake"]
ITERABLE_SCALAR_TYPES = (str, bytes, bytearray, memoryview)
TCaseFunc = Callable[[str], str]
def can_be_recursive(obj):
if isinstance(obj, Mapping):
return True
if isinstance(obj, Iterable) and not isinstance(obj, ITERABLE_SCALAR_TYPES):
return True
return False
def case_convert(obj: Any, case: Union[str, TCaseFunc], inplace: bool = False) -> Any:
"""Recursively covert dictionary's string key naming style inside `obj`
When `obj` is
* `dict`: Convert naming style of each key, if the key is string.
* `list`: Iterate the list and apply naming style conversion to every item, if the item is a dictionary.
* `str`: Simply convert it's naming style. `inplace` argument is ignored in this case.
And do above **recursively**.
Parameters
----------
obj:
object to make a naming case convert on it.
case:
The naming style want to convert to, usually `"snake"`, `"camel"`, `"pascal"`.
Or a function apply the conversion.
inplace:
Whether to perform an in-place conversion.
Returns
-------
Converted object
"""
if callable(case):
fct = case
elif isinstance(case, str):
# dynamic load case-convert function from stringcase module
case = case.lower().strip()
fct: TCaseFunc = getattr(stringcase, case) if case.endswith("case") else getattr(stringcase, f"{case}case")
else:
raise TypeError("Argument `case` should be str or callable")
if isinstance(obj, Mapping):
if inplace:
if not isinstance(obj, MutableMapping):
raise ValueError(f"Can not apply inplace modification on {type(obj)} object")
key_pairs = [(k, fct(k)) for k in obj.keys() if isinstance(k, str)]
for k0, k1 in key_pairs:
if k0 == k1:
continue
v = obj.pop(k0)
if can_be_recursive(v):
obj[k1] = case_convert(v, fct, inplace)
else:
obj[k1] = v
else:
return {
fct(k) if isinstance(k, str) else k: case_convert(v, fct, inplace) if can_be_recursive(v) else v
for k, v in obj.items()
}
elif isinstance(obj, Iterable) and not isinstance(obj, ITERABLE_SCALAR_TYPES):
if inplace:
if not isinstance(obj, MutableSequence):
raise ValueError(f"Can not apply inplace modification on {type(obj)} object")
for i, v in enumerate(obj):
if can_be_recursive(i):
obj[i] = case_convert(v, fct, inplace)
else:
return [case_convert(m, fct, inplace) if can_be_recursive(m) else m for m in obj]
elif isinstance(obj, str):
return fct(obj)
return obj
def to_camel(obj, inplace=False):
return case_convert(obj, stringcase.camelcase, inplace)
def to_pascal(obj, inplace=False):
return case_convert(obj, stringcase.pascalcase, inplace)
def to_snake(obj, inplace=False):
return case_convert(obj, stringcase.snakecase, inplace)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment