usenadia 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.
Files changed (250) hide show
  1. usenadia/__init__.py +278 -0
  2. usenadia/generated/__init__.py +8 -0
  3. usenadia/generated/api/__init__.py +1 -0
  4. usenadia/generated/api/public_api/__init__.py +1 -0
  5. usenadia/generated/api/public_api/call_video_function.py +176 -0
  6. usenadia/generated/api/public_api/change_post_status.py +192 -0
  7. usenadia/generated/api/public_api/check_connection.py +144 -0
  8. usenadia/generated/api/public_api/create_automation_flow.py +176 -0
  9. usenadia/generated/api/public_api/create_content.py +181 -0
  10. usenadia/generated/api/public_api/create_post.py +183 -0
  11. usenadia/generated/api/public_api/create_webhook.py +176 -0
  12. usenadia/generated/api/public_api/delete_automation_flow.py +171 -0
  13. usenadia/generated/api/public_api/delete_automation_pose.py +171 -0
  14. usenadia/generated/api/public_api/delete_channel.py +171 -0
  15. usenadia/generated/api/public_api/delete_comment.py +185 -0
  16. usenadia/generated/api/public_api/delete_content.py +171 -0
  17. usenadia/generated/api/public_api/delete_post.py +171 -0
  18. usenadia/generated/api/public_api/delete_post_group.py +171 -0
  19. usenadia/generated/api/public_api/delete_webhook.py +171 -0
  20. usenadia/generated/api/public_api/find_slot.py +171 -0
  21. usenadia/generated/api/public_api/generate_video.py +176 -0
  22. usenadia/generated/api/public_api/get_account_history.py +252 -0
  23. usenadia/generated/api/public_api/get_automation_flow.py +171 -0
  24. usenadia/generated/api/public_api/get_automation_pose.py +171 -0
  25. usenadia/generated/api/public_api/get_channel_analytics.py +197 -0
  26. usenadia/generated/api/public_api/get_channel_settings.py +171 -0
  27. usenadia/generated/api/public_api/get_content.py +171 -0
  28. usenadia/generated/api/public_api/get_contents_performance.py +146 -0
  29. usenadia/generated/api/public_api/get_conversation.py +190 -0
  30. usenadia/generated/api/public_api/get_hook_forms.py +265 -0
  31. usenadia/generated/api/public_api/get_music_track.py +171 -0
  32. usenadia/generated/api/public_api/get_post_analytics.py +192 -0
  33. usenadia/generated/api/public_api/get_video_history.py +252 -0
  34. usenadia/generated/api/public_api/get_webhook.py +171 -0
  35. usenadia/generated/api/public_api/hide_comment.py +206 -0
  36. usenadia/generated/api/public_api/import_hooks.py +171 -0
  37. usenadia/generated/api/public_api/import_music.py +176 -0
  38. usenadia/generated/api/public_api/link_content_post.py +208 -0
  39. usenadia/generated/api/public_api/list_automation_flow_poses.py +178 -0
  40. usenadia/generated/api/public_api/list_automation_flows.py +151 -0
  41. usenadia/generated/api/public_api/list_automation_pose_runs.py +199 -0
  42. usenadia/generated/api/public_api/list_channel_automation_poses.py +198 -0
  43. usenadia/generated/api/public_api/list_channel_media.py +176 -0
  44. usenadia/generated/api/public_api/list_channel_stories.py +176 -0
  45. usenadia/generated/api/public_api/list_channels.py +181 -0
  46. usenadia/generated/api/public_api/list_contents.py +239 -0
  47. usenadia/generated/api/public_api/list_conversations.py +197 -0
  48. usenadia/generated/api/public_api/list_groups.py +149 -0
  49. usenadia/generated/api/public_api/list_hook_families.py +196 -0
  50. usenadia/generated/api/public_api/list_media_comments.py +190 -0
  51. usenadia/generated/api/public_api/list_missing_media.py +192 -0
  52. usenadia/generated/api/public_api/list_music.py +252 -0
  53. usenadia/generated/api/public_api/list_music_pillars.py +152 -0
  54. usenadia/generated/api/public_api/list_notifications.py +176 -0
  55. usenadia/generated/api/public_api/list_posts.py +236 -0
  56. usenadia/generated/api/public_api/list_video_performance.py +209 -0
  57. usenadia/generated/api/public_api/list_webhook_events.py +144 -0
  58. usenadia/generated/api/public_api/list_webhooks.py +149 -0
  59. usenadia/generated/api/public_api/move_content.py +192 -0
  60. usenadia/generated/api/public_api/poll_automation_pose.py +171 -0
  61. usenadia/generated/api/public_api/poll_inbox.py +171 -0
  62. usenadia/generated/api/public_api/pose_automation_flow.py +192 -0
  63. usenadia/generated/api/public_api/preview_automation_email.py +176 -0
  64. usenadia/generated/api/public_api/replace_hook_families.py +176 -0
  65. usenadia/generated/api/public_api/replay_automation_pose.py +199 -0
  66. usenadia/generated/api/public_api/reply_to_comment.py +192 -0
  67. usenadia/generated/api/public_api/run_channel_tool.py +192 -0
  68. usenadia/generated/api/public_api/search_hooks.py +267 -0
  69. usenadia/generated/api/public_api/send_automation_test_email.py +176 -0
  70. usenadia/generated/api/public_api/send_direct_message.py +192 -0
  71. usenadia/generated/api/public_api/set_automation_flow_status.py +192 -0
  72. usenadia/generated/api/public_api/set_post_release_id.py +192 -0
  73. usenadia/generated/api/public_api/start_channel_connection.py +192 -0
  74. usenadia/generated/api/public_api/subscribe_inbox_webhook.py +171 -0
  75. usenadia/generated/api/public_api/update_automation_flow.py +192 -0
  76. usenadia/generated/api/public_api/update_automation_pose.py +192 -0
  77. usenadia/generated/api/public_api/update_content.py +192 -0
  78. usenadia/generated/api/public_api/upload_file.py +176 -0
  79. usenadia/generated/api/public_api/upload_from_url.py +176 -0
  80. usenadia/generated/api/public_api/upload_music_file.py +197 -0
  81. usenadia/generated/api/public_api/upsert_content.py +188 -0
  82. usenadia/generated/client.py +280 -0
  83. usenadia/generated/errors.py +16 -0
  84. usenadia/generated/models/__init__.py +335 -0
  85. usenadia/generated/models/account_history_item_dto.py +86 -0
  86. usenadia/generated/models/analytics_metric_dto.py +94 -0
  87. usenadia/generated/models/analytics_point_dto.py +70 -0
  88. usenadia/generated/models/api_error_dto.py +61 -0
  89. usenadia/generated/models/automation_flow_counts_dto.py +69 -0
  90. usenadia/generated/models/automation_flow_dto.py +97 -0
  91. usenadia/generated/models/automation_flow_dto_definition.py +45 -0
  92. usenadia/generated/models/automation_flow_issue_dto.py +116 -0
  93. usenadia/generated/models/automation_flow_issue_dto_level.py +9 -0
  94. usenadia/generated/models/automation_flow_issue_dto_params.py +45 -0
  95. usenadia/generated/models/automation_flow_poll_result_dto.py +69 -0
  96. usenadia/generated/models/automation_flow_pose_response_dto.py +206 -0
  97. usenadia/generated/models/automation_flow_pose_response_dto_source.py +9 -0
  98. usenadia/generated/models/automation_flow_pose_response_dto_variables.py +47 -0
  99. usenadia/generated/models/automation_flow_replay_result_dto.py +119 -0
  100. usenadia/generated/models/automation_flow_response_dto.py +173 -0
  101. usenadia/generated/models/automation_flow_response_dto_definition.py +50 -0
  102. usenadia/generated/models/automation_flow_response_dto_status.py +10 -0
  103. usenadia/generated/models/automation_flow_run_response_dto.py +269 -0
  104. usenadia/generated/models/automation_flow_run_response_dto_status.py +14 -0
  105. usenadia/generated/models/automation_flow_status_dto.py +63 -0
  106. usenadia/generated/models/automation_flow_status_dto_status.py +10 -0
  107. usenadia/generated/models/automation_pose_counts_dto.py +93 -0
  108. usenadia/generated/models/automation_pose_flow_dto.py +79 -0
  109. usenadia/generated/models/automation_pose_flow_dto_status.py +10 -0
  110. usenadia/generated/models/call_video_function_response_201.py +45 -0
  111. usenadia/generated/models/change_post_status_dto.py +63 -0
  112. usenadia/generated/models/change_post_status_dto_status.py +9 -0
  113. usenadia/generated/models/content_channel_performance_dto.py +368 -0
  114. usenadia/generated/models/content_dto.py +408 -0
  115. usenadia/generated/models/content_dto_resource_status.py +9 -0
  116. usenadia/generated/models/content_dto_stage.py +14 -0
  117. usenadia/generated/models/content_list_item_dto.py +441 -0
  118. usenadia/generated/models/content_list_item_dto_resource_status.py +9 -0
  119. usenadia/generated/models/content_list_item_dto_stage.py +14 -0
  120. usenadia/generated/models/content_performance_dto.py +175 -0
  121. usenadia/generated/models/content_post_dto.py +109 -0
  122. usenadia/generated/models/content_post_dto_state.py +12 -0
  123. usenadia/generated/models/content_upsert_dto.py +75 -0
  124. usenadia/generated/models/create_content_dto.py +202 -0
  125. usenadia/generated/models/create_content_dto_resource_status.py +9 -0
  126. usenadia/generated/models/create_content_dto_stage.py +14 -0
  127. usenadia/generated/models/create_post_result_item_dto.py +69 -0
  128. usenadia/generated/models/create_public_webhook_dto.py +133 -0
  129. usenadia/generated/models/create_public_webhook_dto_events_item.py +12 -0
  130. usenadia/generated/models/deleted_id_dto.py +61 -0
  131. usenadia/generated/models/email_preview_dto.py +143 -0
  132. usenadia/generated/models/email_preview_dto_response.py +122 -0
  133. usenadia/generated/models/email_preview_dto_response_params.py +47 -0
  134. usenadia/generated/models/email_preview_dto_response_sender.py +47 -0
  135. usenadia/generated/models/email_sent_dto.py +69 -0
  136. usenadia/generated/models/email_test_dto.py +163 -0
  137. usenadia/generated/models/find_slot_dto.py +62 -0
  138. usenadia/generated/models/generated_video_dto.py +119 -0
  139. usenadia/generated/models/get_contents_performance_response_200.py +62 -0
  140. usenadia/generated/models/get_hook_forms_language.py +10 -0
  141. usenadia/generated/models/get_post_analytics_response_200.py +45 -0
  142. usenadia/generated/models/group_dto.py +69 -0
  143. usenadia/generated/models/hide_comment_dto.py +61 -0
  144. usenadia/generated/models/history_page_dto.py +116 -0
  145. usenadia/generated/models/hook_dto.py +311 -0
  146. usenadia/generated/models/hook_families_import_dto.py +83 -0
  147. usenadia/generated/models/hook_family_dto.py +220 -0
  148. usenadia/generated/models/hook_family_stat_dto.py +212 -0
  149. usenadia/generated/models/hook_form_dto.py +186 -0
  150. usenadia/generated/models/hook_import_item_dto.py +211 -0
  151. usenadia/generated/models/hooks_import_dto.py +75 -0
  152. usenadia/generated/models/hooks_import_result_dto.py +69 -0
  153. usenadia/generated/models/hooks_search_result_dto.py +83 -0
  154. usenadia/generated/models/inbox_conversation_dto.py +128 -0
  155. usenadia/generated/models/inbox_conversation_dto_inbox.py +47 -0
  156. usenadia/generated/models/inbox_conversation_dto_messages.py +47 -0
  157. usenadia/generated/models/inbox_conversation_dto_participants.py +47 -0
  158. usenadia/generated/models/inbox_message_dto.py +102 -0
  159. usenadia/generated/models/inbox_participant_dto.py +72 -0
  160. usenadia/generated/models/inbox_poll_result_dto.py +69 -0
  161. usenadia/generated/models/instagram_comment_author_dto.py +70 -0
  162. usenadia/generated/models/instagram_comment_dto.py +123 -0
  163. usenadia/generated/models/instagram_media_dto.py +145 -0
  164. usenadia/generated/models/integration.py +61 -0
  165. usenadia/generated/models/integration_list_item_dto.py +138 -0
  166. usenadia/generated/models/integration_settings_dto.py +69 -0
  167. usenadia/generated/models/integration_settings_output_dto.py +106 -0
  168. usenadia/generated/models/integration_settings_output_dto_settings.py +47 -0
  169. usenadia/generated/models/integration_tool_dto.py +91 -0
  170. usenadia/generated/models/integration_tool_param_dto.py +77 -0
  171. usenadia/generated/models/integration_trigger_dto.py +85 -0
  172. usenadia/generated/models/integration_trigger_dto_data.py +47 -0
  173. usenadia/generated/models/integration_trigger_result_dto.py +71 -0
  174. usenadia/generated/models/integration_trigger_result_dto_output.py +47 -0
  175. usenadia/generated/models/is_connected_dto.py +61 -0
  176. usenadia/generated/models/link_content_post_dto.py +61 -0
  177. usenadia/generated/models/list_contents_stage.py +14 -0
  178. usenadia/generated/models/list_music_pillar.py +11 -0
  179. usenadia/generated/models/list_video_performance_response_200.py +45 -0
  180. usenadia/generated/models/media_dto.py +89 -0
  181. usenadia/generated/models/media_response_dto.py +119 -0
  182. usenadia/generated/models/missing_content_item_dto.py +69 -0
  183. usenadia/generated/models/move_content_dto.py +73 -0
  184. usenadia/generated/models/move_content_dto_stage.py +14 -0
  185. usenadia/generated/models/music_import_dto.py +75 -0
  186. usenadia/generated/models/music_import_item_dto.py +238 -0
  187. usenadia/generated/models/music_import_item_dto_pillar.py +11 -0
  188. usenadia/generated/models/music_import_item_dto_verdict.py +10 -0
  189. usenadia/generated/models/music_import_result_dto.py +69 -0
  190. usenadia/generated/models/music_list_result_dto.py +91 -0
  191. usenadia/generated/models/music_pillar_dto.py +85 -0
  192. usenadia/generated/models/music_track_dto.py +318 -0
  193. usenadia/generated/models/notification_dto.py +100 -0
  194. usenadia/generated/models/notifications_page_dto.py +107 -0
  195. usenadia/generated/models/ok_dto.py +61 -0
  196. usenadia/generated/models/pose_automation_flow_dto.py +125 -0
  197. usenadia/generated/models/pose_automation_flow_dto_kind.py +9 -0
  198. usenadia/generated/models/pose_automation_flow_dto_variables.py +45 -0
  199. usenadia/generated/models/post.py +114 -0
  200. usenadia/generated/models/post_content.py +99 -0
  201. usenadia/generated/models/post_dto.py +219 -0
  202. usenadia/generated/models/post_dto_creation_method.py +11 -0
  203. usenadia/generated/models/post_dto_state.py +12 -0
  204. usenadia/generated/models/post_integration_summary_dto.py +91 -0
  205. usenadia/generated/models/post_list_dto.py +75 -0
  206. usenadia/generated/models/post_settings.py +45 -0
  207. usenadia/generated/models/post_status_dto.py +71 -0
  208. usenadia/generated/models/post_status_dto_state.py +11 -0
  209. usenadia/generated/models/post_tag_dto.py +67 -0
  210. usenadia/generated/models/post_tag_dto_tag.py +47 -0
  211. usenadia/generated/models/public_create_post_dto.py +160 -0
  212. usenadia/generated/models/public_create_post_dto_creation_method.py +9 -0
  213. usenadia/generated/models/public_create_post_dto_type.py +11 -0
  214. usenadia/generated/models/replay_pose_dto.py +72 -0
  215. usenadia/generated/models/reply_to_comment_dto.py +69 -0
  216. usenadia/generated/models/search_hooks_language.py +10 -0
  217. usenadia/generated/models/send_message_dto.py +89 -0
  218. usenadia/generated/models/social_auth_url_dto.py +61 -0
  219. usenadia/generated/models/success_dto.py +61 -0
  220. usenadia/generated/models/tags.py +69 -0
  221. usenadia/generated/models/update_automation_flow_dto.py +107 -0
  222. usenadia/generated/models/update_automation_flow_dto_definition.py +45 -0
  223. usenadia/generated/models/update_automation_flow_pose_dto.py +98 -0
  224. usenadia/generated/models/update_automation_flow_pose_dto_variables.py +45 -0
  225. usenadia/generated/models/update_content_dto.py +300 -0
  226. usenadia/generated/models/update_content_dto_resource_status.py +9 -0
  227. usenadia/generated/models/update_release_id_dto.py +61 -0
  228. usenadia/generated/models/upload_dto.py +61 -0
  229. usenadia/generated/models/upload_file_body.py +75 -0
  230. usenadia/generated/models/upload_music_file_body.py +75 -0
  231. usenadia/generated/models/upsert_content_dto.py +200 -0
  232. usenadia/generated/models/upsert_content_dto_resource_status.py +9 -0
  233. usenadia/generated/models/upsert_content_dto_stage.py +14 -0
  234. usenadia/generated/models/video_dto.py +85 -0
  235. usenadia/generated/models/video_dto_custom_params.py +45 -0
  236. usenadia/generated/models/video_dto_output.py +9 -0
  237. usenadia/generated/models/video_function_dto.py +93 -0
  238. usenadia/generated/models/video_function_dto_params.py +45 -0
  239. usenadia/generated/models/video_history_page_dto.py +143 -0
  240. usenadia/generated/models/video_identity_dto.py +123 -0
  241. usenadia/generated/models/webhook_created_dto.py +135 -0
  242. usenadia/generated/models/webhook_dto.py +125 -0
  243. usenadia/generated/models/webhook_event_dto.py +69 -0
  244. usenadia/generated/models/webhook_event_list_dto.py +75 -0
  245. usenadia/generated/models/webhooks_integration_dto.py +61 -0
  246. usenadia/generated/types.py +54 -0
  247. usenadia-0.1.0.dist-info/METADATA +98 -0
  248. usenadia-0.1.0.dist-info/RECORD +250 -0
  249. usenadia-0.1.0.dist-info/WHEEL +4 -0
  250. usenadia-0.1.0.dist-info/licenses/LICENSE +21 -0
usenadia/__init__.py ADDED
@@ -0,0 +1,278 @@
1
+ """UseNadia Python SDK.
2
+
3
+ Two layers, on purpose (plan 19) — this module is the second one.
4
+
5
+ usenadia/generated/ transport, regenerated from ``openapi.public.json``
6
+ by openapi-python-client, covers every operation,
7
+ never edited by hand, not committed.
8
+ usenadia/__init__.py this file: the small, hand-written surface that makes
9
+ the common calls pleasant, and that we take
10
+ responsibility for.
11
+
12
+ Anything the facade does not wrap stays reachable through :attr:`Nadia.raw`,
13
+ the configured generated client.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import time
19
+ from typing import Any, Callable, Mapping, Optional, TypeVar
20
+
21
+ import httpx
22
+
23
+ __all__ = ["Nadia", "NadiaError", "DEFAULT_BASE_URL"]
24
+
25
+ DEFAULT_BASE_URL = "https://app.usenadia.com/api"
26
+
27
+ T = TypeVar("T")
28
+
29
+
30
+ class NadiaError(Exception):
31
+ """Raised for every response with a status >= 400."""
32
+
33
+ def __init__(self, status: int, body: Any, message: Optional[str] = None):
34
+ self.status = status
35
+ self.body = body
36
+ super().__init__(message or f"UseNadia API responded {status}")
37
+
38
+ @property
39
+ def is_transient(self) -> bool:
40
+ """True when retrying later could plausibly work (rate limit, server hiccup)."""
41
+ return self.status == 429 or self.status >= 500
42
+
43
+
44
+ def _message_of(body: Any) -> Optional[str]:
45
+ """Read the API's ``{"msg": ...}`` error shape without assuming it is there."""
46
+ if isinstance(body, Mapping):
47
+ msg = body.get("msg")
48
+ if isinstance(msg, str):
49
+ return msg
50
+ return None
51
+
52
+
53
+ class Nadia:
54
+ """Client for the UseNadia public API.
55
+
56
+ >>> nadia = Nadia(api_key="...")
57
+ >>> channels = nadia.integrations()
58
+ """
59
+
60
+ def __init__(
61
+ self,
62
+ api_key: str,
63
+ base_url: str = DEFAULT_BASE_URL,
64
+ *,
65
+ retries: int = 2,
66
+ timeout: float = 30.0,
67
+ client: Optional[httpx.Client] = None,
68
+ ):
69
+ if not api_key:
70
+ raise ValueError("An API key is required.")
71
+
72
+ self._api_key = api_key
73
+ self._retries = retries
74
+ self._typed: Any = None
75
+ self._client = client or httpx.Client(
76
+ base_url=base_url.rstrip("/"),
77
+ headers={"Authorization": api_key},
78
+ timeout=timeout,
79
+ )
80
+
81
+ # ------------------------------------------------------------------ #
82
+
83
+ def _request(self, method: str, path: str, **kwargs: Any) -> Any:
84
+ """Send a request, raise :class:`NadiaError` on non-2xx, retry the
85
+ transient ones. Every method below goes through here — that is the
86
+ whole point of the facade."""
87
+ last: Optional[NadiaError] = None
88
+
89
+ for attempt in range(self._retries + 1):
90
+ response = self._client.request(method, path, **kwargs)
91
+
92
+ if response.is_success:
93
+ if not response.content:
94
+ return None
95
+ try:
96
+ return response.json()
97
+ except ValueError:
98
+ return response.content
99
+
100
+ try:
101
+ body: Any = response.json()
102
+ except ValueError:
103
+ body = response.text
104
+
105
+ last = NadiaError(response.status_code, body, _message_of(body))
106
+
107
+ if not last.is_transient or attempt == self._retries:
108
+ raise last
109
+
110
+ time.sleep(2**attempt * 0.5)
111
+
112
+ assert last is not None
113
+ raise last
114
+
115
+ # -- the five calls upstream's Node SDK had, same names -------------- #
116
+
117
+ def post(self, body: Mapping[str, Any]) -> Any:
118
+ """Schedule, publish or draft a post on one or more channels."""
119
+ return self._request("POST", "/public/v1/posts", json=body)
120
+
121
+ def post_list(self, **query: Any) -> Any:
122
+ """List posts in a date range."""
123
+ return self._request("GET", "/public/v1/posts", params=query or None)
124
+
125
+ def upload(self, file: bytes, filename: str = "upload") -> Any:
126
+ """Upload a file and get a URL usable in a post."""
127
+ return self._request(
128
+ "POST", "/public/v1/upload", files={"file": (filename, file)}
129
+ )
130
+
131
+ def integrations(self, group: Optional[str] = None) -> Any:
132
+ """List your connected channels."""
133
+ return self._request(
134
+ "GET",
135
+ "/public/v1/integrations",
136
+ params={"group": group} if group else None,
137
+ )
138
+
139
+ def delete_post(self, post_id: str) -> Any:
140
+ """Delete a post and everything published with it."""
141
+ return self._request("DELETE", f"/public/v1/posts/{post_id}")
142
+
143
+ # -- the rest of our surface, which upstream never had --------------- #
144
+
145
+ def is_connected(self) -> bool:
146
+ """Check the API key. Raises :class:`NadiaError` with status 401 if invalid."""
147
+ self._request("GET", "/public/v1/is-connected")
148
+ return True
149
+
150
+ def find_slot(self, integration_id: str) -> Any:
151
+ """Next free slot in a channel's schedule."""
152
+ return self._request("GET", f"/public/v1/find-slot/{integration_id}")
153
+
154
+ def groups(self) -> Any:
155
+ """List your groups (brands)."""
156
+ return self._request("GET", "/public/v1/groups")
157
+
158
+ def inbox(self, integration_id: str, limit: Optional[int] = None) -> Any:
159
+ """Direct message conversations of a channel."""
160
+ return self._request(
161
+ "GET",
162
+ f"/public/v1/inbox/{integration_id}",
163
+ params={"limit": limit} if limit else None,
164
+ )
165
+
166
+ # -- the content board: one card per video, idea to numbers --------- #
167
+
168
+ def contents(self, **filters: Any) -> Any:
169
+ """The cards of the board, with the posts scheduled from each.
170
+
171
+ Filters combine: ``stage``, ``tag``, ``externalId``; ``fields``
172
+ (``"id,title,stage"``) leaves the scripts out.
173
+ """
174
+ return self._request(
175
+ "GET", "/public/v1/contents", params=filters or None
176
+ )
177
+
178
+ def content(self, content_id: str) -> Any:
179
+ """One card."""
180
+ return self._request("GET", f"/public/v1/contents/{content_id}")
181
+
182
+ def create_content(self, title: str, **fields: Any) -> Any:
183
+ """Create a card — in ``IDEA`` unless ``stage`` says otherwise.
184
+
185
+ Other fields: ``externalId``, ``hook``, ``hookText``, ``script``,
186
+ ``notes``, ``tags``, ``resourceStatus``, ``resourceUrl``,
187
+ ``resourceBrief``, ``integrationIds``, ``plannedAt``, ``parentId``.
188
+ """
189
+ return self._request(
190
+ "POST", "/public/v1/contents", json={"title": title, **fields}
191
+ )
192
+
193
+ def upsert_content(self, external_id: str, title: str, **fields: Any) -> Any:
194
+ """Create or update the card holding ``external_id`` (your own id).
195
+
196
+ Re-running an import never duplicates; an existing card keeps its
197
+ column. Returns ``{"created": bool, "content": {...}}``.
198
+ """
199
+ return self._request(
200
+ "POST",
201
+ "/public/v1/contents/upsert",
202
+ json={"externalId": external_id, "title": title, **fields},
203
+ )
204
+
205
+ def update_content(self, content_id: str, **fields: Any) -> Any:
206
+ """Update a card: only the fields passed change, ``None`` clears one."""
207
+ return self._request(
208
+ "PUT", f"/public/v1/contents/{content_id}", json=fields
209
+ )
210
+
211
+ def move_content(
212
+ self, content_id: str, stage: str, position: Optional[float] = None
213
+ ) -> Any:
214
+ """Move a card to another column (to the bottom unless ``position`` is given)."""
215
+ body: dict = {"stage": stage}
216
+ if position is not None:
217
+ body["position"] = position
218
+ return self._request(
219
+ "PUT", f"/public/v1/contents/{content_id}/move", json=body
220
+ )
221
+
222
+ def link_content_post(self, content_id: str, post_group: str) -> Any:
223
+ """Tie a card to a post scheduled from it (the post's ``group``).
224
+
225
+ A card holds several posts (a TikTok draft, an Instagram post): each
226
+ call adds one. The card moves to ``PUBLISHED`` when the first of them
227
+ goes live.
228
+ """
229
+ return self._request(
230
+ "PUT",
231
+ f"/public/v1/contents/{content_id}/post",
232
+ json={"postGroup": post_group},
233
+ )
234
+
235
+ def delete_content(self, content_id: str) -> Any:
236
+ """Delete a card."""
237
+ return self._request("DELETE", f"/public/v1/contents/{content_id}")
238
+
239
+ def contents_performance(self) -> Any:
240
+ """The numbers of the published cards, keyed by card id."""
241
+ return self._request("GET", "/public/v1/contents/performance")
242
+
243
+ # ------------------------------------------------------------------ #
244
+
245
+ @property
246
+ def raw(self) -> httpx.Client:
247
+ """The configured httpx client, for a call the facade does not wrap."""
248
+ return self._client
249
+
250
+ @property
251
+ def typed(self) -> Any:
252
+ """The generated, fully typed client — every operation, with models.
253
+
254
+ >>> from usenadia.generated.api.public_api import list_channels
255
+ >>> list_channels.sync(client=nadia.typed)
256
+
257
+ Imported lazily so the facade still works from a checkout where the
258
+ client has not been generated yet (it is gitignored, see the README).
259
+ """
260
+ if self._typed is None:
261
+ from .generated import AuthenticatedClient # noqa: PLC0415
262
+
263
+ self._typed = AuthenticatedClient(
264
+ base_url=str(self._client.base_url),
265
+ token=self._api_key,
266
+ # The API reads `Authorization: <key>`, with no `Bearer` prefix.
267
+ prefix="",
268
+ )
269
+ return self._typed
270
+
271
+ def close(self) -> None:
272
+ self._client.close()
273
+
274
+ def __enter__(self) -> "Nadia":
275
+ return self
276
+
277
+ def __exit__(self, *exc: Any) -> None:
278
+ self.close()
@@ -0,0 +1,8 @@
1
+ """A client library for accessing UseNadia public API"""
2
+
3
+ from .client import AuthenticatedClient, Client
4
+
5
+ __all__ = (
6
+ "AuthenticatedClient",
7
+ "Client",
8
+ )
@@ -0,0 +1 @@
1
+ """Contains methods for accessing the API"""
@@ -0,0 +1 @@
1
+ """Contains endpoint functions for accessing the API"""
@@ -0,0 +1,176 @@
1
+ from http import HTTPStatus
2
+ from typing import Any
3
+
4
+ import httpx
5
+
6
+ from ... import errors
7
+ from ...client import AuthenticatedClient, Client
8
+ from ...models.api_error_dto import ApiErrorDto
9
+ from ...models.call_video_function_response_201 import CallVideoFunctionResponse201
10
+ from ...models.video_function_dto import VideoFunctionDto
11
+ from ...types import Response
12
+
13
+
14
+ def _get_kwargs(
15
+ *,
16
+ body: VideoFunctionDto,
17
+ ) -> dict[str, Any]:
18
+ headers: dict[str, Any] = {}
19
+
20
+ _kwargs: dict[str, Any] = {
21
+ "method": "post",
22
+ "url": "/public/v1/video/function",
23
+ }
24
+
25
+ _kwargs["json"] = body.to_dict()
26
+
27
+ headers["Content-Type"] = "application/json"
28
+
29
+ _kwargs["headers"] = headers
30
+ return _kwargs
31
+
32
+
33
+ def _parse_response(
34
+ *, client: AuthenticatedClient | Client, response: httpx.Response
35
+ ) -> ApiErrorDto | CallVideoFunctionResponse201 | None:
36
+ if response.status_code == 201:
37
+ response_201 = CallVideoFunctionResponse201.from_dict(response.json())
38
+
39
+ return response_201
40
+
41
+ if response.status_code == 400:
42
+ response_400 = ApiErrorDto.from_dict(response.json())
43
+
44
+ return response_400
45
+
46
+ if response.status_code == 401:
47
+ response_401 = ApiErrorDto.from_dict(response.json())
48
+
49
+ return response_401
50
+
51
+ if response.status_code == 404:
52
+ response_404 = ApiErrorDto.from_dict(response.json())
53
+
54
+ return response_404
55
+
56
+ if client.raise_on_unexpected_status:
57
+ raise errors.UnexpectedStatus(response.status_code, response.content)
58
+ else:
59
+ return None
60
+
61
+
62
+ def _build_response(
63
+ *, client: AuthenticatedClient | Client, response: httpx.Response
64
+ ) -> Response[ApiErrorDto | CallVideoFunctionResponse201]:
65
+ return Response(
66
+ status_code=HTTPStatus(response.status_code),
67
+ content=response.content,
68
+ headers=response.headers,
69
+ parsed=_parse_response(client=client, response=response),
70
+ )
71
+
72
+
73
+ def sync_detailed(
74
+ *,
75
+ client: AuthenticatedClient | Client,
76
+ body: VideoFunctionDto,
77
+ ) -> Response[ApiErrorDto | CallVideoFunctionResponse201]:
78
+ """Call a function of a video provider
79
+
80
+ Args:
81
+ body (VideoFunctionDto):
82
+
83
+ Raises:
84
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
85
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
86
+
87
+ Returns:
88
+ Response[ApiErrorDto | CallVideoFunctionResponse201]
89
+ """
90
+
91
+ kwargs = _get_kwargs(
92
+ body=body,
93
+ )
94
+
95
+ response = client.get_httpx_client().request(
96
+ **kwargs,
97
+ )
98
+
99
+ return _build_response(client=client, response=response)
100
+
101
+
102
+ def sync(
103
+ *,
104
+ client: AuthenticatedClient | Client,
105
+ body: VideoFunctionDto,
106
+ ) -> ApiErrorDto | CallVideoFunctionResponse201 | None:
107
+ """Call a function of a video provider
108
+
109
+ Args:
110
+ body (VideoFunctionDto):
111
+
112
+ Raises:
113
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
114
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
115
+
116
+ Returns:
117
+ ApiErrorDto | CallVideoFunctionResponse201
118
+ """
119
+
120
+ return sync_detailed(
121
+ client=client,
122
+ body=body,
123
+ ).parsed
124
+
125
+
126
+ async def asyncio_detailed(
127
+ *,
128
+ client: AuthenticatedClient | Client,
129
+ body: VideoFunctionDto,
130
+ ) -> Response[ApiErrorDto | CallVideoFunctionResponse201]:
131
+ """Call a function of a video provider
132
+
133
+ Args:
134
+ body (VideoFunctionDto):
135
+
136
+ Raises:
137
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
138
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
139
+
140
+ Returns:
141
+ Response[ApiErrorDto | CallVideoFunctionResponse201]
142
+ """
143
+
144
+ kwargs = _get_kwargs(
145
+ body=body,
146
+ )
147
+
148
+ response = await client.get_async_httpx_client().request(**kwargs)
149
+
150
+ return _build_response(client=client, response=response)
151
+
152
+
153
+ async def asyncio(
154
+ *,
155
+ client: AuthenticatedClient | Client,
156
+ body: VideoFunctionDto,
157
+ ) -> ApiErrorDto | CallVideoFunctionResponse201 | None:
158
+ """Call a function of a video provider
159
+
160
+ Args:
161
+ body (VideoFunctionDto):
162
+
163
+ Raises:
164
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
165
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
166
+
167
+ Returns:
168
+ ApiErrorDto | CallVideoFunctionResponse201
169
+ """
170
+
171
+ return (
172
+ await asyncio_detailed(
173
+ client=client,
174
+ body=body,
175
+ )
176
+ ).parsed
@@ -0,0 +1,192 @@
1
+ from http import HTTPStatus
2
+ from typing import Any
3
+ from urllib.parse import quote
4
+
5
+ import httpx
6
+
7
+ from ... import errors
8
+ from ...client import AuthenticatedClient, Client
9
+ from ...models.api_error_dto import ApiErrorDto
10
+ from ...models.change_post_status_dto import ChangePostStatusDto
11
+ from ...models.post_status_dto import PostStatusDto
12
+ from ...types import Response
13
+
14
+
15
+ def _get_kwargs(
16
+ id: str,
17
+ *,
18
+ body: ChangePostStatusDto,
19
+ ) -> dict[str, Any]:
20
+ headers: dict[str, Any] = {}
21
+
22
+ _kwargs: dict[str, Any] = {
23
+ "method": "put",
24
+ "url": "/public/v1/posts/{id}/status".format(
25
+ id=quote(str(id), safe=""),
26
+ ),
27
+ }
28
+
29
+ _kwargs["json"] = body.to_dict()
30
+
31
+ headers["Content-Type"] = "application/json"
32
+
33
+ _kwargs["headers"] = headers
34
+ return _kwargs
35
+
36
+
37
+ def _parse_response(
38
+ *, client: AuthenticatedClient | Client, response: httpx.Response
39
+ ) -> ApiErrorDto | PostStatusDto | None:
40
+ if response.status_code == 200:
41
+ response_200 = PostStatusDto.from_dict(response.json())
42
+
43
+ return response_200
44
+
45
+ if response.status_code == 400:
46
+ response_400 = ApiErrorDto.from_dict(response.json())
47
+
48
+ return response_400
49
+
50
+ if response.status_code == 401:
51
+ response_401 = ApiErrorDto.from_dict(response.json())
52
+
53
+ return response_401
54
+
55
+ if response.status_code == 404:
56
+ response_404 = ApiErrorDto.from_dict(response.json())
57
+
58
+ return response_404
59
+
60
+ if client.raise_on_unexpected_status:
61
+ raise errors.UnexpectedStatus(response.status_code, response.content)
62
+ else:
63
+ return None
64
+
65
+
66
+ def _build_response(
67
+ *, client: AuthenticatedClient | Client, response: httpx.Response
68
+ ) -> Response[ApiErrorDto | PostStatusDto]:
69
+ return Response(
70
+ status_code=HTTPStatus(response.status_code),
71
+ content=response.content,
72
+ headers=response.headers,
73
+ parsed=_parse_response(client=client, response=response),
74
+ )
75
+
76
+
77
+ def sync_detailed(
78
+ id: str,
79
+ *,
80
+ client: AuthenticatedClient | Client,
81
+ body: ChangePostStatusDto,
82
+ ) -> Response[ApiErrorDto | PostStatusDto]:
83
+ """Change a post's status
84
+
85
+ Args:
86
+ id (str):
87
+ body (ChangePostStatusDto):
88
+
89
+ Raises:
90
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
91
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
92
+
93
+ Returns:
94
+ Response[ApiErrorDto | PostStatusDto]
95
+ """
96
+
97
+ kwargs = _get_kwargs(
98
+ id=id,
99
+ body=body,
100
+ )
101
+
102
+ response = client.get_httpx_client().request(
103
+ **kwargs,
104
+ )
105
+
106
+ return _build_response(client=client, response=response)
107
+
108
+
109
+ def sync(
110
+ id: str,
111
+ *,
112
+ client: AuthenticatedClient | Client,
113
+ body: ChangePostStatusDto,
114
+ ) -> ApiErrorDto | PostStatusDto | None:
115
+ """Change a post's status
116
+
117
+ Args:
118
+ id (str):
119
+ body (ChangePostStatusDto):
120
+
121
+ Raises:
122
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
123
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
124
+
125
+ Returns:
126
+ ApiErrorDto | PostStatusDto
127
+ """
128
+
129
+ return sync_detailed(
130
+ id=id,
131
+ client=client,
132
+ body=body,
133
+ ).parsed
134
+
135
+
136
+ async def asyncio_detailed(
137
+ id: str,
138
+ *,
139
+ client: AuthenticatedClient | Client,
140
+ body: ChangePostStatusDto,
141
+ ) -> Response[ApiErrorDto | PostStatusDto]:
142
+ """Change a post's status
143
+
144
+ Args:
145
+ id (str):
146
+ body (ChangePostStatusDto):
147
+
148
+ Raises:
149
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
150
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
151
+
152
+ Returns:
153
+ Response[ApiErrorDto | PostStatusDto]
154
+ """
155
+
156
+ kwargs = _get_kwargs(
157
+ id=id,
158
+ body=body,
159
+ )
160
+
161
+ response = await client.get_async_httpx_client().request(**kwargs)
162
+
163
+ return _build_response(client=client, response=response)
164
+
165
+
166
+ async def asyncio(
167
+ id: str,
168
+ *,
169
+ client: AuthenticatedClient | Client,
170
+ body: ChangePostStatusDto,
171
+ ) -> ApiErrorDto | PostStatusDto | None:
172
+ """Change a post's status
173
+
174
+ Args:
175
+ id (str):
176
+ body (ChangePostStatusDto):
177
+
178
+ Raises:
179
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
180
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
181
+
182
+ Returns:
183
+ ApiErrorDto | PostStatusDto
184
+ """
185
+
186
+ return (
187
+ await asyncio_detailed(
188
+ id=id,
189
+ client=client,
190
+ body=body,
191
+ )
192
+ ).parsed