circle-so-python-sdk 0.1.0__py3-none-any.whl
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.
- circle/__init__.py +24 -0
- circle/api/__init__.py +1 -0
- circle/api/admin_access_groups.py +133 -0
- circle/api/admin_community.py +122 -0
- circle/api/admin_courses.py +172 -0
- circle/api/admin_events.py +101 -0
- circle/api/admin_misc.py +305 -0
- circle/api/admin_posts.py +200 -0
- circle/api/admin_spaces.py +301 -0
- circle/api/admin_tags.py +146 -0
- circle/api/auth.py +63 -0
- circle/api/headless_chat_notif_members.py +323 -0
- circle/api/headless_misc.py +218 -0
- circle/api/headless_spaces_posts.py +279 -0
- circle/client.py +197 -0
- circle/constants.py +5 -0
- circle/exceptions.py +34 -0
- circle/http.py +227 -0
- circle/models/__init__.py +5 -0
- circle/models/admin/__init__.py +47 -0
- circle/models/admin/community.py +82 -0
- circle/models/admin/courses.py +30 -0
- circle/models/admin/events.py +67 -0
- circle/models/admin/members.py +105 -0
- circle/models/admin/misc.py +240 -0
- circle/models/admin/posts.py +172 -0
- circle/models/admin/spaces.py +146 -0
- circle/models/admin/tags.py +85 -0
- circle/models/auth.py +21 -0
- circle/models/base.py +28 -0
- circle/models/headless/__init__.py +50 -0
- circle/models/headless/chat.py +134 -0
- circle/models/headless/comments.py +60 -0
- circle/models/headless/courses.py +109 -0
- circle/models/headless/members.py +105 -0
- circle/models/headless/misc.py +120 -0
- circle/models/headless/notifications.py +66 -0
- circle/models/headless/posts.py +137 -0
- circle/models/headless/spaces.py +80 -0
- circle/pagination.py +116 -0
- circle/py.typed +1 -0
- circle/rate_limit.py +36 -0
- circle/validation.py +67 -0
- circle/webhooks.py +39 -0
- circle_so_python_sdk-0.1.0.dist-info/METADATA +120 -0
- circle_so_python_sdk-0.1.0.dist-info/RECORD +48 -0
- circle_so_python_sdk-0.1.0.dist-info/WHEEL +4 -0
- circle_so_python_sdk-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"""Headless Client API -- Spaces, Posts, Comments."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from typing import Any, Dict, List, Optional
|
|
4
|
+
from circle.constants import HEADLESS_V1_PREFIX as _P
|
|
5
|
+
from circle.http import AsyncTransport, SyncTransport
|
|
6
|
+
from circle.models.headless.spaces import (
|
|
7
|
+
HeadlessSpace, HeadlessSpaceList, SpaceNotificationDetail, SpaceBookmarkList, HeadlessSpaceTopicList,
|
|
8
|
+
)
|
|
9
|
+
from circle.models.headless.posts import HeadlessPost, HeadlessPostList, HeadlessImagePost
|
|
10
|
+
from circle.models.headless.comments import HeadlessComment, HeadlessCommentList, UserLikeList
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _list_posts_params(page=1, per_page=10, sort=None, status=None, topics=None, **kw):
|
|
14
|
+
p: Dict[str, Any] = {"page": page, "per_page": per_page, **kw}
|
|
15
|
+
if sort: p["sort"] = sort
|
|
16
|
+
if status: p["status"] = status
|
|
17
|
+
if topics: p["topics"] = topics
|
|
18
|
+
return p
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _comment_params(page=1, per_page=60, sort=None):
|
|
22
|
+
p: Dict[str, Any] = {"page": page, "per_page": per_page}
|
|
23
|
+
if sort: p["sort"] = sort
|
|
24
|
+
return p
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _home_params(page=1, per_page=10, sort=None):
|
|
28
|
+
p: Dict[str, Any] = {"page": page, "per_page": per_page}
|
|
29
|
+
if sort: p["sort"] = sort
|
|
30
|
+
return p
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class HeadlessSpacesPostsClient:
|
|
34
|
+
def __init__(self, transport: SyncTransport) -> None:
|
|
35
|
+
self._t = transport
|
|
36
|
+
|
|
37
|
+
def list_spaces(self) -> List[HeadlessSpace]:
|
|
38
|
+
return [HeadlessSpace.model_validate(s) for s in self._t.request("GET", f"{_P}/spaces")]
|
|
39
|
+
|
|
40
|
+
def get_space(self, space_id: int) -> HeadlessSpace:
|
|
41
|
+
return HeadlessSpace.model_validate(self._t.request("GET", f"{_P}/spaces/{space_id}"))
|
|
42
|
+
|
|
43
|
+
def get_home_space(self) -> HeadlessSpace:
|
|
44
|
+
return HeadlessSpace.model_validate(self._t.request("GET", f"{_P}/spaces/home"))
|
|
45
|
+
|
|
46
|
+
def join_space(self, space_id: int) -> HeadlessSpace:
|
|
47
|
+
return HeadlessSpace.model_validate(self._t.request("POST", f"{_P}/spaces/{space_id}/join"))
|
|
48
|
+
|
|
49
|
+
def leave_space(self, space_id: int) -> HeadlessSpace:
|
|
50
|
+
return HeadlessSpace.model_validate(self._t.request("POST", f"{_P}/spaces/{space_id}/leave"))
|
|
51
|
+
|
|
52
|
+
def get_space_topics(self, space_id: int, *, page: int = 1, per_page: int = 10) -> HeadlessSpaceTopicList:
|
|
53
|
+
return HeadlessSpaceTopicList.model_validate(
|
|
54
|
+
self._t.request("GET", f"{_P}/spaces/{space_id}/topics", params={"page": page, "per_page": per_page}))
|
|
55
|
+
|
|
56
|
+
def get_space_bookmarks(self, space_id: int, *, page: int = 1, per_page: int = 10) -> SpaceBookmarkList:
|
|
57
|
+
return SpaceBookmarkList.model_validate(
|
|
58
|
+
self._t.request("GET", f"{_P}/spaces/{space_id}/bookmarks", params={"page": page, "per_page": per_page}))
|
|
59
|
+
|
|
60
|
+
def get_space_notification_details(self, *, space_ids: Optional[str] = None) -> List[SpaceNotificationDetail]:
|
|
61
|
+
p: Dict[str, Any] = {}
|
|
62
|
+
if space_ids: p["space_ids"] = space_ids
|
|
63
|
+
return [SpaceNotificationDetail.model_validate(d) for d in
|
|
64
|
+
self._t.request("GET", f"{_P}/space_notification_details", params=p)]
|
|
65
|
+
|
|
66
|
+
def list_posts(self, space_id: int, **kw: Any) -> HeadlessPostList:
|
|
67
|
+
return HeadlessPostList.model_validate(
|
|
68
|
+
self._t.request("GET", f"{_P}/spaces/{space_id}/posts", params=_list_posts_params(**kw)))
|
|
69
|
+
|
|
70
|
+
def create_post(self, space_id: int, **kw: Any) -> HeadlessPost:
|
|
71
|
+
return HeadlessPost.model_validate(self._t.request("POST", f"{_P}/spaces/{space_id}/posts", json=kw))
|
|
72
|
+
|
|
73
|
+
def get_post(self, space_id: int, post_id: int) -> HeadlessPost:
|
|
74
|
+
return HeadlessPost.model_validate(self._t.request("GET", f"{_P}/spaces/{space_id}/posts/{post_id}"))
|
|
75
|
+
|
|
76
|
+
def delete_post(self, space_id: int, post_id: int) -> Dict[str, Any]:
|
|
77
|
+
return self._t.request("DELETE", f"{_P}/spaces/{space_id}/posts/{post_id}")
|
|
78
|
+
|
|
79
|
+
def create_image_post(self, space_id: int, **kw: Any) -> HeadlessImagePost:
|
|
80
|
+
return HeadlessImagePost.model_validate(
|
|
81
|
+
self._t.request("POST", f"{_P}/spaces/{space_id}/images/posts", json=kw))
|
|
82
|
+
|
|
83
|
+
def update_image_post(self, space_id: int, post_id: int, **kw: Any) -> HeadlessImagePost:
|
|
84
|
+
return HeadlessImagePost.model_validate(
|
|
85
|
+
self._t.request("PUT", f"{_P}/spaces/{space_id}/images/posts/{post_id}", json=kw))
|
|
86
|
+
|
|
87
|
+
def follow_post(self, post_id: int) -> Dict[str, Any]:
|
|
88
|
+
return self._t.request("POST", f"{_P}/posts/{post_id}/post_followers")
|
|
89
|
+
|
|
90
|
+
def unfollow_post(self, post_id: int) -> Dict[str, Any]:
|
|
91
|
+
return self._t.request("DELETE", f"{_P}/posts/{post_id}/post_followers")
|
|
92
|
+
|
|
93
|
+
def like_post(self, post_id: int) -> Dict[str, Any]:
|
|
94
|
+
return self._t.request("POST", f"{_P}/posts/{post_id}/user_likes")
|
|
95
|
+
|
|
96
|
+
def unlike_post(self, post_id: int) -> Dict[str, Any]:
|
|
97
|
+
return self._t.request("DELETE", f"{_P}/posts/{post_id}/user_likes")
|
|
98
|
+
|
|
99
|
+
def get_post_likes(self, post_id: int, *, page: int = 1, per_page: int = 10) -> UserLikeList:
|
|
100
|
+
return UserLikeList.model_validate(
|
|
101
|
+
self._t.request("GET", f"{_P}/posts/{post_id}/user_likes", params={"page": page, "per_page": per_page}))
|
|
102
|
+
|
|
103
|
+
def get_home_posts(self, **kw: Any) -> HeadlessPostList:
|
|
104
|
+
return HeadlessPostList.model_validate(self._t.request("GET", f"{_P}/home", params=_home_params(**kw)))
|
|
105
|
+
|
|
106
|
+
def list_comments(self, post_id: int, **kw: Any) -> HeadlessCommentList:
|
|
107
|
+
return HeadlessCommentList.model_validate(
|
|
108
|
+
self._t.request("GET", f"{_P}/posts/{post_id}/comments", params=_comment_params(**kw)))
|
|
109
|
+
|
|
110
|
+
def create_comment(self, post_id: int, **kw: Any) -> HeadlessComment:
|
|
111
|
+
return HeadlessComment.model_validate(
|
|
112
|
+
self._t.request("POST", f"{_P}/posts/{post_id}/comments", json={"comment": kw}))
|
|
113
|
+
|
|
114
|
+
def get_comment(self, post_id: int, comment_id: int) -> HeadlessComment:
|
|
115
|
+
return HeadlessComment.model_validate(
|
|
116
|
+
self._t.request("GET", f"{_P}/posts/{post_id}/comments/{comment_id}"))
|
|
117
|
+
|
|
118
|
+
def update_comment(self, post_id: int, comment_id: int, **kw: Any) -> HeadlessComment:
|
|
119
|
+
return HeadlessComment.model_validate(
|
|
120
|
+
self._t.request("PATCH", f"{_P}/posts/{post_id}/comments/{comment_id}", json={"comment": kw}))
|
|
121
|
+
|
|
122
|
+
def delete_comment(self, post_id: int, comment_id: int) -> Dict[str, Any]:
|
|
123
|
+
return self._t.request("DELETE", f"{_P}/posts/{post_id}/comments/{comment_id}")
|
|
124
|
+
|
|
125
|
+
def list_replies(self, comment_id: int, **kw: Any) -> HeadlessCommentList:
|
|
126
|
+
return HeadlessCommentList.model_validate(
|
|
127
|
+
self._t.request("GET", f"{_P}/comments/{comment_id}/replies", params=_comment_params(**kw)))
|
|
128
|
+
|
|
129
|
+
def create_reply(self, comment_id: int, **kw: Any) -> HeadlessComment:
|
|
130
|
+
return HeadlessComment.model_validate(
|
|
131
|
+
self._t.request("POST", f"{_P}/comments/{comment_id}/replies", json={"comment": kw}))
|
|
132
|
+
|
|
133
|
+
def get_reply(self, comment_id: int, reply_id: int) -> HeadlessComment:
|
|
134
|
+
return HeadlessComment.model_validate(
|
|
135
|
+
self._t.request("GET", f"{_P}/comments/{comment_id}/replies/{reply_id}"))
|
|
136
|
+
|
|
137
|
+
def update_reply(self, comment_id: int, reply_id: int, **kw: Any) -> HeadlessComment:
|
|
138
|
+
return HeadlessComment.model_validate(
|
|
139
|
+
self._t.request("PATCH", f"{_P}/comments/{comment_id}/replies/{reply_id}", json={"comment": kw}))
|
|
140
|
+
|
|
141
|
+
def delete_reply(self, comment_id: int, reply_id: int) -> Dict[str, Any]:
|
|
142
|
+
return self._t.request("DELETE", f"{_P}/comments/{comment_id}/replies/{reply_id}")
|
|
143
|
+
|
|
144
|
+
def like_comment(self, comment_id: int) -> Dict[str, Any]:
|
|
145
|
+
return self._t.request("POST", f"{_P}/comments/{comment_id}/user_likes")
|
|
146
|
+
|
|
147
|
+
def unlike_comment(self, comment_id: int) -> Dict[str, Any]:
|
|
148
|
+
return self._t.request("DELETE", f"{_P}/comments/{comment_id}/user_likes")
|
|
149
|
+
|
|
150
|
+
def get_comment_likes(self, comment_id: int, *, page: int = 1, per_page: int = 10) -> UserLikeList:
|
|
151
|
+
return UserLikeList.model_validate(
|
|
152
|
+
self._t.request("GET", f"{_P}/comments/{comment_id}/user_likes",
|
|
153
|
+
params={"page": page, "per_page": per_page}))
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class AsyncHeadlessSpacesPostsClient:
|
|
157
|
+
def __init__(self, transport: AsyncTransport) -> None:
|
|
158
|
+
self._t = transport
|
|
159
|
+
|
|
160
|
+
async def list_spaces(self) -> List[HeadlessSpace]:
|
|
161
|
+
return [HeadlessSpace.model_validate(s) for s in await self._t.request("GET", f"{_P}/spaces")]
|
|
162
|
+
|
|
163
|
+
async def get_space(self, space_id: int) -> HeadlessSpace:
|
|
164
|
+
return HeadlessSpace.model_validate(await self._t.request("GET", f"{_P}/spaces/{space_id}"))
|
|
165
|
+
|
|
166
|
+
async def get_home_space(self) -> HeadlessSpace:
|
|
167
|
+
return HeadlessSpace.model_validate(await self._t.request("GET", f"{_P}/spaces/home"))
|
|
168
|
+
|
|
169
|
+
async def join_space(self, space_id: int) -> HeadlessSpace:
|
|
170
|
+
return HeadlessSpace.model_validate(await self._t.request("POST", f"{_P}/spaces/{space_id}/join"))
|
|
171
|
+
|
|
172
|
+
async def leave_space(self, space_id: int) -> HeadlessSpace:
|
|
173
|
+
return HeadlessSpace.model_validate(await self._t.request("POST", f"{_P}/spaces/{space_id}/leave"))
|
|
174
|
+
|
|
175
|
+
async def get_space_topics(self, space_id: int, *, page: int = 1, per_page: int = 10) -> HeadlessSpaceTopicList:
|
|
176
|
+
return HeadlessSpaceTopicList.model_validate(
|
|
177
|
+
await self._t.request("GET", f"{_P}/spaces/{space_id}/topics",
|
|
178
|
+
params={"page": page, "per_page": per_page}))
|
|
179
|
+
|
|
180
|
+
async def get_space_bookmarks(self, space_id: int, *, page: int = 1, per_page: int = 10) -> SpaceBookmarkList:
|
|
181
|
+
return SpaceBookmarkList.model_validate(
|
|
182
|
+
await self._t.request("GET", f"{_P}/spaces/{space_id}/bookmarks",
|
|
183
|
+
params={"page": page, "per_page": per_page}))
|
|
184
|
+
|
|
185
|
+
async def get_space_notification_details(self, *, space_ids: Optional[str] = None) -> List[SpaceNotificationDetail]:
|
|
186
|
+
p: Dict[str, Any] = {}
|
|
187
|
+
if space_ids: p["space_ids"] = space_ids
|
|
188
|
+
return [SpaceNotificationDetail.model_validate(d) for d in
|
|
189
|
+
await self._t.request("GET", f"{_P}/space_notification_details", params=p)]
|
|
190
|
+
|
|
191
|
+
async def list_posts(self, space_id: int, **kw: Any) -> HeadlessPostList:
|
|
192
|
+
return HeadlessPostList.model_validate(
|
|
193
|
+
await self._t.request("GET", f"{_P}/spaces/{space_id}/posts", params=_list_posts_params(**kw)))
|
|
194
|
+
|
|
195
|
+
async def create_post(self, space_id: int, **kw: Any) -> HeadlessPost:
|
|
196
|
+
return HeadlessPost.model_validate(await self._t.request("POST", f"{_P}/spaces/{space_id}/posts", json=kw))
|
|
197
|
+
|
|
198
|
+
async def get_post(self, space_id: int, post_id: int) -> HeadlessPost:
|
|
199
|
+
return HeadlessPost.model_validate(await self._t.request("GET", f"{_P}/spaces/{space_id}/posts/{post_id}"))
|
|
200
|
+
|
|
201
|
+
async def delete_post(self, space_id: int, post_id: int) -> Dict[str, Any]:
|
|
202
|
+
return await self._t.request("DELETE", f"{_P}/spaces/{space_id}/posts/{post_id}")
|
|
203
|
+
|
|
204
|
+
async def create_image_post(self, space_id: int, **kw: Any) -> HeadlessImagePost:
|
|
205
|
+
return HeadlessImagePost.model_validate(
|
|
206
|
+
await self._t.request("POST", f"{_P}/spaces/{space_id}/images/posts", json=kw))
|
|
207
|
+
|
|
208
|
+
async def update_image_post(self, space_id: int, post_id: int, **kw: Any) -> HeadlessImagePost:
|
|
209
|
+
return HeadlessImagePost.model_validate(
|
|
210
|
+
await self._t.request("PUT", f"{_P}/spaces/{space_id}/images/posts/{post_id}", json=kw))
|
|
211
|
+
|
|
212
|
+
async def follow_post(self, post_id: int) -> Dict[str, Any]:
|
|
213
|
+
return await self._t.request("POST", f"{_P}/posts/{post_id}/post_followers")
|
|
214
|
+
|
|
215
|
+
async def unfollow_post(self, post_id: int) -> Dict[str, Any]:
|
|
216
|
+
return await self._t.request("DELETE", f"{_P}/posts/{post_id}/post_followers")
|
|
217
|
+
|
|
218
|
+
async def like_post(self, post_id: int) -> Dict[str, Any]:
|
|
219
|
+
return await self._t.request("POST", f"{_P}/posts/{post_id}/user_likes")
|
|
220
|
+
|
|
221
|
+
async def unlike_post(self, post_id: int) -> Dict[str, Any]:
|
|
222
|
+
return await self._t.request("DELETE", f"{_P}/posts/{post_id}/user_likes")
|
|
223
|
+
|
|
224
|
+
async def get_post_likes(self, post_id: int, *, page: int = 1, per_page: int = 10) -> UserLikeList:
|
|
225
|
+
return UserLikeList.model_validate(
|
|
226
|
+
await self._t.request("GET", f"{_P}/posts/{post_id}/user_likes",
|
|
227
|
+
params={"page": page, "per_page": per_page}))
|
|
228
|
+
|
|
229
|
+
async def get_home_posts(self, **kw: Any) -> HeadlessPostList:
|
|
230
|
+
return HeadlessPostList.model_validate(await self._t.request("GET", f"{_P}/home", params=_home_params(**kw)))
|
|
231
|
+
|
|
232
|
+
async def list_comments(self, post_id: int, **kw: Any) -> HeadlessCommentList:
|
|
233
|
+
return HeadlessCommentList.model_validate(
|
|
234
|
+
await self._t.request("GET", f"{_P}/posts/{post_id}/comments", params=_comment_params(**kw)))
|
|
235
|
+
|
|
236
|
+
async def create_comment(self, post_id: int, **kw: Any) -> HeadlessComment:
|
|
237
|
+
return HeadlessComment.model_validate(
|
|
238
|
+
await self._t.request("POST", f"{_P}/posts/{post_id}/comments", json={"comment": kw}))
|
|
239
|
+
|
|
240
|
+
async def get_comment(self, post_id: int, comment_id: int) -> HeadlessComment:
|
|
241
|
+
return HeadlessComment.model_validate(
|
|
242
|
+
await self._t.request("GET", f"{_P}/posts/{post_id}/comments/{comment_id}"))
|
|
243
|
+
|
|
244
|
+
async def update_comment(self, post_id: int, comment_id: int, **kw: Any) -> HeadlessComment:
|
|
245
|
+
return HeadlessComment.model_validate(
|
|
246
|
+
await self._t.request("PATCH", f"{_P}/posts/{post_id}/comments/{comment_id}", json={"comment": kw}))
|
|
247
|
+
|
|
248
|
+
async def delete_comment(self, post_id: int, comment_id: int) -> Dict[str, Any]:
|
|
249
|
+
return await self._t.request("DELETE", f"{_P}/posts/{post_id}/comments/{comment_id}")
|
|
250
|
+
|
|
251
|
+
async def list_replies(self, comment_id: int, **kw: Any) -> HeadlessCommentList:
|
|
252
|
+
return HeadlessCommentList.model_validate(
|
|
253
|
+
await self._t.request("GET", f"{_P}/comments/{comment_id}/replies", params=_comment_params(**kw)))
|
|
254
|
+
|
|
255
|
+
async def create_reply(self, comment_id: int, **kw: Any) -> HeadlessComment:
|
|
256
|
+
return HeadlessComment.model_validate(
|
|
257
|
+
await self._t.request("POST", f"{_P}/comments/{comment_id}/replies", json={"comment": kw}))
|
|
258
|
+
|
|
259
|
+
async def get_reply(self, comment_id: int, reply_id: int) -> HeadlessComment:
|
|
260
|
+
return HeadlessComment.model_validate(
|
|
261
|
+
await self._t.request("GET", f"{_P}/comments/{comment_id}/replies/{reply_id}"))
|
|
262
|
+
|
|
263
|
+
async def update_reply(self, comment_id: int, reply_id: int, **kw: Any) -> HeadlessComment:
|
|
264
|
+
return HeadlessComment.model_validate(
|
|
265
|
+
await self._t.request("PATCH", f"{_P}/comments/{comment_id}/replies/{reply_id}", json={"comment": kw}))
|
|
266
|
+
|
|
267
|
+
async def delete_reply(self, comment_id: int, reply_id: int) -> Dict[str, Any]:
|
|
268
|
+
return await self._t.request("DELETE", f"{_P}/comments/{comment_id}/replies/{reply_id}")
|
|
269
|
+
|
|
270
|
+
async def like_comment(self, comment_id: int) -> Dict[str, Any]:
|
|
271
|
+
return await self._t.request("POST", f"{_P}/comments/{comment_id}/user_likes")
|
|
272
|
+
|
|
273
|
+
async def unlike_comment(self, comment_id: int) -> Dict[str, Any]:
|
|
274
|
+
return await self._t.request("DELETE", f"{_P}/comments/{comment_id}/user_likes")
|
|
275
|
+
|
|
276
|
+
async def get_comment_likes(self, comment_id: int, *, page: int = 1, per_page: int = 10) -> UserLikeList:
|
|
277
|
+
return UserLikeList.model_validate(
|
|
278
|
+
await self._t.request("GET", f"{_P}/comments/{comment_id}/user_likes",
|
|
279
|
+
params={"page": page, "per_page": per_page}))
|
circle/client.py
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""Top-level CircleClient and AsyncCircleClient facade."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
import os
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from circle.http import DEFAULT_BASE_URL, AsyncTransport, SyncTransport
|
|
7
|
+
from circle.api.auth import AsyncHeadlessAuthClient, HeadlessAuthClient
|
|
8
|
+
from circle.api.admin_access_groups import AccessGroupsClient, AsyncAccessGroupsClient
|
|
9
|
+
from circle.api.admin_community import CommunityClient, AsyncCommunityClient
|
|
10
|
+
from circle.api.admin_spaces import SpacesClient, AsyncSpacesClient
|
|
11
|
+
from circle.api.admin_posts import PostsClient, AsyncPostsClient
|
|
12
|
+
from circle.api.admin_events import EventsClient, AsyncEventsClient
|
|
13
|
+
from circle.api.admin_courses import CoursesClient, AsyncCoursesClient
|
|
14
|
+
from circle.api.admin_tags import TagsClient, AsyncTagsClient
|
|
15
|
+
from circle.api.admin_misc import AdminMiscClient, AsyncAdminMiscClient
|
|
16
|
+
from circle.api.headless_spaces_posts import HeadlessSpacesPostsClient, AsyncHeadlessSpacesPostsClient
|
|
17
|
+
from circle.api.headless_chat_notif_members import HeadlessChatNotifMembersClient, AsyncHeadlessChatNotifMembersClient
|
|
18
|
+
from circle.api.headless_misc import HeadlessMiscClient, AsyncHeadlessMiscClient
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class _AdminNamespace:
|
|
22
|
+
"""Groups all Admin API V2 sub-clients under client.admin.*"""
|
|
23
|
+
def __init__(self, transport: SyncTransport) -> None:
|
|
24
|
+
self.access_groups = AccessGroupsClient(transport)
|
|
25
|
+
self.community = CommunityClient(transport)
|
|
26
|
+
self.spaces = SpacesClient(transport)
|
|
27
|
+
self.posts = PostsClient(transport)
|
|
28
|
+
self.events = EventsClient(transport)
|
|
29
|
+
self.courses = CoursesClient(transport)
|
|
30
|
+
self.tags = TagsClient(transport)
|
|
31
|
+
self.misc = AdminMiscClient(transport)
|
|
32
|
+
|
|
33
|
+
# Convenience delegates to community sub-client
|
|
34
|
+
def get_community(self, **kw):
|
|
35
|
+
return self.community.get_community(**kw)
|
|
36
|
+
|
|
37
|
+
def list_community_members(self, **kw):
|
|
38
|
+
return self.community.list_community_members(**kw)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class _AsyncAdminNamespace:
|
|
42
|
+
def __init__(self, transport: AsyncTransport) -> None:
|
|
43
|
+
self.access_groups = AsyncAccessGroupsClient(transport)
|
|
44
|
+
self.community = AsyncCommunityClient(transport)
|
|
45
|
+
self.spaces = AsyncSpacesClient(transport)
|
|
46
|
+
self.posts = AsyncPostsClient(transport)
|
|
47
|
+
self.events = AsyncEventsClient(transport)
|
|
48
|
+
self.courses = AsyncCoursesClient(transport)
|
|
49
|
+
self.tags = AsyncTagsClient(transport)
|
|
50
|
+
self.misc = AsyncAdminMiscClient(transport)
|
|
51
|
+
|
|
52
|
+
async def get_community(self, **kw):
|
|
53
|
+
return await self.community.get_community(**kw)
|
|
54
|
+
|
|
55
|
+
async def list_community_members(self, **kw):
|
|
56
|
+
return await self.community.list_community_members(**kw)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class _HeadlessNamespace:
|
|
60
|
+
"""Groups all Headless Client V1 sub-clients under client.headless.*"""
|
|
61
|
+
def __init__(self, transport: SyncTransport) -> None:
|
|
62
|
+
self.spaces_posts = HeadlessSpacesPostsClient(transport)
|
|
63
|
+
self.chat_notif_members = HeadlessChatNotifMembersClient(transport)
|
|
64
|
+
self.misc = HeadlessMiscClient(transport)
|
|
65
|
+
|
|
66
|
+
# Convenience delegates
|
|
67
|
+
def list_spaces(self, **kw):
|
|
68
|
+
return self.spaces_posts.list_spaces(**kw)
|
|
69
|
+
|
|
70
|
+
def list_posts(self, space_id, **kw):
|
|
71
|
+
return self.spaces_posts.list_posts(space_id, **kw)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class _AsyncHeadlessNamespace:
|
|
75
|
+
def __init__(self, transport: AsyncTransport) -> None:
|
|
76
|
+
self.spaces_posts = AsyncHeadlessSpacesPostsClient(transport)
|
|
77
|
+
self.chat_notif_members = AsyncHeadlessChatNotifMembersClient(transport)
|
|
78
|
+
self.misc = AsyncHeadlessMiscClient(transport)
|
|
79
|
+
|
|
80
|
+
async def list_spaces(self, **kw):
|
|
81
|
+
return await self.spaces_posts.list_spaces(**kw)
|
|
82
|
+
|
|
83
|
+
async def list_posts(self, space_id, **kw):
|
|
84
|
+
return await self.spaces_posts.list_posts(space_id, **kw)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class CircleClient:
|
|
88
|
+
"""Synchronous Circle SDK client composing all three APIs.
|
|
89
|
+
|
|
90
|
+
Provides access to the Headless Auth, Admin V2, and Headless Client V1
|
|
91
|
+
APIs through the ``auth``, ``admin``, and ``headless`` namespaces.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
api_token: Circle API token. Falls back to the ``CIRCLE_API_TOKEN``
|
|
95
|
+
environment variable if not provided.
|
|
96
|
+
base_url: Base URL for the Circle API. Falls back to the
|
|
97
|
+
``CIRCLE_BASE_URL`` environment variable, then the default
|
|
98
|
+
``https://app.circle.so``.
|
|
99
|
+
community_url: Optional community-specific URL. Falls back to the
|
|
100
|
+
``CIRCLE_COMMUNITY_URL`` environment variable.
|
|
101
|
+
rate_limit: Optional requests-per-second limit. When set, the client
|
|
102
|
+
proactively throttles to avoid 429 responses.
|
|
103
|
+
|
|
104
|
+
Raises:
|
|
105
|
+
ValueError: If no api_token is provided and ``CIRCLE_API_TOKEN`` is
|
|
106
|
+
not set.
|
|
107
|
+
|
|
108
|
+
Examples:
|
|
109
|
+
>>> client = CircleClient(api_token="YOUR_TOKEN")
|
|
110
|
+
>>> community = client.admin.get_community()
|
|
111
|
+
|
|
112
|
+
>>> # Using environment variables (CIRCLE_API_TOKEN must be set)
|
|
113
|
+
>>> client = CircleClient()
|
|
114
|
+
|
|
115
|
+
>>> # With rate limiting
|
|
116
|
+
>>> client = CircleClient(api_token="YOUR_TOKEN", rate_limit=10)
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
def __init__(
|
|
120
|
+
self,
|
|
121
|
+
api_token: Optional[str] = None,
|
|
122
|
+
base_url: Optional[str] = None,
|
|
123
|
+
community_url: Optional[str] = None,
|
|
124
|
+
rate_limit: Optional[float] = None,
|
|
125
|
+
) -> None:
|
|
126
|
+
token = api_token or os.environ.get("CIRCLE_API_TOKEN")
|
|
127
|
+
if not token:
|
|
128
|
+
raise ValueError(
|
|
129
|
+
"api_token is required. Pass it directly or set the CIRCLE_API_TOKEN environment variable."
|
|
130
|
+
)
|
|
131
|
+
resolved_base = base_url or os.environ.get("CIRCLE_BASE_URL", DEFAULT_BASE_URL)
|
|
132
|
+
resolved_community = community_url or os.environ.get("CIRCLE_COMMUNITY_URL")
|
|
133
|
+
url = (resolved_community or resolved_base).rstrip("/")
|
|
134
|
+
self._admin_transport = SyncTransport(api_token=token, base_url=url, auth_scheme="Token", rate_limit=rate_limit)
|
|
135
|
+
self._bearer_transport = SyncTransport(api_token=token, base_url=url, auth_scheme="Bearer", rate_limit=rate_limit)
|
|
136
|
+
self.auth = HeadlessAuthClient(self._bearer_transport)
|
|
137
|
+
self.admin = _AdminNamespace(self._admin_transport)
|
|
138
|
+
self.headless = _HeadlessNamespace(self._bearer_transport)
|
|
139
|
+
|
|
140
|
+
def close(self) -> None:
|
|
141
|
+
self._admin_transport.close()
|
|
142
|
+
self._bearer_transport.close()
|
|
143
|
+
|
|
144
|
+
def __enter__(self) -> CircleClient:
|
|
145
|
+
return self
|
|
146
|
+
|
|
147
|
+
def __exit__(self, *args: object) -> None:
|
|
148
|
+
self.close()
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class AsyncCircleClient:
|
|
152
|
+
"""Asynchronous Circle SDK client composing all three APIs.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
api_token: Circle API token. Falls back to ``CIRCLE_API_TOKEN`` env var.
|
|
156
|
+
base_url: Base URL. Falls back to ``CIRCLE_BASE_URL`` env var.
|
|
157
|
+
community_url: Community URL. Falls back to ``CIRCLE_COMMUNITY_URL`` env var.
|
|
158
|
+
rate_limit: Optional requests-per-second limit.
|
|
159
|
+
|
|
160
|
+
Raises:
|
|
161
|
+
ValueError: If no api_token is provided and ``CIRCLE_API_TOKEN`` is not set.
|
|
162
|
+
|
|
163
|
+
Examples:
|
|
164
|
+
>>> async with AsyncCircleClient(api_token="YOUR_TOKEN") as client:
|
|
165
|
+
... members = await client.admin.list_community_members()
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
def __init__(
|
|
169
|
+
self,
|
|
170
|
+
api_token: Optional[str] = None,
|
|
171
|
+
base_url: Optional[str] = None,
|
|
172
|
+
community_url: Optional[str] = None,
|
|
173
|
+
rate_limit: Optional[float] = None,
|
|
174
|
+
) -> None:
|
|
175
|
+
token = api_token or os.environ.get("CIRCLE_API_TOKEN")
|
|
176
|
+
if not token:
|
|
177
|
+
raise ValueError(
|
|
178
|
+
"api_token is required. Pass it directly or set the CIRCLE_API_TOKEN environment variable."
|
|
179
|
+
)
|
|
180
|
+
resolved_base = base_url or os.environ.get("CIRCLE_BASE_URL", DEFAULT_BASE_URL)
|
|
181
|
+
resolved_community = community_url or os.environ.get("CIRCLE_COMMUNITY_URL")
|
|
182
|
+
url = (resolved_community or resolved_base).rstrip("/")
|
|
183
|
+
self._admin_transport = AsyncTransport(api_token=token, base_url=url, auth_scheme="Token", rate_limit=rate_limit)
|
|
184
|
+
self._bearer_transport = AsyncTransport(api_token=token, base_url=url, auth_scheme="Bearer", rate_limit=rate_limit)
|
|
185
|
+
self.auth = AsyncHeadlessAuthClient(self._bearer_transport)
|
|
186
|
+
self.admin = _AsyncAdminNamespace(self._admin_transport)
|
|
187
|
+
self.headless = _AsyncHeadlessNamespace(self._bearer_transport)
|
|
188
|
+
|
|
189
|
+
async def close(self) -> None:
|
|
190
|
+
await self._admin_transport.close()
|
|
191
|
+
await self._bearer_transport.close()
|
|
192
|
+
|
|
193
|
+
async def __aenter__(self) -> AsyncCircleClient:
|
|
194
|
+
return self
|
|
195
|
+
|
|
196
|
+
async def __aexit__(self, *args: object) -> None:
|
|
197
|
+
await self.close()
|
circle/constants.py
ADDED
circle/exceptions.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Typed exceptions for Circle API errors."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CircleAPIError(Exception):
|
|
9
|
+
"""Base exception for all Circle API errors."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, message: str, status_code: int | None = None, error_details: Any = None) -> None:
|
|
12
|
+
self.status_code = status_code
|
|
13
|
+
self.error_details = error_details
|
|
14
|
+
super().__init__(message)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AuthenticationError(CircleAPIError):
|
|
18
|
+
"""401 Unauthorized."""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ForbiddenError(CircleAPIError):
|
|
22
|
+
"""403 Forbidden."""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class NotFoundError(CircleAPIError):
|
|
26
|
+
"""404 Not Found."""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ValidationError(CircleAPIError):
|
|
30
|
+
"""422 Unprocessable Entity."""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class RateLimitError(CircleAPIError):
|
|
34
|
+
"""429 Too Many Requests."""
|