From 0133ae637f4432c6cf26333125f06d6821348935 Mon Sep 17 00:00:00 2001 From: chyroc Date: Mon, 23 Sep 2024 16:36:47 +0800 Subject: [PATCH 1/2] chore: add black to format code --- .github/workflows/ci.yml | 4 ++ poetry.lock | 84 +++++++++++++++++++++++++++++++++++++++- pyproject.toml | 14 +++++++ 3 files changed, 101 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cb8cafc..b594cdc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,6 +29,10 @@ jobs: run: | pip install poetry poetry install + - name: Build + run: | + poetry run black . --check + poetry build - name: Run tests run: poetry run pytest env: diff --git a/poetry.lock b/poetry.lock index 7344d50..b4c708c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -28,6 +28,52 @@ files = [ [package.dependencies] cryptography = "*" +[[package]] +name = "black" +version = "24.8.0" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-24.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09cdeb74d494ec023ded657f7092ba518e8cf78fa8386155e4a03fdcc44679e6"}, + {file = "black-24.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:81c6742da39f33b08e791da38410f32e27d632260e599df7245cccee2064afeb"}, + {file = "black-24.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:707a1ca89221bc8a1a64fb5e15ef39cd755633daa672a9db7498d1c19de66a42"}, + {file = "black-24.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d6417535d99c37cee4091a2f24eb2b6d5ec42b144d50f1f2e436d9fe1916fe1a"}, + {file = "black-24.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb6e2c0b86bbd43dee042e48059c9ad7830abd5c94b0bc518c0eeec57c3eddc1"}, + {file = "black-24.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:837fd281f1908d0076844bc2b801ad2d369c78c45cf800cad7b61686051041af"}, + {file = "black-24.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62e8730977f0b77998029da7971fa896ceefa2c4c4933fcd593fa599ecbf97a4"}, + {file = "black-24.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:72901b4913cbac8972ad911dc4098d5753704d1f3c56e44ae8dce99eecb0e3af"}, + {file = "black-24.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c046c1d1eeb7aea9335da62472481d3bbf3fd986e093cffd35f4385c94ae368"}, + {file = "black-24.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:649f6d84ccbae73ab767e206772cc2d7a393a001070a4c814a546afd0d423aed"}, + {file = "black-24.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b59b250fdba5f9a9cd9d0ece6e6d993d91ce877d121d161e4698af3eb9c1018"}, + {file = "black-24.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e55d30d44bed36593c3163b9bc63bf58b3b30e4611e4d88a0c3c239930ed5b2"}, + {file = "black-24.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:505289f17ceda596658ae81b61ebbe2d9b25aa78067035184ed0a9d855d18afd"}, + {file = "black-24.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b19c9ad992c7883ad84c9b22aaa73562a16b819c1d8db7a1a1a49fb7ec13c7d2"}, + {file = "black-24.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f13f7f386f86f8121d76599114bb8c17b69d962137fc70efe56137727c7047e"}, + {file = "black-24.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:f490dbd59680d809ca31efdae20e634f3fae27fba3ce0ba3208333b713bc3920"}, + {file = "black-24.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eab4dd44ce80dea27dc69db40dab62d4ca96112f87996bca68cd75639aeb2e4c"}, + {file = "black-24.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3c4285573d4897a7610054af5a890bde7c65cb466040c5f0c8b732812d7f0e5e"}, + {file = "black-24.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e84e33b37be070ba135176c123ae52a51f82306def9f7d063ee302ecab2cf47"}, + {file = "black-24.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:73bbf84ed136e45d451a260c6b73ed674652f90a2b3211d6a35e78054563a9bb"}, + {file = "black-24.8.0-py3-none-any.whl", hash = "sha256:972085c618ee94f402da1af548a4f218c754ea7e5dc70acb168bfaca4c2542ed"}, + {file = "black-24.8.0.tar.gz", hash = "sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + [[package]] name = "cachetools" version = "5.5.0" @@ -239,6 +285,20 @@ files = [ {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + [[package]] name = "colorama" version = "0.4.6" @@ -365,6 +425,17 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + [[package]] name = "packaging" version = "24.1" @@ -376,6 +447,17 @@ files = [ {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + [[package]] name = "platformdirs" version = "4.3.6" @@ -693,4 +775,4 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "89226780c9c1f2da81886c86bcce46692884b4ad03047037b316a70f97aeda00" +content-hash = "49063bb98b1cc0e6b33d27ec23ab072332feb2a78275e3fa27fd6601a2abef68" diff --git a/pyproject.toml b/pyproject.toml index 62919b3..de5e9e2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,20 @@ authlib = "^1.3.2" [tool.poetry.group.dev.dependencies] pytest = "^8.3.3" tox = "^4.20.0" +black = "^24.8.0" + +[tool.black] +line-length = 120 +target-version = ['py38'] +include = '\.pyi?$' +extend-exclude = ''' +/( + # The following are specific to Black, you probably don't want those. + \.git + | .venv + | dist +) +''' [build-system] requires = ["poetry-core"] From 4289dba19edae67199233e214d3276204082ff68 Mon Sep 17 00:00:00 2001 From: chyroc Date: Mon, 23 Sep 2024 16:38:21 +0800 Subject: [PATCH 2/2] format code --- cozepy/__init__.py | 19 ++++++++----------- cozepy/auth.py | 34 ++++++++++++++++------------------ cozepy/bot.py | 27 ++++++++++++--------------- cozepy/config.py | 4 ++-- cozepy/coze.py | 12 +++++++----- cozepy/model.py | 12 ++++++------ cozepy/request.py | 39 ++++++++++++++++++++++----------------- tests/test_auth.py | 12 ++++++------ tests/test_bot.py | 8 ++++---- 9 files changed, 83 insertions(+), 84 deletions(-) diff --git a/cozepy/__init__.py b/cozepy/__init__.py index b7f390a..925b5c6 100644 --- a/cozepy/__init__.py +++ b/cozepy/__init__.py @@ -4,15 +4,12 @@ from .model import TokenPaged, NumberPaged __all__ = [ - 'ApplicationOAuth', - 'Auth', - 'TokenAuth', - - 'COZE_COM_BASE_URL', - 'COZE_CN_BASE_URL', - - 'Coze', - - 'TokenPaged', - 'NumberPaged' + "ApplicationOAuth", + "Auth", + "TokenAuth", + "COZE_COM_BASE_URL", + "COZE_CN_BASE_URL", + "Coze", + "TokenPaged", + "NumberPaged", ] diff --git a/cozepy/auth.py b/cozepy/auth.py index 7386a58..9781f43 100644 --- a/cozepy/auth.py +++ b/cozepy/auth.py @@ -11,8 +11,8 @@ def _random_hex(length): - hex_characters = '0123456789abcdef' - return ''.join(random.choice(hex_characters) for _ in range(length)) + hex_characters = "0123456789abcdef" + return "".join(random.choice(hex_characters) for _ in range(length)) class OAuthToken(CozeModel): @@ -21,9 +21,9 @@ class OAuthToken(CozeModel): # How long the access token is valid, in seconds (UNIX timestamp) expires_in: int # An OAuth 2.0 refresh token. The app can use this token to acquire other access tokens after the current access token expires. Refresh tokens are long-lived. - refresh_token: str = '' + refresh_token: str = "" # fixed: Bearer - token_type: str = '' + token_type: str = "" class DeviceAuthCode(CozeModel): @@ -40,7 +40,7 @@ class DeviceAuthCode(CozeModel): @property def verification_url(self): - return f'{self.verification_uri}?user_code={self.user_code}' + return f"{self.verification_uri}?user_code={self.user_code}" class ApplicationOAuth(object): @@ -48,12 +48,12 @@ class ApplicationOAuth(object): App OAuth process to support obtaining token and refreshing token. """ - def __init__(self, client_id: str, client_secret: str = '', base_url: str = COZE_COM_BASE_URL): + def __init__(self, client_id: str, client_secret: str = "", base_url: str = COZE_COM_BASE_URL): self._client_id = client_id self._client_secret = client_secret self._base_url = base_url self._api_endpoint = urlparse(base_url).netloc - self._token = '' + self._token = "" self._requester = Requester() def jwt_auth(self, private_key: str, kid: str, ttl: int): @@ -61,28 +61,26 @@ def jwt_auth(self, private_key: str, kid: str, ttl: int): Get the token by jwt with jwt auth flow. """ jwt_token = self._gen_jwt(self._api_endpoint, private_key, self._client_id, kid, 3600) - url = f'{self._base_url}/api/permission/oauth2/token' - headers = { - 'Authorization': f'Bearer {jwt_token}' - } + url = f"{self._base_url}/api/permission/oauth2/token" + headers = {"Authorization": f"Bearer {jwt_token}"} body = { - 'duration_seconds': ttl, - 'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer', + "duration_seconds": ttl, + "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer", } - return self._requester.request('post', url, OAuthToken, headers=headers, body=body) + return self._requester.request("post", url, OAuthToken, headers=headers, body=body) def _gen_jwt(self, api_endpoint: str, private_key: str, client_id: str, kid: str, ttl: int): now = int(time.time()) - header = {'alg': 'RS256', 'typ': 'JWT', 'kid': kid} + header = {"alg": "RS256", "typ": "JWT", "kid": kid} payload = { "iss": client_id, - 'aud': api_endpoint, + "aud": api_endpoint, "iat": now, "exp": now + ttl, - 'jti': _random_hex(16), + "jti": _random_hex(16), } s = jwt.encode(header, payload, private_key) - return s.decode('utf-8') + return s.decode("utf-8") class Auth(abc.ABC): diff --git a/cozepy/bot.py b/cozepy/bot.py index c927dc6..0f239e2 100644 --- a/cozepy/bot.py +++ b/cozepy/bot.py @@ -13,7 +13,7 @@ class BotPromptInfo(CozeModel): class BotOnboardingInfo(CozeModel): # Configured prologue of the bot. - prologue: str = '' + prologue: str = "" # The list of recommended questions configured for the bot. # This field will not be returned when user suggested questions are not enabled. suggested_questions: List[str] = [] @@ -113,30 +113,27 @@ def get_online_info_v1(self, *, bot_id: str) -> Bot: :docs: https://www.coze.com/docs/developer_guides/get_metadata?_lang=en :calls: `GET /v1/bot/get_online_info` """ - url = f'{self._base_url}/v1/bot/get_online_info' - params = { - 'bot_id': bot_id - } + url = f"{self._base_url}/v1/bot/get_online_info" + params = {"bot_id": bot_id} - return self._requester.request('get', url, Bot, params=params) + return self._requester.request("get", url, Bot, params=params) - def list_published_bots_v1(self, *, - space_id: str, - page_num: int = 1, - page_size: int = 20) -> NumberPaged[SimpleBot]: + def list_published_bots_v1( + self, *, space_id: str, page_num: int = 1, page_size: int = 20 + ) -> NumberPaged[SimpleBot]: """ Get the bots published as API service. :docs: https://www.coze.com/docs/developer_guides/published_bots_list?_lang=en :calls: `GET /v1/space/published_bots_list` """ - url = f'{self._base_url}/v1/space/published_bots_list' + url = f"{self._base_url}/v1/space/published_bots_list" params = { - 'space_id': space_id, - 'page_size': page_size, - 'page_index': page_num, + "space_id": space_id, + "page_size": page_size, + "page_index": page_num, } - data = self._requester.request('get', url, self._PrivateListPublishedBotsV1Data, params=params) + data = self._requester.request("get", url, self._PrivateListPublishedBotsV1Data, params=params) return NumberPaged( items=data.space_bots, page_num=page_num, diff --git a/cozepy/config.py b/cozepy/config.py index 487230a..d2cd7d4 100644 --- a/cozepy/config.py +++ b/cozepy/config.py @@ -1,2 +1,2 @@ -COZE_COM_BASE_URL = 'https://api.coze.com' -COZE_CN_BASE_URL = 'https://api.coze.cn' +COZE_COM_BASE_URL = "https://api.coze.com" +COZE_CN_BASE_URL = "https://api.coze.cn" diff --git a/cozepy/coze.py b/cozepy/coze.py index 5a9d088..b04543e 100644 --- a/cozepy/coze.py +++ b/cozepy/coze.py @@ -9,10 +9,11 @@ class Coze(object): - def __init__(self, - auth: Auth, - base_url: str = COZE_COM_BASE_URL, - ): + def __init__( + self, + auth: Auth, + base_url: str = COZE_COM_BASE_URL, + ): self._auth = auth self._base_url = base_url self._requester = Requester(auth=auth) @@ -21,8 +22,9 @@ def __init__(self, self._bot = None @property - def bot(self) -> 'BotClient': + def bot(self) -> "BotClient": if not self._bot: from cozepy.bot import BotClient + self._bot = BotClient(self._base_url, self._auth, self._requester) return self._bot diff --git a/cozepy/model.py b/cozepy/model.py index e02f905..351e068 100644 --- a/cozepy/model.py +++ b/cozepy/model.py @@ -6,9 +6,7 @@ class CozeModel(BaseModel): - model_config = ConfigDict( - protected_namespaces=() - ) + model_config = ConfigDict(protected_namespaces=()) class PagedBase(Generic[T]): @@ -30,8 +28,8 @@ class TokenPaged(PagedBase[T]): return is next_page_token + has_more. """ - def __init__(self, items: List[T], next_page_token: str = '', has_more: bool = None): - has_more = has_more if has_more is not None else next_page_token != '' + def __init__(self, items: List[T], next_page_token: str = "", has_more: bool = None): + has_more = has_more if has_more is not None else next_page_token != "" super().__init__(items, has_more) self.next_page_token = next_page_token @@ -48,4 +46,6 @@ def __init__(self, items: List[T], page_num: int, page_size: int, total: int = N self.total = total def __repr__(self): - return f"NumberPaged(items={self.items}, page_num={self.page_num}, page_size={self.page_size}, total={self.total})" + return ( + f"NumberPaged(items={self.items}, page_num={self.page_num}, page_size={self.page_size}, total={self.total})" + ) diff --git a/cozepy/request.py b/cozepy/request.py index 8b4ffe2..5bf051a 100644 --- a/cozepy/request.py +++ b/cozepy/request.py @@ -10,7 +10,7 @@ from pydantic import BaseModel -T = TypeVar('T', bound=BaseModel) +T = TypeVar("T", bound=BaseModel) def json_obj_to_pydantic(json_obj: dict, model: Type[T]) -> T: @@ -25,13 +25,18 @@ class Requester(object): http request helper class. """ - def __init__(self, - auth: 'Auth' = None - ): + def __init__(self, auth: "Auth" = None): self._auth = auth - def request(self, method: str, url: str, model: Type[T], params: dict = None, headers: dict = None, - body: dict = None, ) -> T: + def request( + self, + method: str, + url: str, + model: Type[T], + params: dict = None, + headers: dict = None, + body: dict = None, + ) -> T: """ Send a request to the server. """ @@ -45,11 +50,11 @@ def request(self, method: str, url: str, model: Type[T], params: dict = None, he if code is not None and code > 0: # TODO: Exception 自定义类型 - logid = r.headers.get('x-tt-logid') - raise Exception(f'{code}: {msg}, logid:{logid}') + logid = r.headers.get("x-tt-logid") + raise Exception(f"{code}: {msg}, logid:{logid}") elif code is None and msg != "": - logid = r.headers.get('x-tt-logid') - raise Exception(f'{msg}, logid:{logid}') + logid = r.headers.get("x-tt-logid") + raise Exception(f"{msg}, logid:{logid}") return model.model_validate(data) async def arequest(self, method: str, path: str, **kwargs) -> dict: @@ -65,10 +70,10 @@ def __parse_requests_code_msg(self, r: Response) -> Tuple[Optional[int], str, Op r.raise_for_status() return - if 'code' in json and 'msg' in json and int(json['code']) > 0: - return int(json['code']), json['msg'], json['data'] - if 'error_message' in json and json['error_message'] != '': - return None, json['error_message'], None - if 'data' in json: - return 0, '', json['data'] - return 0, '', json + if "code" in json and "msg" in json and int(json["code"]) > 0: + return int(json["code"]), json["msg"], json["data"] + if "error_message" in json and json["error_message"] != "": + return None, json["error_message"], None + if "data" in json: + return 0, "", json["data"] + return 0, "", json diff --git a/tests/test_auth.py b/tests/test_auth.py index e678421..e63e9c7 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -5,16 +5,16 @@ def test_jwt_auth(): - client_id = os.getenv('COZE_JWT_AUTH_CLIENT_ID') - private_key = os.getenv('COZE_JWT_AUTH_PRIVATE_KEY') - key_id = os.getenv('COZE_JWT_AUTH_KEY_ID') + client_id = os.getenv("COZE_JWT_AUTH_CLIENT_ID") + private_key = os.getenv("COZE_JWT_AUTH_PRIVATE_KEY") + key_id = os.getenv("COZE_JWT_AUTH_KEY_ID") app = ApplicationOAuth( client_id, base_url=COZE_CN_BASE_URL, ) token = app.jwt_auth(private_key, key_id, 30) - assert token.access_token != '' - assert token.token_type == 'Bearer' + assert token.access_token != "" + assert token.token_type == "Bearer" assert token.expires_in - int(time.time()) <= 30 - assert token.refresh_token == '' + assert token.refresh_token == "" diff --git a/tests/test_bot.py b/tests/test_bot.py index e0fb235..333ce3a 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -6,12 +6,12 @@ class TestBotClient(TestCase): def test_list_published_bots_v1(self): - space_id = os.getenv('SPACE_ID_1').strip() - token = os.getenv('COZE_TOKEN').strip() + space_id = os.getenv("SPACE_ID_1").strip() + token = os.getenv("COZE_TOKEN").strip() for i in token: - print('token', i) + print("token", i) auth = TokenAuth(token) - cli = Coze(auth=auth, base_url='https://api.coze.cn') + cli = Coze(auth=auth, base_url="https://api.coze.cn") res = cli.bot.list_published_bots_v1(space_id=space_id, page_size=2) assert res.total > 1