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

feat: Enhance OAuth configuration and improve example scripts #162

Merged
merged 1 commit into from
Jan 15, 2025
Merged
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
2 changes: 2 additions & 0 deletions cozepy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
Scope,
TokenAuth,
WebOAuthApp,
load_oauth_app_from_config,
)
from .bots import (
Bot,
Expand Down Expand Up @@ -159,6 +160,7 @@
# audio.transcriptions
"CreateTranscriptionsResp",
# auth
"load_oauth_app_from_config",
"AsyncDeviceOAuthApp",
"AsyncJWTOAuthApp",
"AsyncPKCEOAuthApp",
Expand Down
25 changes: 24 additions & 1 deletion cozepy/auth/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import abc
import json
import time
from typing import List, Optional
from typing import List, Optional, Union
from urllib.parse import quote_plus, urlparse

from authlib.jose import jwt # type: ignore
Expand Down Expand Up @@ -660,6 +661,28 @@
return await self._arefresh_access_token(refresh_token)


def load_oauth_app_from_config(conf: str) -> Union[PKCEOAuthApp, JWTOAuthApp, DeviceOAuthApp, WebOAuthApp]:
config = json.loads(conf)
client_id = config.get("client_id", "")
client_type = config.get("client_type", "")
coze_api_base = config.get("coze_api_base", "")
coze_www_base = config.get("coze_www_base", "")

Check warning on line 669 in cozepy/auth/__init__.py

View check run for this annotation

Codecov / codecov/patch

cozepy/auth/__init__.py#L665-L669

Added lines #L665 - L669 were not covered by tests

if client_type == "single_page":
return PKCEOAuthApp(client_id, coze_api_base, coze_www_base)
elif client_type == "server":
private_key = config.get("private_key", "")
public_key_id = config.get("public_key_id", "")
return JWTOAuthApp(client_id, private_key, public_key_id, coze_api_base)
elif client_type == "device":
return DeviceOAuthApp(client_id, coze_api_base, coze_www_base)
elif client_type == "web":
client_secret = config.get("client_secret", "")
return WebOAuthApp(client_id, client_secret, coze_api_base, coze_www_base)

Check warning on line 681 in cozepy/auth/__init__.py

View check run for this annotation

Codecov / codecov/patch

cozepy/auth/__init__.py#L671-L681

Added lines #L671 - L681 were not covered by tests
else:
raise ValueError(f"Invalid OAuth client_type: {client_type}")

Check warning on line 683 in cozepy/auth/__init__.py

View check run for this annotation

Codecov / codecov/patch

cozepy/auth/__init__.py#L683

Added line #L683 was not covered by tests


class Auth(abc.ABC):
"""
This class is the base class for all authorization types.
Expand Down
33 changes: 26 additions & 7 deletions examples/chat_stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,37 @@
"""

import os
from typing import Optional

from cozepy import COZE_COM_BASE_URL
from examples.utils import get_coze_api_token
from cozepy import COZE_CN_BASE_URL, ChatEventType, Coze, DeviceOAuthApp, Message, TokenAuth # noqa

# The default access is api.coze.com, but if you need to access api.coze.cn,
# please use base_url to configure the api endpoint to access
coze_api_base = os.getenv("COZE_API_BASE") or COZE_COM_BASE_URL

from cozepy import Coze, TokenAuth, Message, ChatStatus, MessageContentType, ChatEventType # noqa
def get_coze_api_base() -> str:
# The default access is api.coze.com, but if you need to access api.coze.cn,
# please use base_url to configure the api endpoint to access
coze_api_base = os.getenv("COZE_API_BASE")
if coze_api_base:
return coze_api_base

return COZE_CN_BASE_URL # default


def get_coze_api_token(workspace_id: Optional[str] = None) -> str:
# Get an access_token through personal access token or oauth.
coze_api_token = os.getenv("COZE_API_TOKEN")
if coze_api_token:
return coze_api_token

coze_api_base = get_coze_api_base()

device_oauth_app = DeviceOAuthApp(client_id="57294420732781205987760324720643.app.coze", base_url=coze_api_base)
device_code = device_oauth_app.get_device_code(workspace_id)
print(f"Please Open: {device_code.verification_url} to get the access token")
return device_oauth_app.get_access_token(device_code=device_code.device_code, poll=True).access_token


# Init the Coze client through the access_token.
coze = Coze(auth=TokenAuth(token=get_coze_api_token()), base_url=coze_api_base)
coze = Coze(auth=TokenAuth(token=get_coze_api_token()), base_url=get_coze_api_base())

# Create a bot instance in Coze, copy the last number from the web link as the bot's ID.
bot_id = os.getenv("COZE_BOT_ID") or "bot id"
Expand Down
35 changes: 0 additions & 35 deletions examples/utils/__init__.py

This file was deleted.

38 changes: 36 additions & 2 deletions examples/websockets_audio_speech.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,61 @@
import asyncio
import json
import logging
import os
from typing import Optional

from cozepy import (
COZE_CN_BASE_URL,
AsyncCoze,
AsyncWebsocketsAudioSpeechClient,
AsyncWebsocketsAudioSpeechEventHandler,
DeviceOAuthApp,
InputTextBufferAppendEvent,
InputTextBufferCompletedEvent,
SpeechAudioCompletedEvent,
SpeechAudioUpdateEvent,
TokenAuth,
setup_logging,
)
from cozepy.log import log_info
from cozepy.util import write_pcm_to_wav_file
from examples.utils import get_coze_api_base, get_coze_api_token, setup_examples_logger


def get_coze_api_base() -> str:
# The default access is api.coze.com, but if you need to access api.coze.cn,
# please use base_url to configure the api endpoint to access
coze_api_base = os.getenv("COZE_API_BASE")
if coze_api_base:
return coze_api_base

return COZE_CN_BASE_URL # default


def get_coze_api_token(workspace_id: Optional[str] = None) -> str:
# Get an access_token through personal access token or oauth.
coze_api_token = os.getenv("COZE_API_TOKEN")
if coze_api_token:
return coze_api_token

coze_api_base = get_coze_api_base()

device_oauth_app = DeviceOAuthApp(client_id="57294420732781205987760324720643.app.coze", base_url=coze_api_base)
device_code = device_oauth_app.get_device_code(workspace_id)
print(f"Please Open: {device_code.verification_url} to get the access token")
return device_oauth_app.get_access_token(device_code=device_code.device_code, poll=True).access_token


def setup_examples_logger():
coze_log = os.getenv("COZE_LOG")
if coze_log:
setup_logging(logging.getLevelNamesMapping().get(coze_log.upper(), logging.INFO))


setup_examples_logger()

kwargs = json.loads(os.getenv("COZE_KWARGS") or "{}")


# todo review
class AsyncWebsocketsAudioSpeechEventHandlerSub(AsyncWebsocketsAudioSpeechEventHandler):
"""
Class is not required, you can also use Dict to set callback
Expand Down
37 changes: 36 additions & 1 deletion examples/websockets_audio_transcriptions.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,54 @@
import asyncio
import json
import logging
import os
from typing import Optional

from cozepy import (
COZE_CN_BASE_URL,
AsyncCoze,
AsyncWebsocketsAudioTranscriptionsClient,
AsyncWebsocketsAudioTranscriptionsEventHandler,
AudioFormat,
DeviceOAuthApp,
InputAudioBufferAppendEvent,
InputAudioBufferCompletedEvent,
TokenAuth,
TranscriptionsMessageUpdateEvent,
setup_logging,
)
from cozepy.log import log_info
from examples.utils import get_coze_api_base, get_coze_api_token, setup_examples_logger


def get_coze_api_base() -> str:
# The default access is api.coze.com, but if you need to access api.coze.cn,
# please use base_url to configure the api endpoint to access
coze_api_base = os.getenv("COZE_API_BASE")
if coze_api_base:
return coze_api_base

return COZE_CN_BASE_URL # default


def get_coze_api_token(workspace_id: Optional[str] = None) -> str:
# Get an access_token through personal access token or oauth.
coze_api_token = os.getenv("COZE_API_TOKEN")
if coze_api_token:
return coze_api_token

coze_api_base = get_coze_api_base()

device_oauth_app = DeviceOAuthApp(client_id="57294420732781205987760324720643.app.coze", base_url=coze_api_base)
device_code = device_oauth_app.get_device_code(workspace_id)
print(f"Please Open: {device_code.verification_url} to get the access token")
return device_oauth_app.get_access_token(device_code=device_code.device_code, poll=True).access_token


def setup_examples_logger():
coze_log = os.getenv("COZE_LOG")
if coze_log:
setup_logging(logging.getLevelNamesMapping().get(coze_log.upper(), logging.INFO))


setup_examples_logger()

Expand Down
37 changes: 36 additions & 1 deletion examples/websockets_chat.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import asyncio
import json
import logging
import os
from typing import Optional

from cozepy import (
COZE_CN_BASE_URL,
AsyncCoze,
AsyncWebsocketsChatClient,
AsyncWebsocketsChatEventHandler,
Expand All @@ -13,13 +16,45 @@
ConversationChatRequiresActionEvent,
ConversationChatSubmitToolOutputsEvent,
ConversationMessageDeltaEvent,
DeviceOAuthApp,
InputAudioBufferAppendEvent,
TokenAuth,
ToolOutput,
setup_logging,
)
from cozepy.log import log_info
from cozepy.util import write_pcm_to_wav_file
from examples.utils import get_coze_api_base, get_coze_api_token, setup_examples_logger


def get_coze_api_base() -> str:
# The default access is api.coze.com, but if you need to access api.coze.cn,
# please use base_url to configure the api endpoint to access
coze_api_base = os.getenv("COZE_API_BASE")
if coze_api_base:
return coze_api_base

return COZE_CN_BASE_URL # default


def get_coze_api_token(workspace_id: Optional[str] = None) -> str:
# Get an access_token through personal access token or oauth.
coze_api_token = os.getenv("COZE_API_TOKEN")
if coze_api_token:
return coze_api_token

coze_api_base = get_coze_api_base()

device_oauth_app = DeviceOAuthApp(client_id="57294420732781205987760324720643.app.coze", base_url=coze_api_base)
device_code = device_oauth_app.get_device_code(workspace_id)
print(f"Please Open: {device_code.verification_url} to get the access token")
return device_oauth_app.get_access_token(device_code=device_code.device_code, poll=True).access_token


def setup_examples_logger():
coze_log = os.getenv("COZE_LOG")
if coze_log:
setup_logging(logging.getLevelNamesMapping().get(coze_log.upper(), logging.INFO))


setup_examples_logger()

Expand Down
12 changes: 10 additions & 2 deletions examples/websockets_chat_realtime_gui.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import asyncio
import json
import logging
import os
import queue
import threading
Expand All @@ -18,11 +19,11 @@
ChatUpdateEvent,
ConversationAudioDeltaEvent,
ConversationChatCompletedEvent,
InputAudio,
InputAudioBufferAppendEvent,
TokenAuth,
setup_logging,
)
from cozepy.websockets.ws import InputAudio
from examples.utils import setup_examples_logger

# 音频参数设置
CHUNK = 1024
Expand All @@ -31,6 +32,13 @@
RATE = 24000
INPUT_BLOCK_TIME = 0.05 # 50ms per block


def setup_examples_logger():
coze_log = os.getenv("COZE_LOG")
if coze_log:
setup_logging(logging.getLevelNamesMapping().get(coze_log.upper(), logging.INFO))


setup_examples_logger()


Expand Down
Loading