pip install convertable-key-model
이 프로젝트의 전체 소스 코드를 다운 받으시려면 저장소를 클론하고 필요한 종속성을 설치하십시오:
git clone https://github.com/jogakdal/convertable-key-model.git
cd <repository-directory>
pip install -r requirements.txt
ConvertableKeyModel 클래스__alias_map_reference__ 필드를 클래스 내부에 정의하거나, 생성자 인자 또는 전역 설정을 통해 이루어집니다.convert_key() 함수를 명시적으로 호출해야 alias 매핑 및 케이스 컨벤션 변환된 결과를 얻을 수 있습니다.alias_map (dict[str, str])
{ '모델의 필드명': 'JSON의 필드명' } case_converter (Callable[[str], str])
case_convention 인자에 정의된 기본 변환 함수가 지정됩니다.case_convention (CaseConvention)
CaseConvention enum class는 다음과 같은 값을 가집니다:CaseConvention.SELF: 변환 없음 (default)CaseConvention.CAMEL: camelCaseCaseConvention.SNAKE: snake_caseCaseConvention.PASCAL: PascalCasecase_converter와 동시에 지정되면 case_converter가 우선 적용됩니다.
case_converter와 case_convention 둘 다 지정되지 않으면 ResponseKeyConverter에 등록된 변환 함수가 적용됩니다.
ResponseKeyConverter 클래스ConvertableKeyModel의 동적 키 변환 과정에서 사용됩니다. CaseConvention.SELF(변환 없음)로 설정합니다.ResponseKeyConverter().add_alias(MyModel, "original_field", "aliasField"){ "original_field": "aliasField" }).CaseConvention.CAMEL).case_converter와 case_convention이 동시에 지정되면, case_converter가 우선 적용됩니다.ResponseKeyConverter 활용)ResponseKeyConverter에 alias 및 케이스 컨벤션을 등록할 수 있습니다.ResponseKeyConverter에 등록된 설정은 ConvertableKeyModel의 인스턴스 생성 시 자동으로 적용됩니다.ResponseKeyConverter().add_alias(MyModel, 'original_field', 'aliasField')
ResponseKeyConverter().add_case_convention(MyModel, CaseConvention.CAMEL)
__alias_map_reference__ 필드 사용 (Deserialize 전용)__alias_map_reference__ 필드를 생성해 둘 수 있습니다.__alias_map_reference__ 필드에 의해 alias 매핑이 적용됩니다.model_dump() 호출 시에는 alias 매핑이나 케이스 컨벤션 변환이 적용되지 않습니다.convert_key() 함수를 호출하여 alias 매핑과 케이스 컨벤션 변환을 적용한 결과를 얻어야 합니다.convert_key() 함수 호출 시 인자로 case_converter 또는 case_convention을 지정할 수 있으며, 이 경우 해당 설정이 하위 객체까지 재귀적으로 적용됩니다.def print_pretty_json(data):
print(json.dumps(data, indent=2, ensure_ascii=False))
class SampleClass(ConvertableKeyModel):
some_value_1: str
someValue2: int
class SampleHaveConvertableKeyModel(ConvertableKeyModel):
field_one: str
ckm_field: SampleClass
class SampleClass2(ConvertableKeyModel):
valueOne: str # camelCase 필드
ValueTwo: int # PascalCase 필드
value_three: bool # snake_case 필드
def test_nested_convertable_mapping():
class SampleAliasClass(ConvertableKeyModel):
some_field_1: str
someValue2: int
class SampleClassWithReverseAliasMap(SampleClass):
__alias_map_reference__ = {"some_value_1": "some_field_1"}
class SampleHaveAliasConvertableKeyModel(ConvertableKeyModel):
field_one: str
ckm_field: SampleAliasClass
class SampleHaveReverseAlistMapKeyModel(ConvertableKeyModel):
field_one: str
ckm_field: SampleClassWithReverseAliasMap
def __lambda():
payload = SampleHaveConvertableKeyModel(
field_one='sample_field_1',
ckm_field=SampleClassWithReverseAliasMap(
some_value_1='sample',
someValue2=0,
alias_map={"some_value_1": "some_field_1"},
case_convention=CaseConvention.CAMEL
)
)
return payload, None, None
json = StandardResponse.build(callback=__lambda).convert_key()
print_pretty_json(json)
# nested model에서 내부 필드 객체 생성자에 alias_map을 지정한 경우 바깥 쪽 객체 매핑이 불가능하다
# 이런 경우 매핑된 필드로 구성된 클래스를 정의하고 해당 클래스를 내부 필드 객체를 가지는 별도의 클래스를 정의하여 매핑시켜야 한다.
mapper = StdResponseMapper(json, SampleHaveAliasConvertableKeyModel)
assert isinstance(mapper.response.payload, SampleHaveAliasConvertableKeyModel)
assert mapper.response.payload.ckm_field.some_field_1 == 'sample'
# 또는 원 클래스에 __alias_map_reference__를 정의해 줄 수 있다.
json = StandardResponse.build(callback=lambda: (
SampleHaveReverseAlistMapKeyModel(
field_one='sample_field_1',
ckm_field=SampleClassWithReverseAliasMap(
some_value_1='sample',
someValue2=0,
alias_map={"some_value_1": "some_field_1"},
case_convention=CaseConvention.CAMEL
)
), None, None
)).convert_key()
mapper = StdResponseMapper(json, SampleHaveReverseAlistMapKeyModel)
assert isinstance(mapper.response.payload, SampleHaveReverseAlistMapKeyModel)
assert mapper.response.payload.ckm_field.some_value_1 == 'sample'
def test_convert_key_map():
def __lambda():
payload = SampleHaveConvertableKeyModel(
field_one='sample_field_1',
ckm_field=SampleClass(
some_value_1='sample_value_1',
someValue2=0
)
)
return payload, None, None
# SampleClass의 some_value_1 필드를 some_value_1_alias로 매핑
# ResponseKeyConverter를 사용하면 지정된 클래스가 모델의 어떤 레벨에 있든 매핑된 필드명으로 serialize/deserialize가 가능하다.
ResponseKeyConverter().add_alias(SampleClass, "some_value_1", "some_value_1_alias")
response_object = StandardResponse.build(callback=__lambda)
json = response_object.convert_key()
print_pretty_json(json)
mapper = StdResponseMapper(json, SampleHaveConvertableKeyModel)
assert isinstance(mapper.response.payload, SampleHaveConvertableKeyModel)
assert mapper.response.payload.ckm_field.some_value_1 == 'sample_value_1'
def test_case_convention_mapping():
alias_map = {
"valueOne": "value_one_alias",
"ValueTwo": "value_two_alias",
}
original_data = {
"value_one": "값1", # alias_map을 통해 매핑되어야 함
"valueTwo": 999, # alias_map과 case 변환 적용
"ValueThree": True, # snake_case로 변환하여 매핑
}
converted_data = {
"value_one_alias": "값2", # alias_map을 통해 매핑되어야 함
"valueTwoAlias": 999, # alias_map과 case 변환 적용
"ValueThree": True, # snake_case로 변환하여 매핑
}
sample = SampleClass2(case_convention=CaseConvention.CAMEL, **original_data)
print()
print(f'sample object: {sample}')
assert sample.valueOne == '값1' # 객체는 camelCase이고 json 데이터는 snake_case이어도 매핑이 잘 되어야 함
assert sample.ValueTwo == 999 # 객체는 PascalCase이고 json 데이터는 camelCase이어도 매핑이 잘 되어야 함
assert sample.value_three # 객체는 snake_case이고 json 데이터는 PascalCase이어도 매핑이 잘 되어야 함
dump_data = sample.convert_key()
print_pretty_json(dump_data)
assert dump_data['valueTwo'] == 999 # convert_key()에 아무런 인자가 없으면 객체의 case_convention에 따라 변환
assert dump_data['valueThree'] # convert_key()에 아무런 인자가 없으면 객체의 case_convention에 따라 변환
dump_data = sample.convert_key(case_convention=CaseConvention.CAMEL)
print_pretty_json(dump_data)
assert dump_data['valueTwo'] == 999 # convert_key()에 case_convention 인자가 있으면 해당 case로 변환
assert dump_data['valueThree'] # convert_key()에 case_convention 인자가 있으면 해당 case로 변환
dump_data = sample.convert_key(case_convention=CaseConvention.SNAKE)
print_pretty_json(dump_data)
assert dump_data['value_one'] == '값1' # convert_key()에 case_convention 인자가 있으면 해당 case로 변환
assert dump_data['value_two'] == 999 # convert_key()에 case_convention 인자가 있으면 해당 case로 변환
dump_data = sample.convert_key(case_convention=CaseConvention.PASCAL)
print_pretty_json(dump_data)
assert dump_data['ValueOne'] == '값1' # convert_key()에 case_convention 인자가 있으면 해당 case로 변환
assert dump_data['ValueThree'] # convert_key()에 case_convention 인자가 있으면 해당 case로 변환
# alias_map을 적용하여 매핑될 필드명이 변경되어도 지정된 case convention으로 serialize/deserialize가 가능해야 한다.
sample2 = SampleClass2(alias_map=alias_map, case_convention=CaseConvention.CAMEL, **converted_data)
print(f'sample2: {sample2}')
assert sample2.valueOne == '값2' # 객체는 camelCase이고 json 데이터는 snake_case이어도 매핑이 잘 되어야 함
assert sample2.ValueTwo == 999 # 객체는 PascalCase이고 json 데이터는 camelCase이어도 매핑이 잘 되어야 함
assert sample2.value_three # 객체는 snake_case이고 json 데이터는 PascalCase이어도 매핑이 잘 되어야 함
dump_data = sample2.convert_key()
print('sample2 dump_data with class parameter: ')
print_pretty_json(dump_data)
assert dump_data['valueOneAlias'] == '값2' # convert_key()에 아무런 인자가 없으면 객체의 case_convention에 따라 변환
assert dump_data['valueTwoAlias'] == 999 # convert_key()에 아무런 인자가 없으면 객체의 case_convention에 따라 변환
assert dump_data['valueThree'] # convert_key()에 아무런 인자가 없으면 객체의 case_convention에 따라 변환, alias_map에 없는 키는 원 필드명이 변환되어야 함
dump_data = sample2.convert_key(case_convention=CaseConvention.CAMEL)
print('sample2 dump_data with convert_key function parameter: ')
print_pretty_json(dump_data)
assert dump_data['valueOneAlias'] == '값2' # convert_key()에 case_convention 인자가 있으면 해당 case로 변환
assert dump_data['valueTwoAlias'] == 999 # convert_key()에 case_convention 인자가 있으면 해당 case로 변환
assert dump_data['valueThree'] # convert_key()에 case_convention 인자가 있으면 해당 case로 변환
dump_data = sample2.convert_key(case_convention=CaseConvention.SNAKE)
print_pretty_json(dump_data)
assert dump_data['value_one_alias'] == '값2' # convert_key()에 case_convention 인자가 있으면 해당 case로 변환
assert dump_data['value_two_alias'] == 999 # convert_key()에 case_convention 인자가 있으면 해당 case로 변환
assert dump_data['value_three'] # convert_key()에 case_convention 인자가 있으면 해당 case로 변환
dump_data = sample2.convert_key(case_convention=CaseConvention.PASCAL)
print_pretty_json(dump_data)
assert dump_data['ValueOneAlias'] == '값2' # convert_key()에 case_convention 인자가 있으면 해당 case로 변환
assert dump_data['ValueTwoAlias'] == 999 # convert_key()에 case_convention 인자가 있으면 해당 case로 변환
assert dump_data['ValueThree'] # convert_key()에 case_convention 인자가 있으면 해당 case로 변환
def test_with_StandardResponse_and_StdResponseMapper():
def __lambda():
payload = SampleClass(
some_value_1='sample',
someValue2=0,
case_convention=CaseConvention.CAMEL
)
return payload, None, None
json = StandardResponse.build(callback=__lambda).convert_key()
print_pretty_json(json)
mapper = StdResponseMapper(json, SampleClass)
assert isinstance(mapper.response.payload, SampleClass)
assert mapper.response.payload.some_value_1 == 'sample'