zeriq-team 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3 @@
1
+ .venv/
2
+ dist/
3
+ __pycache__/
@@ -0,0 +1,12 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 — first published release
4
+
5
+ First release: knowledge (documents, upsert, search), personas and ask, conversations, rooms
6
+ (create with AI teammates and people, messages, end, summarize, reports), action items and
7
+ meeting reads. Retries, idempotency keys, typed errors, pagination, `wait_until_ready`,
8
+ `wait_for_replies` and `wait_for_report`; sync and async clients.
9
+
10
+ Room streams: `rooms.stream` (server-sent events: `persona.thinking`, `message.created`, `run.completed`, `run.failed`), resuming with Last-Event-ID; `messages.send` returns the reply `run_id`.
11
+
12
+ Webhooks: endpoints (create, update, rotate secret, test, delete), the deliveries log and redelivery, and signature verification (`zeriq_team.webhooks.verify`).
@@ -0,0 +1,118 @@
1
+ Metadata-Version: 2.5
2
+ Name: zeriq-team
3
+ Version: 0.1.0
4
+ Summary: Official Python SDK for the Zeriq Teams API
5
+ License-Expression: MIT
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: Typing :: Typed
8
+ Requires-Python: >=3.9
9
+ Requires-Dist: httpx<1,>=0.24
10
+ Requires-Dist: typing-extensions>=4.7
11
+ Provides-Extra: dev
12
+ Requires-Dist: black==24.10.0; extra == 'dev'
13
+ Requires-Dist: datamodel-code-generator==0.26.5; extra == 'dev'
14
+ Requires-Dist: isort==5.13.2; extra == 'dev'
15
+ Requires-Dist: pytest>=7; extra == 'dev'
16
+ Description-Content-Type: text/markdown
17
+
18
+ # zeriq-team
19
+
20
+ The official Python SDK for the [Zeriq Teams API](../../docs/developers/README.md): your AI teammates' knowledge, answers, rooms, action items and meetings, from your own systems.
21
+
22
+ - Python 3.9+, sync and async clients, built on `httpx`.
23
+ - Results are plain dicts, typed with `TypedDict`s generated from the API's OpenAPI spec (`zeriq_team.types`).
24
+ - Retries 429, 5xx and network errors with backoff, and honours `Retry-After`. Every POST gets an `Idempotency-Key`, so retries never create anything twice.
25
+
26
+ ```bash
27
+ pip install zeriq-team
28
+ ```
29
+
30
+ ## Quick start
31
+
32
+ ```python
33
+ from zeriq_team import Zeriq
34
+
35
+ zq = Zeriq() # reads ZERIQ_TEAM_API_KEY (and ZERIQ_TEAM_BASE_URL)
36
+
37
+ # Knowledge: upsert by your own id, then search (no AI model, no credits)
38
+ zq.knowledge.documents.upsert(external_id="wiki:refunds", title="Refunds", text="…")
39
+ hits = zq.knowledge.search(query="refund policy", top_k=5)["data"]
40
+
41
+ # Ask an AI teammate (no tools unless you allow them)
42
+ answer = zq.personas.ask("prs_…", input="Draft a reply to this customer: …")
43
+ print(answer["answer"], answer["usage"])
44
+
45
+ # Rooms: create, post, stream the replies as they arrive, end
46
+ room = zq.rooms.create(title="Pricing review", personas=["prs_…"])
47
+ sent = zq.rooms.messages.send(room["id"], content="Should we add annual plans?")
48
+ for event in zq.rooms.stream(room["id"], run_id=sent["run_id"]): # ends with the run
49
+ if event["event"] == "message.created":
50
+ print(event["data"]["message"]["content"])
51
+ zq.rooms.end(room["id"])
52
+
53
+ # Lists return the first page; iterate to walk every page
54
+ for item in zq.action_items.list(status="open"):
55
+ zq.action_items.update(item["id"], external_ref="https://linear.app/…")
56
+ ```
57
+
58
+ Async is the same API, awaited:
59
+
60
+ ```python
61
+ from zeriq_team import AsyncZeriq
62
+
63
+ async with AsyncZeriq() as zq:
64
+ answer = await zq.personas.ask("prs_…", input="…")
65
+ async for item in zq.action_items.list(status="open"):
66
+ ...
67
+ ```
68
+
69
+ For QA, set `base_url="https://api.qa.zeriq.app"`.
70
+
71
+ ## Webhooks
72
+
73
+ ```python
74
+ endpoint = zq.webhooks.endpoints.create(url="https://example.com/zeriq", event_types=["action_item.created"])
75
+ secret = endpoint["secret"] # shown only now
76
+
77
+ # In your handler, with the raw request body:
78
+ from zeriq_team import webhooks, WebhookVerificationError
79
+
80
+ event = webhooks.verify(raw_body, headers["Zeriq-Signature"], secret)
81
+ if event["type"] == "action_item.created":
82
+ ...
83
+ ```
84
+
85
+ ## Errors
86
+
87
+ Every API error is an `APIError` with `status`, `code`, `param` and `request_id`. Subclasses:
88
+ `InvalidRequestError` (400), `AuthenticationError` (401), `CreditsExhaustedError` (402 — retry with
89
+ `allow_downgrade=True`), `PlanLimitError` (402), `InsufficientScopeError` (403, `param` is the
90
+ missing scope), `ToolNotPermittedError` (403), `NotFoundError` (404, also for other workspaces'
91
+ objects), `RateLimitError` (429 after retries), plus `APIConnectionError` / `APITimeoutError`.
92
+ Results carry the ID too: `answer.request_id`.
93
+
94
+ ## Options
95
+
96
+ ```python
97
+ Zeriq(api_key=..., base_url=..., timeout=60.0, max_retries=2, http_client=httpx.Client(...))
98
+ zq.rooms.create(title="x", idempotency_key="my-key")
99
+ ```
100
+
101
+ ## Examples
102
+
103
+ [`examples/`](examples): a knowledge-base sync, an async room bot, and action items → Linear.
104
+
105
+ ```bash
106
+ ZERIQ_TEAM_API_KEY=zqt_sk_… python examples/kb_sync.py
107
+ ```
108
+
109
+ ## Development
110
+
111
+ ```bash
112
+ python -m venv .venv && .venv/bin/pip install -e '.[dev]'
113
+ scripts/generate.sh # types from ../team-api-spec/openapi.json
114
+ .venv/bin/python -m pytest
115
+ ```
116
+
117
+ The contract tests that run this SDK against the real API app live in
118
+ `services/team-agent/tests/test_sdk_contract.py`.
@@ -0,0 +1,101 @@
1
+ # zeriq-team
2
+
3
+ The official Python SDK for the [Zeriq Teams API](../../docs/developers/README.md): your AI teammates' knowledge, answers, rooms, action items and meetings, from your own systems.
4
+
5
+ - Python 3.9+, sync and async clients, built on `httpx`.
6
+ - Results are plain dicts, typed with `TypedDict`s generated from the API's OpenAPI spec (`zeriq_team.types`).
7
+ - Retries 429, 5xx and network errors with backoff, and honours `Retry-After`. Every POST gets an `Idempotency-Key`, so retries never create anything twice.
8
+
9
+ ```bash
10
+ pip install zeriq-team
11
+ ```
12
+
13
+ ## Quick start
14
+
15
+ ```python
16
+ from zeriq_team import Zeriq
17
+
18
+ zq = Zeriq() # reads ZERIQ_TEAM_API_KEY (and ZERIQ_TEAM_BASE_URL)
19
+
20
+ # Knowledge: upsert by your own id, then search (no AI model, no credits)
21
+ zq.knowledge.documents.upsert(external_id="wiki:refunds", title="Refunds", text="…")
22
+ hits = zq.knowledge.search(query="refund policy", top_k=5)["data"]
23
+
24
+ # Ask an AI teammate (no tools unless you allow them)
25
+ answer = zq.personas.ask("prs_…", input="Draft a reply to this customer: …")
26
+ print(answer["answer"], answer["usage"])
27
+
28
+ # Rooms: create, post, stream the replies as they arrive, end
29
+ room = zq.rooms.create(title="Pricing review", personas=["prs_…"])
30
+ sent = zq.rooms.messages.send(room["id"], content="Should we add annual plans?")
31
+ for event in zq.rooms.stream(room["id"], run_id=sent["run_id"]): # ends with the run
32
+ if event["event"] == "message.created":
33
+ print(event["data"]["message"]["content"])
34
+ zq.rooms.end(room["id"])
35
+
36
+ # Lists return the first page; iterate to walk every page
37
+ for item in zq.action_items.list(status="open"):
38
+ zq.action_items.update(item["id"], external_ref="https://linear.app/…")
39
+ ```
40
+
41
+ Async is the same API, awaited:
42
+
43
+ ```python
44
+ from zeriq_team import AsyncZeriq
45
+
46
+ async with AsyncZeriq() as zq:
47
+ answer = await zq.personas.ask("prs_…", input="…")
48
+ async for item in zq.action_items.list(status="open"):
49
+ ...
50
+ ```
51
+
52
+ For QA, set `base_url="https://api.qa.zeriq.app"`.
53
+
54
+ ## Webhooks
55
+
56
+ ```python
57
+ endpoint = zq.webhooks.endpoints.create(url="https://example.com/zeriq", event_types=["action_item.created"])
58
+ secret = endpoint["secret"] # shown only now
59
+
60
+ # In your handler, with the raw request body:
61
+ from zeriq_team import webhooks, WebhookVerificationError
62
+
63
+ event = webhooks.verify(raw_body, headers["Zeriq-Signature"], secret)
64
+ if event["type"] == "action_item.created":
65
+ ...
66
+ ```
67
+
68
+ ## Errors
69
+
70
+ Every API error is an `APIError` with `status`, `code`, `param` and `request_id`. Subclasses:
71
+ `InvalidRequestError` (400), `AuthenticationError` (401), `CreditsExhaustedError` (402 — retry with
72
+ `allow_downgrade=True`), `PlanLimitError` (402), `InsufficientScopeError` (403, `param` is the
73
+ missing scope), `ToolNotPermittedError` (403), `NotFoundError` (404, also for other workspaces'
74
+ objects), `RateLimitError` (429 after retries), plus `APIConnectionError` / `APITimeoutError`.
75
+ Results carry the ID too: `answer.request_id`.
76
+
77
+ ## Options
78
+
79
+ ```python
80
+ Zeriq(api_key=..., base_url=..., timeout=60.0, max_retries=2, http_client=httpx.Client(...))
81
+ zq.rooms.create(title="x", idempotency_key="my-key")
82
+ ```
83
+
84
+ ## Examples
85
+
86
+ [`examples/`](examples): a knowledge-base sync, an async room bot, and action items → Linear.
87
+
88
+ ```bash
89
+ ZERIQ_TEAM_API_KEY=zqt_sk_… python examples/kb_sync.py
90
+ ```
91
+
92
+ ## Development
93
+
94
+ ```bash
95
+ python -m venv .venv && .venv/bin/pip install -e '.[dev]'
96
+ scripts/generate.sh # types from ../team-api-spec/openapi.json
97
+ .venv/bin/python -m pytest
98
+ ```
99
+
100
+ The contract tests that run this SDK against the real API app live in
101
+ `services/team-agent/tests/test_sdk_contract.py`.
@@ -0,0 +1,43 @@
1
+ """Mirror open action items into Linear and link them back with ``external_ref``.
2
+ Without LINEAR_API_KEY it's a dry run that only lists what it would create.
3
+
4
+ ZERIQ_TEAM_API_KEY=zqt_sk_… [LINEAR_API_KEY=… LINEAR_TEAM_ID=…] python examples/action_items_to_linear.py
5
+ Scopes: tasks:read, tasks:write (plus rooms:read:all to see rooms the key didn't create)
6
+ """
7
+
8
+ import os
9
+ from datetime import datetime, timedelta, timezone
10
+
11
+ import httpx
12
+
13
+ from zeriq_team import Zeriq
14
+
15
+ zq = Zeriq()
16
+ linear_key = os.environ.get("LINEAR_API_KEY")
17
+
18
+
19
+ def create_linear_issue(title: str, description: str) -> str:
20
+ resp = httpx.post(
21
+ "https://api.linear.app/graphql",
22
+ headers={"Authorization": linear_key or ""},
23
+ json={"query": "mutation($input: IssueCreateInput!) { issueCreate(input: $input) { issue { url } } }",
24
+ "variables": {"input": {"teamId": os.environ.get("LINEAR_TEAM_ID"), "title": title, "description": description}}},
25
+ ).json()
26
+ url = (((resp.get("data") or {}).get("issueCreate") or {}).get("issue") or {}).get("url")
27
+ if not url:
28
+ raise RuntimeError(f"Linear: {resp.get('errors')}")
29
+ return url
30
+
31
+
32
+ count = 0
33
+ for item in zq.action_items.list(status="open", since=datetime.now(timezone.utc) - timedelta(days=7)):
34
+ if item.get("external_ref"):
35
+ continue # already tracked somewhere
36
+ count += 1
37
+ if not linear_key:
38
+ print(f"[dry run] would create: {item['title']} ({item.get('assignee') or 'unassigned'})")
39
+ continue
40
+ url = create_linear_issue(item["title"], f"From Zeriq room {item['room_id']}")
41
+ zq.action_items.update(item["id"], external_ref=url)
42
+ print(f"{item['title']} → {url}")
43
+ print(f"{count} open action item(s) without a link in the last 7 days")
@@ -0,0 +1,4 @@
1
+ # Onboarding checklist (SDK example)
2
+
3
+ New teammates get a laptop on day one, meet their buddy on day two,
4
+ and ship a small change to production by the end of week one.
@@ -0,0 +1,5 @@
1
+ # Refund policy (SDK example)
2
+
3
+ Customers on annual plans can request a full refund within 30 days of purchase.
4
+ Monthly plans are not refunded, but can be cancelled at any time.
5
+ Refunds reach the original payment method within 5 business days.
@@ -0,0 +1,23 @@
1
+ """Keep your docs in Zeriq's knowledge base: upsert each file by its own id, wait until it's
2
+ searchable, then search. Safe to re-run.
3
+
4
+ ZERIQ_TEAM_API_KEY=zqt_sk_… python examples/kb_sync.py
5
+ Scopes: knowledge:read, knowledge:write
6
+ """
7
+
8
+ from pathlib import Path
9
+
10
+ from zeriq_team import Zeriq
11
+
12
+ zq = Zeriq()
13
+ for path in sorted((Path(__file__).parent / "kb").glob("*.md")):
14
+ text = path.read_text()
15
+ title = text.splitlines()[0].lstrip("# ").strip()
16
+ doc = zq.knowledge.documents.upsert(external_id=f"sdk-example:kb:{path.name}", title=title, text=text)
17
+ ready = zq.knowledge.documents.wait_until_ready(doc["id"])
18
+ print(f"synced {path.name} → {ready['id']} ({ready['status']}, {ready['chunk_count']} chunks)")
19
+
20
+ hits = zq.knowledge.search(query="How long do refunds take?", top_k=3)["data"]
21
+ for hit in hits:
22
+ print(f"{hit['score']:.2f} {hit['title']}: {hit['content'][:80]}…")
23
+ assert any(h["title"].startswith("Refund policy") for h in hits), "Expected the refund policy in the results"
@@ -0,0 +1,25 @@
1
+ """Run a room from your own system: create it with an AI teammate, post a question, wait for
2
+ the replies, then end it. Uses the async client.
3
+
4
+ ZERIQ_TEAM_API_KEY=zqt_sk_… python examples/room_bot.py
5
+ Scopes: personas:read, rooms:read, rooms:write
6
+ """
7
+
8
+ import asyncio
9
+ import os
10
+
11
+ from zeriq_team import AsyncZeriq
12
+
13
+
14
+ async def main() -> None:
15
+ async with AsyncZeriq() as zq:
16
+ persona_id = os.environ.get("ZERIQ_EXAMPLE_PERSONA") or (await zq.personas.list())["data"][0]["id"]
17
+ room = await zq.rooms.create(title="[SDK example] Launch check", personas=[persona_id])
18
+ print(f"room {room['id']} with {', '.join(p['name'] for p in room['personas'])}")
19
+ sent = await zq.rooms.messages.send(room["id"], content="In two sentences: what should a launch checklist cover?")
20
+ for m in await zq.rooms.wait_for_replies(room["id"], sent["id"]):
21
+ print(f"{m['sender']['name']}: {m['content']}")
22
+ print("ended:", (await zq.rooms.end(room["id"], outcome="success"))["status"])
23
+
24
+
25
+ asyncio.run(main())
@@ -0,0 +1,26 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.18"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "zeriq-team"
7
+ version = "0.1.0"
8
+ description = "Official Python SDK for the Zeriq Teams API"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.9"
12
+ dependencies = ["httpx>=0.24,<1", "typing_extensions>=4.7"]
13
+ classifiers = [
14
+ "Programming Language :: Python :: 3",
15
+ "Typing :: Typed",
16
+ ]
17
+
18
+ [project.optional-dependencies]
19
+ dev = ["pytest>=7", "datamodel-code-generator==0.26.5", "black==24.10.0", "isort==5.13.2"]
20
+
21
+ [tool.hatch.build.targets.wheel]
22
+ packages = ["src/zeriq_team"]
23
+
24
+ [tool.pytest.ini_options]
25
+ testpaths = ["tests"]
26
+ markers = ["req(id): plan task this test covers"]
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env sh
2
+ # Regenerates src/zeriq_team/types.py (TypedDicts) from the committed OpenAPI spec.
3
+ set -e
4
+ cd "$(dirname "$0")/.."
5
+ datamodel-codegen --input ../team-api-spec/openapi.json --input-file-type openapi \
6
+ --output-model-type typing.TypedDict --target-python-version 3.9 \
7
+ --use-schema-description --use-field-description --disable-timestamp \
8
+ --enum-field-as-literal all --use-standard-collections --use-union-operator \
9
+ --output src/zeriq_team/types.py
@@ -0,0 +1,36 @@
1
+ """Zeriq Teams API — official Python SDK.
2
+
3
+ from zeriq_team import Zeriq
4
+
5
+ zq = Zeriq() # reads ZERIQ_TEAM_API_KEY
6
+ answer = zq.personas.ask("prs_…", input="What changed in our refund policy?")
7
+ """
8
+
9
+ from ._client import AsyncZeriq, Zeriq
10
+ from ._core import AsyncPager, Page, SyncPage, ZeriqObject
11
+ from ._errors import (
12
+ APIConnectionError,
13
+ APIError,
14
+ APITimeoutError,
15
+ AuthenticationError,
16
+ ConflictError,
17
+ CreditsExhaustedError,
18
+ InsufficientScopeError,
19
+ InternalServerError,
20
+ InvalidRequestError,
21
+ NotFoundError,
22
+ PermissionDeniedError,
23
+ PlanLimitError,
24
+ RateLimitError,
25
+ ToolNotPermittedError,
26
+ ZeriqError,
27
+ )
28
+ from ._version import __version__
29
+ from .webhooks import WebhookVerificationError
30
+
31
+ __all__ = [
32
+ "APIConnectionError", "APIError", "APITimeoutError", "AsyncPager", "AsyncZeriq", "AuthenticationError",
33
+ "ConflictError", "CreditsExhaustedError", "InsufficientScopeError", "InternalServerError", "InvalidRequestError",
34
+ "NotFoundError", "Page", "PermissionDeniedError", "PlanLimitError", "RateLimitError", "SyncPage",
35
+ "ToolNotPermittedError", "WebhookVerificationError", "Zeriq", "ZeriqError", "ZeriqObject", "__version__",
36
+ ]
@@ -0,0 +1,116 @@
1
+ """The sync (``Zeriq``) and async (``AsyncZeriq``) clients."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Any, Mapping, Optional
6
+
7
+ import httpx
8
+
9
+ from ._core import DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT, AsyncCore, SyncCore
10
+ from ._resources import (
11
+ ActionItems,
12
+ AsyncBots,
13
+ AsyncDocuments,
14
+ AsyncRooms,
15
+ Bots,
16
+ Conversations,
17
+ Documents,
18
+ Knowledge,
19
+ Meetings,
20
+ Personas,
21
+ RoomMessages,
22
+ Rooms,
23
+ Webhooks,
24
+ )
25
+
26
+ if TYPE_CHECKING:
27
+ from .types import Me
28
+
29
+
30
+ class Zeriq:
31
+ """Zeriq Teams API client.
32
+
33
+ >>> zq = Zeriq() # reads ZERIQ_TEAM_API_KEY
34
+ >>> zq.personas.ask("prs_…", input="What changed in our refund policy?")["answer"]
35
+ """
36
+
37
+ def __init__(
38
+ self,
39
+ *,
40
+ api_key: Optional[str] = None,
41
+ base_url: Optional[str] = None,
42
+ timeout: float = DEFAULT_TIMEOUT,
43
+ max_retries: int = DEFAULT_MAX_RETRIES,
44
+ default_headers: Optional[Mapping[str, str]] = None,
45
+ http_client: Optional[httpx.Client] = None,
46
+ ) -> None:
47
+ self._core = SyncCore(api_key=api_key, base_url=base_url, timeout=timeout, max_retries=max_retries,
48
+ default_headers=default_headers, http_client=http_client)
49
+ self.knowledge = Knowledge(self._core, Documents(self._core))
50
+ self.personas = Personas(self._core)
51
+ self.conversations = Conversations(self._core)
52
+ self.rooms = Rooms(self._core, RoomMessages(self._core))
53
+ self.action_items = ActionItems(self._core)
54
+ self.meetings = Meetings(Bots(self._core))
55
+ self.webhooks = Webhooks(self._core)
56
+
57
+ def me(self) -> "Me":
58
+ """The workspace, key, scopes and tool access behind this API key."""
59
+ return self._core.request("GET", "/v1/me") # type: ignore[no-any-return]
60
+
61
+ def request(self, method: str, path: str, **kwargs: Any) -> Any:
62
+ """Low-level access for endpoints the SDK doesn't wrap yet."""
63
+ return self._core.request(method, path, **kwargs)
64
+
65
+ def close(self) -> None:
66
+ self._core.close()
67
+
68
+ def __enter__(self) -> "Zeriq":
69
+ return self
70
+
71
+ def __exit__(self, *exc: object) -> None:
72
+ self.close()
73
+
74
+
75
+ class AsyncZeriq:
76
+ """Async Zeriq Teams API client: the same API, awaited.
77
+
78
+ >>> async with AsyncZeriq() as zq:
79
+ ... async for item in zq.action_items.list(status="open"):
80
+ ... print(item["title"])
81
+ """
82
+
83
+ def __init__(
84
+ self,
85
+ *,
86
+ api_key: Optional[str] = None,
87
+ base_url: Optional[str] = None,
88
+ timeout: float = DEFAULT_TIMEOUT,
89
+ max_retries: int = DEFAULT_MAX_RETRIES,
90
+ default_headers: Optional[Mapping[str, str]] = None,
91
+ http_client: Optional[httpx.AsyncClient] = None,
92
+ ) -> None:
93
+ self._core = AsyncCore(api_key=api_key, base_url=base_url, timeout=timeout, max_retries=max_retries,
94
+ default_headers=default_headers, http_client=http_client)
95
+ self.knowledge = Knowledge(self._core, AsyncDocuments(self._core))
96
+ self.personas = Personas(self._core)
97
+ self.conversations = Conversations(self._core)
98
+ self.rooms = AsyncRooms(self._core, RoomMessages(self._core))
99
+ self.action_items = ActionItems(self._core)
100
+ self.meetings = Meetings(AsyncBots(self._core))
101
+ self.webhooks = Webhooks(self._core)
102
+
103
+ async def me(self) -> "Me":
104
+ return await self._core.request("GET", "/v1/me") # type: ignore[no-any-return]
105
+
106
+ async def request(self, method: str, path: str, **kwargs: Any) -> Any:
107
+ return await self._core.request(method, path, **kwargs)
108
+
109
+ async def close(self) -> None:
110
+ await self._core.close()
111
+
112
+ async def __aenter__(self) -> "AsyncZeriq":
113
+ return self
114
+
115
+ async def __aexit__(self, *exc: object) -> None:
116
+ await self.close()