diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 8304697..0e3898a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -21,7 +21,7 @@ jobs:
runs-on: ${{ github.repository == 'stainless-sdks/post-for-me-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata')
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install Rye
run: |
@@ -46,7 +46,7 @@ jobs:
id-token: write
runs-on: ${{ github.repository == 'stainless-sdks/post-for-me-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install Rye
run: |
@@ -67,7 +67,7 @@ jobs:
github.repository == 'stainless-sdks/post-for-me-python' &&
!startsWith(github.ref, 'refs/heads/stl/')
id: github-oidc
- uses: actions/github-script@v8
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: core.setOutput('github_token', await core.getIDToken());
@@ -87,7 +87,7 @@ jobs:
runs-on: ${{ github.repository == 'stainless-sdks/post-for-me-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install Rye
run: |
diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml
index b515eb6..a1d25b3 100644
--- a/.github/workflows/publish-pypi.yml
+++ b/.github/workflows/publish-pypi.yml
@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install Rye
run: |
diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml
index df36800..0044a6a 100644
--- a/.github/workflows/release-doctor.yml
+++ b/.github/workflows/release-doctor.yml
@@ -12,7 +12,7 @@ jobs:
if: github.repository == 'DayMoonDevelopment/post-for-me-python' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next')
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Check release environment
run: |
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 7ccfe12..bc845f3 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "1.15.0"
+ ".": "1.16.0"
}
\ No newline at end of file
diff --git a/.stats.yml b/.stats.yml
index 0a2314b..f6bd627 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 15
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/day-moon-development%2Fpost-for-me-d7bde21e6d3328e90ec781ff8e2629faeaae4bb5d8e0d350703326ec8aadf898.yml
-openapi_spec_hash: dcb2130480c4476fe08fcb080e369ce0
-config_hash: 0ec19602e41aea0526548245a59d4253
+configured_endpoints: 21
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/day-moon-development/post-for-me-b4207c01320058903d7a081df1d83e7c750eebce2c8cb1dbfc73514bf318ea87.yml
+openapi_spec_hash: ffccc75cdb734af5746192289c4fce9b
+config_hash: 600d71be044aa28b6759ce7f55a86948
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9787795..7ac5cd2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,33 @@
# Changelog
+## 1.16.0 (2026-05-13)
+
+Full Changelog: [v1.15.0...v1.16.0](https://github.com/DayMoonDevelopment/post-for-me-python/compare/v1.15.0...v1.16.0)
+
+### Features
+
+* **api:** api update ([836ea67](https://github.com/DayMoonDevelopment/post-for-me-python/commit/836ea6730f3902793fbd6f2c2658408f14fdd26a))
+* **api:** manual updates ([d04ba06](https://github.com/DayMoonDevelopment/post-for-me-python/commit/d04ba064d3473551fbb9f5fa58f7e0746e4e964a))
+* **internal/types:** support eagerly validating pydantic iterators ([e452557](https://github.com/DayMoonDevelopment/post-for-me-python/commit/e452557c17b3a855f1721b22c31d866b3c820ca7))
+* support setting headers via env ([2aa1883](https://github.com/DayMoonDevelopment/post-for-me-python/commit/2aa18833f588027ae62e86d4d4e9e376cb509405))
+
+
+### Bug Fixes
+
+* **client:** add missing f-string prefix in file type error message ([66a4a79](https://github.com/DayMoonDevelopment/post-for-me-python/commit/66a4a79e88650652dcc5fd7653edc5110c5f4590))
+* use correct field name format for multipart file arrays ([8165256](https://github.com/DayMoonDevelopment/post-for-me-python/commit/8165256e26d948d2be4711116ed30004007ba6bf))
+
+
+### Performance Improvements
+
+* **client:** optimize file structure copying in multipart requests ([897e06a](https://github.com/DayMoonDevelopment/post-for-me-python/commit/897e06a740ac67aaacd96fc1c70a2cb07407d02a))
+
+
+### Chores
+
+* **internal:** more robust bootstrap script ([0c3c4f0](https://github.com/DayMoonDevelopment/post-for-me-python/commit/0c3c4f06ec91632e5b34d0651c058f4b2ad51065))
+* **internal:** reformat pyproject.toml ([a1ee9d9](https://github.com/DayMoonDevelopment/post-for-me-python/commit/a1ee9d96ce4e823150ef165b857ce5e839beea3f))
+
## 1.15.0 (2026-04-11)
Full Changelog: [v1.14.0...v1.15.0](https://github.com/DayMoonDevelopment/post-for-me-python/compare/v1.14.0...v1.15.0)
diff --git a/api.md b/api.md
index 0acf4fa..d14efcb 100644
--- a/api.md
+++ b/api.md
@@ -16,20 +16,23 @@ Types:
```python
from post_for_me.types import (
+ AccountConfiguration,
BlueskyConfigurationDto,
CreateSocialPost,
+ DeleteEntityResponse,
FacebookConfigurationDto,
InstagramConfigurationDto,
LinkedinConfigurationDto,
PinterestConfigurationDto,
PlatformConfigurationsDto,
SocialPost,
+ SocialPostMedia,
ThreadsConfigurationDto,
TiktokConfiguration,
TwitterConfigurationDto,
+ TwitterPoll,
YoutubeConfigurationDto,
SocialPostListResponse,
- SocialPostDeleteResponse,
)
```
@@ -39,7 +42,7 @@ Methods:
- client.social_posts.retrieve(id) -> SocialPost
- client.social_posts.update(id, \*\*params) -> SocialPost
- client.social_posts.list(\*\*params) -> SocialPostListResponse
-- client.social_posts.delete(id) -> SocialPostDeleteResponse
+- client.social_posts.delete(id) -> DeleteEntityResponse
# SocialPostResults
@@ -61,6 +64,7 @@ Types:
```python
from post_for_me.types import (
SocialAccount,
+ SocialAccountMetadata,
SocialAccountListResponse,
SocialAccountCreateAuthURLResponse,
SocialAccountDisconnectResponse,
@@ -81,9 +85,50 @@ Methods:
Types:
```python
-from post_for_me.types import PlatformPost, SocialAccountFeedListResponse
+from post_for_me.types import (
+ FacebookActivityByActionType,
+ FacebookVideoRetentionGraph,
+ FacebookVideoViewTimeByDemographic,
+ PinterestMetricsWindow,
+ PlatformPost,
+ TiktokBusinessVideoMetricPercentage,
+ YoutubePostPlatformData,
+ SocialAccountFeedListResponse,
+)
```
Methods:
- client.social_account_feeds.list(social_account_id, \*\*params) -> SocialAccountFeedListResponse
+
+# Webhooks
+
+Types:
+
+```python
+from post_for_me.types import Webhook, WebhookListResponse
+```
+
+Methods:
+
+- client.webhooks.create(\*\*params) -> Webhook
+- client.webhooks.retrieve(id) -> Webhook
+- client.webhooks.update(id, \*\*params) -> Webhook
+- client.webhooks.list(\*\*params) -> WebhookListResponse
+- client.webhooks.delete(id) -> DeleteEntityResponse
+
+# SocialPostPreviews
+
+Types:
+
+```python
+from post_for_me.types import (
+ CreateSocialPostPreview,
+ SocialPostPreview,
+ SocialPostPreviewCreateResponse,
+)
+```
+
+Methods:
+
+- client.social_post_previews.create(\*\*params) -> SocialPostPreviewCreateResponse
diff --git a/pyproject.toml b/pyproject.toml
index 7cff57c..281550e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "post_for_me"
-version = "1.15.0"
+version = "1.16.0"
description = "The official Python library for the post-for-me API"
dynamic = ["readme"]
license = "Apache-2.0"
@@ -168,7 +168,7 @@ show_error_codes = true
#
# We also exclude our `tests` as mypy doesn't always infer
# types correctly and Pyright will still catch any type errors.
-exclude = ['src/post_for_me/_files.py', '_dev/.*.py', 'tests/.*']
+exclude = ["src/post_for_me/_files.py", "_dev/.*.py", "tests/.*"]
strict_equality = true
implicit_reexport = true
diff --git a/scripts/bootstrap b/scripts/bootstrap
index b430fee..fe8451e 100755
--- a/scripts/bootstrap
+++ b/scripts/bootstrap
@@ -4,7 +4,7 @@ set -e
cd "$(dirname "$0")/.."
-if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ] && [ -t 0 ]; then
+if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "${SKIP_BREW:-}" != "1" ] && [ -t 0 ]; then
brew bundle check >/dev/null 2>&1 || {
echo -n "==> Install Homebrew dependencies? (y/N): "
read -r response
diff --git a/src/post_for_me/_client.py b/src/post_for_me/_client.py
index 31f98be..b9d5271 100644
--- a/src/post_for_me/_client.py
+++ b/src/post_for_me/_client.py
@@ -19,7 +19,11 @@
RequestOptions,
not_given,
)
-from ._utils import is_given, get_async_library
+from ._utils import (
+ is_given,
+ is_mapping_t,
+ get_async_library,
+)
from ._compat import cached_property
from ._version import __version__
from ._streaming import Stream as Stream, AsyncStream as AsyncStream
@@ -31,12 +35,22 @@
)
if TYPE_CHECKING:
- from .resources import media, social_posts, social_accounts, social_post_results, social_account_feeds
+ from .resources import (
+ media,
+ webhooks,
+ social_posts,
+ social_accounts,
+ social_post_results,
+ social_account_feeds,
+ social_post_previews,
+ )
from .resources.media import MediaResource, AsyncMediaResource
+ from .resources.webhooks import WebhooksResource, AsyncWebhooksResource
from .resources.social_posts import SocialPostsResource, AsyncSocialPostsResource
from .resources.social_accounts import SocialAccountsResource, AsyncSocialAccountsResource
from .resources.social_post_results import SocialPostResultsResource, AsyncSocialPostResultsResource
from .resources.social_account_feeds import SocialAccountFeedsResource, AsyncSocialAccountFeedsResource
+ from .resources.social_post_previews import SocialPostPreviewsResource, AsyncSocialPostPreviewsResource
__all__ = [
"Timeout",
@@ -94,6 +108,15 @@ def __init__(
if base_url is None:
base_url = f"https://api.postforme.dev"
+ custom_headers_env = os.environ.get("POST_FOR_ME_CUSTOM_HEADERS")
+ if custom_headers_env is not None:
+ parsed: dict[str, str] = {}
+ for line in custom_headers_env.split("\n"):
+ colon = line.find(":")
+ if colon >= 0:
+ parsed[line[:colon].strip()] = line[colon + 1 :].strip()
+ default_headers = {**parsed, **(default_headers if is_mapping_t(default_headers) else {})}
+
super().__init__(
version=__version__,
base_url=base_url,
@@ -182,6 +205,45 @@ def social_account_feeds(self) -> SocialAccountFeedsResource:
return SocialAccountFeedsResource(self)
+ @cached_property
+ def webhooks(self) -> WebhooksResource:
+ """Webhooks enable you to subscribe to certain events.
+
+ This involves Post for Me making a POST request to the URL of any webhooks you create.
+ Only the events you subscribe to will be sent to your webhook URL.
+
+ ## Payload
+ When an event happens that your webhook is subscribed to, we will make a POST request with the following JSON body
+
+ ```
+ {
+ "event_type": "",
+ "data": {}
+ }
+ ```
+
+ The event_type will be the event that triggered the webhook POST, data will be the resulting entity from the event
+
+ ## Security
+ To verify the POST to your webhook URL is from us we will include a secret in the header "Post-For-Me-Webhook-Secret".
+ When you create a webhook you will receive the secret in the response.
+
+ ## Retries
+ If your server fails to respond with a 2XX code, requests to it will be retried with exponential backoff around 8 times over the course of just over a day.
+ """
+ from .resources.webhooks import WebhooksResource
+
+ return WebhooksResource(self)
+
+ @cached_property
+ def social_post_previews(self) -> SocialPostPreviewsResource:
+ """
+ Social Post Previews allow you to see what a Social Post will create for each account in the post.
+ """
+ from .resources.social_post_previews import SocialPostPreviewsResource
+
+ return SocialPostPreviewsResource(self)
+
@cached_property
def with_raw_response(self) -> PostForMeWithRawResponse:
return PostForMeWithRawResponse(self)
@@ -339,6 +401,15 @@ def __init__(
if base_url is None:
base_url = f"https://api.postforme.dev"
+ custom_headers_env = os.environ.get("POST_FOR_ME_CUSTOM_HEADERS")
+ if custom_headers_env is not None:
+ parsed: dict[str, str] = {}
+ for line in custom_headers_env.split("\n"):
+ colon = line.find(":")
+ if colon >= 0:
+ parsed[line[:colon].strip()] = line[colon + 1 :].strip()
+ default_headers = {**parsed, **(default_headers if is_mapping_t(default_headers) else {})}
+
super().__init__(
version=__version__,
base_url=base_url,
@@ -427,6 +498,45 @@ def social_account_feeds(self) -> AsyncSocialAccountFeedsResource:
return AsyncSocialAccountFeedsResource(self)
+ @cached_property
+ def webhooks(self) -> AsyncWebhooksResource:
+ """Webhooks enable you to subscribe to certain events.
+
+ This involves Post for Me making a POST request to the URL of any webhooks you create.
+ Only the events you subscribe to will be sent to your webhook URL.
+
+ ## Payload
+ When an event happens that your webhook is subscribed to, we will make a POST request with the following JSON body
+
+ ```
+ {
+ "event_type": "",
+ "data": {}
+ }
+ ```
+
+ The event_type will be the event that triggered the webhook POST, data will be the resulting entity from the event
+
+ ## Security
+ To verify the POST to your webhook URL is from us we will include a secret in the header "Post-For-Me-Webhook-Secret".
+ When you create a webhook you will receive the secret in the response.
+
+ ## Retries
+ If your server fails to respond with a 2XX code, requests to it will be retried with exponential backoff around 8 times over the course of just over a day.
+ """
+ from .resources.webhooks import AsyncWebhooksResource
+
+ return AsyncWebhooksResource(self)
+
+ @cached_property
+ def social_post_previews(self) -> AsyncSocialPostPreviewsResource:
+ """
+ Social Post Previews allow you to see what a Social Post will create for each account in the post.
+ """
+ from .resources.social_post_previews import AsyncSocialPostPreviewsResource
+
+ return AsyncSocialPostPreviewsResource(self)
+
@cached_property
def with_raw_response(self) -> AsyncPostForMeWithRawResponse:
return AsyncPostForMeWithRawResponse(self)
@@ -623,6 +733,45 @@ def social_account_feeds(self) -> social_account_feeds.SocialAccountFeedsResourc
return SocialAccountFeedsResourceWithRawResponse(self._client.social_account_feeds)
+ @cached_property
+ def webhooks(self) -> webhooks.WebhooksResourceWithRawResponse:
+ """Webhooks enable you to subscribe to certain events.
+
+ This involves Post for Me making a POST request to the URL of any webhooks you create.
+ Only the events you subscribe to will be sent to your webhook URL.
+
+ ## Payload
+ When an event happens that your webhook is subscribed to, we will make a POST request with the following JSON body
+
+ ```
+ {
+ "event_type": "",
+ "data": {}
+ }
+ ```
+
+ The event_type will be the event that triggered the webhook POST, data will be the resulting entity from the event
+
+ ## Security
+ To verify the POST to your webhook URL is from us we will include a secret in the header "Post-For-Me-Webhook-Secret".
+ When you create a webhook you will receive the secret in the response.
+
+ ## Retries
+ If your server fails to respond with a 2XX code, requests to it will be retried with exponential backoff around 8 times over the course of just over a day.
+ """
+ from .resources.webhooks import WebhooksResourceWithRawResponse
+
+ return WebhooksResourceWithRawResponse(self._client.webhooks)
+
+ @cached_property
+ def social_post_previews(self) -> social_post_previews.SocialPostPreviewsResourceWithRawResponse:
+ """
+ Social Post Previews allow you to see what a Social Post will create for each account in the post.
+ """
+ from .resources.social_post_previews import SocialPostPreviewsResourceWithRawResponse
+
+ return SocialPostPreviewsResourceWithRawResponse(self._client.social_post_previews)
+
class AsyncPostForMeWithRawResponse:
_client: AsyncPostForMe
@@ -707,6 +856,45 @@ def social_account_feeds(self) -> social_account_feeds.AsyncSocialAccountFeedsRe
return AsyncSocialAccountFeedsResourceWithRawResponse(self._client.social_account_feeds)
+ @cached_property
+ def webhooks(self) -> webhooks.AsyncWebhooksResourceWithRawResponse:
+ """Webhooks enable you to subscribe to certain events.
+
+ This involves Post for Me making a POST request to the URL of any webhooks you create.
+ Only the events you subscribe to will be sent to your webhook URL.
+
+ ## Payload
+ When an event happens that your webhook is subscribed to, we will make a POST request with the following JSON body
+
+ ```
+ {
+ "event_type": "",
+ "data": {}
+ }
+ ```
+
+ The event_type will be the event that triggered the webhook POST, data will be the resulting entity from the event
+
+ ## Security
+ To verify the POST to your webhook URL is from us we will include a secret in the header "Post-For-Me-Webhook-Secret".
+ When you create a webhook you will receive the secret in the response.
+
+ ## Retries
+ If your server fails to respond with a 2XX code, requests to it will be retried with exponential backoff around 8 times over the course of just over a day.
+ """
+ from .resources.webhooks import AsyncWebhooksResourceWithRawResponse
+
+ return AsyncWebhooksResourceWithRawResponse(self._client.webhooks)
+
+ @cached_property
+ def social_post_previews(self) -> social_post_previews.AsyncSocialPostPreviewsResourceWithRawResponse:
+ """
+ Social Post Previews allow you to see what a Social Post will create for each account in the post.
+ """
+ from .resources.social_post_previews import AsyncSocialPostPreviewsResourceWithRawResponse
+
+ return AsyncSocialPostPreviewsResourceWithRawResponse(self._client.social_post_previews)
+
class PostForMeWithStreamedResponse:
_client: PostForMe
@@ -791,6 +979,45 @@ def social_account_feeds(self) -> social_account_feeds.SocialAccountFeedsResourc
return SocialAccountFeedsResourceWithStreamingResponse(self._client.social_account_feeds)
+ @cached_property
+ def webhooks(self) -> webhooks.WebhooksResourceWithStreamingResponse:
+ """Webhooks enable you to subscribe to certain events.
+
+ This involves Post for Me making a POST request to the URL of any webhooks you create.
+ Only the events you subscribe to will be sent to your webhook URL.
+
+ ## Payload
+ When an event happens that your webhook is subscribed to, we will make a POST request with the following JSON body
+
+ ```
+ {
+ "event_type": "",
+ "data": {}
+ }
+ ```
+
+ The event_type will be the event that triggered the webhook POST, data will be the resulting entity from the event
+
+ ## Security
+ To verify the POST to your webhook URL is from us we will include a secret in the header "Post-For-Me-Webhook-Secret".
+ When you create a webhook you will receive the secret in the response.
+
+ ## Retries
+ If your server fails to respond with a 2XX code, requests to it will be retried with exponential backoff around 8 times over the course of just over a day.
+ """
+ from .resources.webhooks import WebhooksResourceWithStreamingResponse
+
+ return WebhooksResourceWithStreamingResponse(self._client.webhooks)
+
+ @cached_property
+ def social_post_previews(self) -> social_post_previews.SocialPostPreviewsResourceWithStreamingResponse:
+ """
+ Social Post Previews allow you to see what a Social Post will create for each account in the post.
+ """
+ from .resources.social_post_previews import SocialPostPreviewsResourceWithStreamingResponse
+
+ return SocialPostPreviewsResourceWithStreamingResponse(self._client.social_post_previews)
+
class AsyncPostForMeWithStreamedResponse:
_client: AsyncPostForMe
@@ -875,6 +1102,45 @@ def social_account_feeds(self) -> social_account_feeds.AsyncSocialAccountFeedsRe
return AsyncSocialAccountFeedsResourceWithStreamingResponse(self._client.social_account_feeds)
+ @cached_property
+ def webhooks(self) -> webhooks.AsyncWebhooksResourceWithStreamingResponse:
+ """Webhooks enable you to subscribe to certain events.
+
+ This involves Post for Me making a POST request to the URL of any webhooks you create.
+ Only the events you subscribe to will be sent to your webhook URL.
+
+ ## Payload
+ When an event happens that your webhook is subscribed to, we will make a POST request with the following JSON body
+
+ ```
+ {
+ "event_type": "",
+ "data": {}
+ }
+ ```
+
+ The event_type will be the event that triggered the webhook POST, data will be the resulting entity from the event
+
+ ## Security
+ To verify the POST to your webhook URL is from us we will include a secret in the header "Post-For-Me-Webhook-Secret".
+ When you create a webhook you will receive the secret in the response.
+
+ ## Retries
+ If your server fails to respond with a 2XX code, requests to it will be retried with exponential backoff around 8 times over the course of just over a day.
+ """
+ from .resources.webhooks import AsyncWebhooksResourceWithStreamingResponse
+
+ return AsyncWebhooksResourceWithStreamingResponse(self._client.webhooks)
+
+ @cached_property
+ def social_post_previews(self) -> social_post_previews.AsyncSocialPostPreviewsResourceWithStreamingResponse:
+ """
+ Social Post Previews allow you to see what a Social Post will create for each account in the post.
+ """
+ from .resources.social_post_previews import AsyncSocialPostPreviewsResourceWithStreamingResponse
+
+ return AsyncSocialPostPreviewsResourceWithStreamingResponse(self._client.social_post_previews)
+
Client = PostForMe
diff --git a/src/post_for_me/_files.py b/src/post_for_me/_files.py
index cc14c14..76da9e0 100644
--- a/src/post_for_me/_files.py
+++ b/src/post_for_me/_files.py
@@ -3,8 +3,8 @@
import io
import os
import pathlib
-from typing import overload
-from typing_extensions import TypeGuard
+from typing import Sequence, cast, overload
+from typing_extensions import TypeVar, TypeGuard
import anyio
@@ -17,7 +17,9 @@
HttpxFileContent,
HttpxRequestFiles,
)
-from ._utils import is_tuple_t, is_mapping_t, is_sequence_t
+from ._utils import is_list, is_mapping, is_tuple_t, is_mapping_t, is_sequence_t
+
+_T = TypeVar("_T")
def is_base64_file_input(obj: object) -> TypeGuard[Base64FileInput]:
@@ -97,7 +99,7 @@ async def async_to_httpx_files(files: RequestFiles | None) -> HttpxRequestFiles
elif is_sequence_t(files):
files = [(key, await _async_transform_file(file)) for key, file in files]
else:
- raise TypeError("Unexpected file type input {type(files)}, expected mapping or sequence")
+ raise TypeError(f"Unexpected file type input {type(files)}, expected mapping or sequence")
return files
@@ -121,3 +123,51 @@ async def async_read_file_content(file: FileContent) -> HttpxFileContent:
return await anyio.Path(file).read_bytes()
return file
+
+
+def deepcopy_with_paths(item: _T, paths: Sequence[Sequence[str]]) -> _T:
+ """Copy only the containers along the given paths.
+
+ Used to guard against mutation by extract_files without copying the entire structure.
+ Only dicts and lists that lie on a path are copied; everything else
+ is returned by reference.
+
+ For example, given paths=[["foo", "files", "file"]] and the structure:
+ {
+ "foo": {
+ "bar": {"baz": {}},
+ "files": {"file": }
+ }
+ }
+ The root dict, "foo", and "files" are copied (they lie on the path).
+ "bar" and "baz" are returned by reference (off the path).
+ """
+ return _deepcopy_with_paths(item, paths, 0)
+
+
+def _deepcopy_with_paths(item: _T, paths: Sequence[Sequence[str]], index: int) -> _T:
+ if not paths:
+ return item
+ if is_mapping(item):
+ key_to_paths: dict[str, list[Sequence[str]]] = {}
+ for path in paths:
+ if index < len(path):
+ key_to_paths.setdefault(path[index], []).append(path)
+
+ # if no path continues through this mapping, it won't be mutated and copying it is redundant
+ if not key_to_paths:
+ return item
+
+ result = dict(item)
+ for key, subpaths in key_to_paths.items():
+ if key in result:
+ result[key] = _deepcopy_with_paths(result[key], subpaths, index + 1)
+ return cast(_T, result)
+ if is_list(item):
+ array_paths = [path for path in paths if index < len(path) and path[index] == ""]
+
+ # if no path expects a list here, nothing will be mutated inside it - return by reference
+ if not array_paths:
+ return cast(_T, item)
+ return cast(_T, [_deepcopy_with_paths(entry, array_paths, index + 1) for entry in item])
+ return item
diff --git a/src/post_for_me/_models.py b/src/post_for_me/_models.py
index 29070e0..8c5ab26 100644
--- a/src/post_for_me/_models.py
+++ b/src/post_for_me/_models.py
@@ -25,7 +25,9 @@
ClassVar,
Protocol,
Required,
+ Annotated,
ParamSpec,
+ TypeAlias,
TypedDict,
TypeGuard,
final,
@@ -79,7 +81,15 @@
from ._constants import RAW_RESPONSE_HEADER
if TYPE_CHECKING:
+ from pydantic import GetCoreSchemaHandler, ValidatorFunctionWrapHandler
+ from pydantic_core import CoreSchema, core_schema
from pydantic_core.core_schema import ModelField, ModelSchema, LiteralSchema, ModelFieldsSchema
+else:
+ try:
+ from pydantic_core import CoreSchema, core_schema
+ except ImportError:
+ CoreSchema = None
+ core_schema = None
__all__ = ["BaseModel", "GenericModel"]
@@ -396,6 +406,76 @@ def model_dump_json(
)
+class _EagerIterable(list[_T], Generic[_T]):
+ """
+ Accepts any Iterable[T] input (including generators), consumes it
+ eagerly, and validates all items upfront.
+
+ Validation preserves the original container type where possible
+ (e.g. a set[T] stays a set[T]). Serialization (model_dump / JSON)
+ always emits a list — round-tripping through model_dump() will not
+ restore the original container type.
+ """
+
+ @classmethod
+ def __get_pydantic_core_schema__(
+ cls,
+ source_type: Any,
+ handler: GetCoreSchemaHandler,
+ ) -> CoreSchema:
+ (item_type,) = get_args(source_type) or (Any,)
+ item_schema: CoreSchema = handler.generate_schema(item_type)
+ list_of_items_schema: CoreSchema = core_schema.list_schema(item_schema)
+
+ return core_schema.no_info_wrap_validator_function(
+ cls._validate,
+ list_of_items_schema,
+ serialization=core_schema.plain_serializer_function_ser_schema(
+ cls._serialize,
+ info_arg=False,
+ ),
+ )
+
+ @staticmethod
+ def _validate(v: Iterable[_T], handler: "ValidatorFunctionWrapHandler") -> Any:
+ original_type: type[Any] = type(v)
+
+ # Normalize to list so list_schema can validate each item
+ if isinstance(v, list):
+ items: list[_T] = v
+ else:
+ try:
+ items = list(v)
+ except TypeError as e:
+ raise TypeError("Value is not iterable") from e
+
+ # Validate items against the inner schema
+ validated: list[_T] = handler(items)
+
+ # Reconstruct original container type
+ if original_type is list:
+ return validated
+ # str(list) produces the list's repr, not a string built from items,
+ # so skip reconstruction for str and its subclasses.
+ if issubclass(original_type, str):
+ return validated
+ try:
+ return original_type(validated)
+ except (TypeError, ValueError):
+ # If the type cannot be reconstructed, just return the validated list
+ return validated
+
+ @staticmethod
+ def _serialize(v: Iterable[_T]) -> list[_T]:
+ """Always serialize as a list so Pydantic's JSON encoder is happy."""
+ if isinstance(v, list):
+ return v
+ return list(v)
+
+
+EagerIterable: TypeAlias = Annotated[Iterable[_T], _EagerIterable]
+
+
def _construct_field(value: object, field: FieldInfo, key: str) -> object:
if value is None:
return field_get_default(field)
diff --git a/src/post_for_me/_qs.py b/src/post_for_me/_qs.py
index de8c99b..4127c19 100644
--- a/src/post_for_me/_qs.py
+++ b/src/post_for_me/_qs.py
@@ -2,17 +2,13 @@
from typing import Any, List, Tuple, Union, Mapping, TypeVar
from urllib.parse import parse_qs, urlencode
-from typing_extensions import Literal, get_args
+from typing_extensions import get_args
-from ._types import NotGiven, not_given
+from ._types import NotGiven, ArrayFormat, NestedFormat, not_given
from ._utils import flatten
_T = TypeVar("_T")
-
-ArrayFormat = Literal["comma", "repeat", "indices", "brackets"]
-NestedFormat = Literal["dots", "brackets"]
-
PrimitiveData = Union[str, int, float, bool, None]
# this should be Data = Union[PrimitiveData, "List[Data]", "Tuple[Data]", "Mapping[str, Data]"]
# https://github.com/microsoft/pyright/issues/3555
diff --git a/src/post_for_me/_types.py b/src/post_for_me/_types.py
index 0290e44..34f4d44 100644
--- a/src/post_for_me/_types.py
+++ b/src/post_for_me/_types.py
@@ -47,6 +47,9 @@
ModelT = TypeVar("ModelT", bound=pydantic.BaseModel)
_T = TypeVar("_T")
+ArrayFormat = Literal["comma", "repeat", "indices", "brackets"]
+NestedFormat = Literal["dots", "brackets"]
+
# Approximates httpx internal ProxiesTypes and RequestFiles types
# while adding support for `PathLike` instances
diff --git a/src/post_for_me/_utils/__init__.py b/src/post_for_me/_utils/__init__.py
index 10cb66d..1c090e5 100644
--- a/src/post_for_me/_utils/__init__.py
+++ b/src/post_for_me/_utils/__init__.py
@@ -24,7 +24,6 @@
coerce_integer as coerce_integer,
file_from_path as file_from_path,
strip_not_given as strip_not_given,
- deepcopy_minimal as deepcopy_minimal,
get_async_library as get_async_library,
maybe_coerce_float as maybe_coerce_float,
get_required_header as get_required_header,
diff --git a/src/post_for_me/_utils/_utils.py b/src/post_for_me/_utils/_utils.py
index 63b8cd6..199cd23 100644
--- a/src/post_for_me/_utils/_utils.py
+++ b/src/post_for_me/_utils/_utils.py
@@ -17,11 +17,11 @@
)
from pathlib import Path
from datetime import date, datetime
-from typing_extensions import TypeGuard
+from typing_extensions import TypeGuard, get_args
import sniffio
-from .._types import Omit, NotGiven, FileTypes, HeadersLike
+from .._types import Omit, NotGiven, FileTypes, ArrayFormat, HeadersLike
_T = TypeVar("_T")
_TupleT = TypeVar("_TupleT", bound=Tuple[object, ...])
@@ -40,25 +40,45 @@ def extract_files(
query: Mapping[str, object],
*,
paths: Sequence[Sequence[str]],
+ array_format: ArrayFormat = "brackets",
) -> list[tuple[str, FileTypes]]:
"""Recursively extract files from the given dictionary based on specified paths.
A path may look like this ['foo', 'files', '', 'data'].
+ ``array_format`` controls how ```` segments contribute to the emitted
+ field name. Supported values: ``"brackets"`` (``foo[]``), ``"repeat"`` and
+ ``"comma"`` (``foo``), ``"indices"`` (``foo[0]``, ``foo[1]``).
+
Note: this mutates the given dictionary.
"""
files: list[tuple[str, FileTypes]] = []
for path in paths:
- files.extend(_extract_items(query, path, index=0, flattened_key=None))
+ files.extend(_extract_items(query, path, index=0, flattened_key=None, array_format=array_format))
return files
+def _array_suffix(array_format: ArrayFormat, array_index: int) -> str:
+ if array_format == "brackets":
+ return "[]"
+ if array_format == "indices":
+ return f"[{array_index}]"
+ if array_format == "repeat" or array_format == "comma":
+ # Both repeat the bare field name for each file part; there is no
+ # meaningful way to comma-join binary parts.
+ return ""
+ raise NotImplementedError(
+ f"Unknown array_format value: {array_format}, choose from {', '.join(get_args(ArrayFormat))}"
+ )
+
+
def _extract_items(
obj: object,
path: Sequence[str],
*,
index: int,
flattened_key: str | None,
+ array_format: ArrayFormat,
) -> list[tuple[str, FileTypes]]:
try:
key = path[index]
@@ -75,9 +95,11 @@ def _extract_items(
if is_list(obj):
files: list[tuple[str, FileTypes]] = []
- for entry in obj:
- assert_is_file_content(entry, key=flattened_key + "[]" if flattened_key else "")
- files.append((flattened_key + "[]", cast(FileTypes, entry)))
+ for array_index, entry in enumerate(obj):
+ suffix = _array_suffix(array_format, array_index)
+ emitted_key = (flattened_key + suffix) if flattened_key else suffix
+ assert_is_file_content(entry, key=emitted_key)
+ files.append((emitted_key, cast(FileTypes, entry)))
return files
assert_is_file_content(obj, key=flattened_key)
@@ -106,6 +128,7 @@ def _extract_items(
path,
index=index,
flattened_key=flattened_key,
+ array_format=array_format,
)
elif is_list(obj):
if key != "":
@@ -117,9 +140,12 @@ def _extract_items(
item,
path,
index=index,
- flattened_key=flattened_key + "[]" if flattened_key is not None else "[]",
+ flattened_key=(
+ (flattened_key if flattened_key is not None else "") + _array_suffix(array_format, array_index)
+ ),
+ array_format=array_format,
)
- for item in obj
+ for array_index, item in enumerate(obj)
]
)
@@ -177,21 +203,6 @@ def is_iterable(obj: object) -> TypeGuard[Iterable[object]]:
return isinstance(obj, Iterable)
-def deepcopy_minimal(item: _T) -> _T:
- """Minimal reimplementation of copy.deepcopy() that will only copy certain object types:
-
- - mappings, e.g. `dict`
- - list
-
- This is done for performance reasons.
- """
- if is_mapping(item):
- return cast(_T, {k: deepcopy_minimal(v) for k, v in item.items()})
- if is_list(item):
- return cast(_T, [deepcopy_minimal(entry) for entry in item])
- return item
-
-
# copied from https://github.com/Rapptz/RoboDanny
def human_join(seq: Sequence[str], *, delim: str = ", ", final: str = "or") -> str:
size = len(seq)
diff --git a/src/post_for_me/_version.py b/src/post_for_me/_version.py
index 207906a..5b74e50 100644
--- a/src/post_for_me/_version.py
+++ b/src/post_for_me/_version.py
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
__title__ = "post_for_me"
-__version__ = "1.15.0" # x-release-please-version
+__version__ = "1.16.0" # x-release-please-version
diff --git a/src/post_for_me/resources/__init__.py b/src/post_for_me/resources/__init__.py
index 46085d5..c99cec3 100644
--- a/src/post_for_me/resources/__init__.py
+++ b/src/post_for_me/resources/__init__.py
@@ -8,6 +8,14 @@
MediaResourceWithStreamingResponse,
AsyncMediaResourceWithStreamingResponse,
)
+from .webhooks import (
+ WebhooksResource,
+ AsyncWebhooksResource,
+ WebhooksResourceWithRawResponse,
+ AsyncWebhooksResourceWithRawResponse,
+ WebhooksResourceWithStreamingResponse,
+ AsyncWebhooksResourceWithStreamingResponse,
+)
from .social_posts import (
SocialPostsResource,
AsyncSocialPostsResource,
@@ -40,6 +48,14 @@
SocialAccountFeedsResourceWithStreamingResponse,
AsyncSocialAccountFeedsResourceWithStreamingResponse,
)
+from .social_post_previews import (
+ SocialPostPreviewsResource,
+ AsyncSocialPostPreviewsResource,
+ SocialPostPreviewsResourceWithRawResponse,
+ AsyncSocialPostPreviewsResourceWithRawResponse,
+ SocialPostPreviewsResourceWithStreamingResponse,
+ AsyncSocialPostPreviewsResourceWithStreamingResponse,
+)
__all__ = [
"MediaResource",
@@ -72,4 +88,16 @@
"AsyncSocialAccountFeedsResourceWithRawResponse",
"SocialAccountFeedsResourceWithStreamingResponse",
"AsyncSocialAccountFeedsResourceWithStreamingResponse",
+ "WebhooksResource",
+ "AsyncWebhooksResource",
+ "WebhooksResourceWithRawResponse",
+ "AsyncWebhooksResourceWithRawResponse",
+ "WebhooksResourceWithStreamingResponse",
+ "AsyncWebhooksResourceWithStreamingResponse",
+ "SocialPostPreviewsResource",
+ "AsyncSocialPostPreviewsResource",
+ "SocialPostPreviewsResourceWithRawResponse",
+ "AsyncSocialPostPreviewsResourceWithRawResponse",
+ "SocialPostPreviewsResourceWithStreamingResponse",
+ "AsyncSocialPostPreviewsResourceWithStreamingResponse",
]
diff --git a/src/post_for_me/resources/social_accounts.py b/src/post_for_me/resources/social_accounts.py
index 923cb40..5208c67 100644
--- a/src/post_for_me/resources/social_accounts.py
+++ b/src/post_for_me/resources/social_accounts.py
@@ -27,6 +27,7 @@
from .._base_client import make_request_options
from ..types.social_account import SocialAccount
from ..types.social_account_list_response import SocialAccountListResponse
+from ..types.social_account_metadata_param import SocialAccountMetadataParam
from ..types.social_account_disconnect_response import SocialAccountDisconnectResponse
from ..types.social_account_create_auth_url_response import SocialAccountCreateAuthURLResponse
@@ -78,7 +79,7 @@ def create(
],
user_id: str,
external_id: Optional[str] | Omit = omit,
- metadata: object | Omit = omit,
+ metadata: SocialAccountMetadataParam | Omit = omit,
refresh_token: Optional[str] | Omit = omit,
refresh_token_expires_at: Union[str, datetime, None] | Omit = omit,
username: Optional[str] | Omit = omit,
@@ -435,7 +436,7 @@ async def create(
],
user_id: str,
external_id: Optional[str] | Omit = omit,
- metadata: object | Omit = omit,
+ metadata: SocialAccountMetadataParam | Omit = omit,
refresh_token: Optional[str] | Omit = omit,
refresh_token_expires_at: Union[str, datetime, None] | Omit = omit,
username: Optional[str] | Omit = omit,
diff --git a/src/post_for_me/resources/social_post_previews.py b/src/post_for_me/resources/social_post_previews.py
new file mode 100644
index 0000000..97b1375
--- /dev/null
+++ b/src/post_for_me/resources/social_post_previews.py
@@ -0,0 +1,224 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Iterable, Optional
+
+import httpx
+
+from ..types import social_post_preview_create_params
+from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from .._utils import maybe_transform, async_maybe_transform
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
+from .._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from .._base_client import make_request_options
+from ..types.social_post_media_param import SocialPostMediaParam
+from ..types.account_configuration_param import AccountConfigurationParam
+from ..types.platform_configurations_dto_param import PlatformConfigurationsDtoParam
+from ..types.social_post_preview_create_response import SocialPostPreviewCreateResponse
+
+__all__ = ["SocialPostPreviewsResource", "AsyncSocialPostPreviewsResource"]
+
+
+class SocialPostPreviewsResource(SyncAPIResource):
+ """
+ Social Post Previews allow you to see what a Social Post will create for each account in the post.
+ """
+
+ @cached_property
+ def with_raw_response(self) -> SocialPostPreviewsResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/DayMoonDevelopment/post-for-me-python#accessing-raw-response-data-eg-headers
+ """
+ return SocialPostPreviewsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> SocialPostPreviewsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/DayMoonDevelopment/post-for-me-python#with_streaming_response
+ """
+ return SocialPostPreviewsResourceWithStreamingResponse(self)
+
+ def create(
+ self,
+ *,
+ caption: str,
+ preview_social_accounts: Iterable[social_post_preview_create_params.PreviewSocialAccount],
+ account_configurations: Optional[Iterable[AccountConfigurationParam]] | Omit = omit,
+ media: Optional[Iterable[SocialPostMediaParam]] | Omit = omit,
+ platform_configurations: Optional[PlatformConfigurationsDtoParam] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SocialPostPreviewCreateResponse:
+ """
+ Create Post Previews
+
+ Args:
+ caption: Caption text for the post
+
+ preview_social_accounts: Array of social accounts. Can preview non connected accounts, just specify a
+ random ID
+
+ account_configurations: Account-specific configurations for the post
+
+ media: Array of media URLs associated with the post
+
+ platform_configurations: Platform-specific configurations for the post
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._post(
+ "/v1/social-post-previews",
+ body=maybe_transform(
+ {
+ "caption": caption,
+ "preview_social_accounts": preview_social_accounts,
+ "account_configurations": account_configurations,
+ "media": media,
+ "platform_configurations": platform_configurations,
+ },
+ social_post_preview_create_params.SocialPostPreviewCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SocialPostPreviewCreateResponse,
+ )
+
+
+class AsyncSocialPostPreviewsResource(AsyncAPIResource):
+ """
+ Social Post Previews allow you to see what a Social Post will create for each account in the post.
+ """
+
+ @cached_property
+ def with_raw_response(self) -> AsyncSocialPostPreviewsResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/DayMoonDevelopment/post-for-me-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncSocialPostPreviewsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncSocialPostPreviewsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/DayMoonDevelopment/post-for-me-python#with_streaming_response
+ """
+ return AsyncSocialPostPreviewsResourceWithStreamingResponse(self)
+
+ async def create(
+ self,
+ *,
+ caption: str,
+ preview_social_accounts: Iterable[social_post_preview_create_params.PreviewSocialAccount],
+ account_configurations: Optional[Iterable[AccountConfigurationParam]] | Omit = omit,
+ media: Optional[Iterable[SocialPostMediaParam]] | Omit = omit,
+ platform_configurations: Optional[PlatformConfigurationsDtoParam] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SocialPostPreviewCreateResponse:
+ """
+ Create Post Previews
+
+ Args:
+ caption: Caption text for the post
+
+ preview_social_accounts: Array of social accounts. Can preview non connected accounts, just specify a
+ random ID
+
+ account_configurations: Account-specific configurations for the post
+
+ media: Array of media URLs associated with the post
+
+ platform_configurations: Platform-specific configurations for the post
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._post(
+ "/v1/social-post-previews",
+ body=await async_maybe_transform(
+ {
+ "caption": caption,
+ "preview_social_accounts": preview_social_accounts,
+ "account_configurations": account_configurations,
+ "media": media,
+ "platform_configurations": platform_configurations,
+ },
+ social_post_preview_create_params.SocialPostPreviewCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SocialPostPreviewCreateResponse,
+ )
+
+
+class SocialPostPreviewsResourceWithRawResponse:
+ def __init__(self, social_post_previews: SocialPostPreviewsResource) -> None:
+ self._social_post_previews = social_post_previews
+
+ self.create = to_raw_response_wrapper(
+ social_post_previews.create,
+ )
+
+
+class AsyncSocialPostPreviewsResourceWithRawResponse:
+ def __init__(self, social_post_previews: AsyncSocialPostPreviewsResource) -> None:
+ self._social_post_previews = social_post_previews
+
+ self.create = async_to_raw_response_wrapper(
+ social_post_previews.create,
+ )
+
+
+class SocialPostPreviewsResourceWithStreamingResponse:
+ def __init__(self, social_post_previews: SocialPostPreviewsResource) -> None:
+ self._social_post_previews = social_post_previews
+
+ self.create = to_streamed_response_wrapper(
+ social_post_previews.create,
+ )
+
+
+class AsyncSocialPostPreviewsResourceWithStreamingResponse:
+ def __init__(self, social_post_previews: AsyncSocialPostPreviewsResource) -> None:
+ self._social_post_previews = social_post_previews
+
+ self.create = async_to_streamed_response_wrapper(
+ social_post_previews.create,
+ )
diff --git a/src/post_for_me/resources/social_posts.py b/src/post_for_me/resources/social_posts.py
index 9b111ce..b737ea7 100644
--- a/src/post_for_me/resources/social_posts.py
+++ b/src/post_for_me/resources/social_posts.py
@@ -25,8 +25,10 @@
)
from .._base_client import make_request_options
from ..types.social_post import SocialPost
+from ..types.delete_entity_response import DeleteEntityResponse
+from ..types.social_post_media_param import SocialPostMediaParam
from ..types.social_post_list_response import SocialPostListResponse
-from ..types.social_post_delete_response import SocialPostDeleteResponse
+from ..types.account_configuration_param import AccountConfigurationParam
from ..types.platform_configurations_dto_param import PlatformConfigurationsDtoParam
__all__ = ["SocialPostsResource", "AsyncSocialPostsResource"]
@@ -67,10 +69,10 @@ def create(
*,
caption: str,
social_accounts: SequenceNotStr[str],
- account_configurations: Optional[Iterable[social_post_create_params.AccountConfiguration]] | Omit = omit,
+ account_configurations: Optional[Iterable[AccountConfigurationParam]] | Omit = omit,
external_id: Optional[str] | Omit = omit,
is_draft: Optional[bool] | Omit = omit,
- media: Optional[Iterable[social_post_create_params.Media]] | Omit = omit,
+ media: Optional[Iterable[SocialPostMediaParam]] | Omit = omit,
platform_configurations: Optional[PlatformConfigurationsDtoParam] | Omit = omit,
scheduled_at: Union[str, datetime, None] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -170,10 +172,10 @@ def update(
*,
caption: str,
social_accounts: SequenceNotStr[str],
- account_configurations: Optional[Iterable[social_post_update_params.AccountConfiguration]] | Omit = omit,
+ account_configurations: Optional[Iterable[AccountConfigurationParam]] | Omit = omit,
external_id: Optional[str] | Omit = omit,
is_draft: Optional[bool] | Omit = omit,
- media: Optional[Iterable[social_post_update_params.Media]] | Omit = omit,
+ media: Optional[Iterable[SocialPostMediaParam]] | Omit = omit,
platform_configurations: Optional[PlatformConfigurationsDtoParam] | Omit = omit,
scheduled_at: Union[str, datetime, None] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -311,7 +313,7 @@ def delete(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> SocialPostDeleteResponse:
+ ) -> DeleteEntityResponse:
"""
Delete Post
@@ -331,7 +333,7 @@ def delete(
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
- cast_to=SocialPostDeleteResponse,
+ cast_to=DeleteEntityResponse,
)
@@ -370,10 +372,10 @@ async def create(
*,
caption: str,
social_accounts: SequenceNotStr[str],
- account_configurations: Optional[Iterable[social_post_create_params.AccountConfiguration]] | Omit = omit,
+ account_configurations: Optional[Iterable[AccountConfigurationParam]] | Omit = omit,
external_id: Optional[str] | Omit = omit,
is_draft: Optional[bool] | Omit = omit,
- media: Optional[Iterable[social_post_create_params.Media]] | Omit = omit,
+ media: Optional[Iterable[SocialPostMediaParam]] | Omit = omit,
platform_configurations: Optional[PlatformConfigurationsDtoParam] | Omit = omit,
scheduled_at: Union[str, datetime, None] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -473,10 +475,10 @@ async def update(
*,
caption: str,
social_accounts: SequenceNotStr[str],
- account_configurations: Optional[Iterable[social_post_update_params.AccountConfiguration]] | Omit = omit,
+ account_configurations: Optional[Iterable[AccountConfigurationParam]] | Omit = omit,
external_id: Optional[str] | Omit = omit,
is_draft: Optional[bool] | Omit = omit,
- media: Optional[Iterable[social_post_update_params.Media]] | Omit = omit,
+ media: Optional[Iterable[SocialPostMediaParam]] | Omit = omit,
platform_configurations: Optional[PlatformConfigurationsDtoParam] | Omit = omit,
scheduled_at: Union[str, datetime, None] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -614,7 +616,7 @@ async def delete(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> SocialPostDeleteResponse:
+ ) -> DeleteEntityResponse:
"""
Delete Post
@@ -634,7 +636,7 @@ async def delete(
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
- cast_to=SocialPostDeleteResponse,
+ cast_to=DeleteEntityResponse,
)
diff --git a/src/post_for_me/resources/webhooks.py b/src/post_for_me/resources/webhooks.py
new file mode 100644
index 0000000..43c4ee6
--- /dev/null
+++ b/src/post_for_me/resources/webhooks.py
@@ -0,0 +1,672 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List
+from typing_extensions import Literal
+
+import httpx
+
+from ..types import webhook_list_params, webhook_create_params, webhook_update_params
+from .._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given
+from .._utils import path_template, maybe_transform, async_maybe_transform
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
+from .._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from .._base_client import make_request_options
+from ..types.webhook import Webhook
+from ..types.webhook_list_response import WebhookListResponse
+from ..types.delete_entity_response import DeleteEntityResponse
+
+__all__ = ["WebhooksResource", "AsyncWebhooksResource"]
+
+
+class WebhooksResource(SyncAPIResource):
+ """Webhooks enable you to subscribe to certain events.
+
+ This involves Post for Me making a POST request to the URL of any webhooks you create.
+ Only the events you subscribe to will be sent to your webhook URL.
+
+ ## Payload
+ When an event happens that your webhook is subscribed to, we will make a POST request with the following JSON body
+
+ ```
+ {
+ "event_type": "",
+ "data": {}
+ }
+ ```
+
+ The event_type will be the event that triggered the webhook POST, data will be the resulting entity from the event
+
+ ## Security
+ To verify the POST to your webhook URL is from us we will include a secret in the header "Post-For-Me-Webhook-Secret".
+ When you create a webhook you will receive the secret in the response.
+
+ ## Retries
+ If your server fails to respond with a 2XX code, requests to it will be retried with exponential backoff around 8 times over the course of just over a day.
+ """
+
+ @cached_property
+ def with_raw_response(self) -> WebhooksResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/DayMoonDevelopment/post-for-me-python#accessing-raw-response-data-eg-headers
+ """
+ return WebhooksResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> WebhooksResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/DayMoonDevelopment/post-for-me-python#with_streaming_response
+ """
+ return WebhooksResourceWithStreamingResponse(self)
+
+ def create(
+ self,
+ *,
+ event_types: List[
+ Literal[
+ "social.post.created",
+ "social.post.updated",
+ "social.post.deleted",
+ "social.post.result.created",
+ "social.account.created",
+ "social.account.updated",
+ ]
+ ],
+ url: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Webhook:
+ """
+ Create Webhook
+
+ Args:
+ event_types: List of events the webhook will recieve
+
+ url: Public url to recieve event data
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._post(
+ "/v1/webhooks",
+ body=maybe_transform(
+ {
+ "event_types": event_types,
+ "url": url,
+ },
+ webhook_create_params.WebhookCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Webhook,
+ )
+
+ def retrieve(
+ self,
+ id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Webhook:
+ """
+ Get webhook by ID
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return self._get(
+ path_template("/v1/webhooks/{id}", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Webhook,
+ )
+
+ def update(
+ self,
+ id: str,
+ *,
+ event_types: List[
+ Literal[
+ "social.post.created",
+ "social.post.updated",
+ "social.post.deleted",
+ "social.post.result.created",
+ "social.account.created",
+ "social.account.updated",
+ ]
+ ]
+ | Omit = omit,
+ url: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Webhook:
+ """
+ Update Webhook
+
+ Args:
+ event_types: List of events the webhook will recieve
+
+ url: Public url to recieve event data
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return self._patch(
+ path_template("/v1/webhooks/{id}", id=id),
+ body=maybe_transform(
+ {
+ "event_types": event_types,
+ "url": url,
+ },
+ webhook_update_params.WebhookUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Webhook,
+ )
+
+ def list(
+ self,
+ *,
+ id: SequenceNotStr[str] | Omit = omit,
+ event_type: SequenceNotStr[str] | Omit = omit,
+ limit: float | Omit = omit,
+ offset: float | Omit = omit,
+ url: SequenceNotStr[str] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> WebhookListResponse:
+ """
+ Get a paginated result for webhooks based on the applied filters
+
+ Args:
+ id: Filter by id(s). Multiple values imply OR logic (e.g.,
+ ?id=wbh_xxxxxx&id=wbh_yyyyyy).
+
+ event_type: Filter by event type(s). Multiple values imply OR logic (e.g.,
+ ?event_type=social.post.created&event_type=social.post.updated).
+
+ limit: Number of items to return
+
+ offset: Number of items to skip
+
+ url: Filter by url(s). Multiple values imply OR logic (e.g.,
+ ?url=https://example.com&url=https://postforme.dev).
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._get(
+ "/v1/webhooks",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "id": id,
+ "event_type": event_type,
+ "limit": limit,
+ "offset": offset,
+ "url": url,
+ },
+ webhook_list_params.WebhookListParams,
+ ),
+ ),
+ cast_to=WebhookListResponse,
+ )
+
+ def delete(
+ self,
+ id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> DeleteEntityResponse:
+ """
+ Delete Webhook
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return self._delete(
+ path_template("/v1/webhooks/{id}", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=DeleteEntityResponse,
+ )
+
+
+class AsyncWebhooksResource(AsyncAPIResource):
+ """Webhooks enable you to subscribe to certain events.
+
+ This involves Post for Me making a POST request to the URL of any webhooks you create.
+ Only the events you subscribe to will be sent to your webhook URL.
+
+ ## Payload
+ When an event happens that your webhook is subscribed to, we will make a POST request with the following JSON body
+
+ ```
+ {
+ "event_type": "",
+ "data": {}
+ }
+ ```
+
+ The event_type will be the event that triggered the webhook POST, data will be the resulting entity from the event
+
+ ## Security
+ To verify the POST to your webhook URL is from us we will include a secret in the header "Post-For-Me-Webhook-Secret".
+ When you create a webhook you will receive the secret in the response.
+
+ ## Retries
+ If your server fails to respond with a 2XX code, requests to it will be retried with exponential backoff around 8 times over the course of just over a day.
+ """
+
+ @cached_property
+ def with_raw_response(self) -> AsyncWebhooksResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/DayMoonDevelopment/post-for-me-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncWebhooksResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncWebhooksResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/DayMoonDevelopment/post-for-me-python#with_streaming_response
+ """
+ return AsyncWebhooksResourceWithStreamingResponse(self)
+
+ async def create(
+ self,
+ *,
+ event_types: List[
+ Literal[
+ "social.post.created",
+ "social.post.updated",
+ "social.post.deleted",
+ "social.post.result.created",
+ "social.account.created",
+ "social.account.updated",
+ ]
+ ],
+ url: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Webhook:
+ """
+ Create Webhook
+
+ Args:
+ event_types: List of events the webhook will recieve
+
+ url: Public url to recieve event data
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._post(
+ "/v1/webhooks",
+ body=await async_maybe_transform(
+ {
+ "event_types": event_types,
+ "url": url,
+ },
+ webhook_create_params.WebhookCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Webhook,
+ )
+
+ async def retrieve(
+ self,
+ id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Webhook:
+ """
+ Get webhook by ID
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return await self._get(
+ path_template("/v1/webhooks/{id}", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Webhook,
+ )
+
+ async def update(
+ self,
+ id: str,
+ *,
+ event_types: List[
+ Literal[
+ "social.post.created",
+ "social.post.updated",
+ "social.post.deleted",
+ "social.post.result.created",
+ "social.account.created",
+ "social.account.updated",
+ ]
+ ]
+ | Omit = omit,
+ url: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Webhook:
+ """
+ Update Webhook
+
+ Args:
+ event_types: List of events the webhook will recieve
+
+ url: Public url to recieve event data
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return await self._patch(
+ path_template("/v1/webhooks/{id}", id=id),
+ body=await async_maybe_transform(
+ {
+ "event_types": event_types,
+ "url": url,
+ },
+ webhook_update_params.WebhookUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Webhook,
+ )
+
+ async def list(
+ self,
+ *,
+ id: SequenceNotStr[str] | Omit = omit,
+ event_type: SequenceNotStr[str] | Omit = omit,
+ limit: float | Omit = omit,
+ offset: float | Omit = omit,
+ url: SequenceNotStr[str] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> WebhookListResponse:
+ """
+ Get a paginated result for webhooks based on the applied filters
+
+ Args:
+ id: Filter by id(s). Multiple values imply OR logic (e.g.,
+ ?id=wbh_xxxxxx&id=wbh_yyyyyy).
+
+ event_type: Filter by event type(s). Multiple values imply OR logic (e.g.,
+ ?event_type=social.post.created&event_type=social.post.updated).
+
+ limit: Number of items to return
+
+ offset: Number of items to skip
+
+ url: Filter by url(s). Multiple values imply OR logic (e.g.,
+ ?url=https://example.com&url=https://postforme.dev).
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._get(
+ "/v1/webhooks",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {
+ "id": id,
+ "event_type": event_type,
+ "limit": limit,
+ "offset": offset,
+ "url": url,
+ },
+ webhook_list_params.WebhookListParams,
+ ),
+ ),
+ cast_to=WebhookListResponse,
+ )
+
+ async def delete(
+ self,
+ id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> DeleteEntityResponse:
+ """
+ Delete Webhook
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return await self._delete(
+ path_template("/v1/webhooks/{id}", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=DeleteEntityResponse,
+ )
+
+
+class WebhooksResourceWithRawResponse:
+ def __init__(self, webhooks: WebhooksResource) -> None:
+ self._webhooks = webhooks
+
+ self.create = to_raw_response_wrapper(
+ webhooks.create,
+ )
+ self.retrieve = to_raw_response_wrapper(
+ webhooks.retrieve,
+ )
+ self.update = to_raw_response_wrapper(
+ webhooks.update,
+ )
+ self.list = to_raw_response_wrapper(
+ webhooks.list,
+ )
+ self.delete = to_raw_response_wrapper(
+ webhooks.delete,
+ )
+
+
+class AsyncWebhooksResourceWithRawResponse:
+ def __init__(self, webhooks: AsyncWebhooksResource) -> None:
+ self._webhooks = webhooks
+
+ self.create = async_to_raw_response_wrapper(
+ webhooks.create,
+ )
+ self.retrieve = async_to_raw_response_wrapper(
+ webhooks.retrieve,
+ )
+ self.update = async_to_raw_response_wrapper(
+ webhooks.update,
+ )
+ self.list = async_to_raw_response_wrapper(
+ webhooks.list,
+ )
+ self.delete = async_to_raw_response_wrapper(
+ webhooks.delete,
+ )
+
+
+class WebhooksResourceWithStreamingResponse:
+ def __init__(self, webhooks: WebhooksResource) -> None:
+ self._webhooks = webhooks
+
+ self.create = to_streamed_response_wrapper(
+ webhooks.create,
+ )
+ self.retrieve = to_streamed_response_wrapper(
+ webhooks.retrieve,
+ )
+ self.update = to_streamed_response_wrapper(
+ webhooks.update,
+ )
+ self.list = to_streamed_response_wrapper(
+ webhooks.list,
+ )
+ self.delete = to_streamed_response_wrapper(
+ webhooks.delete,
+ )
+
+
+class AsyncWebhooksResourceWithStreamingResponse:
+ def __init__(self, webhooks: AsyncWebhooksResource) -> None:
+ self._webhooks = webhooks
+
+ self.create = async_to_streamed_response_wrapper(
+ webhooks.create,
+ )
+ self.retrieve = async_to_streamed_response_wrapper(
+ webhooks.retrieve,
+ )
+ self.update = async_to_streamed_response_wrapper(
+ webhooks.update,
+ )
+ self.list = async_to_streamed_response_wrapper(
+ webhooks.list,
+ )
+ self.delete = async_to_streamed_response_wrapper(
+ webhooks.delete,
+ )
diff --git a/src/post_for_me/types/__init__.py b/src/post_for_me/types/__init__.py
index 5987569..062d8a6 100644
--- a/src/post_for_me/types/__init__.py
+++ b/src/post_for_me/types/__init__.py
@@ -2,12 +2,25 @@
from __future__ import annotations
+from .webhook import Webhook as Webhook
from .social_post import SocialPost as SocialPost
+from .twitter_poll import TwitterPoll as TwitterPoll
from .platform_post import PlatformPost as PlatformPost
from .social_account import SocialAccount as SocialAccount
+from .social_post_media import SocialPostMedia as SocialPostMedia
from .social_post_result import SocialPostResult as SocialPostResult
+from .twitter_poll_param import TwitterPollParam as TwitterPollParam
+from .social_post_preview import SocialPostPreview as SocialPostPreview
+from .webhook_list_params import WebhookListParams as WebhookListParams
from .tiktok_configuration import TiktokConfiguration as TiktokConfiguration
+from .account_configuration import AccountConfiguration as AccountConfiguration
+from .webhook_create_params import WebhookCreateParams as WebhookCreateParams
+from .webhook_list_response import WebhookListResponse as WebhookListResponse
+from .webhook_update_params import WebhookUpdateParams as WebhookUpdateParams
+from .delete_entity_response import DeleteEntityResponse as DeleteEntityResponse
from .social_post_list_params import SocialPostListParams as SocialPostListParams
+from .social_post_media_param import SocialPostMediaParam as SocialPostMediaParam
+from .pinterest_metrics_window import PinterestMetricsWindow as PinterestMetricsWindow
from .bluesky_configuration_dto import BlueskyConfigurationDto as BlueskyConfigurationDto
from .social_post_create_params import SocialPostCreateParams as SocialPostCreateParams
from .social_post_list_response import SocialPostListResponse as SocialPostListResponse
@@ -19,19 +32,23 @@
from .linkedin_configuration_dto import LinkedinConfigurationDto as LinkedinConfigurationDto
from .social_account_list_params import SocialAccountListParams as SocialAccountListParams
from .tiktok_configuration_param import TiktokConfigurationParam as TiktokConfigurationParam
+from .youtube_post_platform_data import YoutubePostPlatformData as YoutubePostPlatformData
+from .account_configuration_param import AccountConfigurationParam as AccountConfigurationParam
from .instagram_configuration_dto import InstagramConfigurationDto as InstagramConfigurationDto
from .pinterest_configuration_dto import PinterestConfigurationDto as PinterestConfigurationDto
from .platform_configurations_dto import PlatformConfigurationsDto as PlatformConfigurationsDto
-from .social_post_delete_response import SocialPostDeleteResponse as SocialPostDeleteResponse
from .social_account_create_params import SocialAccountCreateParams as SocialAccountCreateParams
from .social_account_list_response import SocialAccountListResponse as SocialAccountListResponse
from .social_account_update_params import SocialAccountUpdateParams as SocialAccountUpdateParams
+from .social_account_metadata_param import SocialAccountMetadataParam as SocialAccountMetadataParam
+from .facebook_video_retention_graph import FacebookVideoRetentionGraph as FacebookVideoRetentionGraph
from .social_post_result_list_params import SocialPostResultListParams as SocialPostResultListParams
from .bluesky_configuration_dto_param import BlueskyConfigurationDtoParam as BlueskyConfigurationDtoParam
from .social_account_feed_list_params import SocialAccountFeedListParams as SocialAccountFeedListParams
from .threads_configuration_dto_param import ThreadsConfigurationDtoParam as ThreadsConfigurationDtoParam
from .twitter_configuration_dto_param import TwitterConfigurationDtoParam as TwitterConfigurationDtoParam
from .youtube_configuration_dto_param import YoutubeConfigurationDtoParam as YoutubeConfigurationDtoParam
+from .facebook_activity_by_action_type import FacebookActivityByActionType as FacebookActivityByActionType
from .facebook_configuration_dto_param import FacebookConfigurationDtoParam as FacebookConfigurationDtoParam
from .linkedin_configuration_dto_param import LinkedinConfigurationDtoParam as LinkedinConfigurationDtoParam
from .media_create_upload_url_response import MediaCreateUploadURLResponse as MediaCreateUploadURLResponse
@@ -40,8 +57,16 @@
from .pinterest_configuration_dto_param import PinterestConfigurationDtoParam as PinterestConfigurationDtoParam
from .platform_configurations_dto_param import PlatformConfigurationsDtoParam as PlatformConfigurationsDtoParam
from .social_account_feed_list_response import SocialAccountFeedListResponse as SocialAccountFeedListResponse
+from .social_post_preview_create_params import SocialPostPreviewCreateParams as SocialPostPreviewCreateParams
from .social_account_disconnect_response import SocialAccountDisconnectResponse as SocialAccountDisconnectResponse
+from .social_post_preview_create_response import SocialPostPreviewCreateResponse as SocialPostPreviewCreateResponse
from .social_account_create_auth_url_params import SocialAccountCreateAuthURLParams as SocialAccountCreateAuthURLParams
+from .facebook_video_view_time_by_demographic import (
+ FacebookVideoViewTimeByDemographic as FacebookVideoViewTimeByDemographic,
+)
from .social_account_create_auth_url_response import (
SocialAccountCreateAuthURLResponse as SocialAccountCreateAuthURLResponse,
)
+from .tiktok_business_video_metric_percentage import (
+ TiktokBusinessVideoMetricPercentage as TiktokBusinessVideoMetricPercentage,
+)
diff --git a/src/post_for_me/types/account_configuration.py b/src/post_for_me/types/account_configuration.py
new file mode 100644
index 0000000..c45866f
--- /dev/null
+++ b/src/post_for_me/types/account_configuration.py
@@ -0,0 +1,117 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from .._models import BaseModel
+from .twitter_poll import TwitterPoll
+from .social_post_media import SocialPostMedia
+
+__all__ = ["AccountConfiguration", "Configuration"]
+
+
+class Configuration(BaseModel):
+ """Configuration for the social account"""
+
+ allow_comment: Optional[bool] = None
+ """Allow comments on TikTok"""
+
+ allow_duet: Optional[bool] = None
+ """Allow duets on TikTok"""
+
+ allow_stitch: Optional[bool] = None
+ """Allow stitch on TikTok"""
+
+ auto_add_music: Optional[bool] = None
+ """Will automatically add music to photo posts on TikTok"""
+
+ board_ids: Optional[List[str]] = None
+ """Pinterest board IDs"""
+
+ caption: Optional[object] = None
+ """Overrides the `caption` from the post"""
+
+ collaborators: Optional[List[List[object]]] = None
+ """
+ List of page ids or users to invite as collaborators for a Video Reel (Instagram
+ and Facebook)
+ """
+
+ community_id: Optional[str] = None
+ """Id of the twitter community to post to"""
+
+ disclose_branded_content: Optional[bool] = None
+ """Disclose branded content on TikTok"""
+
+ disclose_your_brand: Optional[bool] = None
+ """Disclose your brand on TikTok"""
+
+ is_ai_generated: Optional[bool] = None
+ """Flag content as AI generated on TikTok"""
+
+ is_draft: Optional[bool] = None
+ """
+ Will create a draft upload to TikTok, posting will need to be completed from
+ within the app
+ """
+
+ link: Optional[str] = None
+ """Pinterest post link"""
+
+ location: Optional[str] = None
+ """
+ Page id with a location that you want to tag the image or video with (Instagram
+ and Facebook)
+ """
+
+ made_for_kids: Optional[bool] = None
+ """If true will notify YouTube the video is intended for kids, defaults to false"""
+
+ media: Optional[List[SocialPostMedia]] = None
+ """Overrides the `media` from the post"""
+
+ placement: Optional[Literal["reels", "timeline", "stories"]] = None
+ """Post placement for Facebook/Instagram/Threads"""
+
+ poll: Optional[TwitterPoll] = None
+ """Poll options for the twitter"""
+
+ privacy_status: Optional[Literal["public", "private", "unlisted"]] = None
+ """
+ Sets the privacy status for TikTok (private, public), or YouTube (private,
+ public, unlisted)
+ """
+
+ quote_tweet_id: Optional[str] = None
+ """Id of the tweet you want to quote"""
+
+ reply_settings: Optional[Literal["following", "mentionedUsers", "subscribers", "verified"]] = None
+ """Who can reply to the tweet"""
+
+ set_caption_for_each_image: Optional[bool] = None
+ """
+ If true, include the caption on each image in a Facebook carousel upload; if
+ false, only include it on the final carousel post
+ """
+
+ share_to_feed: Optional[bool] = None
+ """If false Instagram video posts will only be shown in the Reels tab"""
+
+ title: Optional[str] = None
+ """Overrides the `title` from the post (Pinterest, TikTok, YouTube)"""
+
+ trial_reel_type: Optional[Literal["manual", "performance"]] = None
+ """Instagram trial reel type, when passed will be created as a trial reel.
+
+ If manual the trial reel can be manually graduated in the native app. If
+ perfomance the trial reel will be automatically graduated if the trial reel
+ performs well.
+ """
+
+
+class AccountConfiguration(BaseModel):
+ configuration: Configuration
+ """Configuration for the social account"""
+
+ social_account_id: str
+ """ID of the social account, you want to apply the configuration to"""
diff --git a/src/post_for_me/types/account_configuration_param.py b/src/post_for_me/types/account_configuration_param.py
new file mode 100644
index 0000000..0966289
--- /dev/null
+++ b/src/post_for_me/types/account_configuration_param.py
@@ -0,0 +1,119 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Iterable, Optional
+from typing_extensions import Literal, Required, TypedDict
+
+from .._types import SequenceNotStr
+from .twitter_poll_param import TwitterPollParam
+from .social_post_media_param import SocialPostMediaParam
+
+__all__ = ["AccountConfigurationParam", "Configuration"]
+
+
+class Configuration(TypedDict, total=False):
+ """Configuration for the social account"""
+
+ allow_comment: Optional[bool]
+ """Allow comments on TikTok"""
+
+ allow_duet: Optional[bool]
+ """Allow duets on TikTok"""
+
+ allow_stitch: Optional[bool]
+ """Allow stitch on TikTok"""
+
+ auto_add_music: Optional[bool]
+ """Will automatically add music to photo posts on TikTok"""
+
+ board_ids: Optional[SequenceNotStr[str]]
+ """Pinterest board IDs"""
+
+ caption: Optional[object]
+ """Overrides the `caption` from the post"""
+
+ collaborators: Optional[Iterable[Iterable[object]]]
+ """
+ List of page ids or users to invite as collaborators for a Video Reel (Instagram
+ and Facebook)
+ """
+
+ community_id: str
+ """Id of the twitter community to post to"""
+
+ disclose_branded_content: Optional[bool]
+ """Disclose branded content on TikTok"""
+
+ disclose_your_brand: Optional[bool]
+ """Disclose your brand on TikTok"""
+
+ is_ai_generated: Optional[bool]
+ """Flag content as AI generated on TikTok"""
+
+ is_draft: Optional[bool]
+ """
+ Will create a draft upload to TikTok, posting will need to be completed from
+ within the app
+ """
+
+ link: Optional[str]
+ """Pinterest post link"""
+
+ location: Optional[str]
+ """
+ Page id with a location that you want to tag the image or video with (Instagram
+ and Facebook)
+ """
+
+ made_for_kids: Optional[bool]
+ """If true will notify YouTube the video is intended for kids, defaults to false"""
+
+ media: Optional[Iterable[SocialPostMediaParam]]
+ """Overrides the `media` from the post"""
+
+ placement: Optional[Literal["reels", "timeline", "stories"]]
+ """Post placement for Facebook/Instagram/Threads"""
+
+ poll: TwitterPollParam
+ """Poll options for the twitter"""
+
+ privacy_status: Optional[Literal["public", "private", "unlisted"]]
+ """
+ Sets the privacy status for TikTok (private, public), or YouTube (private,
+ public, unlisted)
+ """
+
+ quote_tweet_id: str
+ """Id of the tweet you want to quote"""
+
+ reply_settings: Optional[Literal["following", "mentionedUsers", "subscribers", "verified"]]
+ """Who can reply to the tweet"""
+
+ set_caption_for_each_image: Optional[bool]
+ """
+ If true, include the caption on each image in a Facebook carousel upload; if
+ false, only include it on the final carousel post
+ """
+
+ share_to_feed: Optional[bool]
+ """If false Instagram video posts will only be shown in the Reels tab"""
+
+ title: Optional[str]
+ """Overrides the `title` from the post (Pinterest, TikTok, YouTube)"""
+
+ trial_reel_type: Optional[Literal["manual", "performance"]]
+ """Instagram trial reel type, when passed will be created as a trial reel.
+
+ If manual the trial reel can be manually graduated in the native app. If
+ perfomance the trial reel will be automatically graduated if the trial reel
+ performs well.
+ """
+
+
+class AccountConfigurationParam(TypedDict, total=False):
+ configuration: Required[Configuration]
+ """Configuration for the social account"""
+
+ social_account_id: Required[str]
+ """ID of the social account, you want to apply the configuration to"""
diff --git a/src/post_for_me/types/bluesky_configuration_dto.py b/src/post_for_me/types/bluesky_configuration_dto.py
index d6141b2..99bdee9 100644
--- a/src/post_for_me/types/bluesky_configuration_dto.py
+++ b/src/post_for_me/types/bluesky_configuration_dto.py
@@ -1,63 +1,16 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
from typing import List, Optional
-from typing_extensions import Literal
from .._models import BaseModel
+from .social_post_media import SocialPostMedia
-__all__ = ["BlueskyConfigurationDto", "Media", "MediaTag"]
-
-
-class MediaTag(BaseModel):
- id: str
- """Facebook User ID, Instagram Username or Instagram product id to tag"""
-
- platform: Literal["facebook", "instagram"]
- """The platform for the tags"""
-
- type: Literal["user", "product"]
- """
- The type of tag, user to tag accounts, product to tag products (only supported
- for instagram)
- """
-
- x: Optional[float] = None
- """
- Percentage distance from left edge of the image, Not required for videos or
- stories
- """
-
- y: Optional[float] = None
- """
- Percentage distance from top edge of the image, Not required for videos or
- stories
- """
-
-
-class Media(BaseModel):
- url: str
- """Public URL of the media"""
-
- skip_processing: Optional[bool] = None
- """
- If true the media will not be processed at all and instead be posted as is, this
- may increase chance of post failure if media does not meet platform's
- requirements. Best used for larger files.
- """
-
- tags: Optional[List[MediaTag]] = None
- """List of tags to attach to the media"""
-
- thumbnail_timestamp_ms: Optional[object] = None
- """Timestamp in milliseconds of frame to use as thumbnail for the media"""
-
- thumbnail_url: Optional[object] = None
- """Public URL of the thumbnail for the media"""
+__all__ = ["BlueskyConfigurationDto"]
class BlueskyConfigurationDto(BaseModel):
caption: Optional[object] = None
"""Overrides the `caption` from the post"""
- media: Optional[List[Media]] = None
+ media: Optional[List[SocialPostMedia]] = None
"""Overrides the `media` from the post"""
diff --git a/src/post_for_me/types/bluesky_configuration_dto_param.py b/src/post_for_me/types/bluesky_configuration_dto_param.py
index eff64a4..5d37bd0 100644
--- a/src/post_for_me/types/bluesky_configuration_dto_param.py
+++ b/src/post_for_me/types/bluesky_configuration_dto_param.py
@@ -3,61 +3,16 @@
from __future__ import annotations
from typing import Iterable, Optional
-from typing_extensions import Literal, Required, TypedDict
+from typing_extensions import TypedDict
-__all__ = ["BlueskyConfigurationDtoParam", "Media", "MediaTag"]
+from .social_post_media_param import SocialPostMediaParam
-
-class MediaTag(TypedDict, total=False):
- id: Required[str]
- """Facebook User ID, Instagram Username or Instagram product id to tag"""
-
- platform: Required[Literal["facebook", "instagram"]]
- """The platform for the tags"""
-
- type: Required[Literal["user", "product"]]
- """
- The type of tag, user to tag accounts, product to tag products (only supported
- for instagram)
- """
-
- x: float
- """
- Percentage distance from left edge of the image, Not required for videos or
- stories
- """
-
- y: float
- """
- Percentage distance from top edge of the image, Not required for videos or
- stories
- """
-
-
-class Media(TypedDict, total=False):
- url: Required[str]
- """Public URL of the media"""
-
- skip_processing: Optional[bool]
- """
- If true the media will not be processed at all and instead be posted as is, this
- may increase chance of post failure if media does not meet platform's
- requirements. Best used for larger files.
- """
-
- tags: Optional[Iterable[MediaTag]]
- """List of tags to attach to the media"""
-
- thumbnail_timestamp_ms: Optional[object]
- """Timestamp in milliseconds of frame to use as thumbnail for the media"""
-
- thumbnail_url: Optional[object]
- """Public URL of the thumbnail for the media"""
+__all__ = ["BlueskyConfigurationDtoParam"]
class BlueskyConfigurationDtoParam(TypedDict, total=False):
caption: Optional[object]
"""Overrides the `caption` from the post"""
- media: Optional[Iterable[Media]]
+ media: Optional[Iterable[SocialPostMediaParam]]
"""Overrides the `media` from the post"""
diff --git a/src/post_for_me/types/social_post_delete_response.py b/src/post_for_me/types/delete_entity_response.py
similarity index 69%
rename from src/post_for_me/types/social_post_delete_response.py
rename to src/post_for_me/types/delete_entity_response.py
index 4983306..374afa1 100644
--- a/src/post_for_me/types/social_post_delete_response.py
+++ b/src/post_for_me/types/delete_entity_response.py
@@ -2,9 +2,9 @@
from .._models import BaseModel
-__all__ = ["SocialPostDeleteResponse"]
+__all__ = ["DeleteEntityResponse"]
-class SocialPostDeleteResponse(BaseModel):
+class DeleteEntityResponse(BaseModel):
success: bool
"""Whether or not the entity was deleted"""
diff --git a/src/post_for_me/types/facebook_activity_by_action_type.py b/src/post_for_me/types/facebook_activity_by_action_type.py
new file mode 100644
index 0000000..85aba5b
--- /dev/null
+++ b/src/post_for_me/types/facebook_activity_by_action_type.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .._models import BaseModel
+
+__all__ = ["FacebookActivityByActionType"]
+
+
+class FacebookActivityByActionType(BaseModel):
+ action_type: str
+ """Action type (e.g., like, comment, share)"""
+
+ value: float
+ """Number of actions"""
diff --git a/src/post_for_me/types/facebook_configuration_dto.py b/src/post_for_me/types/facebook_configuration_dto.py
index 246b33e..74a0a0d 100644
--- a/src/post_for_me/types/facebook_configuration_dto.py
+++ b/src/post_for_me/types/facebook_configuration_dto.py
@@ -4,55 +4,9 @@
from typing_extensions import Literal
from .._models import BaseModel
+from .social_post_media import SocialPostMedia
-__all__ = ["FacebookConfigurationDto", "Media", "MediaTag"]
-
-
-class MediaTag(BaseModel):
- id: str
- """Facebook User ID, Instagram Username or Instagram product id to tag"""
-
- platform: Literal["facebook", "instagram"]
- """The platform for the tags"""
-
- type: Literal["user", "product"]
- """
- The type of tag, user to tag accounts, product to tag products (only supported
- for instagram)
- """
-
- x: Optional[float] = None
- """
- Percentage distance from left edge of the image, Not required for videos or
- stories
- """
-
- y: Optional[float] = None
- """
- Percentage distance from top edge of the image, Not required for videos or
- stories
- """
-
-
-class Media(BaseModel):
- url: str
- """Public URL of the media"""
-
- skip_processing: Optional[bool] = None
- """
- If true the media will not be processed at all and instead be posted as is, this
- may increase chance of post failure if media does not meet platform's
- requirements. Best used for larger files.
- """
-
- tags: Optional[List[MediaTag]] = None
- """List of tags to attach to the media"""
-
- thumbnail_timestamp_ms: Optional[object] = None
- """Timestamp in milliseconds of frame to use as thumbnail for the media"""
-
- thumbnail_url: Optional[object] = None
- """Public URL of the thumbnail for the media"""
+__all__ = ["FacebookConfigurationDto"]
class FacebookConfigurationDto(BaseModel):
@@ -65,7 +19,7 @@ class FacebookConfigurationDto(BaseModel):
location: Optional[str] = None
"""Page id with a location that you want to tag the image or video with"""
- media: Optional[List[Media]] = None
+ media: Optional[List[SocialPostMedia]] = None
"""Overrides the `media` from the post"""
placement: Optional[Literal["reels", "stories", "timeline"]] = None
diff --git a/src/post_for_me/types/facebook_configuration_dto_param.py b/src/post_for_me/types/facebook_configuration_dto_param.py
index a19274a..4e0cc19 100644
--- a/src/post_for_me/types/facebook_configuration_dto_param.py
+++ b/src/post_for_me/types/facebook_configuration_dto_param.py
@@ -3,56 +3,11 @@
from __future__ import annotations
from typing import Iterable, Optional
-from typing_extensions import Literal, Required, TypedDict
+from typing_extensions import Literal, TypedDict
-__all__ = ["FacebookConfigurationDtoParam", "Media", "MediaTag"]
+from .social_post_media_param import SocialPostMediaParam
-
-class MediaTag(TypedDict, total=False):
- id: Required[str]
- """Facebook User ID, Instagram Username or Instagram product id to tag"""
-
- platform: Required[Literal["facebook", "instagram"]]
- """The platform for the tags"""
-
- type: Required[Literal["user", "product"]]
- """
- The type of tag, user to tag accounts, product to tag products (only supported
- for instagram)
- """
-
- x: float
- """
- Percentage distance from left edge of the image, Not required for videos or
- stories
- """
-
- y: float
- """
- Percentage distance from top edge of the image, Not required for videos or
- stories
- """
-
-
-class Media(TypedDict, total=False):
- url: Required[str]
- """Public URL of the media"""
-
- skip_processing: Optional[bool]
- """
- If true the media will not be processed at all and instead be posted as is, this
- may increase chance of post failure if media does not meet platform's
- requirements. Best used for larger files.
- """
-
- tags: Optional[Iterable[MediaTag]]
- """List of tags to attach to the media"""
-
- thumbnail_timestamp_ms: Optional[object]
- """Timestamp in milliseconds of frame to use as thumbnail for the media"""
-
- thumbnail_url: Optional[object]
- """Public URL of the thumbnail for the media"""
+__all__ = ["FacebookConfigurationDtoParam"]
class FacebookConfigurationDtoParam(TypedDict, total=False):
@@ -65,7 +20,7 @@ class FacebookConfigurationDtoParam(TypedDict, total=False):
location: Optional[str]
"""Page id with a location that you want to tag the image or video with"""
- media: Optional[Iterable[Media]]
+ media: Optional[Iterable[SocialPostMediaParam]]
"""Overrides the `media` from the post"""
placement: Optional[Literal["reels", "stories", "timeline"]]
diff --git a/src/post_for_me/types/facebook_video_retention_graph.py b/src/post_for_me/types/facebook_video_retention_graph.py
new file mode 100644
index 0000000..44b9b46
--- /dev/null
+++ b/src/post_for_me/types/facebook_video_retention_graph.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .._models import BaseModel
+
+__all__ = ["FacebookVideoRetentionGraph"]
+
+
+class FacebookVideoRetentionGraph(BaseModel):
+ rate: float
+ """Percentage of viewers at this time"""
+
+ time: float
+ """Time in seconds"""
diff --git a/src/post_for_me/types/facebook_video_view_time_by_demographic.py b/src/post_for_me/types/facebook_video_view_time_by_demographic.py
new file mode 100644
index 0000000..ca9fb67
--- /dev/null
+++ b/src/post_for_me/types/facebook_video_view_time_by_demographic.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .._models import BaseModel
+
+__all__ = ["FacebookVideoViewTimeByDemographic"]
+
+
+class FacebookVideoViewTimeByDemographic(BaseModel):
+ key: str
+ """Demographic key (e.g., age_gender, region, country)"""
+
+ value: float
+ """Total view time in milliseconds"""
diff --git a/src/post_for_me/types/instagram_configuration_dto.py b/src/post_for_me/types/instagram_configuration_dto.py
index cde14e8..2f361da 100644
--- a/src/post_for_me/types/instagram_configuration_dto.py
+++ b/src/post_for_me/types/instagram_configuration_dto.py
@@ -4,55 +4,9 @@
from typing_extensions import Literal
from .._models import BaseModel
+from .social_post_media import SocialPostMedia
-__all__ = ["InstagramConfigurationDto", "Media", "MediaTag"]
-
-
-class MediaTag(BaseModel):
- id: str
- """Facebook User ID, Instagram Username or Instagram product id to tag"""
-
- platform: Literal["facebook", "instagram"]
- """The platform for the tags"""
-
- type: Literal["user", "product"]
- """
- The type of tag, user to tag accounts, product to tag products (only supported
- for instagram)
- """
-
- x: Optional[float] = None
- """
- Percentage distance from left edge of the image, Not required for videos or
- stories
- """
-
- y: Optional[float] = None
- """
- Percentage distance from top edge of the image, Not required for videos or
- stories
- """
-
-
-class Media(BaseModel):
- url: str
- """Public URL of the media"""
-
- skip_processing: Optional[bool] = None
- """
- If true the media will not be processed at all and instead be posted as is, this
- may increase chance of post failure if media does not meet platform's
- requirements. Best used for larger files.
- """
-
- tags: Optional[List[MediaTag]] = None
- """List of tags to attach to the media"""
-
- thumbnail_timestamp_ms: Optional[object] = None
- """Timestamp in milliseconds of frame to use as thumbnail for the media"""
-
- thumbnail_url: Optional[object] = None
- """Public URL of the thumbnail for the media"""
+__all__ = ["InstagramConfigurationDto"]
class InstagramConfigurationDto(BaseModel):
@@ -65,7 +19,7 @@ class InstagramConfigurationDto(BaseModel):
location: Optional[str] = None
"""Page id with a location that you want to tag the image or video with"""
- media: Optional[List[Media]] = None
+ media: Optional[List[SocialPostMedia]] = None
"""Overrides the `media` from the post"""
placement: Optional[Literal["reels", "stories", "timeline"]] = None
diff --git a/src/post_for_me/types/instagram_configuration_dto_param.py b/src/post_for_me/types/instagram_configuration_dto_param.py
index cbdf9c7..fb22a45 100644
--- a/src/post_for_me/types/instagram_configuration_dto_param.py
+++ b/src/post_for_me/types/instagram_configuration_dto_param.py
@@ -3,58 +3,12 @@
from __future__ import annotations
from typing import Iterable, Optional
-from typing_extensions import Literal, Required, TypedDict
+from typing_extensions import Literal, TypedDict
from .._types import SequenceNotStr
+from .social_post_media_param import SocialPostMediaParam
-__all__ = ["InstagramConfigurationDtoParam", "Media", "MediaTag"]
-
-
-class MediaTag(TypedDict, total=False):
- id: Required[str]
- """Facebook User ID, Instagram Username or Instagram product id to tag"""
-
- platform: Required[Literal["facebook", "instagram"]]
- """The platform for the tags"""
-
- type: Required[Literal["user", "product"]]
- """
- The type of tag, user to tag accounts, product to tag products (only supported
- for instagram)
- """
-
- x: float
- """
- Percentage distance from left edge of the image, Not required for videos or
- stories
- """
-
- y: float
- """
- Percentage distance from top edge of the image, Not required for videos or
- stories
- """
-
-
-class Media(TypedDict, total=False):
- url: Required[str]
- """Public URL of the media"""
-
- skip_processing: Optional[bool]
- """
- If true the media will not be processed at all and instead be posted as is, this
- may increase chance of post failure if media does not meet platform's
- requirements. Best used for larger files.
- """
-
- tags: Optional[Iterable[MediaTag]]
- """List of tags to attach to the media"""
-
- thumbnail_timestamp_ms: Optional[object]
- """Timestamp in milliseconds of frame to use as thumbnail for the media"""
-
- thumbnail_url: Optional[object]
- """Public URL of the thumbnail for the media"""
+__all__ = ["InstagramConfigurationDtoParam"]
class InstagramConfigurationDtoParam(TypedDict, total=False):
@@ -67,7 +21,7 @@ class InstagramConfigurationDtoParam(TypedDict, total=False):
location: Optional[str]
"""Page id with a location that you want to tag the image or video with"""
- media: Optional[Iterable[Media]]
+ media: Optional[Iterable[SocialPostMediaParam]]
"""Overrides the `media` from the post"""
placement: Optional[Literal["reels", "stories", "timeline"]]
diff --git a/src/post_for_me/types/linkedin_configuration_dto.py b/src/post_for_me/types/linkedin_configuration_dto.py
index 4b9e4da..e47ea0f 100644
--- a/src/post_for_me/types/linkedin_configuration_dto.py
+++ b/src/post_for_me/types/linkedin_configuration_dto.py
@@ -1,63 +1,16 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
from typing import List, Optional
-from typing_extensions import Literal
from .._models import BaseModel
+from .social_post_media import SocialPostMedia
-__all__ = ["LinkedinConfigurationDto", "Media", "MediaTag"]
-
-
-class MediaTag(BaseModel):
- id: str
- """Facebook User ID, Instagram Username or Instagram product id to tag"""
-
- platform: Literal["facebook", "instagram"]
- """The platform for the tags"""
-
- type: Literal["user", "product"]
- """
- The type of tag, user to tag accounts, product to tag products (only supported
- for instagram)
- """
-
- x: Optional[float] = None
- """
- Percentage distance from left edge of the image, Not required for videos or
- stories
- """
-
- y: Optional[float] = None
- """
- Percentage distance from top edge of the image, Not required for videos or
- stories
- """
-
-
-class Media(BaseModel):
- url: str
- """Public URL of the media"""
-
- skip_processing: Optional[bool] = None
- """
- If true the media will not be processed at all and instead be posted as is, this
- may increase chance of post failure if media does not meet platform's
- requirements. Best used for larger files.
- """
-
- tags: Optional[List[MediaTag]] = None
- """List of tags to attach to the media"""
-
- thumbnail_timestamp_ms: Optional[object] = None
- """Timestamp in milliseconds of frame to use as thumbnail for the media"""
-
- thumbnail_url: Optional[object] = None
- """Public URL of the thumbnail for the media"""
+__all__ = ["LinkedinConfigurationDto"]
class LinkedinConfigurationDto(BaseModel):
caption: Optional[object] = None
"""Overrides the `caption` from the post"""
- media: Optional[List[Media]] = None
+ media: Optional[List[SocialPostMedia]] = None
"""Overrides the `media` from the post"""
diff --git a/src/post_for_me/types/linkedin_configuration_dto_param.py b/src/post_for_me/types/linkedin_configuration_dto_param.py
index 2ead64d..d28230e 100644
--- a/src/post_for_me/types/linkedin_configuration_dto_param.py
+++ b/src/post_for_me/types/linkedin_configuration_dto_param.py
@@ -3,61 +3,16 @@
from __future__ import annotations
from typing import Iterable, Optional
-from typing_extensions import Literal, Required, TypedDict
+from typing_extensions import TypedDict
-__all__ = ["LinkedinConfigurationDtoParam", "Media", "MediaTag"]
+from .social_post_media_param import SocialPostMediaParam
-
-class MediaTag(TypedDict, total=False):
- id: Required[str]
- """Facebook User ID, Instagram Username or Instagram product id to tag"""
-
- platform: Required[Literal["facebook", "instagram"]]
- """The platform for the tags"""
-
- type: Required[Literal["user", "product"]]
- """
- The type of tag, user to tag accounts, product to tag products (only supported
- for instagram)
- """
-
- x: float
- """
- Percentage distance from left edge of the image, Not required for videos or
- stories
- """
-
- y: float
- """
- Percentage distance from top edge of the image, Not required for videos or
- stories
- """
-
-
-class Media(TypedDict, total=False):
- url: Required[str]
- """Public URL of the media"""
-
- skip_processing: Optional[bool]
- """
- If true the media will not be processed at all and instead be posted as is, this
- may increase chance of post failure if media does not meet platform's
- requirements. Best used for larger files.
- """
-
- tags: Optional[Iterable[MediaTag]]
- """List of tags to attach to the media"""
-
- thumbnail_timestamp_ms: Optional[object]
- """Timestamp in milliseconds of frame to use as thumbnail for the media"""
-
- thumbnail_url: Optional[object]
- """Public URL of the thumbnail for the media"""
+__all__ = ["LinkedinConfigurationDtoParam"]
class LinkedinConfigurationDtoParam(TypedDict, total=False):
caption: Optional[object]
"""Overrides the `caption` from the post"""
- media: Optional[Iterable[Media]]
+ media: Optional[Iterable[SocialPostMediaParam]]
"""Overrides the `media` from the post"""
diff --git a/src/post_for_me/types/pinterest_configuration_dto.py b/src/post_for_me/types/pinterest_configuration_dto.py
index ba6f082..67e0be5 100644
--- a/src/post_for_me/types/pinterest_configuration_dto.py
+++ b/src/post_for_me/types/pinterest_configuration_dto.py
@@ -1,58 +1,11 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
from typing import List, Optional
-from typing_extensions import Literal
from .._models import BaseModel
+from .social_post_media import SocialPostMedia
-__all__ = ["PinterestConfigurationDto", "Media", "MediaTag"]
-
-
-class MediaTag(BaseModel):
- id: str
- """Facebook User ID, Instagram Username or Instagram product id to tag"""
-
- platform: Literal["facebook", "instagram"]
- """The platform for the tags"""
-
- type: Literal["user", "product"]
- """
- The type of tag, user to tag accounts, product to tag products (only supported
- for instagram)
- """
-
- x: Optional[float] = None
- """
- Percentage distance from left edge of the image, Not required for videos or
- stories
- """
-
- y: Optional[float] = None
- """
- Percentage distance from top edge of the image, Not required for videos or
- stories
- """
-
-
-class Media(BaseModel):
- url: str
- """Public URL of the media"""
-
- skip_processing: Optional[bool] = None
- """
- If true the media will not be processed at all and instead be posted as is, this
- may increase chance of post failure if media does not meet platform's
- requirements. Best used for larger files.
- """
-
- tags: Optional[List[MediaTag]] = None
- """List of tags to attach to the media"""
-
- thumbnail_timestamp_ms: Optional[object] = None
- """Timestamp in milliseconds of frame to use as thumbnail for the media"""
-
- thumbnail_url: Optional[object] = None
- """Public URL of the thumbnail for the media"""
+__all__ = ["PinterestConfigurationDto"]
class PinterestConfigurationDto(BaseModel):
@@ -65,5 +18,8 @@ class PinterestConfigurationDto(BaseModel):
link: Optional[str] = None
"""Pinterest post link"""
- media: Optional[List[Media]] = None
+ media: Optional[List[SocialPostMedia]] = None
"""Overrides the `media` from the post"""
+
+ title: Optional[str] = None
+ """Overrides the `title` from the post for Pinterest"""
diff --git a/src/post_for_me/types/pinterest_configuration_dto_param.py b/src/post_for_me/types/pinterest_configuration_dto_param.py
index 60845c8..e192080 100644
--- a/src/post_for_me/types/pinterest_configuration_dto_param.py
+++ b/src/post_for_me/types/pinterest_configuration_dto_param.py
@@ -3,58 +3,12 @@
from __future__ import annotations
from typing import Iterable, Optional
-from typing_extensions import Literal, Required, TypedDict
+from typing_extensions import TypedDict
from .._types import SequenceNotStr
+from .social_post_media_param import SocialPostMediaParam
-__all__ = ["PinterestConfigurationDtoParam", "Media", "MediaTag"]
-
-
-class MediaTag(TypedDict, total=False):
- id: Required[str]
- """Facebook User ID, Instagram Username or Instagram product id to tag"""
-
- platform: Required[Literal["facebook", "instagram"]]
- """The platform for the tags"""
-
- type: Required[Literal["user", "product"]]
- """
- The type of tag, user to tag accounts, product to tag products (only supported
- for instagram)
- """
-
- x: float
- """
- Percentage distance from left edge of the image, Not required for videos or
- stories
- """
-
- y: float
- """
- Percentage distance from top edge of the image, Not required for videos or
- stories
- """
-
-
-class Media(TypedDict, total=False):
- url: Required[str]
- """Public URL of the media"""
-
- skip_processing: Optional[bool]
- """
- If true the media will not be processed at all and instead be posted as is, this
- may increase chance of post failure if media does not meet platform's
- requirements. Best used for larger files.
- """
-
- tags: Optional[Iterable[MediaTag]]
- """List of tags to attach to the media"""
-
- thumbnail_timestamp_ms: Optional[object]
- """Timestamp in milliseconds of frame to use as thumbnail for the media"""
-
- thumbnail_url: Optional[object]
- """Public URL of the thumbnail for the media"""
+__all__ = ["PinterestConfigurationDtoParam"]
class PinterestConfigurationDtoParam(TypedDict, total=False):
@@ -67,5 +21,8 @@ class PinterestConfigurationDtoParam(TypedDict, total=False):
link: Optional[str]
"""Pinterest post link"""
- media: Optional[Iterable[Media]]
+ media: Optional[Iterable[SocialPostMediaParam]]
"""Overrides the `media` from the post"""
+
+ title: Optional[str]
+ """Overrides the `title` from the post for Pinterest"""
diff --git a/src/post_for_me/types/pinterest_metrics_window.py b/src/post_for_me/types/pinterest_metrics_window.py
new file mode 100644
index 0000000..04728d8
--- /dev/null
+++ b/src/post_for_me/types/pinterest_metrics_window.py
@@ -0,0 +1,51 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from .._models import BaseModel
+
+__all__ = ["PinterestMetricsWindow"]
+
+
+class PinterestMetricsWindow(BaseModel):
+ comment: Optional[float] = None
+ """Number of comments on the Pin"""
+
+ impression: Optional[float] = None
+ """Number of times the Pin was shown (impressions)"""
+
+ last_updated: Optional[str] = None
+ """The last time Pinterest updated these metrics"""
+
+ outbound_click: Optional[float] = None
+ """Number of clicks from the Pin to an external destination (outbound clicks)"""
+
+ pin_click: Optional[float] = None
+ """Number of clicks on the Pin to view it in closeup (Pin clicks)"""
+
+ profile_visit: Optional[object] = None
+ """Number of visits to the author's profile driven from the Pin"""
+
+ reaction: Optional[float] = None
+ """Total number of reactions on the Pin"""
+
+ save: Optional[float] = None
+ """Number of saves of the Pin"""
+
+ user_follow: Optional[object] = None
+ """Number of follows driven from the Pin"""
+
+ video_10s_views: Optional[float] = None
+ """Number of video views of at least 10 seconds"""
+
+ video_average_time: Optional[float] = None
+ """Average watch time for the video"""
+
+ video_p95_views: Optional[float] = None
+ """Number of video views that reached 95% completion"""
+
+ video_total_time: Optional[float] = None
+ """Total watch time for the video"""
+
+ video_views: Optional[float] = None
+ """Number of video views"""
diff --git a/src/post_for_me/types/platform_post.py b/src/post_for_me/types/platform_post.py
index 6299598..f5ebe53 100644
--- a/src/post_for_me/types/platform_post.py
+++ b/src/post_for_me/types/platform_post.py
@@ -7,6 +7,12 @@
from pydantic import Field as FieldInfo
from .._models import BaseModel
+from .pinterest_metrics_window import PinterestMetricsWindow
+from .youtube_post_platform_data import YoutubePostPlatformData
+from .facebook_video_retention_graph import FacebookVideoRetentionGraph
+from .facebook_activity_by_action_type import FacebookActivityByActionType
+from .facebook_video_view_time_by_demographic import FacebookVideoViewTimeByDemographic
+from .tiktok_business_video_metric_percentage import TiktokBusinessVideoMetricPercentage
__all__ = [
"PlatformPost",
@@ -16,20 +22,11 @@
"MetricsTikTokBusinessMetricsDtoAudienceCountry",
"MetricsTikTokBusinessMetricsDtoAudienceGender",
"MetricsTikTokBusinessMetricsDtoAudienceType",
- "MetricsTikTokBusinessMetricsDtoEngagementLike",
"MetricsTikTokBusinessMetricsDtoImpressionSource",
- "MetricsTikTokBusinessMetricsDtoVideoViewRetention",
"MetricsTikTokPostMetricsDto",
"MetricsInstagramPostMetricsDto",
"MetricsYouTubePostMetricsDto",
"MetricsFacebookPostMetricsDto",
- "MetricsFacebookPostMetricsDtoActivityByActionType",
- "MetricsFacebookPostMetricsDtoActivityByActionTypeUnique",
- "MetricsFacebookPostMetricsDtoVideoRetentionGraphAutoplayed",
- "MetricsFacebookPostMetricsDtoVideoRetentionGraphClickedToPlay",
- "MetricsFacebookPostMetricsDtoVideoViewTimeByAgeGender",
- "MetricsFacebookPostMetricsDtoVideoViewTimeByCountry",
- "MetricsFacebookPostMetricsDtoVideoViewTimeByRegion",
"MetricsTwitterPostMetricsDto",
"MetricsTwitterPostMetricsDtoNonPublicMetrics",
"MetricsTwitterPostMetricsDtoOrganicMetrics",
@@ -38,9 +35,6 @@
"MetricsLinkedInPostMetricsDto",
"MetricsBlueskyPostMetricsDto",
"MetricsPinterestPostMetricsDto",
- "MetricsPinterestPostMetricsDto_90d",
- "MetricsPinterestPostMetricsDtoLifetimeMetrics",
- "PlatformData",
]
@@ -76,14 +70,6 @@ class MetricsTikTokBusinessMetricsDtoAudienceType(BaseModel):
"""Type of audience"""
-class MetricsTikTokBusinessMetricsDtoEngagementLike(BaseModel):
- percentage: float
- """Percentage value for the metric"""
-
- second: str
- """Time in seconds for the metric"""
-
-
class MetricsTikTokBusinessMetricsDtoImpressionSource(BaseModel):
impression_source: str
"""Name of the impression source"""
@@ -92,14 +78,6 @@ class MetricsTikTokBusinessMetricsDtoImpressionSource(BaseModel):
"""Percentage of impressions from this source"""
-class MetricsTikTokBusinessMetricsDtoVideoViewRetention(BaseModel):
- percentage: float
- """Percentage value for the metric"""
-
- second: str
- """Time in seconds for the metric"""
-
-
class MetricsTikTokBusinessMetricsDto(BaseModel):
address_clicks: float
"""Number of address clicks"""
@@ -128,7 +106,7 @@ class MetricsTikTokBusinessMetricsDto(BaseModel):
email_clicks: float
"""Number of email clicks"""
- engagement_likes: List[MetricsTikTokBusinessMetricsDtoEngagementLike]
+ engagement_likes: List[TiktokBusinessVideoMetricPercentage]
"""Engagement likes data by percentage and time"""
favorites: float
@@ -164,7 +142,7 @@ class MetricsTikTokBusinessMetricsDto(BaseModel):
total_time_watched: float
"""Total time watched in seconds"""
- video_view_retention: List[MetricsTikTokBusinessMetricsDtoVideoViewRetention]
+ video_view_retention: List[TiktokBusinessVideoMetricPercentage]
"""Video view retention data by percentage and time"""
video_views: float
@@ -318,67 +296,11 @@ class MetricsYouTubePostMetricsDto(BaseModel):
"""Number of times the video was removed from playlists"""
-class MetricsFacebookPostMetricsDtoActivityByActionType(BaseModel):
- action_type: str
- """Action type (e.g., like, comment, share)"""
-
- value: float
- """Number of actions"""
-
-
-class MetricsFacebookPostMetricsDtoActivityByActionTypeUnique(BaseModel):
- action_type: str
- """Action type (e.g., like, comment, share)"""
-
- value: float
- """Number of actions"""
-
-
-class MetricsFacebookPostMetricsDtoVideoRetentionGraphAutoplayed(BaseModel):
- rate: float
- """Percentage of viewers at this time"""
-
- time: float
- """Time in seconds"""
-
-
-class MetricsFacebookPostMetricsDtoVideoRetentionGraphClickedToPlay(BaseModel):
- rate: float
- """Percentage of viewers at this time"""
-
- time: float
- """Time in seconds"""
-
-
-class MetricsFacebookPostMetricsDtoVideoViewTimeByAgeGender(BaseModel):
- key: str
- """Demographic key (e.g., age_gender, region, country)"""
-
- value: float
- """Total view time in milliseconds"""
-
-
-class MetricsFacebookPostMetricsDtoVideoViewTimeByCountry(BaseModel):
- key: str
- """Demographic key (e.g., age_gender, region, country)"""
-
- value: float
- """Total view time in milliseconds"""
-
-
-class MetricsFacebookPostMetricsDtoVideoViewTimeByRegion(BaseModel):
- key: str
- """Demographic key (e.g., age_gender, region, country)"""
-
- value: float
- """Total view time in milliseconds"""
-
-
class MetricsFacebookPostMetricsDto(BaseModel):
- activity_by_action_type: Optional[List[MetricsFacebookPostMetricsDtoActivityByActionType]] = None
+ activity_by_action_type: Optional[List[FacebookActivityByActionType]] = None
"""Total activity breakdown by action type"""
- activity_by_action_type_unique: Optional[List[MetricsFacebookPostMetricsDtoActivityByActionTypeUnique]] = None
+ activity_by_action_type_unique: Optional[List[FacebookActivityByActionType]] = None
"""Unique users activity breakdown by action type"""
comments: Optional[float] = None
@@ -447,12 +369,10 @@ class MetricsFacebookPostMetricsDto(BaseModel):
video_length: Optional[float] = None
"""Length of the video in milliseconds"""
- video_retention_graph_autoplayed: Optional[List[MetricsFacebookPostMetricsDtoVideoRetentionGraphAutoplayed]] = None
+ video_retention_graph_autoplayed: Optional[List[FacebookVideoRetentionGraph]] = None
"""Video retention graph for autoplayed views"""
- video_retention_graph_clicked_to_play: Optional[
- List[MetricsFacebookPostMetricsDtoVideoRetentionGraphClickedToPlay]
- ] = None
+ video_retention_graph_clicked_to_play: Optional[List[FacebookVideoRetentionGraph]] = None
"""Video retention graph for clicked-to-play views"""
video_social_actions_unique: Optional[float] = None
@@ -461,16 +381,16 @@ class MetricsFacebookPostMetricsDto(BaseModel):
video_view_time: Optional[float] = None
"""Total time video was viewed in milliseconds"""
- video_view_time_by_age_gender: Optional[List[MetricsFacebookPostMetricsDtoVideoViewTimeByAgeGender]] = None
+ video_view_time_by_age_gender: Optional[List[FacebookVideoViewTimeByDemographic]] = None
"""Video view time breakdown by age and gender"""
- video_view_time_by_country: Optional[List[MetricsFacebookPostMetricsDtoVideoViewTimeByCountry]] = None
+ video_view_time_by_country: Optional[List[FacebookVideoViewTimeByDemographic]] = None
"""Video view time breakdown by country"""
video_view_time_by_distribution_type: Optional[object] = None
"""Video view time breakdown by distribution type"""
- video_view_time_by_region: Optional[List[MetricsFacebookPostMetricsDtoVideoViewTimeByRegion]] = None
+ video_view_time_by_region: Optional[List[FacebookVideoViewTimeByDemographic]] = None
"""Video view time breakdown by region"""
video_view_time_organic: Optional[float] = None
@@ -675,103 +595,11 @@ class MetricsBlueskyPostMetricsDto(BaseModel):
"""Number of reposts of the post"""
-class MetricsPinterestPostMetricsDto_90d(BaseModel):
- """Last 90 days of Pin metrics"""
-
- comment: Optional[float] = None
- """Number of comments on the Pin"""
-
- impression: Optional[float] = None
- """Number of times the Pin was shown (impressions)"""
-
- last_updated: Optional[str] = None
- """The last time Pinterest updated these metrics"""
-
- outbound_click: Optional[float] = None
- """Number of clicks from the Pin to an external destination (outbound clicks)"""
-
- pin_click: Optional[float] = None
- """Number of clicks on the Pin to view it in closeup (Pin clicks)"""
-
- profile_visit: Optional[object] = None
- """Number of visits to the author's profile driven from the Pin"""
-
- reaction: Optional[float] = None
- """Total number of reactions on the Pin"""
-
- save: Optional[float] = None
- """Number of saves of the Pin"""
-
- user_follow: Optional[object] = None
- """Number of follows driven from the Pin"""
-
- video_10s_views: Optional[float] = None
- """Number of video views of at least 10 seconds"""
-
- video_average_time: Optional[float] = None
- """Average watch time for the video"""
-
- video_p95_views: Optional[float] = None
- """Number of video views that reached 95% completion"""
-
- video_total_time: Optional[float] = None
- """Total watch time for the video"""
-
- video_views: Optional[float] = None
- """Number of video views"""
-
-
-class MetricsPinterestPostMetricsDtoLifetimeMetrics(BaseModel):
- """Lifetime Pin metrics"""
-
- comment: Optional[float] = None
- """Number of comments on the Pin"""
-
- impression: Optional[float] = None
- """Number of times the Pin was shown (impressions)"""
-
- last_updated: Optional[str] = None
- """The last time Pinterest updated these metrics"""
-
- outbound_click: Optional[float] = None
- """Number of clicks from the Pin to an external destination (outbound clicks)"""
-
- pin_click: Optional[float] = None
- """Number of clicks on the Pin to view it in closeup (Pin clicks)"""
-
- profile_visit: Optional[object] = None
- """Number of visits to the author's profile driven from the Pin"""
-
- reaction: Optional[float] = None
- """Total number of reactions on the Pin"""
-
- save: Optional[float] = None
- """Number of saves of the Pin"""
-
- user_follow: Optional[object] = None
- """Number of follows driven from the Pin"""
-
- video_10s_views: Optional[float] = None
- """Number of video views of at least 10 seconds"""
-
- video_average_time: Optional[float] = None
- """Average watch time for the video"""
-
- video_p95_views: Optional[float] = None
- """Number of video views that reached 95% completion"""
-
- video_total_time: Optional[float] = None
- """Total watch time for the video"""
-
- video_views: Optional[float] = None
- """Number of video views"""
-
-
class MetricsPinterestPostMetricsDto(BaseModel):
- api_90d: Optional[MetricsPinterestPostMetricsDto_90d] = FieldInfo(alias="90d", default=None)
+ api_90d: Optional[PinterestMetricsWindow] = FieldInfo(alias="90d", default=None)
"""Last 90 days of Pin metrics"""
- lifetime_metrics: Optional[MetricsPinterestPostMetricsDtoLifetimeMetrics] = None
+ lifetime_metrics: Optional[PinterestMetricsWindow] = None
"""Lifetime Pin metrics"""
@@ -789,13 +617,6 @@ class MetricsPinterestPostMetricsDto(BaseModel):
]
-class PlatformData(BaseModel):
- """Platform-specific data for the post"""
-
- title: str
- """Title of the post"""
-
-
class PlatformPost(BaseModel):
caption: str
"""Caption or text content of the post"""
@@ -827,7 +648,7 @@ class PlatformPost(BaseModel):
metrics: Optional[Metrics] = None
"""Post metrics and analytics data"""
- platform_data: Optional[PlatformData] = None
+ platform_data: Optional[YoutubePostPlatformData] = None
"""Platform-specific data for the post"""
posted_at: Optional[datetime] = None
diff --git a/src/post_for_me/types/social_account_create_params.py b/src/post_for_me/types/social_account_create_params.py
index 15fc13b..c9e9bee 100644
--- a/src/post_for_me/types/social_account_create_params.py
+++ b/src/post_for_me/types/social_account_create_params.py
@@ -7,6 +7,7 @@
from typing_extensions import Literal, Required, Annotated, TypedDict
from .._utils import PropertyInfo
+from .social_account_metadata_param import SocialAccountMetadataParam
__all__ = ["SocialAccountCreateParams"]
@@ -40,7 +41,7 @@ class SocialAccountCreateParams(TypedDict, total=False):
external_id: Optional[str]
"""The external id of the social account"""
- metadata: object
+ metadata: SocialAccountMetadataParam
"""The metadata of the social account"""
refresh_token: Optional[str]
diff --git a/src/post_for_me/types/social_account_metadata_param.py b/src/post_for_me/types/social_account_metadata_param.py
new file mode 100644
index 0000000..649c9ca
--- /dev/null
+++ b/src/post_for_me/types/social_account_metadata_param.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypeAlias
+
+__all__ = ["SocialAccountMetadataParam"]
+
+SocialAccountMetadataParam: TypeAlias = object
diff --git a/src/post_for_me/types/social_post.py b/src/post_for_me/types/social_post.py
index 9cf83cc..d492426 100644
--- a/src/post_for_me/types/social_post.py
+++ b/src/post_for_me/types/social_post.py
@@ -5,232 +5,11 @@
from .._models import BaseModel
from .social_account import SocialAccount
+from .social_post_media import SocialPostMedia
+from .account_configuration import AccountConfiguration
from .platform_configurations_dto import PlatformConfigurationsDto
-__all__ = [
- "SocialPost",
- "AccountConfiguration",
- "AccountConfigurationConfiguration",
- "AccountConfigurationConfigurationMedia",
- "AccountConfigurationConfigurationMediaTag",
- "AccountConfigurationConfigurationPoll",
- "Media",
- "MediaTag",
-]
-
-
-class AccountConfigurationConfigurationMediaTag(BaseModel):
- id: str
- """Facebook User ID, Instagram Username or Instagram product id to tag"""
-
- platform: Literal["facebook", "instagram"]
- """The platform for the tags"""
-
- type: Literal["user", "product"]
- """
- The type of tag, user to tag accounts, product to tag products (only supported
- for instagram)
- """
-
- x: Optional[float] = None
- """
- Percentage distance from left edge of the image, Not required for videos or
- stories
- """
-
- y: Optional[float] = None
- """
- Percentage distance from top edge of the image, Not required for videos or
- stories
- """
-
-
-class AccountConfigurationConfigurationMedia(BaseModel):
- url: str
- """Public URL of the media"""
-
- skip_processing: Optional[bool] = None
- """
- If true the media will not be processed at all and instead be posted as is, this
- may increase chance of post failure if media does not meet platform's
- requirements. Best used for larger files.
- """
-
- tags: Optional[List[AccountConfigurationConfigurationMediaTag]] = None
- """List of tags to attach to the media"""
-
- thumbnail_timestamp_ms: Optional[object] = None
- """Timestamp in milliseconds of frame to use as thumbnail for the media"""
-
- thumbnail_url: Optional[object] = None
- """Public URL of the thumbnail for the media"""
-
-
-class AccountConfigurationConfigurationPoll(BaseModel):
- """Poll options for the twitter"""
-
- duration_minutes: float
- """Duration of the poll in minutes"""
-
- options: List[str]
- """The choices of the poll, requiring 2-4 options"""
-
- reply_settings: Optional[Literal["following", "mentionedUsers", "subscribers", "verified"]] = None
- """Who can reply to the tweet"""
-
-
-class AccountConfigurationConfiguration(BaseModel):
- """Configuration for the social account"""
-
- allow_comment: Optional[bool] = None
- """Allow comments on TikTok"""
-
- allow_duet: Optional[bool] = None
- """Allow duets on TikTok"""
-
- allow_stitch: Optional[bool] = None
- """Allow stitch on TikTok"""
-
- auto_add_music: Optional[bool] = None
- """Will automatically add music to photo posts on TikTok"""
-
- board_ids: Optional[List[str]] = None
- """Pinterest board IDs"""
-
- caption: Optional[object] = None
- """Overrides the `caption` from the post"""
-
- collaborators: Optional[List[List[object]]] = None
- """
- List of page ids or users to invite as collaborators for a Video Reel (Instagram
- and Facebook)
- """
-
- community_id: Optional[str] = None
- """Id of the twitter community to post to"""
-
- disclose_branded_content: Optional[bool] = None
- """Disclose branded content on TikTok"""
-
- disclose_your_brand: Optional[bool] = None
- """Disclose your brand on TikTok"""
-
- is_ai_generated: Optional[bool] = None
- """Flag content as AI generated on TikTok"""
-
- is_draft: Optional[bool] = None
- """
- Will create a draft upload to TikTok, posting will need to be completed from
- within the app
- """
-
- link: Optional[str] = None
- """Pinterest post link"""
-
- location: Optional[str] = None
- """
- Page id with a location that you want to tag the image or video with (Instagram
- and Facebook)
- """
-
- made_for_kids: Optional[bool] = None
- """If true will notify YouTube the video is intended for kids, defaults to false"""
-
- media: Optional[List[AccountConfigurationConfigurationMedia]] = None
- """Overrides the `media` from the post"""
-
- placement: Optional[Literal["reels", "timeline", "stories"]] = None
- """Post placement for Facebook/Instagram/Threads"""
-
- poll: Optional[AccountConfigurationConfigurationPoll] = None
- """Poll options for the twitter"""
-
- privacy_status: Optional[Literal["public", "private", "unlisted"]] = None
- """
- Sets the privacy status for TikTok (private, public), or YouTube (private,
- public, unlisted)
- """
-
- quote_tweet_id: Optional[str] = None
- """Id of the tweet you want to quote"""
-
- reply_settings: Optional[Literal["following", "mentionedUsers", "subscribers", "verified"]] = None
- """Who can reply to the tweet"""
-
- set_caption_for_each_image: Optional[bool] = None
- """
- If true, include the caption on each image in a Facebook carousel upload; if
- false, only include it on the final carousel post
- """
-
- share_to_feed: Optional[bool] = None
- """If false Instagram video posts will only be shown in the Reels tab"""
-
- title: Optional[str] = None
- """Overrides the `title` from the post"""
-
- trial_reel_type: Optional[Literal["manual", "performance"]] = None
- """Instagram trial reel type, when passed will be created as a trial reel.
-
- If manual the trial reel can be manually graduated in the native app. If
- perfomance the trial reel will be automatically graduated if the trial reel
- performs well.
- """
-
-
-class AccountConfiguration(BaseModel):
- configuration: AccountConfigurationConfiguration
- """Configuration for the social account"""
-
- social_account_id: str
- """ID of the social account, you want to apply the configuration to"""
-
-
-class MediaTag(BaseModel):
- id: str
- """Facebook User ID, Instagram Username or Instagram product id to tag"""
-
- platform: Literal["facebook", "instagram"]
- """The platform for the tags"""
-
- type: Literal["user", "product"]
- """
- The type of tag, user to tag accounts, product to tag products (only supported
- for instagram)
- """
-
- x: Optional[float] = None
- """
- Percentage distance from left edge of the image, Not required for videos or
- stories
- """
-
- y: Optional[float] = None
- """
- Percentage distance from top edge of the image, Not required for videos or
- stories
- """
-
-
-class Media(BaseModel):
- url: str
- """Public URL of the media"""
-
- skip_processing: Optional[bool] = None
- """
- If true the media will not be processed at all and instead be posted as is, this
- may increase chance of post failure if media does not meet platform's
- requirements. Best used for larger files.
- """
-
- tags: Optional[List[MediaTag]] = None
- """List of tags to attach to the media"""
-
- thumbnail_timestamp_ms: Optional[object] = None
- """Timestamp in milliseconds of frame to use as thumbnail for the media"""
-
- thumbnail_url: Optional[object] = None
- """Public URL of the thumbnail for the media"""
+__all__ = ["SocialPost"]
class SocialPost(BaseModel):
@@ -249,7 +28,7 @@ class SocialPost(BaseModel):
external_id: Optional[str] = None
"""Provided unique identifier of the post"""
- media: Optional[List[Media]] = None
+ media: Optional[List[SocialPostMedia]] = None
"""Array of media associated with the post"""
platform_configurations: Optional[PlatformConfigurationsDto] = None
diff --git a/src/post_for_me/types/social_post_create_params.py b/src/post_for_me/types/social_post_create_params.py
index d456fbf..001ef0a 100644
--- a/src/post_for_me/types/social_post_create_params.py
+++ b/src/post_for_me/types/social_post_create_params.py
@@ -4,22 +4,15 @@
from typing import Union, Iterable, Optional
from datetime import datetime
-from typing_extensions import Literal, Required, Annotated, TypedDict
+from typing_extensions import Required, Annotated, TypedDict
from .._types import SequenceNotStr
from .._utils import PropertyInfo
+from .social_post_media_param import SocialPostMediaParam
+from .account_configuration_param import AccountConfigurationParam
from .platform_configurations_dto_param import PlatformConfigurationsDtoParam
-__all__ = [
- "SocialPostCreateParams",
- "AccountConfiguration",
- "AccountConfigurationConfiguration",
- "AccountConfigurationConfigurationMedia",
- "AccountConfigurationConfigurationMediaTag",
- "AccountConfigurationConfigurationPoll",
- "Media",
- "MediaTag",
-]
+__all__ = ["SocialPostCreateParams"]
class SocialPostCreateParams(TypedDict, total=False):
@@ -29,7 +22,7 @@ class SocialPostCreateParams(TypedDict, total=False):
social_accounts: Required[SequenceNotStr[str]]
"""Array of social account IDs for posting"""
- account_configurations: Optional[Iterable[AccountConfiguration]]
+ account_configurations: Optional[Iterable[AccountConfigurationParam]]
"""Account-specific configurations for the post"""
external_id: Optional[str]
@@ -38,7 +31,7 @@ class SocialPostCreateParams(TypedDict, total=False):
is_draft: Annotated[Optional[bool], PropertyInfo(alias="isDraft")]
"""If isDraft is set then the post will not be processed"""
- media: Optional[Iterable[Media]]
+ media: Optional[Iterable[SocialPostMediaParam]]
"""Array of media associated with the post.
If multiple media items are provided and the placement is `stories`, individual
@@ -53,217 +46,3 @@ class SocialPostCreateParams(TypedDict, total=False):
Scheduled date and time for the post, setting to null or undefined will post
instantly
"""
-
-
-class AccountConfigurationConfigurationMediaTag(TypedDict, total=False):
- id: Required[str]
- """Facebook User ID, Instagram Username or Instagram product id to tag"""
-
- platform: Required[Literal["facebook", "instagram"]]
- """The platform for the tags"""
-
- type: Required[Literal["user", "product"]]
- """
- The type of tag, user to tag accounts, product to tag products (only supported
- for instagram)
- """
-
- x: float
- """
- Percentage distance from left edge of the image, Not required for videos or
- stories
- """
-
- y: float
- """
- Percentage distance from top edge of the image, Not required for videos or
- stories
- """
-
-
-class AccountConfigurationConfigurationMedia(TypedDict, total=False):
- url: Required[str]
- """Public URL of the media"""
-
- skip_processing: Optional[bool]
- """
- If true the media will not be processed at all and instead be posted as is, this
- may increase chance of post failure if media does not meet platform's
- requirements. Best used for larger files.
- """
-
- tags: Optional[Iterable[AccountConfigurationConfigurationMediaTag]]
- """List of tags to attach to the media"""
-
- thumbnail_timestamp_ms: Optional[object]
- """Timestamp in milliseconds of frame to use as thumbnail for the media"""
-
- thumbnail_url: Optional[object]
- """Public URL of the thumbnail for the media"""
-
-
-class AccountConfigurationConfigurationPoll(TypedDict, total=False):
- """Poll options for the twitter"""
-
- duration_minutes: Required[float]
- """Duration of the poll in minutes"""
-
- options: Required[SequenceNotStr[str]]
- """The choices of the poll, requiring 2-4 options"""
-
- reply_settings: Literal["following", "mentionedUsers", "subscribers", "verified"]
- """Who can reply to the tweet"""
-
-
-class AccountConfigurationConfiguration(TypedDict, total=False):
- """Configuration for the social account"""
-
- allow_comment: Optional[bool]
- """Allow comments on TikTok"""
-
- allow_duet: Optional[bool]
- """Allow duets on TikTok"""
-
- allow_stitch: Optional[bool]
- """Allow stitch on TikTok"""
-
- auto_add_music: Optional[bool]
- """Will automatically add music to photo posts on TikTok"""
-
- board_ids: Optional[SequenceNotStr[str]]
- """Pinterest board IDs"""
-
- caption: Optional[object]
- """Overrides the `caption` from the post"""
-
- collaborators: Optional[Iterable[Iterable[object]]]
- """
- List of page ids or users to invite as collaborators for a Video Reel (Instagram
- and Facebook)
- """
-
- community_id: str
- """Id of the twitter community to post to"""
-
- disclose_branded_content: Optional[bool]
- """Disclose branded content on TikTok"""
-
- disclose_your_brand: Optional[bool]
- """Disclose your brand on TikTok"""
-
- is_ai_generated: Optional[bool]
- """Flag content as AI generated on TikTok"""
-
- is_draft: Optional[bool]
- """
- Will create a draft upload to TikTok, posting will need to be completed from
- within the app
- """
-
- link: Optional[str]
- """Pinterest post link"""
-
- location: Optional[str]
- """
- Page id with a location that you want to tag the image or video with (Instagram
- and Facebook)
- """
-
- made_for_kids: Optional[bool]
- """If true will notify YouTube the video is intended for kids, defaults to false"""
-
- media: Optional[Iterable[AccountConfigurationConfigurationMedia]]
- """Overrides the `media` from the post"""
-
- placement: Optional[Literal["reels", "timeline", "stories"]]
- """Post placement for Facebook/Instagram/Threads"""
-
- poll: AccountConfigurationConfigurationPoll
- """Poll options for the twitter"""
-
- privacy_status: Optional[Literal["public", "private", "unlisted"]]
- """
- Sets the privacy status for TikTok (private, public), or YouTube (private,
- public, unlisted)
- """
-
- quote_tweet_id: str
- """Id of the tweet you want to quote"""
-
- reply_settings: Optional[Literal["following", "mentionedUsers", "subscribers", "verified"]]
- """Who can reply to the tweet"""
-
- set_caption_for_each_image: Optional[bool]
- """
- If true, include the caption on each image in a Facebook carousel upload; if
- false, only include it on the final carousel post
- """
-
- share_to_feed: Optional[bool]
- """If false Instagram video posts will only be shown in the Reels tab"""
-
- title: Optional[str]
- """Overrides the `title` from the post"""
-
- trial_reel_type: Optional[Literal["manual", "performance"]]
- """Instagram trial reel type, when passed will be created as a trial reel.
-
- If manual the trial reel can be manually graduated in the native app. If
- perfomance the trial reel will be automatically graduated if the trial reel
- performs well.
- """
-
-
-class AccountConfiguration(TypedDict, total=False):
- configuration: Required[AccountConfigurationConfiguration]
- """Configuration for the social account"""
-
- social_account_id: Required[str]
- """ID of the social account, you want to apply the configuration to"""
-
-
-class MediaTag(TypedDict, total=False):
- id: Required[str]
- """Facebook User ID, Instagram Username or Instagram product id to tag"""
-
- platform: Required[Literal["facebook", "instagram"]]
- """The platform for the tags"""
-
- type: Required[Literal["user", "product"]]
- """
- The type of tag, user to tag accounts, product to tag products (only supported
- for instagram)
- """
-
- x: float
- """
- Percentage distance from left edge of the image, Not required for videos or
- stories
- """
-
- y: float
- """
- Percentage distance from top edge of the image, Not required for videos or
- stories
- """
-
-
-class Media(TypedDict, total=False):
- url: Required[str]
- """Public URL of the media"""
-
- skip_processing: Optional[bool]
- """
- If true the media will not be processed at all and instead be posted as is, this
- may increase chance of post failure if media does not meet platform's
- requirements. Best used for larger files.
- """
-
- tags: Optional[Iterable[MediaTag]]
- """List of tags to attach to the media"""
-
- thumbnail_timestamp_ms: Optional[object]
- """Timestamp in milliseconds of frame to use as thumbnail for the media"""
-
- thumbnail_url: Optional[object]
- """Public URL of the thumbnail for the media"""
diff --git a/src/post_for_me/types/social_post_media.py b/src/post_for_me/types/social_post_media.py
new file mode 100644
index 0000000..6a1577f
--- /dev/null
+++ b/src/post_for_me/types/social_post_media.py
@@ -0,0 +1,55 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from .._models import BaseModel
+
+__all__ = ["SocialPostMedia", "Tag"]
+
+
+class Tag(BaseModel):
+ id: str
+ """Facebook User ID, Instagram Username or Instagram product id to tag"""
+
+ platform: Literal["facebook", "instagram"]
+ """The platform for the tags"""
+
+ type: Literal["user", "product"]
+ """
+ The type of tag, user to tag accounts, product to tag products (only supported
+ for instagram)
+ """
+
+ x: Optional[float] = None
+ """
+ Percentage distance from left edge of the image, Not required for videos or
+ stories
+ """
+
+ y: Optional[float] = None
+ """
+ Percentage distance from top edge of the image, Not required for videos or
+ stories
+ """
+
+
+class SocialPostMedia(BaseModel):
+ url: str
+ """Public URL of the media"""
+
+ skip_processing: Optional[bool] = None
+ """
+ If true the media will not be processed at all and instead be posted as is, this
+ may increase chance of post failure if media does not meet platform's
+ requirements. Best used for larger files.
+ """
+
+ tags: Optional[List[Tag]] = None
+ """List of tags to attach to the media"""
+
+ thumbnail_timestamp_ms: Optional[object] = None
+ """Timestamp in milliseconds of frame to use as thumbnail for the media"""
+
+ thumbnail_url: Optional[object] = None
+ """Public URL of the thumbnail for the media"""
diff --git a/src/post_for_me/types/social_post_media_param.py b/src/post_for_me/types/social_post_media_param.py
new file mode 100644
index 0000000..4c2079a
--- /dev/null
+++ b/src/post_for_me/types/social_post_media_param.py
@@ -0,0 +1,55 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Iterable, Optional
+from typing_extensions import Literal, Required, TypedDict
+
+__all__ = ["SocialPostMediaParam", "Tag"]
+
+
+class Tag(TypedDict, total=False):
+ id: Required[str]
+ """Facebook User ID, Instagram Username or Instagram product id to tag"""
+
+ platform: Required[Literal["facebook", "instagram"]]
+ """The platform for the tags"""
+
+ type: Required[Literal["user", "product"]]
+ """
+ The type of tag, user to tag accounts, product to tag products (only supported
+ for instagram)
+ """
+
+ x: float
+ """
+ Percentage distance from left edge of the image, Not required for videos or
+ stories
+ """
+
+ y: float
+ """
+ Percentage distance from top edge of the image, Not required for videos or
+ stories
+ """
+
+
+class SocialPostMediaParam(TypedDict, total=False):
+ url: Required[str]
+ """Public URL of the media"""
+
+ skip_processing: Optional[bool]
+ """
+ If true the media will not be processed at all and instead be posted as is, this
+ may increase chance of post failure if media does not meet platform's
+ requirements. Best used for larger files.
+ """
+
+ tags: Optional[Iterable[Tag]]
+ """List of tags to attach to the media"""
+
+ thumbnail_timestamp_ms: Optional[object]
+ """Timestamp in milliseconds of frame to use as thumbnail for the media"""
+
+ thumbnail_url: Optional[object]
+ """Public URL of the thumbnail for the media"""
diff --git a/src/post_for_me/types/social_post_preview.py b/src/post_for_me/types/social_post_preview.py
new file mode 100644
index 0000000..0e6a932
--- /dev/null
+++ b/src/post_for_me/types/social_post_preview.py
@@ -0,0 +1,31 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+
+from .._models import BaseModel
+from .social_post_media import SocialPostMedia
+
+__all__ = ["SocialPostPreview"]
+
+
+class SocialPostPreview(BaseModel):
+ caption: str
+ """Caption text for the post"""
+
+ platform: str
+ """Platform of the post"""
+
+ social_account_id: str
+ """Id of the social account"""
+
+ configuration: Optional[object] = None
+ """Additional configuration for this platform"""
+
+ media: Optional[List[SocialPostMedia]] = None
+ """Array of media URLs associated with the post"""
+
+ social_account_profile_picture_url: Optional[object] = None
+ """Url of the social account profile picture"""
+
+ social_account_username: Optional[object] = None
+ """Username of the social account"""
diff --git a/src/post_for_me/types/social_post_preview_create_params.py b/src/post_for_me/types/social_post_preview_create_params.py
new file mode 100644
index 0000000..c9d8cfd
--- /dev/null
+++ b/src/post_for_me/types/social_post_preview_create_params.py
@@ -0,0 +1,43 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Iterable, Optional
+from typing_extensions import Required, TypedDict
+
+from .social_post_media_param import SocialPostMediaParam
+from .account_configuration_param import AccountConfigurationParam
+from .platform_configurations_dto_param import PlatformConfigurationsDtoParam
+
+__all__ = ["SocialPostPreviewCreateParams", "PreviewSocialAccount"]
+
+
+class SocialPostPreviewCreateParams(TypedDict, total=False):
+ caption: Required[str]
+ """Caption text for the post"""
+
+ preview_social_accounts: Required[Iterable[PreviewSocialAccount]]
+ """Array of social accounts.
+
+ Can preview non connected accounts, just specify a random ID
+ """
+
+ account_configurations: Optional[Iterable[AccountConfigurationParam]]
+ """Account-specific configurations for the post"""
+
+ media: Optional[Iterable[SocialPostMediaParam]]
+ """Array of media URLs associated with the post"""
+
+ platform_configurations: Optional[PlatformConfigurationsDtoParam]
+ """Platform-specific configurations for the post"""
+
+
+class PreviewSocialAccount(TypedDict, total=False):
+ id: Required[str]
+ """ID of the social account, ex: spc_12312"""
+
+ platform: Required[str]
+ """Platform of the social account"""
+
+ username: str
+ """username of the social account"""
diff --git a/src/post_for_me/types/social_post_preview_create_response.py b/src/post_for_me/types/social_post_preview_create_response.py
new file mode 100644
index 0000000..624108c
--- /dev/null
+++ b/src/post_for_me/types/social_post_preview_create_response.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List
+from typing_extensions import TypeAlias
+
+from .social_post_preview import SocialPostPreview
+
+__all__ = ["SocialPostPreviewCreateResponse"]
+
+SocialPostPreviewCreateResponse: TypeAlias = List[SocialPostPreview]
diff --git a/src/post_for_me/types/social_post_result.py b/src/post_for_me/types/social_post_result.py
index c29fbda..ef0bd03 100644
--- a/src/post_for_me/types/social_post_result.py
+++ b/src/post_for_me/types/social_post_result.py
@@ -1,58 +1,11 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
from typing import List, Optional
-from typing_extensions import Literal
from .._models import BaseModel
+from .social_post_media import SocialPostMedia
-__all__ = ["SocialPostResult", "Media", "MediaTag", "PlatformData"]
-
-
-class MediaTag(BaseModel):
- id: str
- """Facebook User ID, Instagram Username or Instagram product id to tag"""
-
- platform: Literal["facebook", "instagram"]
- """The platform for the tags"""
-
- type: Literal["user", "product"]
- """
- The type of tag, user to tag accounts, product to tag products (only supported
- for instagram)
- """
-
- x: Optional[float] = None
- """
- Percentage distance from left edge of the image, Not required for videos or
- stories
- """
-
- y: Optional[float] = None
- """
- Percentage distance from top edge of the image, Not required for videos or
- stories
- """
-
-
-class Media(BaseModel):
- url: str
- """Public URL of the media"""
-
- skip_processing: Optional[bool] = None
- """
- If true the media will not be processed at all and instead be posted as is, this
- may increase chance of post failure if media does not meet platform's
- requirements. Best used for larger files.
- """
-
- tags: Optional[List[MediaTag]] = None
- """List of tags to attach to the media"""
-
- thumbnail_timestamp_ms: Optional[object] = None
- """Timestamp in milliseconds of frame to use as thumbnail for the media"""
-
- thumbnail_url: Optional[object] = None
- """Public URL of the thumbnail for the media"""
+__all__ = ["SocialPostResult", "PlatformData"]
class PlatformData(BaseModel):
@@ -75,7 +28,7 @@ class SocialPostResult(BaseModel):
error: object
"""Error message if the post failed"""
- media: Optional[List[Media]] = None
+ media: Optional[List[SocialPostMedia]] = None
"""Array of media URLs associated with the post"""
platform_data: PlatformData
diff --git a/src/post_for_me/types/social_post_update_params.py b/src/post_for_me/types/social_post_update_params.py
index fa55930..0d89a1b 100644
--- a/src/post_for_me/types/social_post_update_params.py
+++ b/src/post_for_me/types/social_post_update_params.py
@@ -4,22 +4,15 @@
from typing import Union, Iterable, Optional
from datetime import datetime
-from typing_extensions import Literal, Required, Annotated, TypedDict
+from typing_extensions import Required, Annotated, TypedDict
from .._types import SequenceNotStr
from .._utils import PropertyInfo
+from .social_post_media_param import SocialPostMediaParam
+from .account_configuration_param import AccountConfigurationParam
from .platform_configurations_dto_param import PlatformConfigurationsDtoParam
-__all__ = [
- "SocialPostUpdateParams",
- "AccountConfiguration",
- "AccountConfigurationConfiguration",
- "AccountConfigurationConfigurationMedia",
- "AccountConfigurationConfigurationMediaTag",
- "AccountConfigurationConfigurationPoll",
- "Media",
- "MediaTag",
-]
+__all__ = ["SocialPostUpdateParams"]
class SocialPostUpdateParams(TypedDict, total=False):
@@ -29,7 +22,7 @@ class SocialPostUpdateParams(TypedDict, total=False):
social_accounts: Required[SequenceNotStr[str]]
"""Array of social account IDs for posting"""
- account_configurations: Optional[Iterable[AccountConfiguration]]
+ account_configurations: Optional[Iterable[AccountConfigurationParam]]
"""Account-specific configurations for the post"""
external_id: Optional[str]
@@ -38,7 +31,7 @@ class SocialPostUpdateParams(TypedDict, total=False):
is_draft: Annotated[Optional[bool], PropertyInfo(alias="isDraft")]
"""If isDraft is set then the post will not be processed"""
- media: Optional[Iterable[Media]]
+ media: Optional[Iterable[SocialPostMediaParam]]
"""Array of media associated with the post.
If multiple media items are provided and the placement is `stories`, individual
@@ -53,217 +46,3 @@ class SocialPostUpdateParams(TypedDict, total=False):
Scheduled date and time for the post, setting to null or undefined will post
instantly
"""
-
-
-class AccountConfigurationConfigurationMediaTag(TypedDict, total=False):
- id: Required[str]
- """Facebook User ID, Instagram Username or Instagram product id to tag"""
-
- platform: Required[Literal["facebook", "instagram"]]
- """The platform for the tags"""
-
- type: Required[Literal["user", "product"]]
- """
- The type of tag, user to tag accounts, product to tag products (only supported
- for instagram)
- """
-
- x: float
- """
- Percentage distance from left edge of the image, Not required for videos or
- stories
- """
-
- y: float
- """
- Percentage distance from top edge of the image, Not required for videos or
- stories
- """
-
-
-class AccountConfigurationConfigurationMedia(TypedDict, total=False):
- url: Required[str]
- """Public URL of the media"""
-
- skip_processing: Optional[bool]
- """
- If true the media will not be processed at all and instead be posted as is, this
- may increase chance of post failure if media does not meet platform's
- requirements. Best used for larger files.
- """
-
- tags: Optional[Iterable[AccountConfigurationConfigurationMediaTag]]
- """List of tags to attach to the media"""
-
- thumbnail_timestamp_ms: Optional[object]
- """Timestamp in milliseconds of frame to use as thumbnail for the media"""
-
- thumbnail_url: Optional[object]
- """Public URL of the thumbnail for the media"""
-
-
-class AccountConfigurationConfigurationPoll(TypedDict, total=False):
- """Poll options for the twitter"""
-
- duration_minutes: Required[float]
- """Duration of the poll in minutes"""
-
- options: Required[SequenceNotStr[str]]
- """The choices of the poll, requiring 2-4 options"""
-
- reply_settings: Literal["following", "mentionedUsers", "subscribers", "verified"]
- """Who can reply to the tweet"""
-
-
-class AccountConfigurationConfiguration(TypedDict, total=False):
- """Configuration for the social account"""
-
- allow_comment: Optional[bool]
- """Allow comments on TikTok"""
-
- allow_duet: Optional[bool]
- """Allow duets on TikTok"""
-
- allow_stitch: Optional[bool]
- """Allow stitch on TikTok"""
-
- auto_add_music: Optional[bool]
- """Will automatically add music to photo posts on TikTok"""
-
- board_ids: Optional[SequenceNotStr[str]]
- """Pinterest board IDs"""
-
- caption: Optional[object]
- """Overrides the `caption` from the post"""
-
- collaborators: Optional[Iterable[Iterable[object]]]
- """
- List of page ids or users to invite as collaborators for a Video Reel (Instagram
- and Facebook)
- """
-
- community_id: str
- """Id of the twitter community to post to"""
-
- disclose_branded_content: Optional[bool]
- """Disclose branded content on TikTok"""
-
- disclose_your_brand: Optional[bool]
- """Disclose your brand on TikTok"""
-
- is_ai_generated: Optional[bool]
- """Flag content as AI generated on TikTok"""
-
- is_draft: Optional[bool]
- """
- Will create a draft upload to TikTok, posting will need to be completed from
- within the app
- """
-
- link: Optional[str]
- """Pinterest post link"""
-
- location: Optional[str]
- """
- Page id with a location that you want to tag the image or video with (Instagram
- and Facebook)
- """
-
- made_for_kids: Optional[bool]
- """If true will notify YouTube the video is intended for kids, defaults to false"""
-
- media: Optional[Iterable[AccountConfigurationConfigurationMedia]]
- """Overrides the `media` from the post"""
-
- placement: Optional[Literal["reels", "timeline", "stories"]]
- """Post placement for Facebook/Instagram/Threads"""
-
- poll: AccountConfigurationConfigurationPoll
- """Poll options for the twitter"""
-
- privacy_status: Optional[Literal["public", "private", "unlisted"]]
- """
- Sets the privacy status for TikTok (private, public), or YouTube (private,
- public, unlisted)
- """
-
- quote_tweet_id: str
- """Id of the tweet you want to quote"""
-
- reply_settings: Optional[Literal["following", "mentionedUsers", "subscribers", "verified"]]
- """Who can reply to the tweet"""
-
- set_caption_for_each_image: Optional[bool]
- """
- If true, include the caption on each image in a Facebook carousel upload; if
- false, only include it on the final carousel post
- """
-
- share_to_feed: Optional[bool]
- """If false Instagram video posts will only be shown in the Reels tab"""
-
- title: Optional[str]
- """Overrides the `title` from the post"""
-
- trial_reel_type: Optional[Literal["manual", "performance"]]
- """Instagram trial reel type, when passed will be created as a trial reel.
-
- If manual the trial reel can be manually graduated in the native app. If
- perfomance the trial reel will be automatically graduated if the trial reel
- performs well.
- """
-
-
-class AccountConfiguration(TypedDict, total=False):
- configuration: Required[AccountConfigurationConfiguration]
- """Configuration for the social account"""
-
- social_account_id: Required[str]
- """ID of the social account, you want to apply the configuration to"""
-
-
-class MediaTag(TypedDict, total=False):
- id: Required[str]
- """Facebook User ID, Instagram Username or Instagram product id to tag"""
-
- platform: Required[Literal["facebook", "instagram"]]
- """The platform for the tags"""
-
- type: Required[Literal["user", "product"]]
- """
- The type of tag, user to tag accounts, product to tag products (only supported
- for instagram)
- """
-
- x: float
- """
- Percentage distance from left edge of the image, Not required for videos or
- stories
- """
-
- y: float
- """
- Percentage distance from top edge of the image, Not required for videos or
- stories
- """
-
-
-class Media(TypedDict, total=False):
- url: Required[str]
- """Public URL of the media"""
-
- skip_processing: Optional[bool]
- """
- If true the media will not be processed at all and instead be posted as is, this
- may increase chance of post failure if media does not meet platform's
- requirements. Best used for larger files.
- """
-
- tags: Optional[Iterable[MediaTag]]
- """List of tags to attach to the media"""
-
- thumbnail_timestamp_ms: Optional[object]
- """Timestamp in milliseconds of frame to use as thumbnail for the media"""
-
- thumbnail_url: Optional[object]
- """Public URL of the thumbnail for the media"""
diff --git a/src/post_for_me/types/threads_configuration_dto.py b/src/post_for_me/types/threads_configuration_dto.py
index 7bd6e43..aa10440 100644
--- a/src/post_for_me/types/threads_configuration_dto.py
+++ b/src/post_for_me/types/threads_configuration_dto.py
@@ -4,62 +4,16 @@
from typing_extensions import Literal
from .._models import BaseModel
+from .social_post_media import SocialPostMedia
-__all__ = ["ThreadsConfigurationDto", "Media", "MediaTag"]
-
-
-class MediaTag(BaseModel):
- id: str
- """Facebook User ID, Instagram Username or Instagram product id to tag"""
-
- platform: Literal["facebook", "instagram"]
- """The platform for the tags"""
-
- type: Literal["user", "product"]
- """
- The type of tag, user to tag accounts, product to tag products (only supported
- for instagram)
- """
-
- x: Optional[float] = None
- """
- Percentage distance from left edge of the image, Not required for videos or
- stories
- """
-
- y: Optional[float] = None
- """
- Percentage distance from top edge of the image, Not required for videos or
- stories
- """
-
-
-class Media(BaseModel):
- url: str
- """Public URL of the media"""
-
- skip_processing: Optional[bool] = None
- """
- If true the media will not be processed at all and instead be posted as is, this
- may increase chance of post failure if media does not meet platform's
- requirements. Best used for larger files.
- """
-
- tags: Optional[List[MediaTag]] = None
- """List of tags to attach to the media"""
-
- thumbnail_timestamp_ms: Optional[object] = None
- """Timestamp in milliseconds of frame to use as thumbnail for the media"""
-
- thumbnail_url: Optional[object] = None
- """Public URL of the thumbnail for the media"""
+__all__ = ["ThreadsConfigurationDto"]
class ThreadsConfigurationDto(BaseModel):
caption: Optional[object] = None
"""Overrides the `caption` from the post"""
- media: Optional[List[Media]] = None
+ media: Optional[List[SocialPostMedia]] = None
"""Overrides the `media` from the post"""
placement: Optional[Literal["reels", "timeline"]] = None
diff --git a/src/post_for_me/types/threads_configuration_dto_param.py b/src/post_for_me/types/threads_configuration_dto_param.py
index 59e29f2..d872b4b 100644
--- a/src/post_for_me/types/threads_configuration_dto_param.py
+++ b/src/post_for_me/types/threads_configuration_dto_param.py
@@ -3,63 +3,18 @@
from __future__ import annotations
from typing import Iterable, Optional
-from typing_extensions import Literal, Required, TypedDict
+from typing_extensions import Literal, TypedDict
-__all__ = ["ThreadsConfigurationDtoParam", "Media", "MediaTag"]
+from .social_post_media_param import SocialPostMediaParam
-
-class MediaTag(TypedDict, total=False):
- id: Required[str]
- """Facebook User ID, Instagram Username or Instagram product id to tag"""
-
- platform: Required[Literal["facebook", "instagram"]]
- """The platform for the tags"""
-
- type: Required[Literal["user", "product"]]
- """
- The type of tag, user to tag accounts, product to tag products (only supported
- for instagram)
- """
-
- x: float
- """
- Percentage distance from left edge of the image, Not required for videos or
- stories
- """
-
- y: float
- """
- Percentage distance from top edge of the image, Not required for videos or
- stories
- """
-
-
-class Media(TypedDict, total=False):
- url: Required[str]
- """Public URL of the media"""
-
- skip_processing: Optional[bool]
- """
- If true the media will not be processed at all and instead be posted as is, this
- may increase chance of post failure if media does not meet platform's
- requirements. Best used for larger files.
- """
-
- tags: Optional[Iterable[MediaTag]]
- """List of tags to attach to the media"""
-
- thumbnail_timestamp_ms: Optional[object]
- """Timestamp in milliseconds of frame to use as thumbnail for the media"""
-
- thumbnail_url: Optional[object]
- """Public URL of the thumbnail for the media"""
+__all__ = ["ThreadsConfigurationDtoParam"]
class ThreadsConfigurationDtoParam(TypedDict, total=False):
caption: Optional[object]
"""Overrides the `caption` from the post"""
- media: Optional[Iterable[Media]]
+ media: Optional[Iterable[SocialPostMediaParam]]
"""Overrides the `media` from the post"""
placement: Optional[Literal["reels", "timeline"]]
diff --git a/src/post_for_me/types/tiktok_business_video_metric_percentage.py b/src/post_for_me/types/tiktok_business_video_metric_percentage.py
new file mode 100644
index 0000000..4d2d561
--- /dev/null
+++ b/src/post_for_me/types/tiktok_business_video_metric_percentage.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .._models import BaseModel
+
+__all__ = ["TiktokBusinessVideoMetricPercentage"]
+
+
+class TiktokBusinessVideoMetricPercentage(BaseModel):
+ percentage: float
+ """Percentage value for the metric"""
+
+ second: str
+ """Time in seconds for the metric"""
diff --git a/src/post_for_me/types/tiktok_configuration.py b/src/post_for_me/types/tiktok_configuration.py
index c367434..db3af61 100644
--- a/src/post_for_me/types/tiktok_configuration.py
+++ b/src/post_for_me/types/tiktok_configuration.py
@@ -1,58 +1,11 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
from typing import List, Optional
-from typing_extensions import Literal
from .._models import BaseModel
+from .social_post_media import SocialPostMedia
-__all__ = ["TiktokConfiguration", "Media", "MediaTag"]
-
-
-class MediaTag(BaseModel):
- id: str
- """Facebook User ID, Instagram Username or Instagram product id to tag"""
-
- platform: Literal["facebook", "instagram"]
- """The platform for the tags"""
-
- type: Literal["user", "product"]
- """
- The type of tag, user to tag accounts, product to tag products (only supported
- for instagram)
- """
-
- x: Optional[float] = None
- """
- Percentage distance from left edge of the image, Not required for videos or
- stories
- """
-
- y: Optional[float] = None
- """
- Percentage distance from top edge of the image, Not required for videos or
- stories
- """
-
-
-class Media(BaseModel):
- url: str
- """Public URL of the media"""
-
- skip_processing: Optional[bool] = None
- """
- If true the media will not be processed at all and instead be posted as is, this
- may increase chance of post failure if media does not meet platform's
- requirements. Best used for larger files.
- """
-
- tags: Optional[List[MediaTag]] = None
- """List of tags to attach to the media"""
-
- thumbnail_timestamp_ms: Optional[object] = None
- """Timestamp in milliseconds of frame to use as thumbnail for the media"""
-
- thumbnail_url: Optional[object] = None
- """Public URL of the thumbnail for the media"""
+__all__ = ["TiktokConfiguration"]
class TiktokConfiguration(BaseModel):
@@ -86,7 +39,7 @@ class TiktokConfiguration(BaseModel):
within the app
"""
- media: Optional[List[Media]] = None
+ media: Optional[List[SocialPostMedia]] = None
"""Overrides the `media` from the post"""
privacy_status: Optional[str] = None
diff --git a/src/post_for_me/types/tiktok_configuration_param.py b/src/post_for_me/types/tiktok_configuration_param.py
index 84afe86..254bc9e 100644
--- a/src/post_for_me/types/tiktok_configuration_param.py
+++ b/src/post_for_me/types/tiktok_configuration_param.py
@@ -3,56 +3,11 @@
from __future__ import annotations
from typing import Iterable, Optional
-from typing_extensions import Literal, Required, TypedDict
+from typing_extensions import TypedDict
-__all__ = ["TiktokConfigurationParam", "Media", "MediaTag"]
+from .social_post_media_param import SocialPostMediaParam
-
-class MediaTag(TypedDict, total=False):
- id: Required[str]
- """Facebook User ID, Instagram Username or Instagram product id to tag"""
-
- platform: Required[Literal["facebook", "instagram"]]
- """The platform for the tags"""
-
- type: Required[Literal["user", "product"]]
- """
- The type of tag, user to tag accounts, product to tag products (only supported
- for instagram)
- """
-
- x: float
- """
- Percentage distance from left edge of the image, Not required for videos or
- stories
- """
-
- y: float
- """
- Percentage distance from top edge of the image, Not required for videos or
- stories
- """
-
-
-class Media(TypedDict, total=False):
- url: Required[str]
- """Public URL of the media"""
-
- skip_processing: Optional[bool]
- """
- If true the media will not be processed at all and instead be posted as is, this
- may increase chance of post failure if media does not meet platform's
- requirements. Best used for larger files.
- """
-
- tags: Optional[Iterable[MediaTag]]
- """List of tags to attach to the media"""
-
- thumbnail_timestamp_ms: Optional[object]
- """Timestamp in milliseconds of frame to use as thumbnail for the media"""
-
- thumbnail_url: Optional[object]
- """Public URL of the thumbnail for the media"""
+__all__ = ["TiktokConfigurationParam"]
class TiktokConfigurationParam(TypedDict, total=False):
@@ -86,7 +41,7 @@ class TiktokConfigurationParam(TypedDict, total=False):
within the app
"""
- media: Optional[Iterable[Media]]
+ media: Optional[Iterable[SocialPostMediaParam]]
"""Overrides the `media` from the post"""
privacy_status: Optional[str]
diff --git a/src/post_for_me/types/twitter_configuration_dto.py b/src/post_for_me/types/twitter_configuration_dto.py
index 1237d86..833bdfa 100644
--- a/src/post_for_me/types/twitter_configuration_dto.py
+++ b/src/post_for_me/types/twitter_configuration_dto.py
@@ -4,68 +4,10 @@
from typing_extensions import Literal
from .._models import BaseModel
+from .twitter_poll import TwitterPoll
+from .social_post_media import SocialPostMedia
-__all__ = ["TwitterConfigurationDto", "Media", "MediaTag", "Poll"]
-
-
-class MediaTag(BaseModel):
- id: str
- """Facebook User ID, Instagram Username or Instagram product id to tag"""
-
- platform: Literal["facebook", "instagram"]
- """The platform for the tags"""
-
- type: Literal["user", "product"]
- """
- The type of tag, user to tag accounts, product to tag products (only supported
- for instagram)
- """
-
- x: Optional[float] = None
- """
- Percentage distance from left edge of the image, Not required for videos or
- stories
- """
-
- y: Optional[float] = None
- """
- Percentage distance from top edge of the image, Not required for videos or
- stories
- """
-
-
-class Media(BaseModel):
- url: str
- """Public URL of the media"""
-
- skip_processing: Optional[bool] = None
- """
- If true the media will not be processed at all and instead be posted as is, this
- may increase chance of post failure if media does not meet platform's
- requirements. Best used for larger files.
- """
-
- tags: Optional[List[MediaTag]] = None
- """List of tags to attach to the media"""
-
- thumbnail_timestamp_ms: Optional[object] = None
- """Timestamp in milliseconds of frame to use as thumbnail for the media"""
-
- thumbnail_url: Optional[object] = None
- """Public URL of the thumbnail for the media"""
-
-
-class Poll(BaseModel):
- """Poll options for the tweet"""
-
- duration_minutes: float
- """Duration of the poll in minutes"""
-
- options: List[str]
- """The choices of the poll, requiring 2-4 options"""
-
- reply_settings: Optional[Literal["following", "mentionedUsers", "subscribers", "verified"]] = None
- """Who can reply to the tweet"""
+__all__ = ["TwitterConfigurationDto"]
class TwitterConfigurationDto(BaseModel):
@@ -75,10 +17,10 @@ class TwitterConfigurationDto(BaseModel):
community_id: Optional[str] = None
"""Id of the community to post to"""
- media: Optional[List[Media]] = None
+ media: Optional[List[SocialPostMedia]] = None
"""Overrides the `media` from the post"""
- poll: Optional[Poll] = None
+ poll: Optional[TwitterPoll] = None
"""Poll options for the tweet"""
quote_tweet_id: Optional[str] = None
diff --git a/src/post_for_me/types/twitter_configuration_dto_param.py b/src/post_for_me/types/twitter_configuration_dto_param.py
index 32baeb6..bde0686 100644
--- a/src/post_for_me/types/twitter_configuration_dto_param.py
+++ b/src/post_for_me/types/twitter_configuration_dto_param.py
@@ -3,71 +3,12 @@
from __future__ import annotations
from typing import Iterable, Optional
-from typing_extensions import Literal, Required, TypedDict
+from typing_extensions import Literal, TypedDict
-from .._types import SequenceNotStr
+from .twitter_poll_param import TwitterPollParam
+from .social_post_media_param import SocialPostMediaParam
-__all__ = ["TwitterConfigurationDtoParam", "Media", "MediaTag", "Poll"]
-
-
-class MediaTag(TypedDict, total=False):
- id: Required[str]
- """Facebook User ID, Instagram Username or Instagram product id to tag"""
-
- platform: Required[Literal["facebook", "instagram"]]
- """The platform for the tags"""
-
- type: Required[Literal["user", "product"]]
- """
- The type of tag, user to tag accounts, product to tag products (only supported
- for instagram)
- """
-
- x: float
- """
- Percentage distance from left edge of the image, Not required for videos or
- stories
- """
-
- y: float
- """
- Percentage distance from top edge of the image, Not required for videos or
- stories
- """
-
-
-class Media(TypedDict, total=False):
- url: Required[str]
- """Public URL of the media"""
-
- skip_processing: Optional[bool]
- """
- If true the media will not be processed at all and instead be posted as is, this
- may increase chance of post failure if media does not meet platform's
- requirements. Best used for larger files.
- """
-
- tags: Optional[Iterable[MediaTag]]
- """List of tags to attach to the media"""
-
- thumbnail_timestamp_ms: Optional[object]
- """Timestamp in milliseconds of frame to use as thumbnail for the media"""
-
- thumbnail_url: Optional[object]
- """Public URL of the thumbnail for the media"""
-
-
-class Poll(TypedDict, total=False):
- """Poll options for the tweet"""
-
- duration_minutes: Required[float]
- """Duration of the poll in minutes"""
-
- options: Required[SequenceNotStr[str]]
- """The choices of the poll, requiring 2-4 options"""
-
- reply_settings: Literal["following", "mentionedUsers", "subscribers", "verified"]
- """Who can reply to the tweet"""
+__all__ = ["TwitterConfigurationDtoParam"]
class TwitterConfigurationDtoParam(TypedDict, total=False):
@@ -77,10 +18,10 @@ class TwitterConfigurationDtoParam(TypedDict, total=False):
community_id: str
"""Id of the community to post to"""
- media: Optional[Iterable[Media]]
+ media: Optional[Iterable[SocialPostMediaParam]]
"""Overrides the `media` from the post"""
- poll: Poll
+ poll: TwitterPollParam
"""Poll options for the tweet"""
quote_tweet_id: str
diff --git a/src/post_for_me/types/twitter_poll.py b/src/post_for_me/types/twitter_poll.py
new file mode 100644
index 0000000..01c4c9b
--- /dev/null
+++ b/src/post_for_me/types/twitter_poll.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from .._models import BaseModel
+
+__all__ = ["TwitterPoll"]
+
+
+class TwitterPoll(BaseModel):
+ duration_minutes: float
+ """Duration of the poll in minutes"""
+
+ options: List[str]
+ """The choices of the poll, requiring 2-4 options"""
+
+ reply_settings: Optional[Literal["following", "mentionedUsers", "subscribers", "verified"]] = None
+ """Who can reply to the tweet"""
diff --git a/src/post_for_me/types/twitter_poll_param.py b/src/post_for_me/types/twitter_poll_param.py
new file mode 100644
index 0000000..2f2013a
--- /dev/null
+++ b/src/post_for_me/types/twitter_poll_param.py
@@ -0,0 +1,20 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, Required, TypedDict
+
+from .._types import SequenceNotStr
+
+__all__ = ["TwitterPollParam"]
+
+
+class TwitterPollParam(TypedDict, total=False):
+ duration_minutes: Required[float]
+ """Duration of the poll in minutes"""
+
+ options: Required[SequenceNotStr[str]]
+ """The choices of the poll, requiring 2-4 options"""
+
+ reply_settings: Literal["following", "mentionedUsers", "subscribers", "verified"]
+ """Who can reply to the tweet"""
diff --git a/src/post_for_me/types/webhook.py b/src/post_for_me/types/webhook.py
new file mode 100644
index 0000000..d55b0ae
--- /dev/null
+++ b/src/post_for_me/types/webhook.py
@@ -0,0 +1,21 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List
+
+from .._models import BaseModel
+
+__all__ = ["Webhook"]
+
+
+class Webhook(BaseModel):
+ id: str
+ """The unique identifier of the webhook"""
+
+ event_types: List[str]
+ """Events that will be sent to the webhook"""
+
+ secret: str
+ """Secret key used to verify webhook post"""
+
+ url: str
+ """The public webhook url"""
diff --git a/src/post_for_me/types/webhook_create_params.py b/src/post_for_me/types/webhook_create_params.py
new file mode 100644
index 0000000..9b25e0b
--- /dev/null
+++ b/src/post_for_me/types/webhook_create_params.py
@@ -0,0 +1,27 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List
+from typing_extensions import Literal, Required, TypedDict
+
+__all__ = ["WebhookCreateParams"]
+
+
+class WebhookCreateParams(TypedDict, total=False):
+ event_types: Required[
+ List[
+ Literal[
+ "social.post.created",
+ "social.post.updated",
+ "social.post.deleted",
+ "social.post.result.created",
+ "social.account.created",
+ "social.account.updated",
+ ]
+ ]
+ ]
+ """List of events the webhook will recieve"""
+
+ url: Required[str]
+ """Public url to recieve event data"""
diff --git a/src/post_for_me/types/webhook_list_params.py b/src/post_for_me/types/webhook_list_params.py
new file mode 100644
index 0000000..bce2d52
--- /dev/null
+++ b/src/post_for_me/types/webhook_list_params.py
@@ -0,0 +1,37 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+from .._types import SequenceNotStr
+
+__all__ = ["WebhookListParams"]
+
+
+class WebhookListParams(TypedDict, total=False):
+ id: SequenceNotStr[str]
+ """Filter by id(s).
+
+ Multiple values imply OR logic (e.g., ?id=wbh_xxxxxx&id=wbh_yyyyyy).
+ """
+
+ event_type: SequenceNotStr[str]
+ """Filter by event type(s).
+
+ Multiple values imply OR logic (e.g.,
+ ?event_type=social.post.created&event_type=social.post.updated).
+ """
+
+ limit: float
+ """Number of items to return"""
+
+ offset: float
+ """Number of items to skip"""
+
+ url: SequenceNotStr[str]
+ """Filter by url(s).
+
+ Multiple values imply OR logic (e.g.,
+ ?url=https://example.com&url=https://postforme.dev).
+ """
diff --git a/src/post_for_me/types/webhook_list_response.py b/src/post_for_me/types/webhook_list_response.py
new file mode 100644
index 0000000..20b5a26
--- /dev/null
+++ b/src/post_for_me/types/webhook_list_response.py
@@ -0,0 +1,28 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+
+from .webhook import Webhook
+from .._models import BaseModel
+
+__all__ = ["WebhookListResponse", "Meta"]
+
+
+class Meta(BaseModel):
+ limit: float
+ """Maximum number of items returned."""
+
+ next: Optional[str] = None
+ """URL to the next page of results, or null if none."""
+
+ offset: float
+ """Number of items skipped."""
+
+ total: float
+ """Total number of items available."""
+
+
+class WebhookListResponse(BaseModel):
+ data: List[Webhook]
+
+ meta: Meta
diff --git a/src/post_for_me/types/webhook_update_params.py b/src/post_for_me/types/webhook_update_params.py
new file mode 100644
index 0000000..972aeb7
--- /dev/null
+++ b/src/post_for_me/types/webhook_update_params.py
@@ -0,0 +1,25 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import List
+from typing_extensions import Literal, TypedDict
+
+__all__ = ["WebhookUpdateParams"]
+
+
+class WebhookUpdateParams(TypedDict, total=False):
+ event_types: List[
+ Literal[
+ "social.post.created",
+ "social.post.updated",
+ "social.post.deleted",
+ "social.post.result.created",
+ "social.account.created",
+ "social.account.updated",
+ ]
+ ]
+ """List of events the webhook will recieve"""
+
+ url: str
+ """Public url to recieve event data"""
diff --git a/src/post_for_me/types/youtube_configuration_dto.py b/src/post_for_me/types/youtube_configuration_dto.py
index 87f3cd8..71bcf6f 100644
--- a/src/post_for_me/types/youtube_configuration_dto.py
+++ b/src/post_for_me/types/youtube_configuration_dto.py
@@ -4,55 +4,9 @@
from typing_extensions import Literal
from .._models import BaseModel
+from .social_post_media import SocialPostMedia
-__all__ = ["YoutubeConfigurationDto", "Media", "MediaTag"]
-
-
-class MediaTag(BaseModel):
- id: str
- """Facebook User ID, Instagram Username or Instagram product id to tag"""
-
- platform: Literal["facebook", "instagram"]
- """The platform for the tags"""
-
- type: Literal["user", "product"]
- """
- The type of tag, user to tag accounts, product to tag products (only supported
- for instagram)
- """
-
- x: Optional[float] = None
- """
- Percentage distance from left edge of the image, Not required for videos or
- stories
- """
-
- y: Optional[float] = None
- """
- Percentage distance from top edge of the image, Not required for videos or
- stories
- """
-
-
-class Media(BaseModel):
- url: str
- """Public URL of the media"""
-
- skip_processing: Optional[bool] = None
- """
- If true the media will not be processed at all and instead be posted as is, this
- may increase chance of post failure if media does not meet platform's
- requirements. Best used for larger files.
- """
-
- tags: Optional[List[MediaTag]] = None
- """List of tags to attach to the media"""
-
- thumbnail_timestamp_ms: Optional[object] = None
- """Timestamp in milliseconds of frame to use as thumbnail for the media"""
-
- thumbnail_url: Optional[object] = None
- """Public URL of the thumbnail for the media"""
+__all__ = ["YoutubeConfigurationDto"]
class YoutubeConfigurationDto(BaseModel):
@@ -62,7 +16,7 @@ class YoutubeConfigurationDto(BaseModel):
made_for_kids: Optional[bool] = None
"""If true will notify YouTube the video is intended for kids, defaults to false"""
- media: Optional[List[Media]] = None
+ media: Optional[List[SocialPostMedia]] = None
"""Overrides the `media` from the post"""
privacy_status: Optional[Literal["public", "private", "unlisted"]] = None
diff --git a/src/post_for_me/types/youtube_configuration_dto_param.py b/src/post_for_me/types/youtube_configuration_dto_param.py
index c44e625..d0ab01c 100644
--- a/src/post_for_me/types/youtube_configuration_dto_param.py
+++ b/src/post_for_me/types/youtube_configuration_dto_param.py
@@ -3,56 +3,11 @@
from __future__ import annotations
from typing import Iterable, Optional
-from typing_extensions import Literal, Required, TypedDict
+from typing_extensions import Literal, TypedDict
-__all__ = ["YoutubeConfigurationDtoParam", "Media", "MediaTag"]
+from .social_post_media_param import SocialPostMediaParam
-
-class MediaTag(TypedDict, total=False):
- id: Required[str]
- """Facebook User ID, Instagram Username or Instagram product id to tag"""
-
- platform: Required[Literal["facebook", "instagram"]]
- """The platform for the tags"""
-
- type: Required[Literal["user", "product"]]
- """
- The type of tag, user to tag accounts, product to tag products (only supported
- for instagram)
- """
-
- x: float
- """
- Percentage distance from left edge of the image, Not required for videos or
- stories
- """
-
- y: float
- """
- Percentage distance from top edge of the image, Not required for videos or
- stories
- """
-
-
-class Media(TypedDict, total=False):
- url: Required[str]
- """Public URL of the media"""
-
- skip_processing: Optional[bool]
- """
- If true the media will not be processed at all and instead be posted as is, this
- may increase chance of post failure if media does not meet platform's
- requirements. Best used for larger files.
- """
-
- tags: Optional[Iterable[MediaTag]]
- """List of tags to attach to the media"""
-
- thumbnail_timestamp_ms: Optional[object]
- """Timestamp in milliseconds of frame to use as thumbnail for the media"""
-
- thumbnail_url: Optional[object]
- """Public URL of the thumbnail for the media"""
+__all__ = ["YoutubeConfigurationDtoParam"]
class YoutubeConfigurationDtoParam(TypedDict, total=False):
@@ -62,7 +17,7 @@ class YoutubeConfigurationDtoParam(TypedDict, total=False):
made_for_kids: Optional[bool]
"""If true will notify YouTube the video is intended for kids, defaults to false"""
- media: Optional[Iterable[Media]]
+ media: Optional[Iterable[SocialPostMediaParam]]
"""Overrides the `media` from the post"""
privacy_status: Optional[Literal["public", "private", "unlisted"]]
diff --git a/src/post_for_me/types/youtube_post_platform_data.py b/src/post_for_me/types/youtube_post_platform_data.py
new file mode 100644
index 0000000..c447143
--- /dev/null
+++ b/src/post_for_me/types/youtube_post_platform_data.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .._models import BaseModel
+
+__all__ = ["YoutubePostPlatformData"]
+
+
+class YoutubePostPlatformData(BaseModel):
+ title: str
+ """Title of the post"""
diff --git a/tests/api_resources/test_social_post_previews.py b/tests/api_resources/test_social_post_previews.py
new file mode 100644
index 0000000..b2fc3a1
--- /dev/null
+++ b/tests/api_resources/test_social_post_previews.py
@@ -0,0 +1,790 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from post_for_me import PostForMe, AsyncPostForMe
+from tests.utils import assert_matches_type
+from post_for_me.types import (
+ SocialPostPreviewCreateResponse,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestSocialPostPreviews:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_create(self, client: PostForMe) -> None:
+ social_post_preview = client.social_post_previews.create(
+ caption="caption",
+ preview_social_accounts=[
+ {
+ "id": "id",
+ "platform": "platform",
+ }
+ ],
+ )
+ assert_matches_type(SocialPostPreviewCreateResponse, social_post_preview, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_create_with_all_params(self, client: PostForMe) -> None:
+ social_post_preview = client.social_post_previews.create(
+ caption="caption",
+ preview_social_accounts=[
+ {
+ "id": "id",
+ "platform": "platform",
+ "username": "username",
+ }
+ ],
+ account_configurations=[
+ {
+ "configuration": {
+ "allow_comment": True,
+ "allow_duet": True,
+ "allow_stitch": True,
+ "auto_add_music": True,
+ "board_ids": ["string"],
+ "caption": {},
+ "collaborators": [[{}]],
+ "community_id": "community_id",
+ "disclose_branded_content": True,
+ "disclose_your_brand": True,
+ "is_ai_generated": True,
+ "is_draft": True,
+ "link": "link",
+ "location": "location",
+ "made_for_kids": True,
+ "media": [
+ {
+ "url": "url",
+ "skip_processing": True,
+ "tags": [
+ {
+ "id": "id",
+ "platform": "facebook",
+ "type": "user",
+ "x": 0,
+ "y": 0,
+ }
+ ],
+ "thumbnail_timestamp_ms": {},
+ "thumbnail_url": {},
+ }
+ ],
+ "placement": "reels",
+ "poll": {
+ "duration_minutes": 0,
+ "options": ["string"],
+ "reply_settings": "following",
+ },
+ "privacy_status": "public",
+ "quote_tweet_id": "quote_tweet_id",
+ "reply_settings": "following",
+ "set_caption_for_each_image": True,
+ "share_to_feed": True,
+ "title": "title",
+ "trial_reel_type": "manual",
+ },
+ "social_account_id": "social_account_id",
+ }
+ ],
+ media=[
+ {
+ "url": "url",
+ "skip_processing": True,
+ "tags": [
+ {
+ "id": "id",
+ "platform": "facebook",
+ "type": "user",
+ "x": 0,
+ "y": 0,
+ }
+ ],
+ "thumbnail_timestamp_ms": {},
+ "thumbnail_url": {},
+ }
+ ],
+ platform_configurations={
+ "bluesky": {
+ "caption": {},
+ "media": [
+ {
+ "url": "url",
+ "skip_processing": True,
+ "tags": [
+ {
+ "id": "id",
+ "platform": "facebook",
+ "type": "user",
+ "x": 0,
+ "y": 0,
+ }
+ ],
+ "thumbnail_timestamp_ms": {},
+ "thumbnail_url": {},
+ }
+ ],
+ },
+ "facebook": {
+ "caption": {},
+ "collaborators": [[{}]],
+ "location": "location",
+ "media": [
+ {
+ "url": "url",
+ "skip_processing": True,
+ "tags": [
+ {
+ "id": "id",
+ "platform": "facebook",
+ "type": "user",
+ "x": 0,
+ "y": 0,
+ }
+ ],
+ "thumbnail_timestamp_ms": {},
+ "thumbnail_url": {},
+ }
+ ],
+ "placement": "reels",
+ "set_caption_for_each_image": True,
+ },
+ "instagram": {
+ "caption": {},
+ "collaborators": ["string"],
+ "location": "location",
+ "media": [
+ {
+ "url": "url",
+ "skip_processing": True,
+ "tags": [
+ {
+ "id": "id",
+ "platform": "facebook",
+ "type": "user",
+ "x": 0,
+ "y": 0,
+ }
+ ],
+ "thumbnail_timestamp_ms": {},
+ "thumbnail_url": {},
+ }
+ ],
+ "placement": "reels",
+ "share_to_feed": True,
+ "trial_reel_type": "manual",
+ },
+ "linkedin": {
+ "caption": {},
+ "media": [
+ {
+ "url": "url",
+ "skip_processing": True,
+ "tags": [
+ {
+ "id": "id",
+ "platform": "facebook",
+ "type": "user",
+ "x": 0,
+ "y": 0,
+ }
+ ],
+ "thumbnail_timestamp_ms": {},
+ "thumbnail_url": {},
+ }
+ ],
+ },
+ "pinterest": {
+ "board_ids": ["string"],
+ "caption": {},
+ "link": "link",
+ "media": [
+ {
+ "url": "url",
+ "skip_processing": True,
+ "tags": [
+ {
+ "id": "id",
+ "platform": "facebook",
+ "type": "user",
+ "x": 0,
+ "y": 0,
+ }
+ ],
+ "thumbnail_timestamp_ms": {},
+ "thumbnail_url": {},
+ }
+ ],
+ "title": "title",
+ },
+ "threads": {
+ "caption": {},
+ "media": [
+ {
+ "url": "url",
+ "skip_processing": True,
+ "tags": [
+ {
+ "id": "id",
+ "platform": "facebook",
+ "type": "user",
+ "x": 0,
+ "y": 0,
+ }
+ ],
+ "thumbnail_timestamp_ms": {},
+ "thumbnail_url": {},
+ }
+ ],
+ "placement": "reels",
+ },
+ "tiktok": {
+ "allow_comment": True,
+ "allow_duet": True,
+ "allow_stitch": True,
+ "auto_add_music": True,
+ "caption": {},
+ "disclose_branded_content": True,
+ "disclose_your_brand": True,
+ "is_ai_generated": True,
+ "is_draft": True,
+ "media": [
+ {
+ "url": "url",
+ "skip_processing": True,
+ "tags": [
+ {
+ "id": "id",
+ "platform": "facebook",
+ "type": "user",
+ "x": 0,
+ "y": 0,
+ }
+ ],
+ "thumbnail_timestamp_ms": {},
+ "thumbnail_url": {},
+ }
+ ],
+ "privacy_status": "privacy_status",
+ "title": "title",
+ },
+ "tiktok_business": {
+ "allow_comment": True,
+ "allow_duet": True,
+ "allow_stitch": True,
+ "auto_add_music": True,
+ "caption": {},
+ "disclose_branded_content": True,
+ "disclose_your_brand": True,
+ "is_ai_generated": True,
+ "is_draft": True,
+ "media": [
+ {
+ "url": "url",
+ "skip_processing": True,
+ "tags": [
+ {
+ "id": "id",
+ "platform": "facebook",
+ "type": "user",
+ "x": 0,
+ "y": 0,
+ }
+ ],
+ "thumbnail_timestamp_ms": {},
+ "thumbnail_url": {},
+ }
+ ],
+ "privacy_status": "privacy_status",
+ "title": "title",
+ },
+ "x": {
+ "caption": {},
+ "community_id": "community_id",
+ "media": [
+ {
+ "url": "url",
+ "skip_processing": True,
+ "tags": [
+ {
+ "id": "id",
+ "platform": "facebook",
+ "type": "user",
+ "x": 0,
+ "y": 0,
+ }
+ ],
+ "thumbnail_timestamp_ms": {},
+ "thumbnail_url": {},
+ }
+ ],
+ "poll": {
+ "duration_minutes": 0,
+ "options": ["string"],
+ "reply_settings": "following",
+ },
+ "quote_tweet_id": "quote_tweet_id",
+ "reply_settings": "following",
+ },
+ "youtube": {
+ "caption": {},
+ "made_for_kids": True,
+ "media": [
+ {
+ "url": "url",
+ "skip_processing": True,
+ "tags": [
+ {
+ "id": "id",
+ "platform": "facebook",
+ "type": "user",
+ "x": 0,
+ "y": 0,
+ }
+ ],
+ "thumbnail_timestamp_ms": {},
+ "thumbnail_url": {},
+ }
+ ],
+ "privacy_status": "public",
+ "title": "title",
+ },
+ },
+ )
+ assert_matches_type(SocialPostPreviewCreateResponse, social_post_preview, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_create(self, client: PostForMe) -> None:
+ response = client.social_post_previews.with_raw_response.create(
+ caption="caption",
+ preview_social_accounts=[
+ {
+ "id": "id",
+ "platform": "platform",
+ }
+ ],
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ social_post_preview = response.parse()
+ assert_matches_type(SocialPostPreviewCreateResponse, social_post_preview, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_create(self, client: PostForMe) -> None:
+ with client.social_post_previews.with_streaming_response.create(
+ caption="caption",
+ preview_social_accounts=[
+ {
+ "id": "id",
+ "platform": "platform",
+ }
+ ],
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ social_post_preview = response.parse()
+ assert_matches_type(SocialPostPreviewCreateResponse, social_post_preview, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncSocialPostPreviews:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_create(self, async_client: AsyncPostForMe) -> None:
+ social_post_preview = await async_client.social_post_previews.create(
+ caption="caption",
+ preview_social_accounts=[
+ {
+ "id": "id",
+ "platform": "platform",
+ }
+ ],
+ )
+ assert_matches_type(SocialPostPreviewCreateResponse, social_post_preview, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_create_with_all_params(self, async_client: AsyncPostForMe) -> None:
+ social_post_preview = await async_client.social_post_previews.create(
+ caption="caption",
+ preview_social_accounts=[
+ {
+ "id": "id",
+ "platform": "platform",
+ "username": "username",
+ }
+ ],
+ account_configurations=[
+ {
+ "configuration": {
+ "allow_comment": True,
+ "allow_duet": True,
+ "allow_stitch": True,
+ "auto_add_music": True,
+ "board_ids": ["string"],
+ "caption": {},
+ "collaborators": [[{}]],
+ "community_id": "community_id",
+ "disclose_branded_content": True,
+ "disclose_your_brand": True,
+ "is_ai_generated": True,
+ "is_draft": True,
+ "link": "link",
+ "location": "location",
+ "made_for_kids": True,
+ "media": [
+ {
+ "url": "url",
+ "skip_processing": True,
+ "tags": [
+ {
+ "id": "id",
+ "platform": "facebook",
+ "type": "user",
+ "x": 0,
+ "y": 0,
+ }
+ ],
+ "thumbnail_timestamp_ms": {},
+ "thumbnail_url": {},
+ }
+ ],
+ "placement": "reels",
+ "poll": {
+ "duration_minutes": 0,
+ "options": ["string"],
+ "reply_settings": "following",
+ },
+ "privacy_status": "public",
+ "quote_tweet_id": "quote_tweet_id",
+ "reply_settings": "following",
+ "set_caption_for_each_image": True,
+ "share_to_feed": True,
+ "title": "title",
+ "trial_reel_type": "manual",
+ },
+ "social_account_id": "social_account_id",
+ }
+ ],
+ media=[
+ {
+ "url": "url",
+ "skip_processing": True,
+ "tags": [
+ {
+ "id": "id",
+ "platform": "facebook",
+ "type": "user",
+ "x": 0,
+ "y": 0,
+ }
+ ],
+ "thumbnail_timestamp_ms": {},
+ "thumbnail_url": {},
+ }
+ ],
+ platform_configurations={
+ "bluesky": {
+ "caption": {},
+ "media": [
+ {
+ "url": "url",
+ "skip_processing": True,
+ "tags": [
+ {
+ "id": "id",
+ "platform": "facebook",
+ "type": "user",
+ "x": 0,
+ "y": 0,
+ }
+ ],
+ "thumbnail_timestamp_ms": {},
+ "thumbnail_url": {},
+ }
+ ],
+ },
+ "facebook": {
+ "caption": {},
+ "collaborators": [[{}]],
+ "location": "location",
+ "media": [
+ {
+ "url": "url",
+ "skip_processing": True,
+ "tags": [
+ {
+ "id": "id",
+ "platform": "facebook",
+ "type": "user",
+ "x": 0,
+ "y": 0,
+ }
+ ],
+ "thumbnail_timestamp_ms": {},
+ "thumbnail_url": {},
+ }
+ ],
+ "placement": "reels",
+ "set_caption_for_each_image": True,
+ },
+ "instagram": {
+ "caption": {},
+ "collaborators": ["string"],
+ "location": "location",
+ "media": [
+ {
+ "url": "url",
+ "skip_processing": True,
+ "tags": [
+ {
+ "id": "id",
+ "platform": "facebook",
+ "type": "user",
+ "x": 0,
+ "y": 0,
+ }
+ ],
+ "thumbnail_timestamp_ms": {},
+ "thumbnail_url": {},
+ }
+ ],
+ "placement": "reels",
+ "share_to_feed": True,
+ "trial_reel_type": "manual",
+ },
+ "linkedin": {
+ "caption": {},
+ "media": [
+ {
+ "url": "url",
+ "skip_processing": True,
+ "tags": [
+ {
+ "id": "id",
+ "platform": "facebook",
+ "type": "user",
+ "x": 0,
+ "y": 0,
+ }
+ ],
+ "thumbnail_timestamp_ms": {},
+ "thumbnail_url": {},
+ }
+ ],
+ },
+ "pinterest": {
+ "board_ids": ["string"],
+ "caption": {},
+ "link": "link",
+ "media": [
+ {
+ "url": "url",
+ "skip_processing": True,
+ "tags": [
+ {
+ "id": "id",
+ "platform": "facebook",
+ "type": "user",
+ "x": 0,
+ "y": 0,
+ }
+ ],
+ "thumbnail_timestamp_ms": {},
+ "thumbnail_url": {},
+ }
+ ],
+ "title": "title",
+ },
+ "threads": {
+ "caption": {},
+ "media": [
+ {
+ "url": "url",
+ "skip_processing": True,
+ "tags": [
+ {
+ "id": "id",
+ "platform": "facebook",
+ "type": "user",
+ "x": 0,
+ "y": 0,
+ }
+ ],
+ "thumbnail_timestamp_ms": {},
+ "thumbnail_url": {},
+ }
+ ],
+ "placement": "reels",
+ },
+ "tiktok": {
+ "allow_comment": True,
+ "allow_duet": True,
+ "allow_stitch": True,
+ "auto_add_music": True,
+ "caption": {},
+ "disclose_branded_content": True,
+ "disclose_your_brand": True,
+ "is_ai_generated": True,
+ "is_draft": True,
+ "media": [
+ {
+ "url": "url",
+ "skip_processing": True,
+ "tags": [
+ {
+ "id": "id",
+ "platform": "facebook",
+ "type": "user",
+ "x": 0,
+ "y": 0,
+ }
+ ],
+ "thumbnail_timestamp_ms": {},
+ "thumbnail_url": {},
+ }
+ ],
+ "privacy_status": "privacy_status",
+ "title": "title",
+ },
+ "tiktok_business": {
+ "allow_comment": True,
+ "allow_duet": True,
+ "allow_stitch": True,
+ "auto_add_music": True,
+ "caption": {},
+ "disclose_branded_content": True,
+ "disclose_your_brand": True,
+ "is_ai_generated": True,
+ "is_draft": True,
+ "media": [
+ {
+ "url": "url",
+ "skip_processing": True,
+ "tags": [
+ {
+ "id": "id",
+ "platform": "facebook",
+ "type": "user",
+ "x": 0,
+ "y": 0,
+ }
+ ],
+ "thumbnail_timestamp_ms": {},
+ "thumbnail_url": {},
+ }
+ ],
+ "privacy_status": "privacy_status",
+ "title": "title",
+ },
+ "x": {
+ "caption": {},
+ "community_id": "community_id",
+ "media": [
+ {
+ "url": "url",
+ "skip_processing": True,
+ "tags": [
+ {
+ "id": "id",
+ "platform": "facebook",
+ "type": "user",
+ "x": 0,
+ "y": 0,
+ }
+ ],
+ "thumbnail_timestamp_ms": {},
+ "thumbnail_url": {},
+ }
+ ],
+ "poll": {
+ "duration_minutes": 0,
+ "options": ["string"],
+ "reply_settings": "following",
+ },
+ "quote_tweet_id": "quote_tweet_id",
+ "reply_settings": "following",
+ },
+ "youtube": {
+ "caption": {},
+ "made_for_kids": True,
+ "media": [
+ {
+ "url": "url",
+ "skip_processing": True,
+ "tags": [
+ {
+ "id": "id",
+ "platform": "facebook",
+ "type": "user",
+ "x": 0,
+ "y": 0,
+ }
+ ],
+ "thumbnail_timestamp_ms": {},
+ "thumbnail_url": {},
+ }
+ ],
+ "privacy_status": "public",
+ "title": "title",
+ },
+ },
+ )
+ assert_matches_type(SocialPostPreviewCreateResponse, social_post_preview, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_create(self, async_client: AsyncPostForMe) -> None:
+ response = await async_client.social_post_previews.with_raw_response.create(
+ caption="caption",
+ preview_social_accounts=[
+ {
+ "id": "id",
+ "platform": "platform",
+ }
+ ],
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ social_post_preview = await response.parse()
+ assert_matches_type(SocialPostPreviewCreateResponse, social_post_preview, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_create(self, async_client: AsyncPostForMe) -> None:
+ async with async_client.social_post_previews.with_streaming_response.create(
+ caption="caption",
+ preview_social_accounts=[
+ {
+ "id": "id",
+ "platform": "platform",
+ }
+ ],
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ social_post_preview = await response.parse()
+ assert_matches_type(SocialPostPreviewCreateResponse, social_post_preview, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_social_posts.py b/tests/api_resources/test_social_posts.py
index b66c6b0..17ee9df 100644
--- a/tests/api_resources/test_social_posts.py
+++ b/tests/api_resources/test_social_posts.py
@@ -11,8 +11,8 @@
from tests.utils import assert_matches_type
from post_for_me.types import (
SocialPost,
+ DeleteEntityResponse,
SocialPostListResponse,
- SocialPostDeleteResponse,
)
from post_for_me._utils import parse_datetime
@@ -219,6 +219,7 @@ def test_method_create_with_all_params(self, client: PostForMe) -> None:
"thumbnail_url": {},
}
],
+ "title": "title",
},
"threads": {
"caption": {},
@@ -626,6 +627,7 @@ def test_method_update_with_all_params(self, client: PostForMe) -> None:
"thumbnail_url": {},
}
],
+ "title": "title",
},
"threads": {
"caption": {},
@@ -851,7 +853,7 @@ def test_method_delete(self, client: PostForMe) -> None:
social_post = client.social_posts.delete(
"id",
)
- assert_matches_type(SocialPostDeleteResponse, social_post, path=["response"])
+ assert_matches_type(DeleteEntityResponse, social_post, path=["response"])
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
@@ -863,7 +865,7 @@ def test_raw_response_delete(self, client: PostForMe) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
social_post = response.parse()
- assert_matches_type(SocialPostDeleteResponse, social_post, path=["response"])
+ assert_matches_type(DeleteEntityResponse, social_post, path=["response"])
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
@@ -875,7 +877,7 @@ def test_streaming_response_delete(self, client: PostForMe) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
social_post = response.parse()
- assert_matches_type(SocialPostDeleteResponse, social_post, path=["response"])
+ assert_matches_type(DeleteEntityResponse, social_post, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -1090,6 +1092,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncPostForMe)
"thumbnail_url": {},
}
],
+ "title": "title",
},
"threads": {
"caption": {},
@@ -1497,6 +1500,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncPostForMe)
"thumbnail_url": {},
}
],
+ "title": "title",
},
"threads": {
"caption": {},
@@ -1722,7 +1726,7 @@ async def test_method_delete(self, async_client: AsyncPostForMe) -> None:
social_post = await async_client.social_posts.delete(
"id",
)
- assert_matches_type(SocialPostDeleteResponse, social_post, path=["response"])
+ assert_matches_type(DeleteEntityResponse, social_post, path=["response"])
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
@@ -1734,7 +1738,7 @@ async def test_raw_response_delete(self, async_client: AsyncPostForMe) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
social_post = await response.parse()
- assert_matches_type(SocialPostDeleteResponse, social_post, path=["response"])
+ assert_matches_type(DeleteEntityResponse, social_post, path=["response"])
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
@@ -1746,7 +1750,7 @@ async def test_streaming_response_delete(self, async_client: AsyncPostForMe) ->
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
social_post = await response.parse()
- assert_matches_type(SocialPostDeleteResponse, social_post, path=["response"])
+ assert_matches_type(DeleteEntityResponse, social_post, path=["response"])
assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_webhooks.py b/tests/api_resources/test_webhooks.py
new file mode 100644
index 0000000..318aea1
--- /dev/null
+++ b/tests/api_resources/test_webhooks.py
@@ -0,0 +1,454 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from post_for_me import PostForMe, AsyncPostForMe
+from tests.utils import assert_matches_type
+from post_for_me.types import (
+ Webhook,
+ WebhookListResponse,
+ DeleteEntityResponse,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestWebhooks:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_create(self, client: PostForMe) -> None:
+ webhook = client.webhooks.create(
+ event_types=["social.post.created"],
+ url="url",
+ )
+ assert_matches_type(Webhook, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_create(self, client: PostForMe) -> None:
+ response = client.webhooks.with_raw_response.create(
+ event_types=["social.post.created"],
+ url="url",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ webhook = response.parse()
+ assert_matches_type(Webhook, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_create(self, client: PostForMe) -> None:
+ with client.webhooks.with_streaming_response.create(
+ event_types=["social.post.created"],
+ url="url",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ webhook = response.parse()
+ assert_matches_type(Webhook, webhook, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_retrieve(self, client: PostForMe) -> None:
+ webhook = client.webhooks.retrieve(
+ "id",
+ )
+ assert_matches_type(Webhook, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_retrieve(self, client: PostForMe) -> None:
+ response = client.webhooks.with_raw_response.retrieve(
+ "id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ webhook = response.parse()
+ assert_matches_type(Webhook, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_retrieve(self, client: PostForMe) -> None:
+ with client.webhooks.with_streaming_response.retrieve(
+ "id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ webhook = response.parse()
+ assert_matches_type(Webhook, webhook, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_retrieve(self, client: PostForMe) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ client.webhooks.with_raw_response.retrieve(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_update(self, client: PostForMe) -> None:
+ webhook = client.webhooks.update(
+ id="id",
+ )
+ assert_matches_type(Webhook, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_update_with_all_params(self, client: PostForMe) -> None:
+ webhook = client.webhooks.update(
+ id="id",
+ event_types=["social.post.created"],
+ url="url",
+ )
+ assert_matches_type(Webhook, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_update(self, client: PostForMe) -> None:
+ response = client.webhooks.with_raw_response.update(
+ id="id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ webhook = response.parse()
+ assert_matches_type(Webhook, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_update(self, client: PostForMe) -> None:
+ with client.webhooks.with_streaming_response.update(
+ id="id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ webhook = response.parse()
+ assert_matches_type(Webhook, webhook, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_update(self, client: PostForMe) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ client.webhooks.with_raw_response.update(
+ id="",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list(self, client: PostForMe) -> None:
+ webhook = client.webhooks.list()
+ assert_matches_type(WebhookListResponse, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list_with_all_params(self, client: PostForMe) -> None:
+ webhook = client.webhooks.list(
+ id=["string"],
+ event_type=["string"],
+ limit=0,
+ offset=0,
+ url=["string"],
+ )
+ assert_matches_type(WebhookListResponse, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_list(self, client: PostForMe) -> None:
+ response = client.webhooks.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ webhook = response.parse()
+ assert_matches_type(WebhookListResponse, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_list(self, client: PostForMe) -> None:
+ with client.webhooks.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ webhook = response.parse()
+ assert_matches_type(WebhookListResponse, webhook, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_delete(self, client: PostForMe) -> None:
+ webhook = client.webhooks.delete(
+ "id",
+ )
+ assert_matches_type(DeleteEntityResponse, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_delete(self, client: PostForMe) -> None:
+ response = client.webhooks.with_raw_response.delete(
+ "id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ webhook = response.parse()
+ assert_matches_type(DeleteEntityResponse, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_delete(self, client: PostForMe) -> None:
+ with client.webhooks.with_streaming_response.delete(
+ "id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ webhook = response.parse()
+ assert_matches_type(DeleteEntityResponse, webhook, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_delete(self, client: PostForMe) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ client.webhooks.with_raw_response.delete(
+ "",
+ )
+
+
+class TestAsyncWebhooks:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_create(self, async_client: AsyncPostForMe) -> None:
+ webhook = await async_client.webhooks.create(
+ event_types=["social.post.created"],
+ url="url",
+ )
+ assert_matches_type(Webhook, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_create(self, async_client: AsyncPostForMe) -> None:
+ response = await async_client.webhooks.with_raw_response.create(
+ event_types=["social.post.created"],
+ url="url",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ webhook = await response.parse()
+ assert_matches_type(Webhook, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_create(self, async_client: AsyncPostForMe) -> None:
+ async with async_client.webhooks.with_streaming_response.create(
+ event_types=["social.post.created"],
+ url="url",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ webhook = await response.parse()
+ assert_matches_type(Webhook, webhook, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_retrieve(self, async_client: AsyncPostForMe) -> None:
+ webhook = await async_client.webhooks.retrieve(
+ "id",
+ )
+ assert_matches_type(Webhook, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_retrieve(self, async_client: AsyncPostForMe) -> None:
+ response = await async_client.webhooks.with_raw_response.retrieve(
+ "id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ webhook = await response.parse()
+ assert_matches_type(Webhook, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_retrieve(self, async_client: AsyncPostForMe) -> None:
+ async with async_client.webhooks.with_streaming_response.retrieve(
+ "id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ webhook = await response.parse()
+ assert_matches_type(Webhook, webhook, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_path_params_retrieve(self, async_client: AsyncPostForMe) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ await async_client.webhooks.with_raw_response.retrieve(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_update(self, async_client: AsyncPostForMe) -> None:
+ webhook = await async_client.webhooks.update(
+ id="id",
+ )
+ assert_matches_type(Webhook, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_update_with_all_params(self, async_client: AsyncPostForMe) -> None:
+ webhook = await async_client.webhooks.update(
+ id="id",
+ event_types=["social.post.created"],
+ url="url",
+ )
+ assert_matches_type(Webhook, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_update(self, async_client: AsyncPostForMe) -> None:
+ response = await async_client.webhooks.with_raw_response.update(
+ id="id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ webhook = await response.parse()
+ assert_matches_type(Webhook, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_update(self, async_client: AsyncPostForMe) -> None:
+ async with async_client.webhooks.with_streaming_response.update(
+ id="id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ webhook = await response.parse()
+ assert_matches_type(Webhook, webhook, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_path_params_update(self, async_client: AsyncPostForMe) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ await async_client.webhooks.with_raw_response.update(
+ id="",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_list(self, async_client: AsyncPostForMe) -> None:
+ webhook = await async_client.webhooks.list()
+ assert_matches_type(WebhookListResponse, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_list_with_all_params(self, async_client: AsyncPostForMe) -> None:
+ webhook = await async_client.webhooks.list(
+ id=["string"],
+ event_type=["string"],
+ limit=0,
+ offset=0,
+ url=["string"],
+ )
+ assert_matches_type(WebhookListResponse, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncPostForMe) -> None:
+ response = await async_client.webhooks.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ webhook = await response.parse()
+ assert_matches_type(WebhookListResponse, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncPostForMe) -> None:
+ async with async_client.webhooks.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ webhook = await response.parse()
+ assert_matches_type(WebhookListResponse, webhook, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_delete(self, async_client: AsyncPostForMe) -> None:
+ webhook = await async_client.webhooks.delete(
+ "id",
+ )
+ assert_matches_type(DeleteEntityResponse, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_delete(self, async_client: AsyncPostForMe) -> None:
+ response = await async_client.webhooks.with_raw_response.delete(
+ "id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ webhook = await response.parse()
+ assert_matches_type(DeleteEntityResponse, webhook, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_delete(self, async_client: AsyncPostForMe) -> None:
+ async with async_client.webhooks.with_streaming_response.delete(
+ "id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ webhook = await response.parse()
+ assert_matches_type(DeleteEntityResponse, webhook, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_path_params_delete(self, async_client: AsyncPostForMe) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ await async_client.webhooks.with_raw_response.delete(
+ "",
+ )
diff --git a/tests/test_deepcopy.py b/tests/test_deepcopy.py
deleted file mode 100644
index d160965..0000000
--- a/tests/test_deepcopy.py
+++ /dev/null
@@ -1,58 +0,0 @@
-from post_for_me._utils import deepcopy_minimal
-
-
-def assert_different_identities(obj1: object, obj2: object) -> None:
- assert obj1 == obj2
- assert id(obj1) != id(obj2)
-
-
-def test_simple_dict() -> None:
- obj1 = {"foo": "bar"}
- obj2 = deepcopy_minimal(obj1)
- assert_different_identities(obj1, obj2)
-
-
-def test_nested_dict() -> None:
- obj1 = {"foo": {"bar": True}}
- obj2 = deepcopy_minimal(obj1)
- assert_different_identities(obj1, obj2)
- assert_different_identities(obj1["foo"], obj2["foo"])
-
-
-def test_complex_nested_dict() -> None:
- obj1 = {"foo": {"bar": [{"hello": "world"}]}}
- obj2 = deepcopy_minimal(obj1)
- assert_different_identities(obj1, obj2)
- assert_different_identities(obj1["foo"], obj2["foo"])
- assert_different_identities(obj1["foo"]["bar"], obj2["foo"]["bar"])
- assert_different_identities(obj1["foo"]["bar"][0], obj2["foo"]["bar"][0])
-
-
-def test_simple_list() -> None:
- obj1 = ["a", "b", "c"]
- obj2 = deepcopy_minimal(obj1)
- assert_different_identities(obj1, obj2)
-
-
-def test_nested_list() -> None:
- obj1 = ["a", [1, 2, 3]]
- obj2 = deepcopy_minimal(obj1)
- assert_different_identities(obj1, obj2)
- assert_different_identities(obj1[1], obj2[1])
-
-
-class MyObject: ...
-
-
-def test_ignores_other_types() -> None:
- # custom classes
- my_obj = MyObject()
- obj1 = {"foo": my_obj}
- obj2 = deepcopy_minimal(obj1)
- assert_different_identities(obj1, obj2)
- assert obj1["foo"] is my_obj
-
- # tuples
- obj3 = ("a", "b")
- obj4 = deepcopy_minimal(obj3)
- assert obj3 is obj4
diff --git a/tests/test_extract_files.py b/tests/test_extract_files.py
index e7aa16e..a17f687 100644
--- a/tests/test_extract_files.py
+++ b/tests/test_extract_files.py
@@ -4,7 +4,7 @@
import pytest
-from post_for_me._types import FileTypes
+from post_for_me._types import FileTypes, ArrayFormat
from post_for_me._utils import extract_files
@@ -37,10 +37,7 @@ def test_multiple_files() -> None:
def test_top_level_file_array() -> None:
query = {"files": [b"file one", b"file two"], "title": "hello"}
- assert extract_files(query, paths=[["files", ""]]) == [
- ("files[]", b"file one"),
- ("files[]", b"file two"),
- ]
+ assert extract_files(query, paths=[["files", ""]]) == [("files[]", b"file one"), ("files[]", b"file two")]
assert query == {"title": "hello"}
@@ -71,3 +68,24 @@ def test_ignores_incorrect_paths(
expected: list[tuple[str, FileTypes]],
) -> None:
assert extract_files(query, paths=paths) == expected
+
+
+@pytest.mark.parametrize(
+ "array_format,expected_top_level,expected_nested",
+ [
+ ("brackets", [("files[]", b"a"), ("files[]", b"b")], [("items[][file]", b"a"), ("items[][file]", b"b")]),
+ ("repeat", [("files", b"a"), ("files", b"b")], [("items[file]", b"a"), ("items[file]", b"b")]),
+ ("comma", [("files", b"a"), ("files", b"b")], [("items[file]", b"a"), ("items[file]", b"b")]),
+ ("indices", [("files[0]", b"a"), ("files[1]", b"b")], [("items[0][file]", b"a"), ("items[1][file]", b"b")]),
+ ],
+)
+def test_array_format_controls_file_field_names(
+ array_format: ArrayFormat,
+ expected_top_level: list[tuple[str, FileTypes]],
+ expected_nested: list[tuple[str, FileTypes]],
+) -> None:
+ top_level = {"files": [b"a", b"b"]}
+ assert extract_files(top_level, paths=[["files", ""]], array_format=array_format) == expected_top_level
+
+ nested = {"items": [{"file": b"a"}, {"file": b"b"}]}
+ assert extract_files(nested, paths=[["items", "", "file"]], array_format=array_format) == expected_nested
diff --git a/tests/test_files.py b/tests/test_files.py
index 9f789a5..c0c8374 100644
--- a/tests/test_files.py
+++ b/tests/test_files.py
@@ -4,7 +4,8 @@
import pytest
from dirty_equals import IsDict, IsList, IsBytes, IsTuple
-from post_for_me._files import to_httpx_files, async_to_httpx_files
+from post_for_me._files import to_httpx_files, deepcopy_with_paths, async_to_httpx_files
+from post_for_me._utils import extract_files
readme_path = Path(__file__).parent.parent.joinpath("README.md")
@@ -49,3 +50,99 @@ def test_string_not_allowed() -> None:
"file": "foo", # type: ignore
}
)
+
+
+def assert_different_identities(obj1: object, obj2: object) -> None:
+ assert obj1 == obj2
+ assert obj1 is not obj2
+
+
+class TestDeepcopyWithPaths:
+ def test_copies_top_level_dict(self) -> None:
+ original = {"file": b"data", "other": "value"}
+ result = deepcopy_with_paths(original, [["file"]])
+ assert_different_identities(result, original)
+
+ def test_file_value_is_same_reference(self) -> None:
+ file_bytes = b"contents"
+ original = {"file": file_bytes}
+ result = deepcopy_with_paths(original, [["file"]])
+ assert_different_identities(result, original)
+ assert result["file"] is file_bytes
+
+ def test_list_popped_wholesale(self) -> None:
+ files = [b"f1", b"f2"]
+ original = {"files": files, "title": "t"}
+ result = deepcopy_with_paths(original, [["files", ""]])
+ assert_different_identities(result, original)
+ result_files = result["files"]
+ assert isinstance(result_files, list)
+ assert_different_identities(result_files, files)
+
+ def test_nested_array_path_copies_list_and_elements(self) -> None:
+ elem1 = {"file": b"f1", "extra": 1}
+ elem2 = {"file": b"f2", "extra": 2}
+ original = {"items": [elem1, elem2]}
+ result = deepcopy_with_paths(original, [["items", "", "file"]])
+ assert_different_identities(result, original)
+ result_items = result["items"]
+ assert isinstance(result_items, list)
+ assert_different_identities(result_items, original["items"])
+ assert_different_identities(result_items[0], elem1)
+ assert_different_identities(result_items[1], elem2)
+
+ def test_empty_paths_returns_same_object(self) -> None:
+ original = {"foo": "bar"}
+ result = deepcopy_with_paths(original, [])
+ assert result is original
+
+ def test_multiple_paths(self) -> None:
+ f1 = b"file1"
+ f2 = b"file2"
+ original = {"a": f1, "b": f2, "c": "unchanged"}
+ result = deepcopy_with_paths(original, [["a"], ["b"]])
+ assert_different_identities(result, original)
+ assert result["a"] is f1
+ assert result["b"] is f2
+ assert result["c"] is original["c"]
+
+ def test_extract_files_does_not_mutate_original_top_level(self) -> None:
+ file_bytes = b"contents"
+ original = {"file": file_bytes, "other": "value"}
+
+ copied = deepcopy_with_paths(original, [["file"]])
+ extracted = extract_files(copied, paths=[["file"]])
+
+ assert extracted == [("file", file_bytes)]
+ assert original == {"file": file_bytes, "other": "value"}
+ assert copied == {"other": "value"}
+
+ def test_extract_files_does_not_mutate_original_nested_array_path(self) -> None:
+ file1 = b"f1"
+ file2 = b"f2"
+ original = {
+ "items": [
+ {"file": file1, "extra": 1},
+ {"file": file2, "extra": 2},
+ ],
+ "title": "example",
+ }
+
+ copied = deepcopy_with_paths(original, [["items", "", "file"]])
+ extracted = extract_files(copied, paths=[["items", "", "file"]])
+
+ assert [entry for _, entry in extracted] == [file1, file2]
+ assert original == {
+ "items": [
+ {"file": file1, "extra": 1},
+ {"file": file2, "extra": 2},
+ ],
+ "title": "example",
+ }
+ assert copied == {
+ "items": [
+ {"extra": 1},
+ {"extra": 2},
+ ],
+ "title": "example",
+ }
diff --git a/tests/test_models.py b/tests/test_models.py
index ad75540..da9c024 100644
--- a/tests/test_models.py
+++ b/tests/test_models.py
@@ -1,7 +1,8 @@
import json
-from typing import TYPE_CHECKING, Any, Dict, List, Union, Optional, cast
+from typing import TYPE_CHECKING, Any, Dict, List, Union, Iterable, Optional, cast
from datetime import datetime, timezone
-from typing_extensions import Literal, Annotated, TypeAliasType
+from collections import deque
+from typing_extensions import Literal, Annotated, TypedDict, TypeAliasType
import pytest
import pydantic
@@ -9,7 +10,7 @@
from post_for_me._utils import PropertyInfo
from post_for_me._compat import PYDANTIC_V1, parse_obj, model_dump, model_json
-from post_for_me._models import DISCRIMINATOR_CACHE, BaseModel, construct_type
+from post_for_me._models import DISCRIMINATOR_CACHE, BaseModel, EagerIterable, construct_type
class BasicModel(BaseModel):
@@ -961,3 +962,56 @@ def __getattr__(self, attr: str) -> Item: ...
assert model.a.prop == 1
assert isinstance(model.a, Item)
assert model.other == "foo"
+
+
+# NOTE: Workaround for Pydantic Iterable behavior.
+# Iterable fields are replaced with a ValidatorIterator and may be consumed
+# during serialization, which can cause subsequent dumps to return empty data.
+# See: https://github.com/pydantic/pydantic/issues/9541
+@pytest.mark.parametrize(
+ "data, expected_validated",
+ [
+ ([1, 2, 3], [1, 2, 3]),
+ ((1, 2, 3), (1, 2, 3)),
+ (set([1, 2, 3]), set([1, 2, 3])),
+ (iter([1, 2, 3]), [1, 2, 3]),
+ ([], []),
+ ((x for x in [1, 2, 3]), [1, 2, 3]),
+ (map(lambda x: x, [1, 2, 3]), [1, 2, 3]),
+ (frozenset([1, 2, 3]), frozenset([1, 2, 3])),
+ (deque([1, 2, 3]), deque([1, 2, 3])),
+ ],
+ ids=["list", "tuple", "set", "iterator", "empty", "generator", "map", "frozenset", "deque"],
+)
+@pytest.mark.skipif(PYDANTIC_V1, reason="this is only supported in pydantic v2")
+def test_iterable_construction(data: Iterable[int], expected_validated: Iterable[int]) -> None:
+ class TypeWithIterable(TypedDict):
+ items: EagerIterable[int]
+
+ class Model(BaseModel):
+ data: TypeWithIterable
+
+ m = Model.model_validate({"data": {"items": data}})
+ assert m.data["items"] == expected_validated
+
+ # Verify repeated dumps don't lose data (the original bug)
+ assert m.model_dump()["data"]["items"] == list(expected_validated)
+ assert m.model_dump()["data"]["items"] == list(expected_validated)
+
+
+@pytest.mark.skipif(PYDANTIC_V1, reason="this is only supported in pydantic v2")
+def test_iterable_construction_str_falls_back_to_list() -> None:
+ # str is iterable (over chars), but str(list_of_chars) produces the list's repr
+ # rather than reconstructing a string from items. We special-case str to fall
+ # back to list instead of attempting reconstruction.
+ class TypeWithIterable(TypedDict):
+ items: EagerIterable[str]
+
+ class Model(BaseModel):
+ data: TypeWithIterable
+
+ m = Model.model_validate({"data": {"items": "hello"}})
+
+ # falls back to list of chars rather than calling str(["h", "e", "l", "l", "o"])
+ assert m.data["items"] == ["h", "e", "l", "l", "o"]
+ assert m.model_dump()["data"]["items"] == ["h", "e", "l", "l", "o"]