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.
- usenadia/__init__.py +278 -0
- usenadia/generated/__init__.py +8 -0
- usenadia/generated/api/__init__.py +1 -0
- usenadia/generated/api/public_api/__init__.py +1 -0
- usenadia/generated/api/public_api/call_video_function.py +176 -0
- usenadia/generated/api/public_api/change_post_status.py +192 -0
- usenadia/generated/api/public_api/check_connection.py +144 -0
- usenadia/generated/api/public_api/create_automation_flow.py +176 -0
- usenadia/generated/api/public_api/create_content.py +181 -0
- usenadia/generated/api/public_api/create_post.py +183 -0
- usenadia/generated/api/public_api/create_webhook.py +176 -0
- usenadia/generated/api/public_api/delete_automation_flow.py +171 -0
- usenadia/generated/api/public_api/delete_automation_pose.py +171 -0
- usenadia/generated/api/public_api/delete_channel.py +171 -0
- usenadia/generated/api/public_api/delete_comment.py +185 -0
- usenadia/generated/api/public_api/delete_content.py +171 -0
- usenadia/generated/api/public_api/delete_post.py +171 -0
- usenadia/generated/api/public_api/delete_post_group.py +171 -0
- usenadia/generated/api/public_api/delete_webhook.py +171 -0
- usenadia/generated/api/public_api/find_slot.py +171 -0
- usenadia/generated/api/public_api/generate_video.py +176 -0
- usenadia/generated/api/public_api/get_account_history.py +252 -0
- usenadia/generated/api/public_api/get_automation_flow.py +171 -0
- usenadia/generated/api/public_api/get_automation_pose.py +171 -0
- usenadia/generated/api/public_api/get_channel_analytics.py +197 -0
- usenadia/generated/api/public_api/get_channel_settings.py +171 -0
- usenadia/generated/api/public_api/get_content.py +171 -0
- usenadia/generated/api/public_api/get_contents_performance.py +146 -0
- usenadia/generated/api/public_api/get_conversation.py +190 -0
- usenadia/generated/api/public_api/get_hook_forms.py +265 -0
- usenadia/generated/api/public_api/get_music_track.py +171 -0
- usenadia/generated/api/public_api/get_post_analytics.py +192 -0
- usenadia/generated/api/public_api/get_video_history.py +252 -0
- usenadia/generated/api/public_api/get_webhook.py +171 -0
- usenadia/generated/api/public_api/hide_comment.py +206 -0
- usenadia/generated/api/public_api/import_hooks.py +171 -0
- usenadia/generated/api/public_api/import_music.py +176 -0
- usenadia/generated/api/public_api/link_content_post.py +208 -0
- usenadia/generated/api/public_api/list_automation_flow_poses.py +178 -0
- usenadia/generated/api/public_api/list_automation_flows.py +151 -0
- usenadia/generated/api/public_api/list_automation_pose_runs.py +199 -0
- usenadia/generated/api/public_api/list_channel_automation_poses.py +198 -0
- usenadia/generated/api/public_api/list_channel_media.py +176 -0
- usenadia/generated/api/public_api/list_channel_stories.py +176 -0
- usenadia/generated/api/public_api/list_channels.py +181 -0
- usenadia/generated/api/public_api/list_contents.py +239 -0
- usenadia/generated/api/public_api/list_conversations.py +197 -0
- usenadia/generated/api/public_api/list_groups.py +149 -0
- usenadia/generated/api/public_api/list_hook_families.py +196 -0
- usenadia/generated/api/public_api/list_media_comments.py +190 -0
- usenadia/generated/api/public_api/list_missing_media.py +192 -0
- usenadia/generated/api/public_api/list_music.py +252 -0
- usenadia/generated/api/public_api/list_music_pillars.py +152 -0
- usenadia/generated/api/public_api/list_notifications.py +176 -0
- usenadia/generated/api/public_api/list_posts.py +236 -0
- usenadia/generated/api/public_api/list_video_performance.py +209 -0
- usenadia/generated/api/public_api/list_webhook_events.py +144 -0
- usenadia/generated/api/public_api/list_webhooks.py +149 -0
- usenadia/generated/api/public_api/move_content.py +192 -0
- usenadia/generated/api/public_api/poll_automation_pose.py +171 -0
- usenadia/generated/api/public_api/poll_inbox.py +171 -0
- usenadia/generated/api/public_api/pose_automation_flow.py +192 -0
- usenadia/generated/api/public_api/preview_automation_email.py +176 -0
- usenadia/generated/api/public_api/replace_hook_families.py +176 -0
- usenadia/generated/api/public_api/replay_automation_pose.py +199 -0
- usenadia/generated/api/public_api/reply_to_comment.py +192 -0
- usenadia/generated/api/public_api/run_channel_tool.py +192 -0
- usenadia/generated/api/public_api/search_hooks.py +267 -0
- usenadia/generated/api/public_api/send_automation_test_email.py +176 -0
- usenadia/generated/api/public_api/send_direct_message.py +192 -0
- usenadia/generated/api/public_api/set_automation_flow_status.py +192 -0
- usenadia/generated/api/public_api/set_post_release_id.py +192 -0
- usenadia/generated/api/public_api/start_channel_connection.py +192 -0
- usenadia/generated/api/public_api/subscribe_inbox_webhook.py +171 -0
- usenadia/generated/api/public_api/update_automation_flow.py +192 -0
- usenadia/generated/api/public_api/update_automation_pose.py +192 -0
- usenadia/generated/api/public_api/update_content.py +192 -0
- usenadia/generated/api/public_api/upload_file.py +176 -0
- usenadia/generated/api/public_api/upload_from_url.py +176 -0
- usenadia/generated/api/public_api/upload_music_file.py +197 -0
- usenadia/generated/api/public_api/upsert_content.py +188 -0
- usenadia/generated/client.py +280 -0
- usenadia/generated/errors.py +16 -0
- usenadia/generated/models/__init__.py +335 -0
- usenadia/generated/models/account_history_item_dto.py +86 -0
- usenadia/generated/models/analytics_metric_dto.py +94 -0
- usenadia/generated/models/analytics_point_dto.py +70 -0
- usenadia/generated/models/api_error_dto.py +61 -0
- usenadia/generated/models/automation_flow_counts_dto.py +69 -0
- usenadia/generated/models/automation_flow_dto.py +97 -0
- usenadia/generated/models/automation_flow_dto_definition.py +45 -0
- usenadia/generated/models/automation_flow_issue_dto.py +116 -0
- usenadia/generated/models/automation_flow_issue_dto_level.py +9 -0
- usenadia/generated/models/automation_flow_issue_dto_params.py +45 -0
- usenadia/generated/models/automation_flow_poll_result_dto.py +69 -0
- usenadia/generated/models/automation_flow_pose_response_dto.py +206 -0
- usenadia/generated/models/automation_flow_pose_response_dto_source.py +9 -0
- usenadia/generated/models/automation_flow_pose_response_dto_variables.py +47 -0
- usenadia/generated/models/automation_flow_replay_result_dto.py +119 -0
- usenadia/generated/models/automation_flow_response_dto.py +173 -0
- usenadia/generated/models/automation_flow_response_dto_definition.py +50 -0
- usenadia/generated/models/automation_flow_response_dto_status.py +10 -0
- usenadia/generated/models/automation_flow_run_response_dto.py +269 -0
- usenadia/generated/models/automation_flow_run_response_dto_status.py +14 -0
- usenadia/generated/models/automation_flow_status_dto.py +63 -0
- usenadia/generated/models/automation_flow_status_dto_status.py +10 -0
- usenadia/generated/models/automation_pose_counts_dto.py +93 -0
- usenadia/generated/models/automation_pose_flow_dto.py +79 -0
- usenadia/generated/models/automation_pose_flow_dto_status.py +10 -0
- usenadia/generated/models/call_video_function_response_201.py +45 -0
- usenadia/generated/models/change_post_status_dto.py +63 -0
- usenadia/generated/models/change_post_status_dto_status.py +9 -0
- usenadia/generated/models/content_channel_performance_dto.py +368 -0
- usenadia/generated/models/content_dto.py +408 -0
- usenadia/generated/models/content_dto_resource_status.py +9 -0
- usenadia/generated/models/content_dto_stage.py +14 -0
- usenadia/generated/models/content_list_item_dto.py +441 -0
- usenadia/generated/models/content_list_item_dto_resource_status.py +9 -0
- usenadia/generated/models/content_list_item_dto_stage.py +14 -0
- usenadia/generated/models/content_performance_dto.py +175 -0
- usenadia/generated/models/content_post_dto.py +109 -0
- usenadia/generated/models/content_post_dto_state.py +12 -0
- usenadia/generated/models/content_upsert_dto.py +75 -0
- usenadia/generated/models/create_content_dto.py +202 -0
- usenadia/generated/models/create_content_dto_resource_status.py +9 -0
- usenadia/generated/models/create_content_dto_stage.py +14 -0
- usenadia/generated/models/create_post_result_item_dto.py +69 -0
- usenadia/generated/models/create_public_webhook_dto.py +133 -0
- usenadia/generated/models/create_public_webhook_dto_events_item.py +12 -0
- usenadia/generated/models/deleted_id_dto.py +61 -0
- usenadia/generated/models/email_preview_dto.py +143 -0
- usenadia/generated/models/email_preview_dto_response.py +122 -0
- usenadia/generated/models/email_preview_dto_response_params.py +47 -0
- usenadia/generated/models/email_preview_dto_response_sender.py +47 -0
- usenadia/generated/models/email_sent_dto.py +69 -0
- usenadia/generated/models/email_test_dto.py +163 -0
- usenadia/generated/models/find_slot_dto.py +62 -0
- usenadia/generated/models/generated_video_dto.py +119 -0
- usenadia/generated/models/get_contents_performance_response_200.py +62 -0
- usenadia/generated/models/get_hook_forms_language.py +10 -0
- usenadia/generated/models/get_post_analytics_response_200.py +45 -0
- usenadia/generated/models/group_dto.py +69 -0
- usenadia/generated/models/hide_comment_dto.py +61 -0
- usenadia/generated/models/history_page_dto.py +116 -0
- usenadia/generated/models/hook_dto.py +311 -0
- usenadia/generated/models/hook_families_import_dto.py +83 -0
- usenadia/generated/models/hook_family_dto.py +220 -0
- usenadia/generated/models/hook_family_stat_dto.py +212 -0
- usenadia/generated/models/hook_form_dto.py +186 -0
- usenadia/generated/models/hook_import_item_dto.py +211 -0
- usenadia/generated/models/hooks_import_dto.py +75 -0
- usenadia/generated/models/hooks_import_result_dto.py +69 -0
- usenadia/generated/models/hooks_search_result_dto.py +83 -0
- usenadia/generated/models/inbox_conversation_dto.py +128 -0
- usenadia/generated/models/inbox_conversation_dto_inbox.py +47 -0
- usenadia/generated/models/inbox_conversation_dto_messages.py +47 -0
- usenadia/generated/models/inbox_conversation_dto_participants.py +47 -0
- usenadia/generated/models/inbox_message_dto.py +102 -0
- usenadia/generated/models/inbox_participant_dto.py +72 -0
- usenadia/generated/models/inbox_poll_result_dto.py +69 -0
- usenadia/generated/models/instagram_comment_author_dto.py +70 -0
- usenadia/generated/models/instagram_comment_dto.py +123 -0
- usenadia/generated/models/instagram_media_dto.py +145 -0
- usenadia/generated/models/integration.py +61 -0
- usenadia/generated/models/integration_list_item_dto.py +138 -0
- usenadia/generated/models/integration_settings_dto.py +69 -0
- usenadia/generated/models/integration_settings_output_dto.py +106 -0
- usenadia/generated/models/integration_settings_output_dto_settings.py +47 -0
- usenadia/generated/models/integration_tool_dto.py +91 -0
- usenadia/generated/models/integration_tool_param_dto.py +77 -0
- usenadia/generated/models/integration_trigger_dto.py +85 -0
- usenadia/generated/models/integration_trigger_dto_data.py +47 -0
- usenadia/generated/models/integration_trigger_result_dto.py +71 -0
- usenadia/generated/models/integration_trigger_result_dto_output.py +47 -0
- usenadia/generated/models/is_connected_dto.py +61 -0
- usenadia/generated/models/link_content_post_dto.py +61 -0
- usenadia/generated/models/list_contents_stage.py +14 -0
- usenadia/generated/models/list_music_pillar.py +11 -0
- usenadia/generated/models/list_video_performance_response_200.py +45 -0
- usenadia/generated/models/media_dto.py +89 -0
- usenadia/generated/models/media_response_dto.py +119 -0
- usenadia/generated/models/missing_content_item_dto.py +69 -0
- usenadia/generated/models/move_content_dto.py +73 -0
- usenadia/generated/models/move_content_dto_stage.py +14 -0
- usenadia/generated/models/music_import_dto.py +75 -0
- usenadia/generated/models/music_import_item_dto.py +238 -0
- usenadia/generated/models/music_import_item_dto_pillar.py +11 -0
- usenadia/generated/models/music_import_item_dto_verdict.py +10 -0
- usenadia/generated/models/music_import_result_dto.py +69 -0
- usenadia/generated/models/music_list_result_dto.py +91 -0
- usenadia/generated/models/music_pillar_dto.py +85 -0
- usenadia/generated/models/music_track_dto.py +318 -0
- usenadia/generated/models/notification_dto.py +100 -0
- usenadia/generated/models/notifications_page_dto.py +107 -0
- usenadia/generated/models/ok_dto.py +61 -0
- usenadia/generated/models/pose_automation_flow_dto.py +125 -0
- usenadia/generated/models/pose_automation_flow_dto_kind.py +9 -0
- usenadia/generated/models/pose_automation_flow_dto_variables.py +45 -0
- usenadia/generated/models/post.py +114 -0
- usenadia/generated/models/post_content.py +99 -0
- usenadia/generated/models/post_dto.py +219 -0
- usenadia/generated/models/post_dto_creation_method.py +11 -0
- usenadia/generated/models/post_dto_state.py +12 -0
- usenadia/generated/models/post_integration_summary_dto.py +91 -0
- usenadia/generated/models/post_list_dto.py +75 -0
- usenadia/generated/models/post_settings.py +45 -0
- usenadia/generated/models/post_status_dto.py +71 -0
- usenadia/generated/models/post_status_dto_state.py +11 -0
- usenadia/generated/models/post_tag_dto.py +67 -0
- usenadia/generated/models/post_tag_dto_tag.py +47 -0
- usenadia/generated/models/public_create_post_dto.py +160 -0
- usenadia/generated/models/public_create_post_dto_creation_method.py +9 -0
- usenadia/generated/models/public_create_post_dto_type.py +11 -0
- usenadia/generated/models/replay_pose_dto.py +72 -0
- usenadia/generated/models/reply_to_comment_dto.py +69 -0
- usenadia/generated/models/search_hooks_language.py +10 -0
- usenadia/generated/models/send_message_dto.py +89 -0
- usenadia/generated/models/social_auth_url_dto.py +61 -0
- usenadia/generated/models/success_dto.py +61 -0
- usenadia/generated/models/tags.py +69 -0
- usenadia/generated/models/update_automation_flow_dto.py +107 -0
- usenadia/generated/models/update_automation_flow_dto_definition.py +45 -0
- usenadia/generated/models/update_automation_flow_pose_dto.py +98 -0
- usenadia/generated/models/update_automation_flow_pose_dto_variables.py +45 -0
- usenadia/generated/models/update_content_dto.py +300 -0
- usenadia/generated/models/update_content_dto_resource_status.py +9 -0
- usenadia/generated/models/update_release_id_dto.py +61 -0
- usenadia/generated/models/upload_dto.py +61 -0
- usenadia/generated/models/upload_file_body.py +75 -0
- usenadia/generated/models/upload_music_file_body.py +75 -0
- usenadia/generated/models/upsert_content_dto.py +200 -0
- usenadia/generated/models/upsert_content_dto_resource_status.py +9 -0
- usenadia/generated/models/upsert_content_dto_stage.py +14 -0
- usenadia/generated/models/video_dto.py +85 -0
- usenadia/generated/models/video_dto_custom_params.py +45 -0
- usenadia/generated/models/video_dto_output.py +9 -0
- usenadia/generated/models/video_function_dto.py +93 -0
- usenadia/generated/models/video_function_dto_params.py +45 -0
- usenadia/generated/models/video_history_page_dto.py +143 -0
- usenadia/generated/models/video_identity_dto.py +123 -0
- usenadia/generated/models/webhook_created_dto.py +135 -0
- usenadia/generated/models/webhook_dto.py +125 -0
- usenadia/generated/models/webhook_event_dto.py +69 -0
- usenadia/generated/models/webhook_event_list_dto.py +75 -0
- usenadia/generated/models/webhooks_integration_dto.py +61 -0
- usenadia/generated/types.py +54 -0
- usenadia-0.1.0.dist-info/METADATA +98 -0
- usenadia-0.1.0.dist-info/RECORD +250 -0
- usenadia-0.1.0.dist-info/WHEEL +4 -0
- 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 @@
|
|
|
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
|