Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/v1.1.0 #6

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Testado com Python 3.7. Para dependências, ver _requirements.txt_.
Para instalação automática do SDK e dependências via `pip`, execute a linha abaixo:

```shell
$ pip install git+https://github.com/CPqD/trd-sdk-python.git@v1.0.1
$ pip install git+https://github.com/CPqD/trd-sdk-python.git@v1.1.0
```

#### Servidor WSGI para _callbacks_ via Webhooks
Expand Down Expand Up @@ -153,6 +153,28 @@ for id in c.results:
)
```

## Autenticação JWT
O SDK passa a fornecer autenticação utilizando tokens de autenticação em
formato JWT. Os tokens são gerados automaticamente com a inicialização da classe
de transcrição, além disso em cada requisição são feitas validações do tempo de
vida do token.

```python
from cpqdtrd import TranscriptionClient

client = TranscriptionClient(
api_url="https://speech.cpqd.com.br/trd/v3",
webhook_port=443, # Outbound, precisa de redirecionamento para a WAN
webhook_host="100.100.100.100", # IP externo ou DNS
webhook_listener='0.0.0.0',
webhook_protocol="https",
sl_username="<Usuário da licença>",
sl_password="<Senha da licença>",
sl_host="<Servidor de Autenticação>",
sl_port="<Porta de Autenticação>"
)
```

## Segurança

O SDK também serve de exemplo para uma implementação aderente aos requisitos
Expand Down
142 changes: 125 additions & 17 deletions cpqdtrd/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,40 @@ def __init__(
password: str = "",
retry: int = 60,
retry_period: float = 2,
sl_host=None,
sl_port=None,
sl_protocol="https",
sl_token=None,
sl_username=None,
sl_password=None,
):
self._log = logging.getLogger("cpqdtrd.api")

i = 0
ok = False
self._url = url
self._sl_host = sl_host
self._sl_port = sl_port
self._sl_protocol = sl_protocol
self._sl_username = sl_username
self._sl_password = sl_password

if sl_token:
self._sl_token = sl_token
self._token_expiration = None
else:
self._sl_token, self._token_expiration = self.create_token()

if self._sl_token:
self._headers = {"Authorization": "Bearer " + self._sl_token}
else:
self._headers = {}

if username and password:
self._auth = requests.auth.HTTPBasicAuth(username, password)
else:
self._auth = None

self._log = logging.getLogger("cpqdtrd.api")
while not ok:
try:
for r in self.query():
Expand All @@ -54,7 +77,15 @@ def __init__(
msg = "API call retries exceeded"
raise self.TimeoutException(msg)

def create(self, file_path: str, tag: str = None, config: List[str] = None, callbacks_url: List = []):
def create(
self,
file_path: str,
tag: str = None,
config: List[str] = None,
callbacks_url: List = [],
):
self.check_token_expiration()

upload_request = "{}/job/create".format(self._url)
if tag:
upload_request += "?tag={}".format(tag)
Expand All @@ -64,34 +95,70 @@ def create(self, file_path: str, tag: str = None, config: List[str] = None, call
data["config"] = config

if len(callbacks_url) > 0:
data["callback_urls"] = ','.join(callbacks_url)
data["callback_urls"] = ",".join(callbacks_url)

with open(file_path, "rb") as f:
upload_file = [("upload_file", f)]
return requests.post(
upload_request, data=data, files=upload_file, auth=self._auth
upload_request,
data=data,
files=upload_file,
auth=self._auth,
headers=self._headers,
)

def list_jobs(self, page: int = 1, limit: int = 100, tag: str = None):
self.check_token_expiration()

params = {"page": page, "limit": limit}
if tag:
params["tag"] = tag
return requests.get("{}/job".format(self._url), params=params, auth=self._auth)
return requests.get(
"{}/job".format(self._url),
params=params,
auth=self._auth,
headers=self._headers,
)

def status(self, job_id: str):
return requests.get("{}/job/status/{}".format(self._url, job_id), auth=self._auth)
self.check_token_expiration()
return requests.get(
"{}/job/status/{}".format(self._url, job_id),
auth=self._auth,
headers=self._headers,
)

def result(self, job_id: str):
return requests.get("{}/job/result/{}".format(self._url, job_id), auth=self._auth)
self.check_token_expiration()
return requests.get(
"{}/job/result/{}".format(self._url, job_id),
auth=self._auth,
headers=self._headers,
)

def stop(self, job_id: str):
return requests.post("{}/job/stop/{}".format(self._url, job_id), auth=self._auth)
self.check_token_expiration()
return requests.post(
"{}/job/stop/{}".format(self._url, job_id),
auth=self._auth,
headers=self._headers,
)

def retry(self, job_id: str):
return requests.post("{}/job/retry/{}".format(self._url, job_id), auth=self._auth)
self.check_token_expiration()
return requests.post(
"{}/job/retry/{}".format(self._url, job_id),
auth=self._auth,
headers=self._headers,
)

def delete(self, job_id: str):
return requests.delete("{}/job/{}".format(self._url, job_id), auth=self._auth)
self.check_token_expiration()
return requests.delete(
"{}/job/{}".format(self._url, job_id),
auth=self._auth,
headers=self._headers,
)

def query(
self,
Expand All @@ -105,6 +172,7 @@ def query(
start_date: datetime = None,
end_date: datetime = None,
):
self.check_token_expiration()
request = "{}/query/job".format(self._url)

params = {}
Expand All @@ -127,13 +195,22 @@ def query(
if end_date:
params["end_date"] = end_date.isoformat()

with closing(requests.get(request, params=params, stream=True, auth=self._auth)) as r:
with closing(
requests.get(
request,
params=params,
stream=True,
auth=self._auth,
headers=self._headers,
)
) as r:
for line in r.iter_lines():
yield line

def webhook_whoami(self):
self.check_token_expiration()
whoami_request = "{}/webhook/whoami".format(self._url)
return requests.get(whoami_request, auth=self._auth)
return requests.get(whoami_request, auth=self._auth, headers=self._headers)

def webhook_validate(
self,
Expand All @@ -144,21 +221,52 @@ def webhook_validate(
token: str = "",
crt: str = "",
):
self.check_token_expiration()
test_request = "{}/webhook/validate".format(self._url)
webhook_url = host
if port is not None:
webhook_url += ":{}".format(port)
payload = {
"url": webhook_url
}
payload = {"url": webhook_url}
if timeout:
payload["timeout"] = int(timeout)
if retries:
payload["retries"] = int(retries)
if crt is not None or token is not None:
r = requests.post(
test_request, params=payload, auth=self._auth, json={"crt": crt, "token": token}
test_request,
params=payload,
auth=self._auth,
json={"crt": crt, "token": token},
headers=self._headers,
)
else:
r = requests.get(test_request, params=payload, auth=self._auth)
r = requests.get(
test_request, params=payload, auth=self._auth, headers=self._headers
)
return r

def create_token(self):
if None not in (
self._sl_host,
self._sl_port,
self._sl_username,
self._sl_password,
self._sl_protocol
):
request = requests.post(
url="{}://{}:{}/auth/token".format(self._sl_protocol, self._sl_host, self._sl_port),
auth=(self._sl_username, self._sl_password),
timeout=10,
)
if request.status_code == 200:
access_token = request.json()["access_token"]
token_expiration = int(request.json()["expires_in"]) + int(time.time())
return access_token, token_expiration
request.raise_for_status()
return None, None

def check_token_expiration(self):
if self._token_expiration:
if time.time() >= self._token_expiration:
self._sl_token, self._token_expiration = self.create_token()
self._headers = {"Authorization": "Bearer " + self._sl_token}
27 changes: 21 additions & 6 deletions cpqdtrd/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,29 @@ def __init__(
password=None,
cert_path=None,
key_path=None,
sl_host=None,
sl_port=None,
sl_protocol="https",
sl_token=None,
sl_username=None,
sl_password=None,
**flask_kwargs
):
self._log = logging.getLogger(self.__class__.__name__)

self._result_events = dd(dict) # Event variable dict

self._flask_kwargs = flask_kwargs
self.api = TranscriptionApi(
api_url, username=username, password=password
url=api_url,
username=username,
password=password,
sl_host=sl_host,
sl_port=sl_port,
sl_protocol=sl_protocol,
sl_token=sl_token,
sl_username=sl_username,
sl_password=sl_password,
)

if webhook_host is not None:
Expand All @@ -64,8 +80,6 @@ def __init__(
self._cert_path = cert_path
self._key_path = key_path

self._log = logging.getLogger(self.__class__.__name__)

# User callbacks
self._callbacks = {}

Expand Down Expand Up @@ -147,7 +161,6 @@ def root_callback(job_id):
)
)


def __del__(self):
self.stop()

Expand All @@ -159,7 +172,7 @@ def stop(self):

def register_callback(self, callback, name=None):
"""Register a callback with optional name."""
#print("register_callback:", callback, name)
# print("register_callback:", callback, name)
if name is None:
name = "_callback_{}".format(len(self._callbacks))
elif name[:9] == "_callback":
Expand Down Expand Up @@ -222,7 +235,9 @@ def unregister_all(self):
del self._callbacks[name]
self._reset_start()

def transcribe(self, path, tag=None, config=None, timeout="auto", delete_after=True):
def transcribe(
self, path, tag=None, config=None, timeout="auto", delete_after=True
):
"""
Transcribe an audio file.

Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

setup(
name="cpqdtrd",
version="1.0.1",
version="1.1.0",
description="CPqD Dialog Transcription Client",
install_requires=install_requires,
author="Akira Miasato",
Expand Down