Skip to content

Commit ff4f074

Browse files
fetcher.py에서 HTTPTransport를 사용하여 재시도 로직 추가, string_date_transfer.py에서 시간대 처리 개선, kakao_option.py에 Bms 모델 추가, message.py에서 전화번호 정규화 로직 추가, voice_option.py 및 bms.py 파일 추가, GetGroupsCriteriaType 이름 수정 및 버전 업데이트
1 parent f5b98f6 commit ff4f074

11 files changed

Lines changed: 92 additions & 10 deletions

File tree

solapi/lib/fetcher.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,17 @@ def default_fetcher(
3939
headers = {
4040
"Authorization": authorization_header_data,
4141
"Content-Type": "application/json",
42+
"Connection": "keep-alive",
4243
}
4344

44-
with httpx.Client() as client:
45+
transport = httpx.HTTPTransport(retries=3)
46+
47+
with httpx.Client(transport=transport) as client:
4548
response: Response = client.request(
46-
method=request["method"], url=request["url"], headers=headers, json=data
49+
method=request["method"],
50+
url=request["url"],
51+
headers=headers,
52+
json=data,
4753
)
4854

4955
# 4xx 에러 처리: 클라이언트 오류일 경우

solapi/lib/string_date_transfer.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,14 @@ def format_iso(date: datetime) -> str:
2727
"""
2828
utc_offset_sec = time.altzone if time.localtime().tm_isdst else time.timezone
2929
utc_offset = timedelta(seconds=-utc_offset_sec)
30-
return date.replace(tzinfo=timezone(offset=utc_offset)).isoformat()
30+
local_tz = timezone(offset=utc_offset)
31+
32+
if date.tzinfo is None:
33+
date = date.replace(tzinfo=local_tz)
34+
else:
35+
date = date.astimezone(local_tz)
36+
37+
return date.isoformat()
3138

3239

3340
def parse_iso(date_string: str) -> datetime:

solapi/model/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from .kakao.kakao_option import KakaoOption
22
from .message_type import MessageType
33
from .request.groups.get_groups import GetGroupsRequest
4+
from .request.kakao.bms import Bms
45
from .request.message import Message as RequestMessage
56
from .request.send_message_request import SendRequestConfig
67
from .response.message import Message as ResponseMessage
@@ -12,4 +13,5 @@
1213
"KakaoOption",
1314
"GetGroupsRequest",
1415
"MessageType",
16+
"Bms",
1517
]

solapi/model/kakao/kakao_option.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,30 @@
44
from pydantic import BaseModel, ConfigDict, field_validator
55
from pydantic.alias_generators import to_camel
66

7+
from solapi.model.request.kakao.bms import Bms
8+
79

810
class KakaoOption(BaseModel):
911
pf_id: Optional[str] = None
1012
template_id: Optional[str] = None
1113
variables: Optional[dict[str, str]] = None
1214
disable_sms: bool = False
1315
image_id: Optional[str] = None
16+
bms: Optional[Bms] = None
1417

1518
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
1619

1720
@field_validator("variables", mode="before")
1821
@classmethod
1922
def stringify_values(cls, v: Mapping[str, object]):
2023
if isinstance(v, Mapping):
21-
# 모든 value를 str로 캐스팅
22-
return {k: str(val) for k, val in v.items()}
24+
# 키값을 #{변수명} 형태로 변환하고 모든 value를 str로 캐스팅
25+
processed_dict = {}
26+
for k, val in v.items():
27+
# 키가 이미 #{변수명} 형태가 아니면 자동으로 감싸기
28+
if not (k.startswith("#{") and k.endswith("}")):
29+
processed_key = f"#{{{k}}}"
30+
else:
31+
processed_key = k
32+
processed_dict[processed_key] = str(val)
33+
return processed_dict

solapi/model/request/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
# NOTE: Python SDK가 업데이트 될 때마다 Version도 갱신해야 함!
2-
VERSION = "python/5.0.1"
2+
VERSION = "python/5.0.2"

solapi/model/request/groups/get_groups.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class GetGroupsRequest(BaseModel):
1616
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
1717

1818

19-
class GetGroupsCrteriaType(str, Enum):
19+
class GetGroupsCriteriaType(str, Enum):
2020
group_id = "groupId"
2121
date_created = "dateCreated"
2222
scheduled_date = "scheduledDate"

solapi/model/request/kakao/bms.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from typing import Literal
2+
3+
from pydantic import BaseModel, ConfigDict
4+
5+
6+
class Bms(BaseModel):
7+
targeting: Literal["M", "N", "I"]
8+
9+
model_config = ConfigDict(
10+
populate_by_name=True,
11+
extra="ignore",
12+
)

solapi/model/request/kakao/kakao_option.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from pydantic.alias_generators import to_camel
55

66
from solapi.model.kakao.kakao_button import KakaoButton
7+
from solapi.model.request.kakao.bms import Bms
78

89

910
class KakaoOption(BaseModel):
@@ -13,5 +14,6 @@ class KakaoOption(BaseModel):
1314
disable_sms: bool = False
1415
image_id: Optional[str] = None
1516
buttons: Optional[list[KakaoButton]] = None
17+
bms: Optional[Bms] = None
1618

1719
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)

solapi/model/request/message.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
from typing import Any, Optional, Union
22

3-
from pydantic import BaseModel, ConfigDict, Field
3+
from pydantic import BaseModel, ConfigDict, Field, field_validator
44
from pydantic.alias_generators import to_camel
55

66
from solapi.model import KakaoOption
77
from solapi.model.message_type import MessageType
88
from solapi.model.rcs.rcs_options import RcsOption
9+
from solapi.model.request.voice.voice_option import VoiceOption
910

1011

1112
class FileIdsType(BaseModel):
@@ -52,6 +53,29 @@ class Message(BaseModel):
5253
fax_options: Optional[FileIdsType] = Field(
5354
default=None, serialization_alias="faxOptions", validation_alias="faxOptions"
5455
)
56+
voice_options: Optional[VoiceOption] = Field(
57+
default=None,
58+
serialization_alias="voiceOptions",
59+
validation_alias="voiceOptions",
60+
)
61+
62+
@field_validator("from_", mode="before")
63+
@classmethod
64+
def normalize_from_phone_number(cls, v: Optional[str]) -> Optional[str]:
65+
if v is None:
66+
return v
67+
return v.replace("-", "")
68+
69+
@field_validator("to", mode="before")
70+
@classmethod
71+
def normalize_to_phone_number(
72+
cls, v: Union[str, list[str]]
73+
) -> Union[str, list[str]]:
74+
if isinstance(v, str):
75+
return v.replace("-", "")
76+
elif isinstance(v, list):
77+
return [phone.replace("-", "") for phone in v]
78+
return v
5579

5680
model_config = ConfigDict(
5781
extra="ignore",
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from typing import Literal, Optional
2+
3+
from pydantic import BaseModel, ConfigDict
4+
from pydantic.alias_generators import to_camel
5+
6+
7+
class VoiceOption(BaseModel):
8+
voice_type: Literal["FEMALE", "MALE"]
9+
header_message: Optional[str] = None
10+
tail_message: Optional[str] = None
11+
reply_range: Optional[int] = None
12+
counselor_number: Optional[str] = None
13+
14+
model_config = ConfigDict(
15+
alias_generator=to_camel,
16+
populate_by_name=True,
17+
extra="ignore",
18+
)

0 commit comments

Comments
 (0)