æè¿æåäºä¸ä¸ª TTS(Text to Speach) åº Tetos
ï¼ä¸ºçå°±æ¯ç»ä¸åç§äº TTS æå¡çè°ç¨æ¥å£ï¼è®©ç¨æ·å¯ä»¥ç¨åä¸å¥ä»£ç ï¼åªéè¦åå¨åæ°å°±å¯ä»¥å¨ä¸åç TTS é´åæ¢ã
https://github.com/frostming/tetos
å¨å®ç°è¿ç¨ä¸ï¼æç¿»é äºå¾å¤äº TTS æå¡çæ¥å£ææ¡£ï¼åç°å®ä»¬æ¥å£ç设计大ç¸å¾åºï¼æçæ¯ RESTfulï¼æçæ¯ä¼ª RESTfulï¼æçææ¡£éçè³åªè®©ä½ ç¨ SDKï¼æ²¡æ HTTP æ¥å£è¯´æã æ¬æ¥åï¼æåçå·¥ä½å°±æ¯è®©ç¨æ·å¯ä»¥ä¸ç¨åè¿äºå·¥ä½ï¼ä½æ¬ç¯æç« è¿æ¯æ³ä¸»è¦å槽ä¸ä¸ç«å±±å¼æçæ¥å£ï¼åå®ç SDK 设计ãæ以è¿ç¯å¯è½ä¸è½å«ãå好ç Pythonãäºï¼å¯ä»¥å½å槽大ä¼æ¥çã
æåºé®é¢
åè®¾ä½ æ¯ä¸åå ¬æäºåå Python SDK çå¼åè ï¼ä½ 们çæ¥å£æä¸ä¸ªé常å¤æçéªç¾æºå¶ï¼ä½ 人微è¨è½»ï¼ä¸è½è´¨çï¼åªè½æç §ä¸é¢äº¤ç»ä½ çææ¡£æ¥åãé£ä¹ä½ ä¼æä¹è®¾è®¡è¿ä¸ª SDK ç»ç¨æ·ä½¿ç¨ï¼ è¿ä¸æ¥ï¼ä¸å¦æ们è±ç¦»ç¾åçå ·ä½ç»èï¼æå®æ½è±¡åºæ¥ï¼
sign(request, randomData, secrets) -> signedRequest
ç¾åçè¾å
¥æä¸ä¸ªï¼HTTP 请æ±ãç°åºéæºçæçæ°æ®ï¼åå¯é¥æ°æ®ãè¾åºæ¯ç¾åç请æ±ï¼è¿ä¸ªç¾åå¯è½ä¿®æ¹äºè¯·æ±å¤´ï¼ææ¯è¯·æ±ä½ï¼æ们ä¸ç®¡å®ï¼æ»ä¹åç»å°±ç¨è¿ä¸ªæ°ç请æ±æ§è¡ã åå¦è¿ä¸ª SDK æ¯æçæ¯ requests
åºï¼ä½ ä¼æä¹è®¾è®¡å¢ï¼ä¸å¦¨å
带çè¿ä¸ªæèï¼æ¥åä¸å£å±çä¸ä¸ç«å±±å¼æç SDKã
ä¸é¢ç代ç æ¯æç´æ¥ä»ç«å±±å¼æçæ¥å£ææ¡£éæªåçã
class SAMIService(Service):
_instance_lock = threading.Lock()
def __new__(cls, *args, **kwargs):
if not hasattr(SAMIService, "_instance"):
with SAMIService._instance_lock:
if not hasattr(SAMIService, "_instance"):
SAMIService._instance = object.__new__(cls)
return SAMIService._instance
def __init__(self):
self.service_info = SAMIService.get_service_info()
self.api_info = SAMIService.get_api_info()
super(SAMIService, self).__init__(self.service_info, self.api_info)
@staticmethod
def get_service_info():
api_url = 'open.volcengineapi.com'
service_info = ServiceInfo(api_url, {},
Credentials('', '', 'sami', 'cn-north-1'), 10, 10)
return service_info
@staticmethod
def get_api_info():
api_info = {
"GetToken": ApiInfo("POST", "/", {"Action": "GetToken", "Version": "2021-07-27"}, {}, {}),
}
return api_info
def common_json_handler(self, api, body):
params = dict()
try:
body = json.dumps(body)
res = self.json(api, params, body)
res_json = json.loads(res)
return res_json
except Exception as e:
res = str(e)
try:
res_json = json.loads(res)
return res_json
except:
raise Exception(str(e))
if __name__ == '__main__':
sami_service = SAMIService()
sami_service.set_ak(ACCESS_KEY)
sami_service.set_sk(SECRET_KEY)
req = {"appkey": APPKEY, "token_version": AUTH_VERSION, "expiration": 3600}
resp = sami_service.common_json_handler("GetToken", req)
try:
print("response task_id=%s status_code=%d status_text=%s expires_at=%s\n\t token=%s" % (
resp["task_id"], resp["status_code"], resp["status_text"], resp["expires_at"], resp["token"]))
except:
print("get token failed, ", resp)
大ç å¾æ²»
è¿æ¯ä¸ä¸ªè·å Token ç请æ±ï¼æå使ç¨çæ¯ common_json_handler()
è¿ä¸ªå½æ°ãä¸ç¼çå»ï¼ä½ åç°ä¸ç¹é½ä¸åæ£å¸¸ç Python HTTP è°ç¨é£æ ¼ï¼ä½ 以为ä»æ¯ç¥ä¼ èªå»ºç HTTP è½®åï¼ä½å
¶å®ä¸æ¯ï¼å®åºå±è¿æ¯ requests
ï¼é£ä¹ä¸ºä»ä¹ SDK ä¼åå¾è¿ä¹ç¸å½¢å¢ï¼
æ们å
å¿½ç¥ set_ak()
, Singleton
è¿ç§ä»å«çè¯è¨è¿æ¥çå¨ Python é毫æ å¿
è¦çåæ³ï¼å¹¶ä¸ä¹å¿½ç¥ä»å¨ except Exception
é»è¾éè¿åæ£å¸¸ååºçè¡ä¸ºï¼æå¾å¬çå槽çæè½å¿ï¼è¿ä¹åæ¯è¦æµ¸çªç¬¼çï¼ã
æ第ä¸ä¸ªå对çæ¯ï¼ä¸ºä»ä¹è¦ç¨ç»§æ¿ + staticmethod
çæ¹æ³æ¥åï¼æ们ç¥é Python éç¨ class åºæ¬æ¯è¦å
±äº«ç¶æçï¼èç¨äº staticmethod
就没å¾å
±äº«äºï¼é£ä¹ä¸ºä»ä¹ä¸è½ç´æ¥æ¹æä¸é¢è¿æ ·ï¼
api_url = 'open.volcengineapi.com'
service_info = ServiceInfo(
api_url, {},
Credentials('', '', 'sami', 'cn-north-1'), 10, 10
)
api_info = {
"GetToken": ApiInfo("POST", "/", {"Action": "GetToken", "Version": "2021-07-27"}, {}, {}),
}
sami_service = Service(service_info, api_info)
...
并ä¸é
读代ç å¯ç¥ Credentials
ç头两个åæ°å°±æ¯ access_key
å secret_key
ï¼é£ä¹ç´æ¥ä¼ å
¥ï¼ä¸å¿
åé¢å set_ak
äºãä¸é¢è¿ä¸ªåæ³åä¹åç»§æ¿ + staticmethod
çææå®å
¨ä¸æ ·ã 好äºç°å¨é¤äº common_json_handler()
以å¤è¿ä¸ªç±»çæåå
¨è¢«æå¹²æäºï¼éè¦æ³¨æå° api_info
é仿ä½å
å«çæ¯ä¸äºè¯·æ±ç¸å
³çä¿¡æ¯ï¼ä¾æ¬¡åå«æ¯ method
, path
, body
å headers
ä¹ç±»çä¸è¥¿ãä¸é¢æ们æ¥ççæä¹æ¹æè¿ä¸ªå½æ°ã
common_json_handler()
å¯ä¸ç¨å°ç Service çæ¹æ³æ¯ self.json()
ï¼ä»ååçæµè¿æ¯ä¸ä¸ªæ¥æ¶ JSON ååºçæ¹æ³ï¼æ³¨æå° body å response é½åå«ç»è¿äº json.dumps
å json.loads
ï¼çäºè¿ä¸ªå为 json()
çå½æ°å¥äºé½è¦èªå·±æ¥å¹²ãæ¢ç¶å¦æ¤ä¸è¦æå®æ¾å¨ç±»éé¢äºï¼ç´æ¥æåºæ¥åæä¸ä¸ªå½æ°ã
def common_json_handler(service, api, body):
params = dict()
try:
body = json.dumps(body)
res = service.json(api, params, body)
res_json = json.loads(res)
return res_json
except Exception as e:
# åé¢ç太å¯æäºï¼ä¸è¦å¦
...
è¿è®°å¾ç´æ¥ç¨ requests
æä¹åéåæ¥æ¶ JSON ååºåï¼
res = requests.post(url, json=body)
res_json = res.json()
好ä¼é
ï¼å¥½èæï¼è¿ä¹ä¼é
èæçåºæä¹è¢«ä»å
æäºè¿æ ·ï¼ä¸è¦å¿äºä¸å¼å§æåºçé®é¢ï¼è¦å¯¹è¯·æ±ç¾åãæ们çç Service.json()
çå®ç°ã
def json(self, api, params, body):
if not (api in self.api_info):
raise Exception("no such api")
api_info = self.api_info[api]
r = self.prepare_request(api_info, params)
r.headers['Content-Type'] = 'application/json'
r.body = body
SignerV4.sign(r, self.service_info.credentials)
url = r.build()
resp = self.session.post(url, headers=r.headers, data=r.body,
timeout=(self.service_info.connection_timeout, self.service_info.socket_timeout))
if resp.status_code == 200:
return json.dumps(resp.json())
else:
raise Exception(resp.text.encode("utf-8"))
好家ä¼ï¼é¾æªæè¦èªå·± json.loads()
å¢ï¼json.dumps(resp.json())
æ¥æ¥æ¥ï¼ä½ è¿æ¥æä¿è¯ä¸ææ»ä½ ã
æ¥ççï¼è¿éåºç°äºå
³é®ç SignerV4.sign()
ï¼åæ°æ¯ä¸ä¸ªèªå·±çæç request 对象ï¼åä¸é¢ææ½è±¡çå·®ä¸å¤ï¼éè¦ä¸äºè¯·æ±çä¿¡æ¯åå¯é¥ãè¿ä¹æ¯ä¸ºä»ä¹è¦ä¸ä¸ªå¦æ¤å¥æªç api_info
ï¼å 为è¿æ¯ç¾åéè¦ç¨ç请æ±çä¿¡æ¯ï¼åªå¥½åç¬ä¼ éã好äºé®é¢æ¾å°äºï¼æè¿ä¹å¥æªï¼å°±æ¯å 为ä»èªå·±å¼äºä¸ªè¯·æ±å¯¹è±¡ï¼ç¶ååè¦è´¹å²æå®åæ requests
æ¥åç对象ï¼r.build()
æ¿ URL å r.headers
, r.body
ï¼ãé£ä¹è¯·é®ä¸ï¼ä¸ºä»ä¹ä¸è½ç¨ requests
å
é¨ç请æ±å¯¹è±¡å»çæç¾åï¼åæ£æç»æ¯è¦é requests
åé请æ±ï¼è¦æçä¿¡æ¯è¿å
¨é½æã就好æ¯ä½ è·é©¬ææ¾ï¼è¡¥ç»ç¹é½æ¯å¨è·éå¿
ç»ä¹å¤ï¼æ³è±¡ä¸ä¸ä½ è¦å个水è¿è¦ä¸é¨è·å²è·¯å»è¡¥ç»ç¹ï¼æçä¸ä¸ªå§æ§½ã
å°½éä¸è¦èªå·±å°è£ æ°ç对象ï¼å ä¸ºä½ è¦æ·è´åæçå±æ§ã
é£ä¹ç°å¨è¦åçäºæ
å°±æ¸
æ¥äºï¼å°±æ¯è¦å¨è¯·æ±åä¿®æ¹ requests
å³å°è¦åéç请æ±å¯¹è±¡ï¼ç»å®å ä¸ç¾åä¿¡æ¯ãè¿å
¶å®æ¯ä¸ç§ interceptorï¼requests
æä»ä¹æºå¶å®ç°è¿ä¸ªéæ±å¢ï¼ æ第ä¸æ³å°çæ¯ Event Hookï¼ä½ä»¿ä½ requests
没æ before_request
è¿ä¸ªé©åï¼æ¾ç»æï¼ï¼é£ä¹æ¥ä¸æ¥èèçæ¯éè½½ï¼ç±äºè¿ä¸ªç¾åæ¹æ³æ¯åºç¨å¨ request 对象ä¸çï¼æ以ä¸åå¨ get
ï¼post
ä¹ååæç« ï¼å 为è¿ä¸¤ä¸ªæ¹æ³é½è¿æ²¡äº§ç request 对象å¢ï¼å¯ä»¥éè½½ Session.send()
è¿ä¸ªæ¹æ³ï¼
class VolcSession(requests.Session):
def send(self, request, **kwargs):
# new_sign å
·ä½å®ç°ç¥ï¼ç
§æå³å¯
new_sign(request, service_info, credentials)
return super().send(request, **kwargs)
ï¼éè½½ Session.prepare_request()
ä¹æ¯ä¸æ ·çææï¼åºå«æ¯å¨ super()
è¿åç对象ä¸ä¿®æ¹ï¼ä¸ç¥å¯¹å¼å§çé®é¢ä½ 们å¿ç®ä¸çæ¹æ¡æ¯ä¸æ¯è¿æ ·ã
ä½æ¯ï¼æ说ä½æ¯äºï¼è¿éæ好çæ¹æ³å©ç¨ï¼requests.auth
ï¼ä»çç¾åæ¯è¿æ ·çï¼
class AuthBase:
"""Base class that all auth implementations derive from"""
def __call__(self, r):
raise NotImplementedError("Auth hooks must be callable.")
æ¥æ¶ä¸ä¸ªå¯ä¸å¯¹è±¡ r
ï¼è¿ä¸ªå°±æ¯å³å°è¦åéç请æ±ï¼å¹¶è¿åä¸ä¸ªæ°ç请æ±ï¼ä½ å¯ä»¥å¯¹å®ä½ä»»ä½ä¿®æ¹ï¼è¿ä¸å°±æ¯æ们è¦åçäºæ
åï¼ç¾åæéçå
¶ä»ä¿¡æ¯ï¼å¯ä»¥ä½ä¸º __init__
çåå§ååæ°ãé£ä¹å°±å¯ä»¥æ¹åæï¼
class VolcAuth(AuthBase):
def __init__(self, service_info, credentials):
self.service_info = service_info
self.credentials = credentials
def __call__(self, r):
# new_sign å
·ä½å®ç°ç¥ï¼ç
§æå³å¯ï¼åºå«æ¯èªå®ä¹ç request 对象æ¹æäº requests ç
new_sign(r, self.service_info, self.credentials)
return r
åªéè¦è¿ä¸ä¸ªå°å°ç对象å³å¯ãå©ç¨åºçå·²åå¨çæ°æ®ç»æç好å¤æ¯ï¼æ们è½æ大åä¿æåæ¥çåºçæ¥å£ï¼å 为请æ±æ¹æ³æ们没æä»»ä½ä¾µå ¥ãç¨è¿ä¸ª Auth 对象请æ±çæ¹æ³æ¯ï¼
auth = VolcAuth(service_info, credentials)
res = requests.post(url, json=body, auth=auth)
è¿æ · post()
æ¹æ³éçææåæ°ï¼å
æ¬ data
, files
, headers
ä½ å¯ä»¥ä»»æ使ç¨ï¼å°±åç¨ requests
ä¸æ ·å»è°ç«å±±çæ¥å£ï¼ä½ è¿å¯ä»¥æå建ä¸ä¸ªå¸¦ auth ç Session
ï¼è¿æ ·åé¢è°ç¨å°±ä¸ç¨æ¯æ¬¡é½ä¼ auth
äºãæ æ亲è¤ï¼å°±åå°ä¸å
裤ä¸æ ·ã
对ä¸ä¸ªåºçéè½½æä¿®æ¹ï¼ä¿®æ¹é¢è¦è¶å°è¶å¥½ï¼å¹¶å°½å¯è½å©ç¨åºæ¬èº«æä¾çæ©å±æ¹å¼ã
è¿ä¸ä¸é¢çæ¹æ¡ç¸æ¯ï¼ä¸é¢éè¦ç»§æ¿ Session
ï¼èå©ç¨ç AuthBase
æ¬æ¥å°±æ¯æä¾ç»ä½ æ©å±çï¼èä¸å建ç对象 Auth æ¯ Session å°å¾å¤ãåªæå½åºæ©å±è½åä¸è¶³æ¶ï¼æèèåé¢çæ¹å¼ï¼ä¸ç´å°æ è½ä¸ºåï¼çè³å¨ç¨ monkey patch è¿ç§æ¦å¨ã
è¿éé¢çç»å¾®ä¼å£ï¼å°±åä½ æ³è¦è½¦çæ个é«çº§åè½ï¼ä½ æ¯å¸æå¾å°ä¸ä¸ªæå°ä»»ä½è½¦ä¸é½è½ç¨çé¶ä»¶ï¼è¿æ¯ä¸å°å级好ç车ï¼ä¸ä½ ä¸ç¥éå®æ¹äºåªéå¢ï¼
åèå®ç°
æå¨ Tetos éåäºä¸ä¸ªé对 httpx
ç Auth
å®ç°ï¼å requests
ç Auth
ä½ç¨å·®ä¸å¤ï¼æå
´è¶£çè¯çè³å¯ä»¥ç¨ä¸ä¸ª Auth
åæ¶æ¯æ httpx
å requests
两个åºã
æ¯è¾ä¸ä¸ï¼è¿ä¸ªå®ç° 62 è¡ï¼å ä¸ä¸è¶ è¿ä¸¤è¡çè°ç¨ï¼å®ç°äºåæ¥ SignerV4.py 207 è¡ï¼å ä¸ Service.py 290 è¡ï¼è¿ 500 è¡ï¼è¿æ²¡ç®ä¸ import çå ¬å ±å½æ°ï¼ååçå·®è·ãå¯è§é 读åºçææ¡£ï¼çæ¸ é»è¾ï¼æ¯å¯ä»¥å¤§å¤§èç代ç éçã
æ»ç»
è¿ä¸ª SDK åæè¿æ ·ï¼å¯è½æ¯ç´æ¥ä»å«çè¯è¨ç´è¯è¿æ¥çãä¸ç¥ä»äº code review ç @piglei å¦ä½çå¾ ï¼è½ä¸è½è¿ä½ è¿å ³ãå¦æé 读æ¬æçä½ æ°å¥½å°±æ¯ç»´æ¤è¿ä¸ª SDK ç人被æä¸ä¼¤äºæ深表æ±æï¼å¹¶ç»å¯¹ä¸æ¹ã