diff --git a/.claude/skills/gumlet-python-sdk/SKILL.md b/.claude/skills/gumlet-python-sdk/SKILL.md new file mode 100644 index 0000000..45a9415 --- /dev/null +++ b/.claude/skills/gumlet-python-sdk/SKILL.md @@ -0,0 +1,117 @@ +--- +name: gumlet-python-sdk +description: "Python SDK for Gumlet API. Use when writing Python code that calls Gumlet API with the gumlet package: installing it, constructing and authenticating the client, and calling API operations." +--- + +# Gumlet Python SDK + +Generated Python client for Gumlet API, published as `gumlet`. Use the generated client instead of hand-writing HTTP requests. + +## Install + +```sh +pip install gumlet +``` + +## Client setup and authentication + +```python +import os + +from gumlet import Gumlet + +client = Gumlet( + api_key=os.environ.get("API_KEY"), +) +``` + +Provide credentials using the options below. Environment variables are read automatically when the target runtime supports them: + +- `api_key` (env: `API_KEY`) — Credential for the API_KEY scheme. + +## Calling operations + +```python +import os + +from gumlet import Gumlet + +client = Gumlet( + api_key=os.environ.get("API_KEY"), +) + +video_asset = client.video_assets.create( + input="http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8", + collection_id="646df1c9173a4a2fcac180b4", + profile_id="646df1c9173a4a2fcac180b7", + format="ABR", + tag=["ball"], + description="some description", + metadata={"headermeta": "metavalue"}, + call_to_actions=[ + { + "start_time": 1, + "end_time": 90, + "text": "some test", + "url": "https://some-url.com", + "position_from_top": 11, + "position_from_right": 23, + "border_radius": "11", + "font_color": "#000001", + "background_color": "#ffffff", + } + ], + playlist_id="6597acd5ed6f26a9c5ca9633", + folder="697375fbfa2d1037283140e4", +) + +print(video_asset) +``` + +Method names, parameter shapes, and response types are generated from the API description — do not guess them. Look up the exact call signature in [api.md](../../../api.md) before writing a call. + +## Error handling + +Non-success responses throw generated API errors. Error objects expose status, headers, response body, and request metadata where the target runtime supports it. + +```python +from gumlet import APIStatusError + +try: + video_asset = client.video_assets.create( + input="http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8", + collection_id="646df1c9173a4a2fcac180b4", + profile_id="646df1c9173a4a2fcac180b7", + format="ABR", + tag=["ball"], + description="some description", + metadata={"headermeta": "metavalue"}, + call_to_actions=[ + { + "start_time": 1, + "end_time": 90, + "text": "some test", + "url": "https://some-url.com", + "position_from_top": 11, + "position_from_right": 23, + "border_radius": "11", + "font_color": "#000001", + "background_color": "#ffffff", + } + ], + playlist_id="6597acd5ed6f26a9c5ca9633", + folder="697375fbfa2d1037283140e4", + ) +except APIStatusError as err: + print(err.status_code, err.message) + raise +``` + +## Requirements + +- Python 3.8 or newer + +## Reference files + +- [README.md](../../../README.md) — full feature tour: client options, retries and timeouts, logging. +- [api.md](../../../api.md) — complete catalogue of every operation with request and response types. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e951962 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +# Generated by Scalar SDK Generator. +__pycache__/ +*.py[cod] +*.egg-info/ +.venv/ +/dist/ +/build/ +.mypy_cache/ +.pytest_cache/ +.ruff_cache/ diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 466df71..2be9c43 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0" + ".": "0.2.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..81fc28a --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,8 @@ +# Changelog + +## [0.2.0](https://github.com/gumlet/python-sdk/compare/v0.1.0...v0.2.0) (2026-09-18) + + +### Features + +* **api:** initial SDK generation ([860d43a](https://github.com/gumlet/python-sdk/commit/860d43a0575952e5e8d658b65f4f99c332b7af59)) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2db3388 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2026 Gumlet + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index e5637d9..dc346cd 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,224 @@ -# python-sdk -Python SDK for Gumlet +# Gumlet + +This library provides convenient access to the Gumlet REST API from Python. + +The full API of this library can be found in [api.md](./api.md). + +
+ +## Contents + +- [Installation](#installation) +- [Usage](#usage) +- [API Reference](./api.md) +- [Async](#async) +- [Authentication](#authentication) +- [Errors](#errors) +- [Client Options](#client-options) +- [Retries and Timeouts](#retries-and-timeouts) +- [Helpers](#helpers) +- [Logging](#logging) +- [Requirements](#requirements) + +
+ +## Installation + +```sh +pip install gumlet +``` + +
+ +## Usage + +```python +import os + +from gumlet import Gumlet + +client = Gumlet( + api_key=os.environ.get("API_KEY"), +) + +video_asset = client.video_assets.create( + input="http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8", + collection_id="646df1c9173a4a2fcac180b4", + profile_id="646df1c9173a4a2fcac180b7", + format="ABR", + tag=["ball"], + description="some description", + metadata={"headermeta": "metavalue"}, + call_to_actions=[ + { + "start_time": 1, + "end_time": 90, + "text": "some test", + "url": "https://some-url.com", + "position_from_top": 11, + "position_from_right": 23, + "border_radius": "11", + "font_color": "#000001", + "background_color": "#ffffff", + } + ], + playlist_id="6597acd5ed6f26a9c5ca9633", + folder="697375fbfa2d1037283140e4", +) + +print(video_asset) +``` + +The examples in the following sections assume a `client` configured as shown above. + +See the [API reference](./api.md) for every available operation. + +
+ +## Async + +Every client has an `Async` counterpart (`AsyncGumlet`) exposing the same resource tree with `await`. + +```python +import asyncio + +from gumlet import AsyncGumlet + + +async def main() -> None: + client = AsyncGumlet() + video_asset = await client.video_assets.create( + input="http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8", + collection_id="646df1c9173a4a2fcac180b4", + profile_id="646df1c9173a4a2fcac180b7", + format="ABR", + tag=["ball"], + description="some description", + metadata={"headermeta": "metavalue"}, + call_to_actions=[ + { + "start_time": 1, + "end_time": 90, + "text": "some test", + "url": "https://some-url.com", + "position_from_top": 11, + "position_from_right": 23, + "border_radius": "11", + "font_color": "#000001", + "background_color": "#ffffff", + } + ], + playlist_id="6597acd5ed6f26a9c5ca9633", + folder="697375fbfa2d1037283140e4", + ) + + +asyncio.run(main()) +``` + +
+ +## Authentication + +Pass credentials to the generated client constructor. Environment variables are read automatically when supported by the target runtime. + +| Option | Type | Default | Description | +| --- | --- | --- | --- | +| `api_key` | `string \| provider` | - | Credential for the API_KEY scheme. Defaults to API_KEY. | + +Declared schemes: + +- `API_KEY` bearer token + +
+ +## Errors + +Non-success responses throw generated API errors. Error objects expose status, headers, response body, and request metadata where the target runtime supports it. + +```python +from gumlet import APIStatusError + +try: + video_asset = client.video_assets.create( + input="http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8", + collection_id="646df1c9173a4a2fcac180b4", + profile_id="646df1c9173a4a2fcac180b7", + format="ABR", + tag=["ball"], + description="some description", + metadata={"headermeta": "metavalue"}, + call_to_actions=[ + { + "start_time": 1, + "end_time": 90, + "text": "some test", + "url": "https://some-url.com", + "position_from_top": 11, + "position_from_right": 23, + "border_radius": "11", + "font_color": "#000001", + "background_color": "#ffffff", + } + ], + playlist_id="6597acd5ed6f26a9c5ca9633", + folder="697375fbfa2d1037283140e4", + ) +except APIStatusError as err: + print(err.status_code, err.message) + raise +``` + +Documented error statuses: `400`, `401`, `403`, `404`, `4XX`. + +
+ +## Client Options + +Configure the generated client by setting any of these options when you create it. + +```python +from gumlet import Gumlet + +client = Gumlet( + timeout=60.0, + max_retries=2, +) +``` + +| Option | Type | Default | Description | +| --- | --- | --- | --- | +| `api_key` | `str \| None` | `os.environ.get("API_KEY")` | Credential for the API_KEY scheme. | +| `base_url` | `str \| httpx.URL \| None` | - | Override the default API base URL. | +| `timeout` | `float \| Timeout \| None` | `60.0` | Maximum time in seconds to wait for a response before aborting a request. | +| `max_retries` | `int` | `2` | Number of retries for temporary failures. | +| `default_headers` | `Mapping[str, str] \| None` | - | Headers sent with every request. | +| `default_query` | `Mapping[str, object] \| None` | - | Query parameters sent with every request. | + +
+ +## Retries and Timeouts + +Generated clients support request timeouts and retry temporary failures such as network errors, 408, 409, 429, and 5xx responses. Retry delays honor `Retry-After` headers when present. Tune the retry and timeout client options shown above, or override them per request. + +
+ +## Helpers + +- Use `client.with_raw_response..(...)` to access the raw `httpx.Response` and parse it yourself. +- Use `client.with_streaming_response..(...)` to stream a response body without buffering it. + +
+ +## Logging + +- Set the `GUMLET_LOG` environment variable to `info` or `debug` to enable HTTP logging. +- Logs are emitted through the standard `logging` module under the `gumlet` logger. + +
+ +## Requirements + +- Python 3.8 or newer + +Powered by Scalar. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..2198e15 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,19 @@ +# Security Policy + +## Reporting Security Issues + +This is a generated Python SDK for Gumlet, produced by the Scalar SDK Generator (https://scalar.com). + +If the issue is in the generated SDK itself — its runtime, authentication handling, path construction, or serialization — report it to the Scalar team at support@scalar.com. We will acknowledge the report, triage it for severity and impact, and communicate a timeline for a fix or an explanation if we decline it. + +## Responsible Disclosure + +Please allow us reasonable time to investigate and address the issue before disclosing it publicly. Do not include credentials, tokens, or private API payloads in a public issue. + +## Reporting Non-SDK Security Issues + +If the issue is in the Gumlet API or service rather than in this SDK, report it through the security process Gumlet has published. + +--- + +Generated code validates emitted paths and escapes spec-derived strings, but OpenAPI documents should still be treated as untrusted input before generation. diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000..8355191 --- /dev/null +++ b/SKILL.md @@ -0,0 +1,117 @@ +--- +name: gumlet-python-sdk +description: "Python SDK for Gumlet API. Use when writing Python code that calls Gumlet API with the gumlet package: installing it, constructing and authenticating the client, and calling API operations." +--- + +# Gumlet Python SDK + +Generated Python client for Gumlet API, published as `gumlet`. Use the generated client instead of hand-writing HTTP requests. + +## Install + +```sh +pip install gumlet +``` + +## Client setup and authentication + +```python +import os + +from gumlet import Gumlet + +client = Gumlet( + api_key=os.environ.get("API_KEY"), +) +``` + +Provide credentials using the options below. Environment variables are read automatically when the target runtime supports them: + +- `api_key` (env: `API_KEY`) — Credential for the API_KEY scheme. + +## Calling operations + +```python +import os + +from gumlet import Gumlet + +client = Gumlet( + api_key=os.environ.get("API_KEY"), +) + +video_asset = client.video_assets.create( + input="http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8", + collection_id="646df1c9173a4a2fcac180b4", + profile_id="646df1c9173a4a2fcac180b7", + format="ABR", + tag=["ball"], + description="some description", + metadata={"headermeta": "metavalue"}, + call_to_actions=[ + { + "start_time": 1, + "end_time": 90, + "text": "some test", + "url": "https://some-url.com", + "position_from_top": 11, + "position_from_right": 23, + "border_radius": "11", + "font_color": "#000001", + "background_color": "#ffffff", + } + ], + playlist_id="6597acd5ed6f26a9c5ca9633", + folder="697375fbfa2d1037283140e4", +) + +print(video_asset) +``` + +Method names, parameter shapes, and response types are generated from the API description — do not guess them. Look up the exact call signature in [api.md](./api.md) before writing a call. + +## Error handling + +Non-success responses throw generated API errors. Error objects expose status, headers, response body, and request metadata where the target runtime supports it. + +```python +from gumlet import APIStatusError + +try: + video_asset = client.video_assets.create( + input="http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8", + collection_id="646df1c9173a4a2fcac180b4", + profile_id="646df1c9173a4a2fcac180b7", + format="ABR", + tag=["ball"], + description="some description", + metadata={"headermeta": "metavalue"}, + call_to_actions=[ + { + "start_time": 1, + "end_time": 90, + "text": "some test", + "url": "https://some-url.com", + "position_from_top": 11, + "position_from_right": 23, + "border_radius": "11", + "font_color": "#000001", + "background_color": "#ffffff", + } + ], + playlist_id="6597acd5ed6f26a9c5ca9633", + folder="697375fbfa2d1037283140e4", + ) +except APIStatusError as err: + print(err.status_code, err.message) + raise +``` + +## Requirements + +- Python 3.8 or newer + +## Reference files + +- [README.md](./README.md) — full feature tour: client options, retries and timeouts, logging. +- [api.md](./api.md) — complete catalogue of every operation with request and response types. diff --git a/VERSIONING.md b/VERSIONING.md new file mode 100644 index 0000000..662f2b6 --- /dev/null +++ b/VERSIONING.md @@ -0,0 +1,86 @@ +# Versioning + +This SDK is configured with the `manual` versioning policy. + +- `manual`: package versions are set explicitly before release. +- `semver`: releases should follow semantic versioning based on API and SDK surface changes. +- `calendar`: releases should use a calendar-derived version chosen by the release workflow or maintainer. + +## Branches and releases + +This repository follows a three-branch flow, managed by the Scalar platform together +with the generated workflows (which need only the default `GITHUB_TOKEN` — no extra +token required): + +- **`scalar-generated`** — pristine generator output. Pushed by the Scalar platform; do + not commit here. +- **`scalar-next`** — generated output merged with this repository's custom code. Commit + your customizations here (directly or via PRs). The Scalar platform merges each + regeneration into this branch and keeps the release PR up to date; merge conflicts + arrive as a PR from `scalar-merge-conflict` for you to resolve. +- **Default branch** — seeded from the first generated snapshot, then only ever receives + released states, each one the merge of a release PR. + +Release PRs are opened by the Scalar platform from `scalar-next` against the default +branch — so the PR diff shows the full pending release — and are versioned from +[Conventional Commits](https://www.conventionalcommits.org). Merging a release PR tags the +release, publishes it, and syncs the version bump and changelog back to `scalar-next`. +Pre-1.0, breaking changes bump the minor version. + +### Choosing an exact version + +To release a specific version — `1.0.0`, a hotfix number, anything the commit history would +not have picked — **edit the release PR title** to the version you want: + +```text +release: 1.0.0 +``` + +The `Release PR version` check turns red as soon as you save, because the version in the +title no longer matches the version committed in the PR. The Scalar platform then re-renders +the release PR at your version (changelog, manifest, and every version-bearing file), the +title comes back as `release: 1.0.0`, and the check turns green. **Wait for it to be green +before merging** — merging in between would tag a release whose own files still carry the +old version. + +The git-native equivalent, if you would rather not touch the PR: push an empty commit with a +`Release-As` footer to `scalar-next`. This is exactly what the title edit does for you. + +```sh +git commit --allow-empty -m "chore: release 1.0.0" -m "Release-As: 1.0.0" +``` + +### When a release PR does not merge cleanly + +Nothing is ever force-pushed automatically: a release PR that conflicts with the default +branch simply cannot be merged, and GitHub disables its merge button. The two causes have +different fixes: + +- **The default branch received direct commits** (for example a hotfix) that are not in + `scalar-next`. Land those commits on `scalar-next` (merge the default branch into it, or + cherry-pick), and the refreshed release PR merges cleanly again. Do not force-push — + that would discard the direct commits. +- **The repository was adopted with pre-existing content**, so the default branch shares + no history with `scalar-next`. The Scalar platform adds a checkbox to the release PR + description offering to replace the default branch with this release; checking it + authorizes the platform to force-push the released state over the old content. This is + destructive for anything on the default branch that never reached `scalar-next`, which + is why it requires that explicit opt-in. + +### Repository prerequisites + +- Branch protection on `scalar-next` and the default branch must allow the Scalar + platform and the `github-actions` bot to push (or be left unprotected); the default + branch only ever advances by merging release PRs, and `scalar-next` receives each + released state back from the release workflow. +- No Actions settings changes are required: the generated workflows declare their own + permissions and never create pull requests. +- If this package publishes through OIDC trusted publishing (for example PyPI or npm), + register the trusted publisher on the registry against the **`release-please.yml`** + workflow filename. Merging a release PR publishes from the `publish` job inside that + same workflow run (checked out at the released tag), so the automated path's OIDC + claims name that file — and, because nothing is dispatched, releasing works from any + release branch, not only the repository default branch. `sdk-release.yml` exists for + manual re-publishes at an existing tag; register it as an additional trusted publisher + only if you use it. If the publish job is configured with a deployment environment, + include that environment in the registration too. diff --git a/api.md b/api.md new file mode 100644 index 0000000..aa0eecc --- /dev/null +++ b/api.md @@ -0,0 +1,1648 @@ +# Gumlet Python API + +Complete reference of every operation, grouped by resource. See [the README](./README.md) for usage and configuration. + +## Contents + +- [`VideoAssets`](#videoassets) + - [Create Asset](#create-asset) + - [Create Asset Direct Upload](#create-asset-direct-upload) + - [Asset Details](#asset-details) + - [Delete Asset](#delete-asset) + - [Update Asset](#update-asset) + - [Update thumbnail from video](#update-thumbnail-from-video) + - [Update thumbnail via upload](#update-thumbnail-via-upload) + - [Create/Update Video Asset Chapters](#createupdate-video-asset-chapters) + - [List Assets](#list-assets) + - [List Assets](#list-assets-1) + - [Bulk Delete](#bulk-delete) + - [Bulk Tag](#bulk-tag) + - [Asset Analytics](#asset-analytics) +- [`SubtitleUpload`](#subtitleupload) + - [Upload Subtitles](#upload-subtitles) + - [Complete Subtitle Upload](#complete-subtitle-upload) +- [`AudioUpload`](#audioupload) + - [Add Audio](#add-audio) + - [Complete Audio Upload](#complete-audio-upload) +- [`VideoUsageAnalytics`](#videousageanalytics) + - [Video Usage Analytics](#video-usage-analytics) + - [Top Streamed Assets](#top-streamed-assets) +- [`MultipartUpload`](#multipartupload) + - [Get Part Upload URL](#get-part-upload-url) + - [Complete Multipart Upload](#complete-multipart-upload) +- [`VideoProfiles`](#videoprofiles) + - [Create Profile](#create-profile) + - [List Profiles](#list-profiles) + - [Update Profile](#update-profile) + - [Get Profile](#get-profile) + - [Delete Profile](#delete-profile) +- [`VideoPlaylists`](#videoplaylists) + - [Create Playlist](#create-playlist) + - [Get all playlists](#get-all-playlists) + - [Add asset to playlist](#add-asset-to-playlist) + - [Remove asset from playlist](#remove-asset-from-playlist) + - [Update Playlist](#update-playlist) + - [`delete`](#delete) + - [Get playlist assets](#get-playlist-assets) + - [Arrange Videos In Playlist](#arrange-videos-in-playlist) +- [`Webhooks`](#webhooks) + - [Create Webhook](#create-webhook) + - [List Webhooks](#list-webhooks) + - [Update Webhook](#update-webhook) + - [Delete Webhook](#delete-webhook) + - [Get History](#get-history) +- [`ImageSources`](#imagesources) + - [Create Source](#create-source) + - [List Sources](#list-sources) + - [Get Image Source](#get-image-source) + - [Update Source](#update-source) + - [Delete Source](#delete-source) + - [Purge Cache](#purge-cache) + - [Purge Image Cache](#purge-image-cache) +- [`ImageUsageAnalytics`](#imageusageanalytics) + - [Image Usage Analytics](#image-usage-analytics) +- [`LiveStreamAssets`](#livestreamassets) + - [Create Live Asset](#create-live-asset) + - [Update Live Asset](#update-live-asset) + - [Get Live Asset Status](#get-live-asset-status) + - [Delete Live Asset](#delete-live-asset) + - [Complete Live Stream](#complete-live-stream) + - [Filter Live Assets](#filter-live-assets) + - [`start`](#start) + - [Upload Live Thumbnails](#upload-live-thumbnails) + - [Get Live Asset Status History](#get-live-asset-status-history) +- [`RecycleBin`](#recyclebin) + - [Recover Deleted Asset](#recover-deleted-asset) + - [List Recycle Bin](#list-recycle-bin) +- [`VideoWorkspaces`](#videoworkspaces) + - [List Workspaces](#list-workspaces) + - [Create Workspace](#create-workspace) + - [Update Workspace](#update-workspace) + - [Get Workspace](#get-workspace) + - [Delete Workspace](#delete-workspace) +- [`Folders`](#folders) + - [Create Folder](#create-folder) + - [List Folders](#list-folders) + - [Get Folder](#get-folder) + - [Update Folder](#update-folder) + - [Delete Folder](#delete-folder) + - [Remove Assets From Folder](#remove-assets-from-folder) +- [`ChannelViewers`](#channelviewers) + - [Invite Channel Viewers](#invite-channel-viewers) + - [Remove Channel Viewers](#remove-channel-viewers) + - [Invite Channel Viewers via CSV](#invite-channel-viewers-via-csv) + - [List Subscribers](#list-subscribers) +- [`VideoAnalytics`](#videoanalytics) + - [Viewer Analytics](#viewer-analytics) + - [Breakdown Data](#breakdown-data) + - [Aggregated Data](#aggregated-data) +- [`OrganizationData`](#organizationdata) + - [Get Organization Details](#get-organization-details) +- [`UserData`](#userdata) + - [Get User](#get-user) +- [`AuditLogs`](#auditlogs) + - [Fetch Audit Logs](#fetch-audit-logs) +- [`Billing`](#billing) + - [List Invoices](#list-invoices) + - [Get Billing Details](#get-billing-details) + - [Update Billing Details](#update-billing-details) + - [Upcoming Invoice](#upcoming-invoice) +- [`LiveStreamWorkspaces`](#livestreamworkspaces) + - [List Workspaces](#list-workspaces-1) + - [Create Workspace](#create-workspace-1) + - [Update Workspace](#update-workspace-1) + - [Delete Workspace](#delete-workspace-1) + +## Setup + +```python +import os + +from gumlet import Gumlet + +client = Gumlet( + api_key=os.environ.get("API_KEY"), +) +``` + +## `VideoAssets` + +Upload, manage, and retrieve video assets, including thumbnails, subtitles, audio tracks, and chapters. + +### Create Asset + +An asset refers to a media content/video that is processed, stored, and delivered through Gumlet. This endpoint creates an asset allowing users to ingest media content into the Gumlet system for processing and delivery. + +| Direction | Type | +| --- | --- | +| Request | [`VideoAssetCreateParams`](./src/gumlet/types/video_asset_create_params.py) | +| Response | [`VideoAssetCreateResponse`](./src/gumlet/types/video_asset_create_response.py) | + +```python +video_asset = client.video_assets.create( + input="http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8", + collection_id="646df1c9173a4a2fcac180b4", + profile_id="646df1c9173a4a2fcac180b7", + format="ABR", + tag=["ball"], + description="some description", + metadata={"headermeta": "metavalue"}, + call_to_actions=[ + { + "start_time": 1, + "end_time": 90, + "text": "some test", + "url": "https://some-url.com", + "position_from_top": 11, + "position_from_right": 23, + "border_radius": "11", + "font_color": "#000001", + "background_color": "#ffffff", + } + ], + playlist_id="6597acd5ed6f26a9c5ca9633", + folder="697375fbfa2d1037283140e4", +) +``` + +### Create Asset Direct Upload + +This endpoint creates a video asset allowing to upload of the video from the local file system and ingest media content into the Gumlet system for processing and delivery.Body Parameters are the same as the Create Asset Body Parameters except for the `input` parameter which this endpoint does not take.A successful response will be returned with `upload_url` field. You can make `PUT` request to that URL to upload video. To upload video using `upload_url` refer to [this](https://docs.gumlet.com/docs/direct-upload#2-use-the-url-to-upload-a-file). + +| Direction | Type | +| --- | --- | +| Request | [`VideoAssetUploadParams`](./src/gumlet/types/video_asset_upload_params.py) | +| Response | [`VideoAssetUploadResponse`](./src/gumlet/types/video_asset_upload_response.py) | + +```python +video_asset = client.video_assets.upload( + collection_id="646df1c9173a4a2fcac180b4", + profile_id="646df1c9173a4a2fcac180b7", + format="ABR", + tag=["ball"], + description="some description", + metadata={"headermeta": "metavalue"}, + call_to_actions=[ + { + "start_time": 1, + "end_time": 90, + "text": "some test", + "url": "https://some-url.com", + "position_from_top": 11, + "position_from_right": 23, + "border_radius": "11", + "font_color": "#000001", + "background_color": "#ffffff", + } + ], + playlist_id="6597acd5ed6f26a9c5ca9633", + folder="697375fbfa2d1037283140e4", +) +``` + +### Asset Details + +This endpoint retrieves the details of an asset that has previously been created. + +| Direction | Type | +| --- | --- | +| Response | [`VideoAssetRetrieveDetailsResponse`](./src/gumlet/types/video_asset_retrieve_details_response.py) | + +```python +video_asset = client.video_assets.retrieve_details( + asset_id="assetId", +) +``` + +### Delete Asset + +This endpoint removes an asset given its unique asset id. The asset will be removed from storage as well, associated URLs will be inaccessible. + +```python +client.video_assets.delete( + asset_id="assetId", +) +``` + +### Update Asset + +This endpoint allows users to update video asset that has previously been created. + +| Direction | Type | +| --- | --- | +| Request | [`VideoAssetUpdateParams`](./src/gumlet/types/video_asset_update_params.py) | +| Response | [`VideoAssetUpdateResponse`](./src/gumlet/types/video_asset_update_response.py) | + +```python +video_asset = client.video_assets.update( + asset_id="", + title="Updated Title", +) +``` + +### Update thumbnail from video + +Select frame from video to use as thumbnail. + +| Direction | Type | +| --- | --- | +| Request | [`VideoAssetThumbnailSelectParams`](./src/gumlet/types/video_asset_thumbnail_select_params.py) | +| Response | [`VideoAssetThumbnailSelectResponse`](./src/gumlet/types/video_asset_thumbnail_select_response.py) | + +```python +video_asset = client.video_assets.thumbnail_select( + asset_id="assetId", + frame_at_second=2, +) +``` + +### Update thumbnail via upload + +Use any image file to use as thumbnail. Once you use the API, you will get `upload_url` in the response, and that can be used to upload the image file. + +Here is the sample curl request. + +```bash +curl --location --request PUT '' \ +--data '' +``` + +| Direction | Type | +| --- | --- | +| Response | [`VideoAssetThumbnailUploadResponse`](./src/gumlet/types/video_asset_thumbnail_upload_response.py) | + +```python +video_asset = client.video_assets.thumbnail_upload( + asset_id="assetId", +) +``` + +### Create/Update Video Asset Chapters + +This endpoint will create/update video asset chapters. + +| Direction | Type | +| --- | --- | +| Request | [`VideoAssetCreateUpdateChapterParams`](./src/gumlet/types/video_asset_create_update_chapter_params.py) | +| Response | [`VideoAssetCreateUpdateChapterResponse`](./src/gumlet/types/video_asset_create_update_chapter_response.py) | + +```python +video_asset = client.video_assets.create_update_chapter( + asset_id="assetId", + chapters=[{"label": "Chapter 1", "startTime": 0}, {"label": "Chapter 2", "startTime": 10}], +) +``` + +### List Assets + +List folders and assets for a workspace in a single response. Use `parent_id` to browse a specific folder, or filters like `title`, `status`, and `playlist_id` to search assets. + +| Direction | Type | +| --- | --- | +| Request | [`VideoAssetListParams`](./src/gumlet/types/video_asset_list_params.py) | +| Response | [`VideoAssetListResponse`](./src/gumlet/types/video_asset_list_response.py) | + +```python +video_asset = client.video_assets.list( + workspace_id="workspaceId", + type="all", + offset=0, + size=20, + signed_token="false", +) +``` + +### List Assets + +[Deprecated] This endpoint list assets in video workspace. You can also pass `status` and `tag` to filter assets. + +| Direction | Type | +| --- | --- | +| Request | [`VideoAssetListDeprecatedParams`](./src/gumlet/types/video_asset_list_deprecated_params.py) | +| Response | [`VideoAssetListDeprecatedResponse`](./src/gumlet/types/video_asset_list_deprecated_response.py) | + +```python +video_asset = client.video_assets.list_deprecated( + workspace_id="workspaceId", + sort_by="created_at", + order_by="desc", +) +``` + +### Bulk Delete + +Delete multiple VOD assets at once. + +| Direction | Type | +| --- | --- | +| Request | [`VideoAssetDeleteManyParams`](./src/gumlet/types/video_asset_delete_many_params.py) | +| Response | [`VideoAssetDeleteManyResponse`](./src/gumlet/types/video_asset_delete_many_response.py) | + +```python +video_asset = client.video_assets.delete_many( + asset_list=["64249a8858fd3a208b987702", "64784bae843b155b829bbf84"], + source_id="60bd2ba353ff754d28179ee6", +) +``` + +### Bulk Tag + +Add / remove tags from multiple assets at once. + +| Direction | Type | +| --- | --- | +| Request | [`VideoAssetTagManyParams`](./src/gumlet/types/video_asset_tag_many_params.py) | +| Response | [`VideoAssetTagManyResponse`](./src/gumlet/types/video_asset_tag_many_response.py) | + +```python +video_asset = client.video_assets.tag_many( + asset_list=["6221db301c8b821b0519fba0", "61e8f2726ec832ab2ac4fa6e"], + source_id="60bd2ba353ff754d28179ee6", + add_tags=["tag-1"], + remove_tags=["playlist-1"], +) +``` + +### Asset Analytics + +Get video analytics for a single asset. + +| Direction | Type | +| --- | --- | +| Request | [`VideoAssetAnalyticsParams`](./src/gumlet/types/video_asset_analytics_params.py) | +| Response | [`VideoAssetAnalyticsResponse`](./src/gumlet/types/video_asset_analytics_response.py) | + +```python +video_asset = client.video_assets.analytics( + asset_id="assetId", + group_by="daily", + date_range={"start_at": "", "end_at": ""}, + metrics=["impressions"], +) +``` + +## `SubtitleUpload` + +Add subtitles to an existing asset. + +### Upload Subtitles + +Upload `.srt` or `.vtt` file to the video asset. The response of this API call gives `upload_url` for each language specified. You need to send a `PUT` request of the subtitle files to those URLs. Once that's done, you need to call the subtitle upload complete API. Only after that, Gumlet will add subtitles to asset. + +| Direction | Type | +| --- | --- | +| Request | [`SubtitleUploadUploadParams`](./src/gumlet/types/subtitle_upload_upload_params.py) | +| Response | [`SubtitleUploadUploadResponse`](./src/gumlet/types/subtitle_upload_upload_response.py) | + +```python +subtitle_upload = client.subtitle_upload.upload( + asset_id="assetId", + language_codes=["en"], +) +``` + +### Complete Subtitle Upload + +This API must be called after adding subtitles; the add subtitle call gives you URLs to upload, and you complete a `PUT` request to those URLs. +Once that is done, calling this initiates the process to actually add the subtitle to the video. + +| Direction | Type | +| --- | --- | +| Request | [`SubtitleUploadCompleteParams`](./src/gumlet/types/subtitle_upload_complete_params.py) | +| Response | [`SubtitleUploadCompleteResponse`](./src/gumlet/types/subtitle_upload_complete_response.py) | + +```python +subtitle_upload = client.subtitle_upload.complete( + asset_id="assetId", + upload_responses=[{"language_code": "en", "uploaded": True}], +) +``` + +## `AudioUpload` + +Add additional audio tracks to an existing asset. + +### Add Audio + +Add any audio file to the video asset. +The response of this API call gives `upload_url` for each language specified. You need to send a `PUT` request of the audio files to those URLs. Once that's done, you need to call the audio upload complete API. Only after that will Gumlet add audio to the asset. + +| Direction | Type | +| --- | --- | +| Request | [`AudioUploadUploadParams`](./src/gumlet/types/audio_upload_upload_params.py) | +| Response | [`AudioUploadUploadResponse`](./src/gumlet/types/audio_upload_upload_response.py) | + +```python +audio_upload = client.audio_upload.upload( + asset_id="assetId", + language_codes=["en"], +) +``` + +### Complete Audio Upload + +This API must be called after adding audio(s); The add audio call gives you URLs to upload, and you complete a `PUT` request to those URLs. +Once that is done, calling this initiates the process to actually add the subtitle to the video. + +| Direction | Type | +| --- | --- | +| Request | [`AudioUploadCompleteParams`](./src/gumlet/types/audio_upload_complete_params.py) | +| Response | [`AudioUploadCompleteResponse`](./src/gumlet/types/audio_upload_complete_response.py) | + +```python +audio_upload = client.audio_upload.complete( + asset_id="assetId", + upload_responses=[{"language_codes": ["en"], "uploaded": True}], +) +``` + +## `VideoUsageAnalytics` + +Query video analytics and streaming duration usage data. + +### Video Usage Analytics + +This endpoint gives usage analytics data of your videos. Ex - top assets, bandwidth consumption + +| Direction | Type | +| --- | --- | +| Request | [`VideoUsageAnalyticRetrieveParams`](./src/gumlet/types/video_usage_analytic_retrieve_params.py) | +| Response | [`VideoUsageAnalyticRetrieveResponse`](./src/gumlet/types/video_usage_analytic_retrieve_response.py) | + +```python +video_usage_analytic = client.video_usage_analytics.retrieve( + metrics=["bandwidth_consumption", "asset_duration", "storage_unit", "top_assets", "drm_requests"], + date_range={"start_at": "2026-08-01", "end_at": "2026-08-20"}, + top_assets_count="5", + top_assets_page="0", + group_by="hourly", +) +``` + +### Top Streamed Assets + +This endpoint lists top streamed assets in a video collection + +| Direction | Type | +| --- | --- | +| Request | [`VideoUsageAnalyticTopAssetsParams`](./src/gumlet/types/video_usage_analytic_top_assets_params.py) | +| Response | [`VideoUsageAnalyticTopAssetsResponse`](./src/gumlet/types/video_usage_analytic_top_assets_response.py) | + +```python +video_usage_analytic = client.video_usage_analytics.top_assets( + start_at="2026-06-21", + end_at="2026-06-30", + page="1", + page_size="1000", +) +``` + +## `MultipartUpload` + +Upload large video files in parts and complete the multipart upload. + +### Get Part Upload URL + +Use this endpoint to retrieve a pre-signed upload URL for the given part number. + +| Direction | Type | +| --- | --- | +| Response | [`MultipartUploadRetrievePartURLResponse`](./src/gumlet/types/multipart_upload_retrieve_part_url_response.py) | + +```python +multipart_upload = client.multipart_upload.retrieve_part_url( + asset_id="assetId", + part_number="partNumber", +) +``` + +### Complete Multipart Upload + +Once you upload all parts to S3 bucket via pre-signed URL, use this endpoint to complete the multipart upload. + +| Direction | Type | +| --- | --- | +| Request | [`MultipartUploadCompleteParams`](./src/gumlet/types/multipart_upload_complete_params.py) | +| Response | [`MultipartUploadCompleteResponse`](./src/gumlet/types/multipart_upload_complete_response.py) | + +```python +multipart_upload = client.multipart_upload.complete( + asset_id="assetId", +) +``` + +## `VideoProfiles` + +Create and manage encoding/output profiles for video assets. + +### Create Profile + +Gumlet provides the functionality of creating multiple video assets using the same set of parameters. A Video profile is a set of parameters that can be referenced/used while creating a video as a single parameter. + +| Direction | Type | +| --- | --- | +| Request | [`VideoProfileCreateParams`](./src/gumlet/types/video_profile_create_params.py) | +| Response | [`VideoProfileCreateResponse`](./src/gumlet/types/video_profile_create_response.py) | + +```python +video_profile = client.video_profiles.create( + name="Gumlet-Profile-1", + format="ABR", +) +``` + +### List Profiles + +This endpoint retrieves the details of all profiles that have previously been created. + +| Direction | Type | +| --- | --- | +| Request | [`VideoProfileListParams`](./src/gumlet/types/video_profile_list_params.py) | +| Response | [`VideoProfileListResponse`](./src/gumlet/types/video_profile_list_response.py) | + +```python +video_profile = client.video_profiles.list() +``` + +### Update Profile + +Update an existing profile. Settings provided in body parameters will only be updated in the existing profile. + +| Direction | Type | +| --- | --- | +| Request | [`VideoProfileUpdateParams`](./src/gumlet/types/video_profile_update_params.py) | +| Response | [`VideoProfileUpdateResponse`](./src/gumlet/types/video_profile_update_response.py) | + +```python +video_profile = client.video_profiles.update( + path_profile_id="profileId", + body_profile_id="", + format="ABR", +) +``` + +### Get Profile + +This endpoint retrieves the details of a video profile that has previously been created. + +| Direction | Type | +| --- | --- | +| Response | [`VideoProfileRetrieveResponse`](./src/gumlet/types/video_profile_retrieve_response.py) | + +```python +video_profile = client.video_profiles.retrieve( + profile_id="profileId", +) +``` + +### Delete Profile + +This endpoint removes a profile given its unique `profile_id`. The profile will be removed but video assets created using the profile will remain as it is. + +| Direction | Type | +| --- | --- | +| Response | [`VideoProfileDeleteResponse`](./src/gumlet/types/video_profile_delete_response.py) | + +```python +video_profile = client.video_profiles.delete( + profile_id="profileId", +) +``` + +## `VideoPlaylists` + +Create and manage playlists, and control which assets belong to them. + +### Create Playlist + +Create new playlist inside video wprkspace + +| Direction | Type | +| --- | --- | +| Request | [`VideoPlaylistCreateParams`](./src/gumlet/types/video_playlist_create_params.py) | +| Response | [`VideoPlaylistCreateResponse`](./src/gumlet/types/video_playlist_create_response.py) | + +```python +video_playlist = client.video_playlists.create( + collection_id="{{video-source-id}}", + title="Playlist-Title", + description="This is description for playlist.", +) +``` + +### Get all playlists + +Get all playlists for given workspace + +| Direction | Type | +| --- | --- | +| Request | [`VideoPlaylistListAllParams`](./src/gumlet/types/video_playlist_list_all_params.py) | +| Response | [`VideoPlaylistListAllResponse`](./src/gumlet/types/video_playlist_list_all_response.py) | + +```python +video_playlist = client.video_playlists.list_all() +``` + +### Add asset to playlist + +This operation adds a single asset or a list of assets to a playlist. + +| Direction | Type | +| --- | --- | +| Request | [`VideoPlaylistCreateAssetParams`](./src/gumlet/types/video_playlist_create_asset_params.py) | +| Response | [`VideoPlaylistCreateAssetResponse`](./src/gumlet/types/video_playlist_create_asset_response.py) | + +```python +video_playlist = client.video_playlists.create_asset( + playlist_id="playlistId", + asset_list=[ + {"asset_id": "6508790283e4d60611846790"}, + {"position": 1, "asset_id": "650878f883e4d6061184677d"}, + {"asset_id": "650878de83e4d6061184676a"}, + {"position": 2, "asset_id": "650878d883e4d60611846757"}, + {"position": 3, "asset_id": "65578dd87eebc22dcdd549a2"}, + ], +) +``` + +### Remove asset from playlist + +Removed an asset or list of assets from a given playlist. + +| Direction | Type | +| --- | --- | +| Request | [`VideoPlaylistDeleteAssetParams`](./src/gumlet/types/video_playlist_delete_asset_params.py) | +| Response | [`VideoPlaylistDeleteAssetResponse`](./src/gumlet/types/video_playlist_delete_asset_response.py) | + +```python +video_playlist = client.video_playlists.delete_asset( + playlist_id="playlistId", + delete_list=["6508790783e4d606118467a3"], +) +``` + +### Update Playlist + +This endpoint allows you to update playlist name, channel visibility, or playlist order on a channel page. + +| Direction | Type | +| --- | --- | +| Request | [`VideoPlaylistUpdateParams`](./src/gumlet/types/video_playlist_update_params.py) | +| Response | [`VideoPlaylistUpdateResponse`](./src/gumlet/types/video_playlist_update_response.py) | + +```python +video_playlist = client.video_playlists.update( + playlist_id="playlistId", + title="Playlist-Title-Updated", + description="This is updated description", + position=6, + player_config={ + "preload": True, + "autoplay": False, + "disable_seek": True, + "disable_player_controls": False, + "powered_by_gumlet_overlay": False, + "allow_drm_protected_videos": False, + "loop": False, + "player_color": "#6658ea", + "include_seo": True, + "subtitle_enabled": True, + "pixel_tags": {}, + "logo_width": 51, + "logo_height": 100, + "dynamic_watermark": False, + "watermark_font_size": 1, + "watermark_font_color": "#ff0000", + "watermark_bg_color": "transparent", + "watermark_interval": 1000, + }, + channel_visibility=False, +) +``` + +### `delete` + +Deletes this playlist. + +```python +client.video_playlists.delete( + playlist_id="playlistId", +) +``` + +### Get playlist assets + +Get a list of all assets inside playlist. You can choose in which order are assets returned. + +| Direction | Type | +| --- | --- | +| Request | [`VideoPlaylistListAssetsParams`](./src/gumlet/types/video_playlist_list_assets_params.py) | +| Response | [`VideoPlaylistListAssetsResponse`](./src/gumlet/types/video_playlist_list_assets_response.py) | + +```python +video_playlist = client.video_playlists.list_assets( + playlist_id="playlistId", + sort_order=1, + page_number=1, + page_size="10", +) +``` + +### Arrange Videos In Playlist + +Reorder videos inside a playlist either by moving a single asset to a position or by sorting the playlist by title or created date. + +| Direction | Type | +| --- | --- | +| Request | [`VideoPlaylistReorderAssetParams`](./src/gumlet/types/video_playlist_reorder_asset_params.py) | +| Response | [`VideoPlaylistReorderAssetResponse`](./src/gumlet/types/video_playlist_reorder_asset_response.py) | + +```python +video_playlist = client.video_playlists.reorder_asset( + playlist_id="playlistId", + asset_id="6e82bf783e88be000ab45ed2", + page_number=1, + page_size=10, + asset_position=0, +) +``` + +## `Webhooks` + +Configure webhooks for account and asset events. + +### Create Webhook + +Creates a new webhook listener. + +| Direction | Type | +| --- | --- | +| Request | [`WebhookCreateParams`](./src/gumlet/types/webhook_create_params.py) | +| Response | [`WebhookCreateResponse`](./src/gumlet/types/webhook_create_response.py) | + +```python +webhook = client.webhooks.create( + url="", + secret_token="", + triggers=[""], + sources=[""], +) +``` + +### List Webhooks + +List all webhooks. + +| Direction | Type | +| --- | --- | +| Response | [`WebhookListResponse`](./src/gumlet/types/webhook_list_response.py) | + +```python +webhook = client.webhooks.list() +``` + +### Update Webhook + +Update a webhook listener. + +| Direction | Type | +| --- | --- | +| Request | [`WebhookUpdateParams`](./src/gumlet/types/webhook_update_params.py) | +| Response | [`WebhookUpdateResponse`](./src/gumlet/types/webhook_update_response.py) | + +```python +webhook = client.webhooks.update( + webhook_id="webhookId", +) +``` + +### Delete Webhook + +Delete webhook listener endpoint. + +| Direction | Type | +| --- | --- | +| Response | [`WebhookDeleteResponse`](./src/gumlet/types/webhook_delete_response.py) | + +```python +webhook = client.webhooks.delete( + webhook_id="webhookId", +) +``` + +### Get History + +Get logs history for a given webhook. + +| Direction | Type | +| --- | --- | +| Response | [`WebhookHistoryResponse`](./src/gumlet/types/webhook_history_response.py) | + +```python +webhook = client.webhooks.history( + webhook_id="webhookId", +) +``` + +## `ImageSources` + +Manage image sources, view image analytics, and purge the image cache. + +### Create Source + +This endpoint allows users to create image source. + +| Direction | Type | +| --- | --- | +| Request | [`ImageSourceCreateParams`](./src/gumlet/types/image_source_create_params.py) | +| Response | [`ImageSourceCreateResponse`](./src/gumlet/types/image_source_create_response.py) | + +```python +image_source = client.image_sources.create( + namespace="google-demo", + type="webfolder", + webfolder={"base_url": "https://www.google.com"}, +) +``` + +### List Sources + +This endpoint list image sources which are assigned to the user or token. + +| Direction | Type | +| --- | --- | +| Request | [`ImageSourceListParams`](./src/gumlet/types/image_source_list_params.py) | +| Response | [`ImageSourceListResponse`](./src/gumlet/types/image_source_list_response.py) | + +```python +image_source = client.image_sources.list( + offset=0, + size=20, +) +``` + +### Get Image Source + +Get all details about image source. + +| Direction | Type | +| --- | --- | +| Response | [`ImageSourceRetrieveResponse`](./src/gumlet/types/image_source_retrieve_response.py) | + +```python +image_source = client.image_sources.retrieve( + image_source_id="imageSourceId", +) +``` + +### Update Source + +This endpoint allows users to update image source that has previously been created. + +| Direction | Type | +| --- | --- | +| Request | [`ImageSourceUpdateParams`](./src/gumlet/types/image_source_update_params.py) | +| Response | [`ImageSourceUpdateResponse`](./src/gumlet/types/image_source_update_response.py) | + +```python +image_source = client.image_sources.update( + image_source_id="imageSourceId", + type="aws", + aws={ + "bucket_name": "my-bucket-test", + "bucket_region": "ap-southeast-1", + "access_key": "BQUA6QFXVWHAAB6IO2X1", + "secret": "aws_secret", + }, +) +``` + +### Delete Source + +This endpoint removes a image source. All image delivery using this subdomain will be stopped. + +| Direction | Type | +| --- | --- | +| Response | [`ImageSourceDeleteResponse`](./src/gumlet/types/image_source_delete_response.py) | + +```python +image_source = client.image_sources.delete( + image_source_id="imageSourceId", +) +``` + +### Purge Cache + +You can purge cache for any image by using our cache purge API. + +| Direction | Type | +| --- | --- | +| Request | [`ImageSourcePurgeCacheParams`](./src/gumlet/types/image_source_purge_cache_params.py) | +| Response | [`ImageSourcePurgeCacheResponse`](./src/gumlet/types/image_source_purge_cache_response.py) | + +```python +image_source = client.image_sources.purge_cache( + subdomain="subdomain", + paths=["image.jpeg", "image2.png"], +) +``` + +### Purge Image Cache + +You can purge the cache for any image path by using this cache purge API. + +| Direction | Type | +| --- | --- | +| Request | [`ImageSourcePurgeParams`](./src/gumlet/types/image_source_purge_params.py) | +| Response | [`ImageSourcePurgeResponse`](./src/gumlet/types/image_source_purge_response.py) | + +```python +image_source = client.image_sources.purge( + source_id="sourceId", + paths=["image.jpeg", "image2.png"], +) +``` + +## `ImageUsageAnalytics` + +Query aggregated and chart-ready image usage analytics data. + +### Image Usage Analytics + +This endpoint helps you get image analytics data like bandwidth consumption, request count, CDN hit ratio, etc. + +| Direction | Type | +| --- | --- | +| Request | [`ImageUsageAnalyticRetrieveParams`](./src/gumlet/types/image_usage_analytic_retrieve_params.py) | +| Response | [`ImageUsageAnalyticRetrieveResponse`](./src/gumlet/types/image_usage_analytic_retrieve_response.py) | + +```python +image_usage_analytic = client.image_usage_analytics.retrieve( + metrics=["bandwidth_consumption"], + date_range={}, + group_by="daily", +) +``` + +## `LiveStreamAssets` + +Create, control, and monitor live stream assets. + +### Create Live Asset + +A live asset refers to a media content/video that is live-streamed through Gumlet. This endpoint creates a live streaming asset allowing users to live stream a video that will be pushed to Gumlet. + +| Direction | Type | +| --- | --- | +| Request | [`LiveStreamAssetCreateParams`](./src/gumlet/types/live_stream_asset_create_params.py) | +| Response | [`LiveStreamAssetCreateResponse`](./src/gumlet/types/live_stream_asset_create_response.py) | + +```python +live_stream_asset = client.live_stream_assets.create( + live_source_id="", + resolution="", +) +``` + +### Update Live Asset + +A live asset refers to a media content/video that is live-streamed through Gumlet. This endpoint allows user to update a live streaming asset. + +| Direction | Type | +| --- | --- | +| Request | [`LiveStreamAssetUpdateParams`](./src/gumlet/types/live_stream_asset_update_params.py) | +| Response | [`LiveStreamAssetUpdateResponse`](./src/gumlet/types/live_stream_asset_update_response.py) | + +```python +live_stream_asset = client.live_stream_assets.update( + live_asset_id="", +) +``` + +### Get Live Asset Status + +This endpoint retrieves the details of a live video asset that has previously been created. + +| Direction | Type | +| --- | --- | +| Response | [`LiveStreamAssetRetrieveStatusResponse`](./src/gumlet/types/live_stream_asset_retrieve_status_response.py) | + +```python +live_stream_asset = client.live_stream_assets.retrieve_status( + live_asset_id="liveAssetId", +) +``` + +### Delete Live Asset + +This endpoint removes a live asset given its unique live asset id. The live asset will be removed from storage as well, associated URLs will be inaccessible. + +| Direction | Type | +| --- | --- | +| Response | [`LiveStreamAssetDeleteResponse`](./src/gumlet/types/live_stream_asset_delete_response.py) | + +```python +live_stream_asset = client.live_stream_assets.delete( + live_asset_id="liveAssetId", +) +``` + +### Complete Live Stream + +This endpoint allows marking live assets complete. Once the live asset is marked complete, it can no longer be used to ingest the live stream on Gumlet. + +| Direction | Type | +| --- | --- | +| Response | [`LiveStreamAssetCompleteResponse`](./src/gumlet/types/live_stream_asset_complete_response.py) | + +```python +live_stream_asset = client.live_stream_assets.complete( + live_asset_id="liveAssetId", +) +``` + +### Filter Live Assets + +This endpoint lists live assets on the basis of `status` for the given `live_source_id`. + +| Direction | Type | +| --- | --- | +| Request | [`LiveStreamAssetFilterParams`](./src/gumlet/types/live_stream_asset_filter_params.py) | +| Response | [`LiveStreamAssetFilterResponse`](./src/gumlet/types/live_stream_asset_filter_response.py) | + +```python +live_stream_asset = client.live_stream_assets.filter( + live_source_id="liveSourceId", +) +``` + +### `start` + +Start a live stream. + +```python +client.live_stream_assets.start( + live_asset_id="liveAssetId", +) +``` + +### Upload Live Thumbnails + +Generate presigned upload URLs for live stream thumbnails. Supported thumbnail states are `preparing`, `disconnected`, and `end`. + +| Direction | Type | +| --- | --- | +| Request | [`LiveStreamAssetUploadParams`](./src/gumlet/types/live_stream_asset_upload_params.py) | +| Response | [`LiveStreamAssetUploadResponse`](./src/gumlet/types/live_stream_asset_upload_response.py) | + +```python +live_stream_asset = client.live_stream_assets.upload( + live_asset_id="68c406b147f9ad0c0d584ce2", + statuses="preparing", +) +``` + +### Get Live Asset Status History + +This endpoint retrieves the history of a live video asset that has previously been created. + +| Direction | Type | +| --- | --- | +| Response | [`LiveStreamAssetStatusHistoryResponse`](./src/gumlet/types/live_stream_asset_status_history_response.py) | + +```python +live_stream_asset = client.live_stream_assets.status_history( + live_asset_id="liveAssetId", +) +``` + +## `RecycleBin` + +Endpoints to list deleted assets and restore them. + +### Recover Deleted Asset + +Recovers a deleted asset from the recycle bin. + +| Direction | Type | +| --- | --- | +| Request | [`RecycleBinRecoverParams`](./src/gumlet/types/recycle_bin_recover_params.py) | + +```python +client.recycle_bin.recover( + asset_id="", +) +``` + +### List Recycle Bin + +List all assets in a recycle bin for a given workspace. The deleted assets are available for 30 days. After that, assets are permanently deleted. + +| Direction | Type | +| --- | --- | +| Request | [`RecycleBinListParams`](./src/gumlet/types/recycle_bin_list_params.py) | +| Response | [`RecycleBinListResponse`](./src/gumlet/types/recycle_bin_list_response.py) | + +```python +recycle_bin = client.recycle_bin.list( + size=20, + workspace_id="workspace_id", +) +``` + +## `VideoWorkspaces` + +Create and manage video workspaces. + +### List Workspaces + +This endpoint list video workspace which are assigned to the user or token. + +| Direction | Type | +| --- | --- | +| Request | [`VideoWorkspaceListParams`](./src/gumlet/types/video_workspace_list_params.py) | +| Response | [`VideoWorkspaceListResponse`](./src/gumlet/types/video_workspace_list_response.py) | + +```python +video_workspace = client.video_workspaces.list( + offset="0", + size="10", +) +``` + +### Create Workspace + +Video workspaces are top-level entities in Gumlet. You can use them to organize videos for different teams/departments or use cases. + +| Direction | Type | +| --- | --- | +| Request | [`VideoWorkspaceCreateParams`](./src/gumlet/types/video_workspace_create_params.py) | +| Response | [`VideoWorkspaceCreateResponse`](./src/gumlet/types/video_workspace_create_response.py) | + +```python +video_workspace = client.video_workspaces.create( + name="zoom-workspace", + type="direct-upload", + zoom={"secret": "yourSecret"}, +) +``` + +### Update Workspace + +This endpoint allows users to update video workspace that has previously been created. + +| Direction | Type | +| --- | --- | +| Request | [`VideoWorkspaceUpdateParams`](./src/gumlet/types/video_workspace_update_params.py) | +| Response | [`VideoWorkspaceUpdateResponse`](./src/gumlet/types/video_workspace_update_response.py) | + +```python +video_workspace = client.video_workspaces.update( + workspace_id="workspaceId", + name="awsrename", + default_profile_id="646df1c9173a4a2fcac180b7", + type="aws", + aws={ + "bucket_name": "my-bucket-test", + "bucket_region": "ap-southeast-1", + "access_key": "BQUA6QFXVWHAAB6IO2X1", + "secret": "aws_secret", + }, +) +``` + +### Get Workspace + +This endpoint get all the data of video workspace that has previously been created. + +| Direction | Type | +| --- | --- | +| Response | [`VideoWorkspaceRetrieveResponse`](./src/gumlet/types/video_workspace_retrieve_response.py) | + +```python +video_workspace = client.video_workspaces.retrieve( + workspace_id="workspaceId", +) +``` + +### Delete Workspace + +This endpoint removes a video workspace given its unique asset id. All the asset in workspace will be removed from storage as well, associated URLs will be inaccessible. + +| Direction | Type | +| --- | --- | +| Response | [`VideoWorkspaceDeleteResponse`](./src/gumlet/types/video_workspace_delete_response.py) | + +```python +video_workspace = client.video_workspaces.delete( + workspace_id="workspaceId", +) +``` + +## `Folders` + +Organize video assets into folders within a workspace. + +### Create Folder + +Create a folder inside a video workspace. Optionally provide `parent_id` to create a nested folder. + +| Direction | Type | +| --- | --- | +| Request | [`FolderCreateParams`](./src/gumlet/types/folder_create_params.py) | +| Response | [`FolderCreateResponse`](./src/gumlet/types/folder_create_response.py) | + +```python +folder = client.folders.create( + workspace_id="workspaceId", + name="Course Assets", + parent_id="", +) +``` + +### List Folders + +List folders for a video workspace. Use `parent_id` to list only folders inside a specific parent folder. + +| Direction | Type | +| --- | --- | +| Request | [`FolderListParams`](./src/gumlet/types/folder_list_params.py) | +| Response | [`FolderListResponse`](./src/gumlet/types/folder_list_response.py) | + +```python +folder = client.folders.list( + workspace_id="workspaceId", +) +``` + +### Get Folder + +Get a single folder by id. + +| Direction | Type | +| --- | --- | +| Response | [`FolderRetrieveResponse`](./src/gumlet/types/folder_retrieve_response.py) | + +```python +folder = client.folders.retrieve( + workspace_id="workspaceId", + folder_id="folderId", +) +``` + +### Update Folder + +Rename a folder, move it to another parent folder, or move assets into the folder by sending `asset_ids`. + +| Direction | Type | +| --- | --- | +| Request | [`FolderUpdateParams`](./src/gumlet/types/folder_update_params.py) | +| Response | [`FolderUpdateResponse`](./src/gumlet/types/folder_update_response.py) | + +```python +folder = client.folders.update( + workspace_id="workspaceId", + folder_id="folderId", + name="Course Assets Updated", +) +``` + +### Delete Folder + +Delete a folder. Descendant folders and assets inside them are deleted by the backend workflow. + +| Direction | Type | +| --- | --- | +| Response | [`FolderDeleteResponse`](./src/gumlet/types/folder_delete_response.py) | + +```python +folder = client.folders.delete( + workspace_id="workspaceId", + folder_id="folderId", +) +``` + +### Remove Assets From Folder + +Remove one or more assets from their current folder assignment inside the workspace. + +| Direction | Type | +| --- | --- | +| Request | [`FolderDeleteAssetsParams`](./src/gumlet/types/folder_delete_assets_params.py) | +| Response | [`FolderDeleteAssetsResponse`](./src/gumlet/types/folder_delete_assets_response.py) | + +```python +folder = client.folders.delete_assets( + workspace_id="workspaceId", + asset_ids=["67e4f2b4403562dbea654301", "67e4f2bb403562dbea654302"], +) +``` + +## `ChannelViewers` + +Invite and remove viewers on a private video channel. + +### Invite Channel Viewers + +Invite one or more viewers to a members-only channel. + +| Direction | Type | +| --- | --- | +| Request | [`ChannelViewerInviteParams`](./src/gumlet/types/channel_viewer_invite_params.py) | +| Response | [`ChannelViewerInviteResponse`](./src/gumlet/types/channel_viewer_invite_response.py) | + +```python +channel_viewer = client.channel_viewers.invite( + video_workspace_id="videoWorkspaceId", + users=[ + {"email": "test@gumlet.com", "name": "Test User-0"}, + {"email": "test+1@gumlet.com", "name": "Test User-1"}, + {"email": "test+2@gumlet.com", "name": "Test User-2"}, + ], +) +``` + +### Remove Channel Viewers + +Remove one or more viewers from a channel by email address. + +| Direction | Type | +| --- | --- | +| Request | [`ChannelViewerDeleteParams`](./src/gumlet/types/channel_viewer_delete_params.py) | +| Response | [`ChannelViewerDeleteResponse`](./src/gumlet/types/channel_viewer_delete_response.py) | + +```python +channel_viewer = client.channel_viewers.delete( + video_workspace_id="videoWorkspaceId", + emails=["test@gumlet.com", "test+2@gumlet.com"], +) +``` + +### Invite Channel Viewers via CSV + +Invite viewers to a channel by uploading a CSV file. + +| Direction | Type | +| --- | --- | +| Request | [`ChannelViewerInviteCsvParams`](./src/gumlet/types/channel_viewer_invite_csv_params.py) | +| Response | [`ChannelViewerInviteCsvResponse`](./src/gumlet/types/channel_viewer_invite_csv_response.py) | + +```python +channel_viewer = client.channel_viewers.invite_csv( + video_workspace_id="videoWorkspaceId", + viewers_csv=b"viewers.csv", +) +``` + +### List Subscribers + +List all channel subscribers. + +| Direction | Type | +| --- | --- | +| Request | [`ChannelViewerListSubscribersParams`](./src/gumlet/types/channel_viewer_list_subscribers_params.py) | +| Response | [`ChannelViewerListSubscribersResponse`](./src/gumlet/types/channel_viewer_list_subscribers_response.py) | + +```python +channel_viewer = client.channel_viewers.list_subscribers( + workspace_id="workspaceId", + page_number=1, + page_size=10, +) +``` + +## `VideoAnalytics` + +Query aggregated and chart-ready video analytics data. + +### Viewer Analytics + +This endpoint retrieves viewer analytics data. This endpoint is use for deep insights on the analytics data. + +| Direction | Type | +| --- | --- | +| Request | [`VideoAnalyticChartDataParams`](./src/gumlet/types/video_analytic_chart_data_params.py) | +| Response | [`VideoAnalyticChartDataResponse`](./src/gumlet/types/video_analytic_chart_data_response.py) | + +```python +video_analytic = client.video_analytics.chart_data( + metrics=[""], + workspace_id="", + date_range={"start_at": "2024-01-01", "end_at": "2024-01-01"}, + group_by="daily", +) +``` + +### Breakdown Data + +This endpoint retrieves breakdown data of the given metrics by given breakdown field + +| Direction | Type | +| --- | --- | +| Request | [`VideoAnalyticBreakdownDataParams`](./src/gumlet/types/video_analytic_breakdown_data_params.py) | +| Response | [`VideoAnalyticBreakdownDataResponse`](./src/gumlet/types/video_analytic_breakdown_data_response.py) | + +```python +video_analytic = client.video_analytics.breakdown_data( + date_range={"start_at": "2026-07-20", "end_at": "2026-08-20"}, + filters=[], + breakdowns=[ + {"name": "custom_video_id", "metric": "views", "page": 1, "page_size": 10}, + {"name": "custom_video_title", "metric": "completion_percent_by_views", "page": 1, "page_size": 10}, + ], + workspace_id="6694c405e63913eecf3cf5fb", +) +``` + +### Aggregated Data + +This endpoint retrieves aggregated data of the given metrics. + +| Direction | Type | +| --- | --- | +| Request | [`VideoAnalyticAggregatedDataParams`](./src/gumlet/types/video_analytic_aggregated_data_params.py) | +| Response | [`VideoAnalyticAggregatedDataResponse`](./src/gumlet/types/video_analytic_aggregated_data_response.py) | + +```python +video_analytic = client.video_analytics.aggregated_data( + aggregate=[{"metric": "views", "function": "sum"}], + workspace_id="", + timeframe={}, +) +``` + +## `OrganizationData` + +Endpoints to get organization details. + +### Get Organization Details + +You can get organization data using this API. + +| Direction | Type | +| --- | --- | +| Response | [`OrganizationDataFetchOrgResponse`](./src/gumlet/types/organization_data_fetch_org_response.py) | + +```python +organization_data = client.organization_data.fetch_org() +``` + +## `UserData` + +Endpoints to get user details. + +### Get User + +This endpoint gives information about the user account. + +| Direction | Type | +| --- | --- | +| Response | [`UserDataFetchResponse`](./src/gumlet/types/user_data_fetch_response.py) | + +```python +user_data = client.user_data.fetch() +``` + +## `AuditLogs` + +Get detailed user activity logs for the entire organisation. + +### Fetch Audit Logs + +Get audit logs for the user activity in your organisation. Please note that this endpoint can only be accessed by `owner` and `admin` role users. + +| Direction | Type | +| --- | --- | +| Request | [`AuditLogFetchParams`](./src/gumlet/types/audit_log_fetch_params.py) | +| Response | [`AuditLogFetchResponse`](./src/gumlet/types/audit_log_fetch_response.py) | + +```python +audit_log = client.audit_logs.fetch( + date_range={"start_at": "2026-08-25", "end_at": "2026-08-29"}, + page_number=1, + page_size=100, +) +``` + +## `Billing` + +Get / change all details about billing and invoices. + +### List Invoices + +Liost all invoices that are generated so far. + +| Direction | Type | +| --- | --- | +| Response | [`BillingListInvoicesResponse`](./src/gumlet/types/billing_list_invoices_response.py) | + +```python +billing = client.billing.list_invoices() +``` + +### Get Billing Details + +Get billing details for this organization. + +| Direction | Type | +| --- | --- | +| Response | [`BillingFetchDetailsResponse`](./src/gumlet/types/billing_fetch_details_response.py) | + +```python +billing = client.billing.fetch_details() +``` + +### Update Billing Details + +Update billing details + +| Direction | Type | +| --- | --- | +| Request | [`BillingUpdateDetailsParams`](./src/gumlet/types/billing_update_details_params.py) | +| Response | [`BillingUpdateDetailsResponse`](./src/gumlet/types/billing_update_details_response.py) | + +```python +billing = client.billing.update_details( + address_line="", + city="", + company_name="", + country_code="", + gst_number="", + postal="", + state_code="", +) +``` + +### Upcoming Invoice + +Get details about upcoming invoice. + +| Direction | Type | +| --- | --- | +| Response | [`BillingFetchUpcomingInvoiceResponse`](./src/gumlet/types/billing_fetch_upcoming_invoice_response.py) | + +```python +billing = client.billing.fetch_upcoming_invoice() +``` + +## `LiveStreamWorkspaces` + +Create and manage live stream workspaces. + +### List Workspaces + +List all live stream workspaces. + +| Direction | Type | +| --- | --- | +| Response | [`LiveStreamWorkspaceListResponse`](./src/gumlet/types/live_stream_workspace_list_response.py) | + +```python +live_stream_workspace = client.live_stream_workspaces.list() +``` + +### Create Workspace + +Create live stream workspace. + +| Direction | Type | +| --- | --- | +| Request | [`LiveStreamWorkspaceCreateParams`](./src/gumlet/types/live_stream_workspace_create_params.py) | +| Response | [`LiveStreamWorkspaceCreateResponse`](./src/gumlet/types/live_stream_workspace_create_response.py) | + +```python +live_stream_workspace = client.live_stream_workspaces.create( + name="", +) +``` + +### Update Workspace + +Update live stream workspace. + +| Direction | Type | +| --- | --- | +| Request | [`LiveStreamWorkspaceUpdateParams`](./src/gumlet/types/live_stream_workspace_update_params.py) | +| Response | [`LiveStreamWorkspaceUpdateResponse`](./src/gumlet/types/live_stream_workspace_update_response.py) | + +```python +live_stream_workspace = client.live_stream_workspaces.update( + live_workspace_id="liveWorkspaceId", + name="live-stream-collections", + video_source_id="67bea1d66ca0059a95bf7de9", +) +``` + +### Delete Workspace + +Delete the live stream workspace. + +| Direction | Type | +| --- | --- | +| Response | [`LiveStreamWorkspaceDeleteResponse`](./src/gumlet/types/live_stream_workspace_delete_response.py) | + +```python +live_stream_workspace = client.live_stream_workspaces.delete( + live_workspace_id="liveWorkspaceId", +) +``` diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..ff73ad3 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,31 @@ +[project] +name = "gumlet" +version = "0.2.0" # x-release-please-version +description = "Gumlet helps developers deliver online video and images. This API encompasses Gumlet Video, Image and Video Analytics functionality to help you build your products better and faster than ever before." +readme = "README.md" +license = "Apache-2.0" +authors = [ + { name = "Gumlet" }, +] +requires-python = ">= 3.9" +dependencies = [ + "httpx>=0.23.0, <1", + "pydantic>=1.9.0, <3", + "typing-extensions>=4.14, <5", + "anyio>=3.5.0, <5", + "distro>=1.7.0, <2", + "sniffio", +] + +[project.optional-dependencies] +aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.9"] + +[build-system] +requires = ["hatchling>=1.18.0"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +packages = ["src/gumlet"] + +[tool.ruff] +line-length = 120 diff --git a/scalar-sdk.manifest.json b/scalar-sdk.manifest.json new file mode 100644 index 0000000..747db48 --- /dev/null +++ b/scalar-sdk.manifest.json @@ -0,0 +1,8477 @@ +{ + "name": "Gumlet", + "slug": "gumlet", + "generatorVersion": "0.32.14", + "servers": [ + "https://api.gumlet.com/v1" + ], + "environments": { + "production": "https://api.gumlet.com/v1" + }, + "environmentOrder": [ + "production" + ], + "auth": [ + "bearer" + ], + "authDetails": [ + { + "kind": "bearer", + "id": "API_KEY" + } + ], + "clientHeaderParams": [], + "schemas": [], + "resources": [ + "videoAssets", + "subtitleUpload", + "audioUpload", + "videoUsageAnalytics", + "multipartUpload", + "videoProfiles", + "videoPlaylists", + "webhooks", + "imageSources", + "imageUsageAnalytics", + "liveStreamAssets", + "recycleBin", + "videoWorkspaces", + "folders", + "channelViewers", + "videoAnalytics", + "organizationData", + "userData", + "auditLogs", + "billing", + "liveStreamWorkspaces" + ], + "publicResources": [ + "videoAssets", + "subtitleUpload", + "audioUpload", + "videoUsageAnalytics", + "multipartUpload", + "videoProfiles", + "videoPlaylists", + "webhooks", + "imageSources", + "imageUsageAnalytics", + "liveStreamAssets", + "recycleBin", + "videoWorkspaces", + "folders", + "channelViewers", + "videoAnalytics", + "organizationData", + "userData", + "auditLogs", + "billing", + "liveStreamWorkspaces" + ], + "operations": [ + { + "resource": "videoAssets", + "publicResource": "videoAssets", + "operation": "create", + "publicOperation": "create", + "deprecated": false, + "method": "POST", + "path": "/video/assets", + "pathParams": [], + "publicPathParams": [], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "input", + "collection_id", + "profile_id", + "format", + "tag", + "title", + "description", + "metadata", + "width", + "height", + "resolution", + "crop", + "pad", + "trim", + "image_overlay", + "text_overlay", + "animated_gif", + "additional_tracks", + "generate_subtitles", + "mp4_access", + "per_title_encoding", + "process_low_resolution_input", + "audio_only", + "enable_drm", + "call_to_actions", + "playlist_id", + "folder" + ], + "publicBodyParams": [ + "input", + "collection_id", + "profile_id", + "format", + "tag", + "title", + "description", + "metadata", + "width", + "height", + "resolution", + "crop", + "pad", + "trim", + "image_overlay", + "text_overlay", + "animated_gif", + "additional_tracks", + "generate_subtitles", + "mp4_access", + "per_title_encoding", + "process_low_resolution_input", + "audio_only", + "enable_drm", + "call_to_actions", + "playlist_id", + "folder" + ], + "bodyParamDetails": [ + { + "name": "input", + "required": true + }, + { + "name": "collection_id", + "required": true + }, + { + "name": "profile_id", + "required": false + }, + { + "name": "format", + "required": true + }, + { + "name": "tag", + "required": false + }, + { + "name": "title", + "required": false + }, + { + "name": "description", + "required": false + }, + { + "name": "metadata", + "required": false + }, + { + "name": "width", + "required": false + }, + { + "name": "height", + "required": false + }, + { + "name": "resolution", + "required": false + }, + { + "name": "crop", + "required": false + }, + { + "name": "pad", + "required": false + }, + { + "name": "trim", + "required": false + }, + { + "name": "image_overlay", + "required": false + }, + { + "name": "text_overlay", + "required": false + }, + { + "name": "animated_gif", + "required": false + }, + { + "name": "additional_tracks", + "required": false + }, + { + "name": "generate_subtitles", + "required": false + }, + { + "name": "mp4_access", + "required": false + }, + { + "name": "per_title_encoding", + "required": false + }, + { + "name": "process_low_resolution_input", + "required": false + }, + { + "name": "audio_only", + "required": false + }, + { + "name": "enable_drm", + "required": false + }, + { + "name": "call_to_actions", + "required": false + }, + { + "name": "playlist_id", + "required": false + }, + { + "name": "folder", + "required": false + } + ], + "pathParamDetails": [], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "VideoAssetCreateParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": false, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "videoAssets", + "publicResource": "videoAssets", + "operation": "upload", + "publicOperation": "upload", + "deprecated": false, + "method": "POST", + "path": "/video/assets/upload", + "pathParams": [], + "publicPathParams": [], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "collection_id", + "profile_id", + "format", + "tag", + "title", + "description", + "metadata", + "width", + "height", + "resolution", + "crop", + "pad", + "trim", + "image_overlay", + "text_overlay", + "animated_gif", + "additional_tracks", + "generate_subtitles", + "mp4_access", + "per_title_encoding", + "process_low_resolution_input", + "audio_only", + "enable_drm", + "call_to_actions", + "playlist_id", + "folder" + ], + "publicBodyParams": [ + "collection_id", + "profile_id", + "format", + "tag", + "title", + "description", + "metadata", + "width", + "height", + "resolution", + "crop", + "pad", + "trim", + "image_overlay", + "text_overlay", + "animated_gif", + "additional_tracks", + "generate_subtitles", + "mp4_access", + "per_title_encoding", + "process_low_resolution_input", + "audio_only", + "enable_drm", + "call_to_actions", + "playlist_id", + "folder" + ], + "bodyParamDetails": [ + { + "name": "collection_id", + "required": true + }, + { + "name": "profile_id", + "required": false + }, + { + "name": "format", + "required": false + }, + { + "name": "tag", + "required": false + }, + { + "name": "title", + "required": false + }, + { + "name": "description", + "required": false + }, + { + "name": "metadata", + "required": false + }, + { + "name": "width", + "required": false + }, + { + "name": "height", + "required": false + }, + { + "name": "resolution", + "required": false + }, + { + "name": "crop", + "required": false + }, + { + "name": "pad", + "required": false + }, + { + "name": "trim", + "required": false + }, + { + "name": "image_overlay", + "required": false + }, + { + "name": "text_overlay", + "required": false + }, + { + "name": "animated_gif", + "required": false + }, + { + "name": "additional_tracks", + "required": false + }, + { + "name": "generate_subtitles", + "required": false + }, + { + "name": "mp4_access", + "required": false + }, + { + "name": "per_title_encoding", + "required": false + }, + { + "name": "process_low_resolution_input", + "required": false + }, + { + "name": "audio_only", + "required": false + }, + { + "name": "enable_drm", + "required": false + }, + { + "name": "call_to_actions", + "required": false + }, + { + "name": "playlist_id", + "required": false + }, + { + "name": "folder", + "required": false + } + ], + "pathParamDetails": [], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "VideoAssetUploadParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": false, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "videoAssets", + "publicResource": "videoAssets", + "operation": "retrieveDetails", + "publicOperation": "retrieveDetails", + "deprecated": false, + "method": "GET", + "path": "/video/assets/{asset_id}", + "pathParams": [ + "asset_id" + ], + "publicPathParams": [ + "asset_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "bodyParamDetails": [], + "pathParamDetails": [ + { + "name": "asset_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "VideoAssetRetrieveDetailsParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "videoAssets", + "publicResource": "videoAssets", + "operation": "delete", + "publicOperation": "delete", + "deprecated": false, + "method": "DELETE", + "path": "/video/assets/{asset_id}", + "pathParams": [ + "asset_id" + ], + "publicPathParams": [ + "asset_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "bodyParamDetails": [], + "pathParamDetails": [ + { + "name": "asset_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "VideoAssetDeleteParams" + }, + "response": { + "status": "204", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "204", + "errorStatuses": [ + "400", + "401" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "videoAssets", + "publicResource": "videoAssets", + "operation": "update", + "publicOperation": "update", + "deprecated": false, + "method": "POST", + "path": "/video/assets/update", + "pathParams": [], + "publicPathParams": [], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "asset_id", + "title", + "description", + "tag", + "call_to_actions", + "metadata", + "remove_subtitles", + "input", + "reprocess" + ], + "publicBodyParams": [ + "asset_id", + "title", + "description", + "tag", + "call_to_actions", + "metadata", + "remove_subtitles", + "input", + "reprocess" + ], + "bodyParamDetails": [ + { + "name": "asset_id", + "required": true + }, + { + "name": "title", + "required": false + }, + { + "name": "description", + "required": false + }, + { + "name": "tag", + "required": false + }, + { + "name": "call_to_actions", + "required": false + }, + { + "name": "metadata", + "required": false + }, + { + "name": "remove_subtitles", + "required": false + }, + { + "name": "input", + "required": false + }, + { + "name": "reprocess", + "required": false + } + ], + "pathParamDetails": [], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "VideoAssetUpdateParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": false, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "videoAssets", + "publicResource": "videoAssets", + "operation": "thumbnailSelect", + "publicOperation": "thumbnailSelect", + "deprecated": false, + "method": "POST", + "path": "/video/assets/{asset_id}/thumbnail-select", + "pathParams": [ + "asset_id" + ], + "publicPathParams": [ + "asset_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "frame_at_second" + ], + "publicBodyParams": [ + "frame_at_second" + ], + "bodyParamDetails": [ + { + "name": "frame_at_second", + "required": true + } + ], + "pathParamDetails": [ + { + "name": "asset_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "VideoAssetThumbnailSelectParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": false, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "text/plain", + "encoding": "text", + "contents": [ + { + "contentType": "text/plain", + "encoding": "text" + } + ] + }, + "errorResponses": [], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "videoAssets", + "publicResource": "videoAssets", + "operation": "thumbnailUpload", + "publicOperation": "thumbnailUpload", + "deprecated": false, + "method": "POST", + "path": "/video/assets/{asset_ID}/thumbnail", + "pathParams": [ + "asset_ID" + ], + "publicPathParams": [ + "asset_ID" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "bodyParamDetails": [], + "pathParamDetails": [ + { + "name": "asset_ID", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "VideoAssetThumbnailUploadParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "videoAssets", + "publicResource": "videoAssets", + "operation": "createUpdateChapter", + "publicOperation": "createUpdateChapter", + "deprecated": false, + "method": "POST", + "path": "/video/assets/{asset_id}/chapters", + "pathParams": [ + "asset_id" + ], + "publicPathParams": [ + "asset_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "chapters" + ], + "publicBodyParams": [ + "chapters" + ], + "bodyParamDetails": [ + { + "name": "chapters", + "required": true + } + ], + "pathParamDetails": [ + { + "name": "asset_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "VideoAssetCreateUpdateChapterParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": false, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "videoAssets", + "publicResource": "videoAssets", + "operation": "list", + "publicOperation": "list", + "deprecated": false, + "method": "GET", + "path": "/video/workspaces/{workspace_id}/list", + "pathParams": [ + "workspace_id" + ], + "publicPathParams": [ + "workspace_id" + ], + "queryParams": [ + "type", + "parent_id", + "title", + "status", + "tag", + "playlist_id", + "start_date", + "end_date", + "min_duration", + "max_duration", + "sortBy", + "orderBy", + "searchIndex", + "offset", + "size", + "signed_token" + ], + "publicQueryParams": [ + "type", + "parent_id", + "title", + "status", + "tag", + "playlist_id", + "start_date", + "end_date", + "min_duration", + "max_duration", + "sortBy", + "orderBy", + "searchIndex", + "offset", + "size", + "signed_token" + ], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "bodyParamDetails": [], + "pathParamDetails": [ + { + "name": "workspace_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [ + { + "name": "type", + "required": false, + "style": "form", + "explode": true, + "defaultValue": "all" + }, + { + "name": "parent_id", + "required": false, + "style": "form", + "explode": true + }, + { + "name": "title", + "required": false, + "style": "form", + "explode": true + }, + { + "name": "status", + "required": false, + "style": "form", + "explode": true + }, + { + "name": "tag", + "required": false, + "style": "form", + "explode": true + }, + { + "name": "playlist_id", + "required": false, + "style": "form", + "explode": true + }, + { + "name": "start_date", + "required": false, + "style": "form", + "explode": true + }, + { + "name": "end_date", + "required": false, + "style": "form", + "explode": true + }, + { + "name": "min_duration", + "required": false, + "style": "form", + "explode": true + }, + { + "name": "max_duration", + "required": false, + "style": "form", + "explode": true + }, + { + "name": "sortBy", + "required": false, + "style": "form", + "explode": true + }, + { + "name": "orderBy", + "required": false, + "style": "form", + "explode": true + }, + { + "name": "searchIndex", + "required": false, + "style": "form", + "explode": true + }, + { + "name": "offset", + "required": false, + "style": "form", + "explode": true, + "defaultValue": 0 + }, + { + "name": "size", + "required": false, + "style": "form", + "explode": true, + "defaultValue": 20 + }, + { + "name": "signed_token", + "required": false, + "style": "form", + "explode": true, + "defaultValue": "false" + } + ], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "VideoAssetListParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "videoAssets", + "publicResource": "videoAssets", + "operation": "listDeprecated", + "publicOperation": "listDeprecated", + "deprecated": true, + "method": "GET", + "path": "/video/assets/list/{workspace_id}", + "pathParams": [ + "workspace_id" + ], + "publicPathParams": [ + "workspace_id" + ], + "queryParams": [ + "status", + "tag", + "title", + "folder", + "offset", + "size", + "playlist_id", + "sortBy", + "orderBy", + "type" + ], + "publicQueryParams": [ + "status", + "tag", + "title", + "folder", + "offset", + "size", + "playlist_id", + "sortBy", + "orderBy", + "type" + ], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "bodyParamDetails": [], + "pathParamDetails": [ + { + "name": "workspace_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [ + { + "name": "status", + "required": false, + "style": "form", + "explode": true + }, + { + "name": "tag", + "required": false, + "style": "form", + "explode": true + }, + { + "name": "title", + "required": false, + "style": "form", + "explode": true + }, + { + "name": "folder", + "required": false, + "style": "form", + "explode": true + }, + { + "name": "offset", + "required": false, + "style": "form", + "explode": true + }, + { + "name": "size", + "required": false, + "style": "form", + "explode": true + }, + { + "name": "playlist_id", + "required": false, + "style": "form", + "explode": true + }, + { + "name": "sortBy", + "required": false, + "style": "form", + "explode": true, + "defaultValue": "created_at" + }, + { + "name": "orderBy", + "required": false, + "style": "form", + "explode": true, + "defaultValue": "desc" + }, + { + "name": "type", + "required": false, + "style": "form", + "explode": true + } + ], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "VideoAssetListDeprecatedParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "videoAssets", + "publicResource": "videoAssets", + "operation": "deleteMany", + "publicOperation": "deleteMany", + "deprecated": false, + "method": "DELETE", + "path": "/video/assets/bulk/delete", + "pathParams": [], + "publicPathParams": [], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "asset_list", + "source_id" + ], + "publicBodyParams": [ + "asset_list", + "source_id" + ], + "bodyParamDetails": [ + { + "name": "asset_list", + "required": true + }, + { + "name": "source_id", + "required": true + } + ], + "pathParamDetails": [], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "VideoAssetDeleteManyParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": false, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contents": [] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "videoAssets", + "publicResource": "videoAssets", + "operation": "tagMany", + "publicOperation": "tagMany", + "deprecated": false, + "method": "POST", + "path": "/video/assets/bulk/tag", + "pathParams": [], + "publicPathParams": [], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "asset_list", + "source_id", + "add_tags", + "remove_tags" + ], + "publicBodyParams": [ + "asset_list", + "source_id", + "add_tags", + "remove_tags" + ], + "bodyParamDetails": [ + { + "name": "asset_list", + "required": true + }, + { + "name": "source_id", + "required": true + }, + { + "name": "add_tags", + "required": true + }, + { + "name": "remove_tags", + "required": true + } + ], + "pathParamDetails": [], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "VideoAssetTagManyParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": false, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contents": [] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "videoAssets", + "publicResource": "videoAssets", + "operation": "analytics", + "publicOperation": "analytics", + "deprecated": false, + "method": "POST", + "path": "/video/assets/{asset_id}/analytics", + "pathParams": [ + "asset_id" + ], + "publicPathParams": [ + "asset_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "group_by", + "date_range", + "metrics", + "page_number", + "page_size" + ], + "publicBodyParams": [ + "group_by", + "date_range", + "metrics", + "page_number", + "page_size" + ], + "bodyParamDetails": [ + { + "name": "group_by", + "required": true + }, + { + "name": "date_range", + "required": true + }, + { + "name": "metrics", + "required": true + }, + { + "name": "page_number", + "required": false + }, + { + "name": "page_size", + "required": false + } + ], + "pathParamDetails": [ + { + "name": "asset_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "VideoAssetAnalyticsParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": false, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "errorResponses": [], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "subtitleUpload", + "publicResource": "subtitleUpload", + "operation": "upload", + "publicOperation": "upload", + "deprecated": false, + "method": "POST", + "path": "/video/assets/{asset_ID}/subtitle/upload", + "pathParams": [ + "asset_ID" + ], + "publicPathParams": [ + "asset_ID" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "language_codes" + ], + "publicBodyParams": [ + "language_codes" + ], + "bodyParamDetails": [ + { + "name": "language_codes", + "required": false + } + ], + "pathParamDetails": [ + { + "name": "asset_ID", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "SubtitleUploadUploadParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": false, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "subtitleUpload", + "publicResource": "subtitleUpload", + "operation": "complete", + "publicOperation": "complete", + "deprecated": false, + "method": "POST", + "path": "/video/assets/{asset_ID}/subtitle/upload/event", + "pathParams": [ + "asset_ID" + ], + "publicPathParams": [ + "asset_ID" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "upload_responses" + ], + "publicBodyParams": [ + "upload_responses" + ], + "bodyParamDetails": [ + { + "name": "upload_responses", + "required": false + } + ], + "pathParamDetails": [ + { + "name": "asset_ID", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "SubtitleUploadCompleteParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": false, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "audioUpload", + "publicResource": "audioUpload", + "operation": "upload", + "publicOperation": "upload", + "deprecated": false, + "method": "POST", + "path": "/video/assets/{asset_ID}/audio/upload", + "pathParams": [ + "asset_ID" + ], + "publicPathParams": [ + "asset_ID" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "language_codes" + ], + "publicBodyParams": [ + "language_codes" + ], + "bodyParamDetails": [ + { + "name": "language_codes", + "required": false + } + ], + "pathParamDetails": [ + { + "name": "asset_ID", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "AudioUploadUploadParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": false, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "audioUpload", + "publicResource": "audioUpload", + "operation": "complete", + "publicOperation": "complete", + "deprecated": false, + "method": "POST", + "path": "/video/assets/{asset_ID}/audio/upload/event", + "pathParams": [ + "asset_ID" + ], + "publicPathParams": [ + "asset_ID" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "upload_responses" + ], + "publicBodyParams": [ + "upload_responses" + ], + "bodyParamDetails": [ + { + "name": "upload_responses", + "required": false + } + ], + "pathParamDetails": [ + { + "name": "asset_ID", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "AudioUploadCompleteParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": false, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "videoUsageAnalytics", + "publicResource": "videoUsageAnalytics", + "operation": "retrieve", + "publicOperation": "retrieve", + "deprecated": false, + "method": "POST", + "path": "/video/analytics", + "pathParams": [], + "publicPathParams": [], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "metrics", + "date_range", + "filters", + "top_assets_count", + "top_assets_page", + "group_by" + ], + "publicBodyParams": [ + "metrics", + "date_range", + "filters", + "top_assets_count", + "top_assets_page", + "group_by" + ], + "bodyParamDetails": [ + { + "name": "metrics", + "required": true + }, + { + "name": "date_range", + "required": true + }, + { + "name": "filters", + "required": false + }, + { + "name": "top_assets_count", + "required": false + }, + { + "name": "top_assets_page", + "required": false + }, + { + "name": "group_by", + "required": false + } + ], + "pathParamDetails": [], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "VideoUsageAnalyticRetrieveParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": false, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "videoUsageAnalytics", + "publicResource": "videoUsageAnalytics", + "operation": "topAssets", + "publicOperation": "topAssets", + "deprecated": false, + "method": "GET", + "path": "/video/streaming-duration", + "pathParams": [], + "publicPathParams": [], + "queryParams": [ + "start_at", + "end_at", + "collection_id", + "page", + "page_size" + ], + "publicQueryParams": [ + "start_at", + "end_at", + "collection_id", + "page", + "page_size" + ], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "bodyParamDetails": [], + "pathParamDetails": [], + "queryParamDetails": [ + { + "name": "start_at", + "required": true, + "style": "form", + "explode": true + }, + { + "name": "end_at", + "required": true, + "style": "form", + "explode": true + }, + { + "name": "collection_id", + "required": false, + "style": "form", + "explode": true + }, + { + "name": "page", + "required": false, + "style": "form", + "explode": true, + "defaultValue": "1" + }, + { + "name": "page_size", + "required": false, + "style": "form", + "explode": true, + "defaultValue": "1000" + } + ], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "VideoUsageAnalyticTopAssetsParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "multipartUpload", + "publicResource": "multipartUpload", + "operation": "retrievePartUrl", + "publicOperation": "retrievePartUrl", + "deprecated": false, + "method": "GET", + "path": "/video/assets/{asset_id}/multipartupload/{part_number}/sign", + "pathParams": [ + "asset_id", + "part_number" + ], + "publicPathParams": [ + "asset_id", + "part_number" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "bodyParamDetails": [], + "pathParamDetails": [ + { + "name": "asset_id", + "required": true, + "style": "simple", + "explode": false + }, + { + "name": "part_number", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "MultipartUploadRetrievePartUrlParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "multipartUpload", + "publicResource": "multipartUpload", + "operation": "complete", + "publicOperation": "complete", + "deprecated": false, + "method": "POST", + "path": "/video/assets/{asset_id}/multipartupload/complete", + "pathParams": [ + "asset_id" + ], + "publicPathParams": [ + "asset_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "parts" + ], + "publicBodyParams": [ + "parts" + ], + "bodyParamDetails": [ + { + "name": "parts", + "required": false + } + ], + "pathParamDetails": [ + { + "name": "asset_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "MultipartUploadCompleteParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": false, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "videoProfiles", + "publicResource": "videoProfiles", + "operation": "create", + "publicOperation": "create", + "deprecated": false, + "method": "POST", + "path": "/video/profiles", + "pathParams": [], + "publicPathParams": [], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "name", + "format", + "width", + "height", + "resolution", + "crop", + "pad", + "trim", + "image_overlay", + "text_overlay", + "animated_gif", + "generate_subtitles", + "mp4_access", + "per_title_encoding", + "process_low_resolution_input", + "audio_only", + "enable_drm" + ], + "publicBodyParams": [ + "name", + "format", + "width", + "height", + "resolution", + "crop", + "pad", + "trim", + "image_overlay", + "text_overlay", + "animated_gif", + "generate_subtitles", + "mp4_access", + "per_title_encoding", + "process_low_resolution_input", + "audio_only", + "enable_drm" + ], + "bodyParamDetails": [ + { + "name": "name", + "required": true + }, + { + "name": "format", + "required": true + }, + { + "name": "width", + "required": false + }, + { + "name": "height", + "required": false + }, + { + "name": "resolution", + "required": false + }, + { + "name": "crop", + "required": false + }, + { + "name": "pad", + "required": false + }, + { + "name": "trim", + "required": false + }, + { + "name": "image_overlay", + "required": false + }, + { + "name": "text_overlay", + "required": false + }, + { + "name": "animated_gif", + "required": false + }, + { + "name": "generate_subtitles", + "required": false + }, + { + "name": "mp4_access", + "required": false + }, + { + "name": "per_title_encoding", + "required": false + }, + { + "name": "process_low_resolution_input", + "required": false + }, + { + "name": "audio_only", + "required": false + }, + { + "name": "enable_drm", + "required": false + } + ], + "pathParamDetails": [], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "VideoProfileCreateParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": false, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "videoProfiles", + "publicResource": "videoProfiles", + "operation": "list", + "publicOperation": "list", + "deprecated": false, + "method": "GET", + "path": "/video/profiles", + "pathParams": [], + "publicPathParams": [], + "queryParams": [ + "offset", + "size" + ], + "publicQueryParams": [ + "offset", + "size" + ], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "bodyParamDetails": [], + "pathParamDetails": [], + "queryParamDetails": [ + { + "name": "offset", + "required": false, + "style": "form", + "explode": true + }, + { + "name": "size", + "required": false, + "style": "form", + "explode": true + } + ], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "VideoProfileListParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "errorResponses": [], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "videoProfiles", + "publicResource": "videoProfiles", + "operation": "update", + "publicOperation": "update", + "deprecated": false, + "method": "POST", + "path": "/video/profiles/{profile_id}", + "pathParams": [ + "profile_id" + ], + "publicPathParams": [ + "profile_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "profile_id", + "name", + "format", + "width", + "height", + "resolution", + "crop", + "pad", + "trim", + "image_overlay", + "text_overlay", + "animated_gif", + "generate_subtitles", + "mp4_access", + "per_title_encoding", + "process_low_resolution_input", + "audio_only", + "enable_drm", + "vc", + "generate_chapters", + "generate_description" + ], + "publicBodyParams": [ + "profile_id", + "name", + "format", + "width", + "height", + "resolution", + "crop", + "pad", + "trim", + "image_overlay", + "text_overlay", + "animated_gif", + "generate_subtitles", + "mp4_access", + "per_title_encoding", + "process_low_resolution_input", + "audio_only", + "enable_drm", + "vc", + "generate_chapters", + "generate_description" + ], + "bodyParamDetails": [ + { + "name": "profile_id", + "required": true + }, + { + "name": "name", + "required": false + }, + { + "name": "format", + "required": false + }, + { + "name": "width", + "required": false + }, + { + "name": "height", + "required": false + }, + { + "name": "resolution", + "required": false + }, + { + "name": "crop", + "required": false + }, + { + "name": "pad", + "required": false + }, + { + "name": "trim", + "required": false + }, + { + "name": "image_overlay", + "required": false + }, + { + "name": "text_overlay", + "required": false + }, + { + "name": "animated_gif", + "required": false + }, + { + "name": "generate_subtitles", + "required": false + }, + { + "name": "mp4_access", + "required": false + }, + { + "name": "per_title_encoding", + "required": false + }, + { + "name": "process_low_resolution_input", + "required": false + }, + { + "name": "audio_only", + "required": false + }, + { + "name": "enable_drm", + "required": false + }, + { + "name": "vc", + "required": false + }, + { + "name": "generate_chapters", + "required": false + }, + { + "name": "generate_description", + "required": false + } + ], + "pathParamDetails": [ + { + "name": "profile_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "VideoProfileUpdateParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": false, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "4XX" + ] + }, + "errorResponses": [ + { + "status": "4XX", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "videoProfiles", + "publicResource": "videoProfiles", + "operation": "retrieve", + "publicOperation": "retrieve", + "deprecated": false, + "method": "GET", + "path": "/video/profiles/{profile_id}", + "pathParams": [ + "profile_id" + ], + "publicPathParams": [ + "profile_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "bodyParamDetails": [], + "pathParamDetails": [ + { + "name": "profile_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "VideoProfileRetrieveParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "videoProfiles", + "publicResource": "videoProfiles", + "operation": "delete", + "publicOperation": "delete", + "deprecated": false, + "method": "DELETE", + "path": "/video/profiles/{profile_id}", + "pathParams": [ + "profile_id" + ], + "publicPathParams": [ + "profile_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "bodyParamDetails": [], + "pathParamDetails": [ + { + "name": "profile_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "VideoProfileDeleteParams" + }, + "response": { + "status": "204", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "204", + "errorStatuses": [ + "400", + "401" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "videoPlaylists", + "publicResource": "videoPlaylists", + "operation": "create", + "publicOperation": "create", + "deprecated": false, + "method": "POST", + "path": "/video/playlist", + "pathParams": [], + "publicPathParams": [], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "collection_id", + "title", + "description" + ], + "publicBodyParams": [ + "collection_id", + "title", + "description" + ], + "bodyParamDetails": [ + { + "name": "collection_id", + "required": true + }, + { + "name": "title", + "required": true + }, + { + "name": "description", + "required": false + } + ], + "pathParamDetails": [], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "VideoPlaylistCreateParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": false, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "videoPlaylists", + "publicResource": "videoPlaylists", + "operation": "listAll", + "publicOperation": "listAll", + "deprecated": false, + "method": "GET", + "path": "/video/playlist", + "pathParams": [], + "publicPathParams": [], + "queryParams": [ + "collection_id" + ], + "publicQueryParams": [ + "collection_id" + ], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "bodyParamDetails": [], + "pathParamDetails": [], + "queryParamDetails": [ + { + "name": "collection_id", + "required": false, + "style": "form", + "explode": true + } + ], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "VideoPlaylistListAllParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "videoPlaylists", + "publicResource": "videoPlaylists", + "operation": "createAsset", + "publicOperation": "createAsset", + "deprecated": false, + "method": "POST", + "path": "/video/playlist/{playlist_id}/asset", + "pathParams": [ + "playlist_id" + ], + "publicPathParams": [ + "playlist_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "asset_list" + ], + "publicBodyParams": [ + "asset_list" + ], + "bodyParamDetails": [ + { + "name": "asset_list", + "required": true + } + ], + "pathParamDetails": [ + { + "name": "playlist_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "VideoPlaylistCreateAssetParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": false, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "videoPlaylists", + "publicResource": "videoPlaylists", + "operation": "deleteAsset", + "publicOperation": "deleteAsset", + "deprecated": false, + "method": "DELETE", + "path": "/video/playlist/{playlist_id}/asset", + "pathParams": [ + "playlist_id" + ], + "publicPathParams": [ + "playlist_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "delete_list" + ], + "publicBodyParams": [ + "delete_list" + ], + "bodyParamDetails": [ + { + "name": "delete_list", + "required": true + } + ], + "pathParamDetails": [ + { + "name": "playlist_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "VideoPlaylistDeleteAssetParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": false, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "videoPlaylists", + "publicResource": "videoPlaylists", + "operation": "update", + "publicOperation": "update", + "deprecated": false, + "method": "POST", + "path": "/video/playlist/{playlist_id}", + "pathParams": [ + "playlist_id" + ], + "publicPathParams": [ + "playlist_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "title", + "description", + "position", + "player_config", + "channel_visibility" + ], + "publicBodyParams": [ + "title", + "description", + "position", + "player_config", + "channel_visibility" + ], + "bodyParamDetails": [ + { + "name": "title", + "required": false + }, + { + "name": "description", + "required": false + }, + { + "name": "position", + "required": false + }, + { + "name": "player_config", + "required": false + }, + { + "name": "channel_visibility", + "required": false + } + ], + "pathParamDetails": [ + { + "name": "playlist_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "VideoPlaylistUpdateParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": false, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "videoPlaylists", + "publicResource": "videoPlaylists", + "operation": "delete", + "publicOperation": "delete", + "deprecated": false, + "method": "DELETE", + "path": "/video/playlist/{playlist_id}", + "pathParams": [ + "playlist_id" + ], + "publicPathParams": [ + "playlist_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "bodyParamDetails": [], + "pathParamDetails": [ + { + "name": "playlist_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "VideoPlaylistDeleteParams" + }, + "errorResponses": [], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "videoPlaylists", + "publicResource": "videoPlaylists", + "operation": "listAssets", + "publicOperation": "listAssets", + "deprecated": false, + "method": "GET", + "path": "/video/playlist/{playlist_id}/assets", + "pathParams": [ + "playlist_id" + ], + "publicPathParams": [ + "playlist_id" + ], + "queryParams": [ + "sort_by", + "sort_order", + "page_number", + "page_size" + ], + "publicQueryParams": [ + "sort_by", + "sort_order", + "page_number", + "page_size" + ], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "bodyParamDetails": [], + "pathParamDetails": [ + { + "name": "playlist_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [ + { + "name": "sort_by", + "required": false, + "style": "form", + "explode": true + }, + { + "name": "sort_order", + "required": false, + "style": "form", + "explode": true, + "defaultValue": 1 + }, + { + "name": "page_number", + "required": false, + "style": "form", + "explode": true, + "defaultValue": 1 + }, + { + "name": "page_size", + "required": false, + "style": "form", + "explode": true, + "defaultValue": "10" + } + ], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "VideoPlaylistListAssetsParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "videoPlaylists", + "publicResource": "videoPlaylists", + "operation": "reorderAsset", + "publicOperation": "reorderAsset", + "deprecated": false, + "method": "POST", + "path": "/video/playlists/{playlist_id}/reorder", + "pathParams": [ + "playlist_id" + ], + "publicPathParams": [ + "playlist_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "body" + ], + "publicBodyParams": [ + "body" + ], + "bodyParamDetails": [ + { + "name": "body", + "required": false + } + ], + "pathParamDetails": [ + { + "name": "playlist_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "VideoPlaylistReorderAssetParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": false, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "404" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "webhooks", + "publicResource": "webhooks", + "operation": "create", + "publicOperation": "create", + "deprecated": false, + "method": "POST", + "path": "/org/webhooks", + "pathParams": [], + "publicPathParams": [], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "url", + "secret_token", + "triggers", + "sources" + ], + "publicBodyParams": [ + "url", + "secret_token", + "triggers", + "sources" + ], + "bodyParamDetails": [ + { + "name": "url", + "required": true + }, + { + "name": "secret_token", + "required": true + }, + { + "name": "triggers", + "required": true + }, + { + "name": "sources", + "required": true + } + ], + "pathParamDetails": [], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "WebhookCreateParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": false, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "webhooks", + "publicResource": "webhooks", + "operation": "list", + "publicOperation": "list", + "deprecated": false, + "method": "GET", + "path": "/org/webhooks", + "pathParams": [], + "publicPathParams": [], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "bodyParamDetails": [], + "pathParamDetails": [], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "errorResponses": [], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "webhooks", + "publicResource": "webhooks", + "operation": "update", + "publicOperation": "update", + "deprecated": false, + "method": "POST", + "path": "/org/webhooks/{webhook_id}", + "pathParams": [ + "webhook_id" + ], + "publicPathParams": [ + "webhook_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "url", + "secret_token", + "triggers", + "sources" + ], + "publicBodyParams": [ + "url", + "secret_token", + "triggers", + "sources" + ], + "bodyParamDetails": [ + { + "name": "url", + "required": false + }, + { + "name": "secret_token", + "required": false + }, + { + "name": "triggers", + "required": false + }, + { + "name": "sources", + "required": false + } + ], + "pathParamDetails": [ + { + "name": "webhook_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "WebhookUpdateParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": false, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "webhooks", + "publicResource": "webhooks", + "operation": "delete", + "publicOperation": "delete", + "deprecated": false, + "method": "DELETE", + "path": "/org/webhooks/{webhook_id}", + "pathParams": [ + "webhook_id" + ], + "publicPathParams": [ + "webhook_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "bodyParamDetails": [], + "pathParamDetails": [ + { + "name": "webhook_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "WebhookDeleteParams" + }, + "response": { + "status": "204", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "204", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "webhooks", + "publicResource": "webhooks", + "operation": "history", + "publicOperation": "history", + "deprecated": false, + "method": "GET", + "path": "/org/webhook/{webhook_id}/history", + "pathParams": [ + "webhook_id" + ], + "publicPathParams": [ + "webhook_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "bodyParamDetails": [], + "pathParamDetails": [ + { + "name": "webhook_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "WebhookHistoryParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "errorResponses": [], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "imageSources", + "publicResource": "imageSources", + "operation": "create", + "publicOperation": "create", + "deprecated": false, + "method": "POST", + "path": "/image/sources", + "pathParams": [], + "publicPathParams": [], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "namespace", + "type", + "aws", + "proxy", + "gcs", + "dostorage", + "wasabi", + "cloudinary", + "azure", + "linode", + "backblaze", + "cloudflare", + "webfolder" + ], + "publicBodyParams": [ + "namespace", + "type", + "aws", + "proxy", + "gcs", + "dostorage", + "wasabi", + "cloudinary", + "azure", + "linode", + "backblaze", + "cloudflare", + "webfolder" + ], + "bodyParamDetails": [ + { + "name": "namespace", + "required": true + }, + { + "name": "type", + "required": true + }, + { + "name": "aws", + "required": false + }, + { + "name": "proxy", + "required": false + }, + { + "name": "gcs", + "required": false + }, + { + "name": "dostorage", + "required": false + }, + { + "name": "wasabi", + "required": false + }, + { + "name": "cloudinary", + "required": false + }, + { + "name": "azure", + "required": false + }, + { + "name": "linode", + "required": false + }, + { + "name": "backblaze", + "required": false + }, + { + "name": "cloudflare", + "required": false + }, + { + "name": "webfolder", + "required": false + } + ], + "pathParamDetails": [], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "ImageSourceCreateParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": false, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "imageSources", + "publicResource": "imageSources", + "operation": "list", + "publicOperation": "list", + "deprecated": false, + "method": "GET", + "path": "/image/sources", + "pathParams": [], + "publicPathParams": [], + "queryParams": [ + "offset", + "size" + ], + "publicQueryParams": [ + "offset", + "size" + ], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "bodyParamDetails": [], + "pathParamDetails": [], + "queryParamDetails": [ + { + "name": "offset", + "required": false, + "style": "form", + "explode": true, + "defaultValue": 0 + }, + { + "name": "size", + "required": false, + "style": "form", + "explode": true, + "defaultValue": 20 + } + ], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "ImageSourceListParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "imageSources", + "publicResource": "imageSources", + "operation": "retrieve", + "publicOperation": "retrieve", + "deprecated": false, + "method": "GET", + "path": "/image/sources/{image_source_id}", + "pathParams": [ + "image_source_id" + ], + "publicPathParams": [ + "image_source_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "bodyParamDetails": [], + "pathParamDetails": [ + { + "name": "image_source_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "ImageSourceRetrieveParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "errorResponses": [], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "imageSources", + "publicResource": "imageSources", + "operation": "update", + "publicOperation": "update", + "deprecated": false, + "method": "POST", + "path": "/image/sources/{image_source_id}", + "pathParams": [ + "image_source_id" + ], + "publicPathParams": [ + "image_source_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "type", + "webfolder", + "aws", + "proxy", + "gcs", + "dostorage", + "wasabi", + "linode", + "backblaze", + "cloudflare", + "cloudinary", + "azure", + "default_params", + "error_image", + "request_headers", + "response_headers", + "temp_cname", + "browser_cache_time", + "cdn_cache_time", + "is_active", + "cname", + "fallback_origins" + ], + "publicBodyParams": [ + "type", + "webfolder", + "aws", + "proxy", + "gcs", + "dostorage", + "wasabi", + "linode", + "backblaze", + "cloudflare", + "cloudinary", + "azure", + "default_params", + "error_image", + "request_headers", + "response_headers", + "temp_cname", + "browser_cache_time", + "cdn_cache_time", + "is_active", + "cname", + "fallback_origins" + ], + "bodyParamDetails": [ + { + "name": "type", + "required": false + }, + { + "name": "webfolder", + "required": false + }, + { + "name": "aws", + "required": false + }, + { + "name": "proxy", + "required": false + }, + { + "name": "gcs", + "required": false + }, + { + "name": "dostorage", + "required": false + }, + { + "name": "wasabi", + "required": false + }, + { + "name": "linode", + "required": false + }, + { + "name": "backblaze", + "required": false + }, + { + "name": "cloudflare", + "required": false + }, + { + "name": "cloudinary", + "required": false + }, + { + "name": "azure", + "required": false + }, + { + "name": "default_params", + "required": false + }, + { + "name": "error_image", + "required": false + }, + { + "name": "request_headers", + "required": false + }, + { + "name": "response_headers", + "required": false + }, + { + "name": "temp_cname", + "required": false + }, + { + "name": "browser_cache_time", + "required": false + }, + { + "name": "cdn_cache_time", + "required": false + }, + { + "name": "is_active", + "required": false + }, + { + "name": "cname", + "required": false + }, + { + "name": "fallback_origins", + "required": false + } + ], + "pathParamDetails": [ + { + "name": "image_source_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "ImageSourceUpdateParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": false, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "imageSources", + "publicResource": "imageSources", + "operation": "delete", + "publicOperation": "delete", + "deprecated": false, + "method": "DELETE", + "path": "/image/sources/{image_source_id}", + "pathParams": [ + "image_source_id" + ], + "publicPathParams": [ + "image_source_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "bodyParamDetails": [], + "pathParamDetails": [ + { + "name": "image_source_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "ImageSourceDeleteParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "imageSources", + "publicResource": "imageSources", + "operation": "purgeCache", + "publicOperation": "purgeCache", + "deprecated": true, + "method": "POST", + "path": "/purge/{subdomain}", + "pathParams": [ + "subdomain" + ], + "publicPathParams": [ + "subdomain" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "paths" + ], + "publicBodyParams": [ + "paths" + ], + "bodyParamDetails": [ + { + "name": "paths", + "required": false + } + ], + "pathParamDetails": [ + { + "name": "subdomain", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "ImageSourcePurgeCacheParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": false, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "imageSources", + "publicResource": "imageSources", + "operation": "purge", + "publicOperation": "purge", + "deprecated": false, + "method": "POST", + "path": "/image/purge/{source_id}", + "pathParams": [ + "source_id" + ], + "publicPathParams": [ + "source_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "paths" + ], + "publicBodyParams": [ + "paths" + ], + "bodyParamDetails": [ + { + "name": "paths", + "required": true + } + ], + "pathParamDetails": [ + { + "name": "source_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "ImageSourcePurgeParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": false, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "imageUsageAnalytics", + "publicResource": "imageUsageAnalytics", + "operation": "retrieve", + "publicOperation": "retrieve", + "deprecated": false, + "method": "POST", + "path": "/image/analytics", + "pathParams": [], + "publicPathParams": [], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "metrics", + "date_range", + "group_by", + "filters" + ], + "publicBodyParams": [ + "metrics", + "date_range", + "group_by", + "filters" + ], + "bodyParamDetails": [ + { + "name": "metrics", + "required": true + }, + { + "name": "date_range", + "required": true + }, + { + "name": "group_by", + "required": false + }, + { + "name": "filters", + "required": false + } + ], + "pathParamDetails": [], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "ImageUsageAnalyticRetrieveParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": false, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "liveStreamAssets", + "publicResource": "liveStreamAssets", + "operation": "create", + "publicOperation": "create", + "deprecated": false, + "method": "POST", + "path": "/video/live/assets", + "pathParams": [], + "publicPathParams": [], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "live_source_id", + "resolution", + "title", + "mp4_access", + "orientation", + "start_at" + ], + "publicBodyParams": [ + "live_source_id", + "resolution", + "title", + "mp4_access", + "orientation", + "start_at" + ], + "bodyParamDetails": [ + { + "name": "live_source_id", + "required": true + }, + { + "name": "resolution", + "required": true + }, + { + "name": "title", + "required": false + }, + { + "name": "mp4_access", + "required": false + }, + { + "name": "orientation", + "required": false + }, + { + "name": "start_at", + "required": false + } + ], + "pathParamDetails": [], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "LiveStreamAssetCreateParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": false, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "liveStreamAssets", + "publicResource": "liveStreamAssets", + "operation": "update", + "publicOperation": "update", + "deprecated": false, + "method": "POST", + "path": "/video/live/assets/update", + "pathParams": [], + "publicPathParams": [], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "live_asset_id", + "title", + "start_at" + ], + "publicBodyParams": [ + "live_asset_id", + "title", + "start_at" + ], + "bodyParamDetails": [ + { + "name": "live_asset_id", + "required": true + }, + { + "name": "title", + "required": false + }, + { + "name": "start_at", + "required": false + } + ], + "pathParamDetails": [], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "LiveStreamAssetUpdateParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": false, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "liveStreamAssets", + "publicResource": "liveStreamAssets", + "operation": "retrieveStatus", + "publicOperation": "retrieveStatus", + "deprecated": false, + "method": "GET", + "path": "/video/live/assets/{live_asset_id}", + "pathParams": [ + "live_asset_id" + ], + "publicPathParams": [ + "live_asset_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "bodyParamDetails": [], + "pathParamDetails": [ + { + "name": "live_asset_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "LiveStreamAssetRetrieveStatusParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "liveStreamAssets", + "publicResource": "liveStreamAssets", + "operation": "delete", + "publicOperation": "delete", + "deprecated": false, + "method": "DELETE", + "path": "/video/live/assets/{live_asset_id}", + "pathParams": [ + "live_asset_id" + ], + "publicPathParams": [ + "live_asset_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "bodyParamDetails": [], + "pathParamDetails": [ + { + "name": "live_asset_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "LiveStreamAssetDeleteParams" + }, + "response": { + "status": "204", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "204", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "liveStreamAssets", + "publicResource": "liveStreamAssets", + "operation": "complete", + "publicOperation": "complete", + "deprecated": false, + "method": "POST", + "path": "/video/live/assets/{live_asset_id}/complete", + "pathParams": [ + "live_asset_id" + ], + "publicPathParams": [ + "live_asset_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "bodyParamDetails": [], + "pathParamDetails": [ + { + "name": "live_asset_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "LiveStreamAssetCompleteParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "liveStreamAssets", + "publicResource": "liveStreamAssets", + "operation": "filter", + "publicOperation": "filter", + "deprecated": false, + "method": "GET", + "path": "/video/live/assets/list/{live_source_id}", + "pathParams": [ + "live_source_id" + ], + "publicPathParams": [ + "live_source_id" + ], + "queryParams": [ + "status", + "offset", + "size" + ], + "publicQueryParams": [ + "status", + "offset", + "size" + ], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "bodyParamDetails": [], + "pathParamDetails": [ + { + "name": "live_source_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [ + { + "name": "status", + "required": false, + "style": "form", + "explode": true + }, + { + "name": "offset", + "required": false, + "style": "form", + "explode": true + }, + { + "name": "size", + "required": false, + "style": "form", + "explode": true + } + ], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "LiveStreamAssetFilterParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "liveStreamAssets", + "publicResource": "liveStreamAssets", + "operation": "start", + "publicOperation": "start", + "deprecated": false, + "method": "POST", + "path": "/video/live/assets/{live_asset_id}/start", + "pathParams": [ + "live_asset_id" + ], + "publicPathParams": [ + "live_asset_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "bodyParamDetails": [], + "pathParamDetails": [ + { + "name": "live_asset_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "LiveStreamAssetStartParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "errorResponses": [], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "liveStreamAssets", + "publicResource": "liveStreamAssets", + "operation": "upload", + "publicOperation": "upload", + "deprecated": false, + "method": "POST", + "path": "/video/live/assets/thumbnail/upload", + "pathParams": [], + "publicPathParams": [], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "live_asset_id", + "statuses" + ], + "publicBodyParams": [ + "live_asset_id", + "statuses" + ], + "bodyParamDetails": [ + { + "name": "live_asset_id", + "required": true + }, + { + "name": "statuses", + "required": true + } + ], + "pathParamDetails": [], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "LiveStreamAssetUploadParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": false, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "403", + "404" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "liveStreamAssets", + "publicResource": "liveStreamAssets", + "operation": "statusHistory", + "publicOperation": "statusHistory", + "deprecated": false, + "method": "GET", + "path": "/video/live/assets/{live_asset_id}/history", + "pathParams": [ + "live_asset_id" + ], + "publicPathParams": [ + "live_asset_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "bodyParamDetails": [], + "pathParamDetails": [ + { + "name": "live_asset_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "LiveStreamAssetStatusHistoryParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "recycleBin", + "publicResource": "recycleBin", + "operation": "recover", + "publicOperation": "recover", + "deprecated": false, + "method": "POST", + "path": "/video/asset/recover", + "pathParams": [], + "publicPathParams": [], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "asset_id" + ], + "publicBodyParams": [ + "asset_id" + ], + "bodyParamDetails": [ + { + "name": "asset_id", + "required": true + } + ], + "pathParamDetails": [], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "RecycleBinRecoverParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": false, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "errorResponses": [], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "recycleBin", + "publicResource": "recycleBin", + "operation": "list", + "publicOperation": "list", + "deprecated": false, + "method": "GET", + "path": "/video/asset/recoverable/list", + "pathParams": [], + "publicPathParams": [], + "queryParams": [ + "offset", + "size", + "workspace_id" + ], + "publicQueryParams": [ + "offset", + "size", + "workspace_id" + ], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "bodyParamDetails": [], + "pathParamDetails": [], + "queryParamDetails": [ + { + "name": "offset", + "required": false, + "style": "form", + "explode": true + }, + { + "name": "size", + "required": false, + "style": "form", + "explode": true, + "defaultValue": 20 + }, + { + "name": "workspace_id", + "required": true, + "style": "form", + "explode": true + } + ], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "RecycleBinListParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "errorResponses": [], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "videoWorkspaces", + "publicResource": "videoWorkspaces", + "operation": "list", + "publicOperation": "list", + "deprecated": false, + "method": "GET", + "path": "/video/workspaces", + "pathParams": [], + "publicPathParams": [], + "queryParams": [ + "offset", + "size" + ], + "publicQueryParams": [ + "offset", + "size" + ], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "bodyParamDetails": [], + "pathParamDetails": [], + "queryParamDetails": [ + { + "name": "offset", + "required": false, + "style": "form", + "explode": true, + "defaultValue": "0" + }, + { + "name": "size", + "required": false, + "style": "form", + "explode": true, + "defaultValue": "10" + } + ], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "VideoWorkspaceListParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "videoWorkspaces", + "publicResource": "videoWorkspaces", + "operation": "create", + "publicOperation": "create", + "deprecated": false, + "method": "POST", + "path": "/video/workspaces", + "pathParams": [], + "publicPathParams": [], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "name", + "type", + "default_profile_id", + "insight_property_id", + "video_protection", + "aws", + "proxy", + "gcs", + "dostorage", + "wasabi", + "cloudinary", + "azure", + "linode", + "backblaze", + "cloudflare", + "zoom" + ], + "publicBodyParams": [ + "name", + "type", + "default_profile_id", + "insight_property_id", + "video_protection", + "aws", + "proxy", + "gcs", + "dostorage", + "wasabi", + "cloudinary", + "azure", + "linode", + "backblaze", + "cloudflare", + "zoom" + ], + "bodyParamDetails": [ + { + "name": "name", + "required": true + }, + { + "name": "type", + "required": true + }, + { + "name": "default_profile_id", + "required": false + }, + { + "name": "insight_property_id", + "required": false + }, + { + "name": "video_protection", + "required": false + }, + { + "name": "aws", + "required": false + }, + { + "name": "proxy", + "required": false + }, + { + "name": "gcs", + "required": false + }, + { + "name": "dostorage", + "required": false + }, + { + "name": "wasabi", + "required": false + }, + { + "name": "cloudinary", + "required": false + }, + { + "name": "azure", + "required": false + }, + { + "name": "linode", + "required": false + }, + { + "name": "backblaze", + "required": false + }, + { + "name": "cloudflare", + "required": false + }, + { + "name": "zoom", + "required": false + } + ], + "pathParamDetails": [], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "VideoWorkspaceCreateParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": false, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "videoWorkspaces", + "publicResource": "videoWorkspaces", + "operation": "update", + "publicOperation": "update", + "deprecated": false, + "method": "POST", + "path": "/video/workspaces/{workspace_id}", + "pathParams": [ + "workspace_id" + ], + "publicPathParams": [ + "workspace_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "name", + "default_profile_id", + "temp_cname", + "insight_property_id", + "player_config", + "video_protection", + "channel_settings", + "type", + "webfolder", + "aws", + "proxy", + "gcs", + "dostorage", + "wasabi", + "linode", + "backblaze", + "cloudflare", + "cloudinary", + "azure", + "zoom" + ], + "publicBodyParams": [ + "name", + "default_profile_id", + "temp_cname", + "insight_property_id", + "player_config", + "video_protection", + "channel_settings", + "type", + "webfolder", + "aws", + "proxy", + "gcs", + "dostorage", + "wasabi", + "linode", + "backblaze", + "cloudflare", + "cloudinary", + "azure", + "zoom" + ], + "bodyParamDetails": [ + { + "name": "name", + "required": false + }, + { + "name": "default_profile_id", + "required": false + }, + { + "name": "temp_cname", + "required": false + }, + { + "name": "insight_property_id", + "required": false + }, + { + "name": "player_config", + "required": false + }, + { + "name": "video_protection", + "required": false + }, + { + "name": "channel_settings", + "required": false + }, + { + "name": "type", + "required": false + }, + { + "name": "webfolder", + "required": false + }, + { + "name": "aws", + "required": false + }, + { + "name": "proxy", + "required": false + }, + { + "name": "gcs", + "required": false + }, + { + "name": "dostorage", + "required": false + }, + { + "name": "wasabi", + "required": false + }, + { + "name": "linode", + "required": false + }, + { + "name": "backblaze", + "required": false + }, + { + "name": "cloudflare", + "required": false + }, + { + "name": "cloudinary", + "required": false + }, + { + "name": "azure", + "required": false + }, + { + "name": "zoom", + "required": false + } + ], + "pathParamDetails": [ + { + "name": "workspace_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "VideoWorkspaceUpdateParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": false, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "videoWorkspaces", + "publicResource": "videoWorkspaces", + "operation": "retrieve", + "publicOperation": "retrieve", + "deprecated": false, + "method": "GET", + "path": "/video/workspaces/{workspace_id}", + "pathParams": [ + "workspace_id" + ], + "publicPathParams": [ + "workspace_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "bodyParamDetails": [], + "pathParamDetails": [ + { + "name": "workspace_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "VideoWorkspaceRetrieveParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "videoWorkspaces", + "publicResource": "videoWorkspaces", + "operation": "delete", + "publicOperation": "delete", + "deprecated": false, + "method": "DELETE", + "path": "/video/workspaces/{workspace_id}", + "pathParams": [ + "workspace_id" + ], + "publicPathParams": [ + "workspace_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "bodyParamDetails": [], + "pathParamDetails": [ + { + "name": "workspace_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "VideoWorkspaceDeleteParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "folders", + "publicResource": "folders", + "operation": "create", + "publicOperation": "create", + "deprecated": false, + "method": "POST", + "path": "/video/workspaces/{workspace_id}/folders", + "pathParams": [ + "workspace_id" + ], + "publicPathParams": [ + "workspace_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "name", + "parent_id" + ], + "publicBodyParams": [ + "name", + "parent_id" + ], + "bodyParamDetails": [ + { + "name": "name", + "required": true + }, + { + "name": "parent_id", + "required": false + } + ], + "pathParamDetails": [ + { + "name": "workspace_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "FolderCreateParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": false, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "folders", + "publicResource": "folders", + "operation": "list", + "publicOperation": "list", + "deprecated": false, + "method": "GET", + "path": "/video/workspaces/{workspace_id}/folders", + "pathParams": [ + "workspace_id" + ], + "publicPathParams": [ + "workspace_id" + ], + "queryParams": [ + "parent_id" + ], + "publicQueryParams": [ + "parent_id" + ], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "bodyParamDetails": [], + "pathParamDetails": [ + { + "name": "workspace_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [ + { + "name": "parent_id", + "required": false, + "style": "form", + "explode": true + } + ], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "FolderListParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "folders", + "publicResource": "folders", + "operation": "retrieve", + "publicOperation": "retrieve", + "deprecated": false, + "method": "GET", + "path": "/video/workspaces/{workspace_id}/folders/{folder_id}", + "pathParams": [ + "workspace_id", + "folder_id" + ], + "publicPathParams": [ + "workspace_id", + "folder_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "bodyParamDetails": [], + "pathParamDetails": [ + { + "name": "workspace_id", + "required": true, + "style": "simple", + "explode": false + }, + { + "name": "folder_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "FolderRetrieveParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "404" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "folders", + "publicResource": "folders", + "operation": "update", + "publicOperation": "update", + "deprecated": false, + "method": "POST", + "path": "/video/workspaces/{workspace_id}/folders/{folder_id}", + "pathParams": [ + "workspace_id", + "folder_id" + ], + "publicPathParams": [ + "workspace_id", + "folder_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "body" + ], + "publicBodyParams": [ + "body" + ], + "bodyParamDetails": [ + { + "name": "body", + "required": false + } + ], + "pathParamDetails": [ + { + "name": "workspace_id", + "required": true, + "style": "simple", + "explode": false + }, + { + "name": "folder_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "FolderUpdateParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": false, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "404" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "folders", + "publicResource": "folders", + "operation": "delete", + "publicOperation": "delete", + "deprecated": false, + "method": "DELETE", + "path": "/video/workspaces/{workspace_id}/folders/{folder_id}", + "pathParams": [ + "workspace_id", + "folder_id" + ], + "publicPathParams": [ + "workspace_id", + "folder_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "bodyParamDetails": [], + "pathParamDetails": [ + { + "name": "workspace_id", + "required": true, + "style": "simple", + "explode": false + }, + { + "name": "folder_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "FolderDeleteParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "404" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "folders", + "publicResource": "folders", + "operation": "deleteAssets", + "publicOperation": "deleteAssets", + "deprecated": false, + "method": "POST", + "path": "/video/workspaces/{workspace_id}/remove-assets-from-folder", + "pathParams": [ + "workspace_id" + ], + "publicPathParams": [ + "workspace_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "asset_ids" + ], + "publicBodyParams": [ + "asset_ids" + ], + "bodyParamDetails": [ + { + "name": "asset_ids", + "required": true + } + ], + "pathParamDetails": [ + { + "name": "workspace_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "FolderDeleteAssetsParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": false, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "channelViewers", + "publicResource": "channelViewers", + "operation": "invite", + "publicOperation": "invite", + "deprecated": false, + "method": "POST", + "path": "/channel/{video_workspace_id}/viewers/invite", + "pathParams": [ + "video_workspace_id" + ], + "publicPathParams": [ + "video_workspace_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "users" + ], + "publicBodyParams": [ + "users" + ], + "bodyParamDetails": [ + { + "name": "users", + "required": true + } + ], + "pathParamDetails": [ + { + "name": "video_workspace_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "ChannelViewerInviteParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": true, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "channelViewers", + "publicResource": "channelViewers", + "operation": "delete", + "publicOperation": "delete", + "deprecated": false, + "method": "POST", + "path": "/channel/{video_workspace_id}/viewers/remove", + "pathParams": [ + "video_workspace_id" + ], + "publicPathParams": [ + "video_workspace_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "emails" + ], + "publicBodyParams": [ + "emails" + ], + "bodyParamDetails": [ + { + "name": "emails", + "required": true + } + ], + "pathParamDetails": [ + { + "name": "video_workspace_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "ChannelViewerDeleteParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": true, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "channelViewers", + "publicResource": "channelViewers", + "operation": "inviteCsv", + "publicOperation": "inviteCsv", + "deprecated": false, + "method": "POST", + "path": "/channel/{video_workspace_id}/viewers/invite/csv", + "pathParams": [ + "video_workspace_id" + ], + "publicPathParams": [ + "video_workspace_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "viewers_csv" + ], + "publicBodyParams": [ + "viewers_csv" + ], + "bodyParamDetails": [ + { + "name": "viewers_csv", + "required": true + } + ], + "pathParamDetails": [ + { + "name": "video_workspace_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "ChannelViewerInviteCsvParams" + }, + "requestBody": { + "contentType": "multipart/form-data", + "encoding": "multipart", + "contents": [ + { + "contentType": "multipart/form-data", + "encoding": "multipart" + } + ], + "required": true, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "channelViewers", + "publicResource": "channelViewers", + "operation": "listSubscribers", + "publicOperation": "listSubscribers", + "deprecated": false, + "method": "GET", + "path": "/channel/{workspace_id}/viewers", + "pathParams": [ + "workspace_id" + ], + "publicPathParams": [ + "workspace_id" + ], + "queryParams": [ + "page_number", + "page_size" + ], + "publicQueryParams": [ + "page_number", + "page_size" + ], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "bodyParamDetails": [], + "pathParamDetails": [ + { + "name": "workspace_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [ + { + "name": "page_number", + "required": false, + "style": "form", + "explode": true, + "defaultValue": 1 + }, + { + "name": "page_size", + "required": false, + "style": "form", + "explode": true, + "defaultValue": 10 + } + ], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "ChannelViewerListSubscribersParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "errorResponses": [], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "videoAnalytics", + "publicResource": "videoAnalytics", + "operation": "chartData", + "publicOperation": "chartData", + "deprecated": false, + "method": "POST", + "path": "/insights/viewer-analytics", + "pathParams": [], + "publicPathParams": [], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "metrics", + "workspace_id", + "date_range", + "filters", + "group_by", + "chart_dimension" + ], + "publicBodyParams": [ + "metrics", + "workspace_id", + "date_range", + "filters", + "group_by", + "chart_dimension" + ], + "bodyParamDetails": [ + { + "name": "metrics", + "required": true + }, + { + "name": "workspace_id", + "required": true + }, + { + "name": "date_range", + "required": true + }, + { + "name": "filters", + "required": false + }, + { + "name": "group_by", + "required": false + }, + { + "name": "chart_dimension", + "required": false + } + ], + "pathParamDetails": [], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "VideoAnalyticChartDataParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": false, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "videoAnalytics", + "publicResource": "videoAnalytics", + "operation": "breakdownData", + "publicOperation": "breakdownData", + "deprecated": false, + "method": "POST", + "path": "/insights/breakdown-data", + "pathParams": [], + "publicPathParams": [], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "date_range", + "filters", + "breakdowns", + "workspace_id" + ], + "publicBodyParams": [ + "date_range", + "filters", + "breakdowns", + "workspace_id" + ], + "bodyParamDetails": [ + { + "name": "date_range", + "required": true + }, + { + "name": "filters", + "required": false + }, + { + "name": "breakdowns", + "required": true + }, + { + "name": "workspace_id", + "required": true + } + ], + "pathParamDetails": [], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "VideoAnalyticBreakdownDataParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": false, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "videoAnalytics", + "publicResource": "videoAnalytics", + "operation": "aggregatedData", + "publicOperation": "aggregatedData", + "deprecated": false, + "method": "POST", + "path": "/insights/aggregated-data", + "pathParams": [], + "publicPathParams": [], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "aggregate", + "workspace_id", + "timeframe", + "filters" + ], + "publicBodyParams": [ + "aggregate", + "workspace_id", + "timeframe", + "filters" + ], + "bodyParamDetails": [ + { + "name": "aggregate", + "required": true + }, + { + "name": "workspace_id", + "required": true + }, + { + "name": "timeframe", + "required": true + }, + { + "name": "filters", + "required": false + } + ], + "pathParamDetails": [], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "VideoAnalyticAggregatedDataParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": false, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "organizationData", + "publicResource": "organizationData", + "operation": "fetchOrg", + "publicOperation": "fetchOrg", + "deprecated": false, + "method": "GET", + "path": "/org/data", + "pathParams": [], + "publicPathParams": [], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "bodyParamDetails": [], + "pathParamDetails": [], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contents": [] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "userData", + "publicResource": "userData", + "operation": "fetch", + "publicOperation": "fetch", + "deprecated": false, + "method": "GET", + "path": "/user/data", + "pathParams": [], + "publicPathParams": [], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "bodyParamDetails": [], + "pathParamDetails": [], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400" + ] + }, + "errorResponses": [ + { + "status": "400", + "contents": [] + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "auditLogs", + "publicResource": "auditLogs", + "operation": "fetch", + "publicOperation": "fetch", + "deprecated": false, + "method": "POST", + "path": "/user/audit-log", + "pathParams": [], + "publicPathParams": [], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "date_range", + "page_number", + "page_size", + "user_email", + "activity_type" + ], + "publicBodyParams": [ + "date_range", + "page_number", + "page_size", + "user_email", + "activity_type" + ], + "bodyParamDetails": [ + { + "name": "date_range", + "required": true + }, + { + "name": "page_number", + "required": false + }, + { + "name": "page_size", + "required": false + }, + { + "name": "user_email", + "required": false + }, + { + "name": "activity_type", + "required": false + } + ], + "pathParamDetails": [], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "AuditLogFetchParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": false, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "errorResponses": [], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "billing", + "publicResource": "billing", + "operation": "listInvoices", + "publicOperation": "listInvoices", + "deprecated": false, + "method": "GET", + "path": "/mixed/billing/invoice/history", + "pathParams": [], + "publicPathParams": [], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "bodyParamDetails": [], + "pathParamDetails": [], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "errorResponses": [], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "billing", + "publicResource": "billing", + "operation": "fetchDetails", + "publicOperation": "fetchDetails", + "deprecated": false, + "method": "GET", + "path": "/mixed/billing/details", + "pathParams": [], + "publicPathParams": [], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "bodyParamDetails": [], + "pathParamDetails": [], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "errorResponses": [], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "billing", + "publicResource": "billing", + "operation": "updateDetails", + "publicOperation": "updateDetails", + "deprecated": false, + "method": "POST", + "path": "/mixed/billing/details", + "pathParams": [], + "publicPathParams": [], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "address_line", + "city", + "company_name", + "country_code", + "gst_number", + "postal", + "state_code" + ], + "publicBodyParams": [ + "address_line", + "city", + "company_name", + "country_code", + "gst_number", + "postal", + "state_code" + ], + "bodyParamDetails": [ + { + "name": "address_line", + "required": true + }, + { + "name": "city", + "required": true + }, + { + "name": "company_name", + "required": true + }, + { + "name": "country_code", + "required": true + }, + { + "name": "gst_number", + "required": true + }, + { + "name": "postal", + "required": true + }, + { + "name": "state_code", + "required": true + } + ], + "pathParamDetails": [], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "BillingUpdateDetailsParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": false, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "errorResponses": [], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "billing", + "publicResource": "billing", + "operation": "fetchUpcomingInvoice", + "publicOperation": "fetchUpcomingInvoice", + "deprecated": false, + "method": "GET", + "path": "/mixed/billing/invoice/upcoming", + "pathParams": [], + "publicPathParams": [], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "bodyParamDetails": [], + "pathParamDetails": [], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "errorResponses": [], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "liveStreamWorkspaces", + "publicResource": "liveStreamWorkspaces", + "operation": "list", + "publicOperation": "list", + "deprecated": false, + "method": "GET", + "path": "/video/sources/live", + "pathParams": [], + "publicPathParams": [], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "bodyParamDetails": [], + "pathParamDetails": [], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "errorResponses": [], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "liveStreamWorkspaces", + "publicResource": "liveStreamWorkspaces", + "operation": "create", + "publicOperation": "create", + "deprecated": false, + "method": "POST", + "path": "/video/sources/live", + "pathParams": [], + "publicPathParams": [], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "name" + ], + "publicBodyParams": [ + "name" + ], + "bodyParamDetails": [ + { + "name": "name", + "required": true + } + ], + "pathParamDetails": [], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "LiveStreamWorkspaceCreateParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": false, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "errorResponses": [], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "liveStreamWorkspaces", + "publicResource": "liveStreamWorkspaces", + "operation": "update", + "publicOperation": "update", + "deprecated": false, + "method": "POST", + "path": "/video/sources/live/{live_workspace_id}", + "pathParams": [ + "live_workspace_id" + ], + "publicPathParams": [ + "live_workspace_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "name", + "video_source_id" + ], + "publicBodyParams": [ + "name", + "video_source_id" + ], + "bodyParamDetails": [ + { + "name": "name", + "required": false + }, + { + "name": "video_source_id", + "required": false + } + ], + "pathParamDetails": [ + { + "name": "live_workspace_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "LiveStreamWorkspaceUpdateParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": false, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "errorResponses": [], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "liveStreamWorkspaces", + "publicResource": "liveStreamWorkspaces", + "operation": "delete", + "publicOperation": "delete", + "deprecated": false, + "method": "DELETE", + "path": "/video/sources/live/{live_workspace_id}", + "pathParams": [ + "live_workspace_id" + ], + "publicPathParams": [ + "live_workspace_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "bodyParamDetails": [], + "pathParamDetails": [ + { + "name": "live_workspace_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "LiveStreamWorkspaceDeleteParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "errorResponses": [], + "responseLinks": [], + "transport": "http" + } + ], + "webhooks": [] +} diff --git a/src/gumlet/__init__.py b/src/gumlet/__init__.py new file mode 100644 index 0000000..7556a41 --- /dev/null +++ b/src/gumlet/__init__.py @@ -0,0 +1,88 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +import typing as _t + +from . import types +from ._types import NOT_GIVEN, Omit, NoneType, NotGiven, Transport, ProxiesTypes, omit, not_given +from ._utils import file_from_path +from ._client import Client, Stream, Gumlet, Timeout, Transport, AsyncClient, AsyncStream, AsyncGumlet, RequestOptions +from ._models import BaseModel +from ._version import __title__, __version__ +from ._response import APIResponse as APIResponse, AsyncAPIResponse as AsyncAPIResponse +from ._constants import DEFAULT_TIMEOUT, DEFAULT_MAX_RETRIES, DEFAULT_CONNECTION_LIMITS +from ._exceptions import ( + APIError, + GumletError, + ConflictError, + NotFoundError, + APIStatusError, + RateLimitError, + APITimeoutError, + BadRequestError, + APIConnectionError, + AuthenticationError, + InternalServerError, + PermissionDeniedError, + UnprocessableEntityError, + APIResponseValidationError, +) +from ._base_client import DefaultHttpxClient, DefaultAioHttpClient, DefaultAsyncHttpxClient +from ._utils._logs import setup_logging as _setup_logging + +__all__ = [ + "types", + "__version__", + "__title__", + "NoneType", + "Transport", + "ProxiesTypes", + "NotGiven", + "NOT_GIVEN", + "not_given", + "Omit", + "omit", + "GumletError", + "APIError", + "APIStatusError", + "APITimeoutError", + "APIConnectionError", + "APIResponseValidationError", + "BadRequestError", + "AuthenticationError", + "PermissionDeniedError", + "NotFoundError", + "ConflictError", + "UnprocessableEntityError", + "RateLimitError", + "InternalServerError", + "Timeout", + "RequestOptions", + "Client", + "AsyncClient", + "Stream", + "AsyncStream", + "Gumlet", + "AsyncGumlet", + "file_from_path", + "BaseModel", + "DEFAULT_TIMEOUT", + "DEFAULT_MAX_RETRIES", + "DEFAULT_CONNECTION_LIMITS", + "DefaultHttpxClient", + "DefaultAsyncHttpxClient", + "DefaultAioHttpClient", +] + +if not _t.TYPE_CHECKING: + from ._utils._resources_proxy import resources as resources + +_setup_logging() + +__locals = locals() +__module_name = __name__ +for __export_name in __all__: + if not __export_name.startswith("__"): + try: + __locals[__export_name].__module__ = __module_name + except (TypeError, AttributeError): + pass diff --git a/src/gumlet/_base_client.py b/src/gumlet/_base_client.py new file mode 100644 index 0000000..f9b71da --- /dev/null +++ b/src/gumlet/_base_client.py @@ -0,0 +1,2236 @@ +from __future__ import annotations + +import sys +import json +import time +import uuid +import email +import asyncio +import inspect +import logging +import platform +import warnings +import email.utils +from types import TracebackType +from random import random +from dataclasses import field, dataclass +from typing import ( + TYPE_CHECKING, + Any, + Dict, + List, + Type, + Union, + Generic, + Mapping, + TypeVar, + Iterable, + Iterator, + Optional, + Generator, + AsyncIterator, + cast, + overload, +) +from typing_extensions import Literal, override, get_origin + +import anyio +import httpx +import distro +import pydantic +from httpx import URL +from pydantic import PrivateAttr + +from . import _exceptions +from ._qs import Querystring +from ._files import to_httpx_files, async_to_httpx_files +from ._types import ( + Body, + Omit, + Query, + Headers, + Timeout, + NotGiven, + ResponseT, + AnyMapping, + PostParser, + BinaryTypes, + RequestFiles, + HttpxSendArgs, + RequestOptions, + AsyncBinaryTypes, + HttpxRequestFiles, + ModelBuilderProtocol, + not_given, +) +from ._utils import is_dict, is_list, asyncify, is_given, lru_cache, is_mapping +from ._compat import PYDANTIC_V1, model_copy, model_dump +from ._models import GenericModel, SecurityOptions, FinalRequestOptions, validate_type, construct_type +from ._response import ( + APIResponse, + BaseAPIResponse, + AsyncAPIResponse, + extract_response_type, +) +from ._constants import ( + DEFAULT_TIMEOUT, + MAX_RETRY_DELAY, + DEFAULT_MAX_RETRIES, + INITIAL_RETRY_DELAY, + RAW_RESPONSE_HEADER, + OVERRIDE_CAST_TO_HEADER, + DEFAULT_CONNECTION_LIMITS, +) +from ._streaming import Stream, SSEDecoder, AsyncStream, SSEBytesDecoder +from ._exceptions import ( + APIStatusError, + APITimeoutError, + APIConnectionError, + APIResponseValidationError, +) +from ._utils._json import openapi_dumps + +log: logging.Logger = logging.getLogger(__name__) + +# TODO: make base page type vars covariant +SyncPageT = TypeVar("SyncPageT", bound="BaseSyncPage[Any]") +AsyncPageT = TypeVar("AsyncPageT", bound="BaseAsyncPage[Any]") + + +_T = TypeVar("_T") +_T_co = TypeVar("_T_co", covariant=True) + +_StreamT = TypeVar("_StreamT", bound=Stream[Any]) +_AsyncStreamT = TypeVar("_AsyncStreamT", bound=AsyncStream[Any]) + +if TYPE_CHECKING: + from httpx._config import ( + DEFAULT_TIMEOUT_CONFIG, # pyright: ignore[reportPrivateImportUsage] + ) + + HTTPX_DEFAULT_TIMEOUT = DEFAULT_TIMEOUT_CONFIG +else: + try: + from httpx._config import DEFAULT_TIMEOUT_CONFIG as HTTPX_DEFAULT_TIMEOUT + except ImportError: + # taken from https://github.com/encode/httpx/blob/3ba5fe0d7ac70222590e759c31442b1cab263791/httpx/_config.py#L366 + HTTPX_DEFAULT_TIMEOUT = Timeout(5.0) + + +@dataclass +class CursorPageConfig: + """Per-operation cursor pagination descriptor baked into a page by the generated list method. + + The Python view of the shared, language-neutral descriptor: where the items array lives, how to + read the next cursor, which request param carries it, and on which carrier (query vs body). + Driving extraction from this config — rather than typed `items`/`next_cursor` model fields — lets + one page class serve every cursor scheme, including `cursor_id` (cursor read from the last item) + and body-carried cursors. + """ + + # Body path to the items array (e.g. ["items"], ["data"]). + items_path: List[str] = field(default_factory=list) + # "field" (a top-level response field) or "item" (a field on the last item — `cursor_id`). + cursor_kind: str = "field" + # Path to the cursor value: from the body root when cursor_kind is "field", or from the last item. + cursor_path: List[str] = field(default_factory=list) + # Request param wire name that carries the cursor. + cursor_param: str = "cursor" + # "query" or "body": the carrier the cursor param rides on the next request. + cursor_location: str = "query" + # Keep paginating across an empty page when set. + continue_on_empty_items: bool = False + # Body path to the scheme's `has_more` boolean. Only an explicit False stops pagination; + # a missing or non-boolean value falls back to cursor-presence semantics. + has_more_path: List[str] = field(default_factory=list) + + +class PageInfo: + """Stores the necessary information to build the request to retrieve the next page. + + Exactly one of `url`, `params` (query carrier), or `json` (body carrier) must be set. + """ + + url: URL | NotGiven + params: Query | NotGiven + json: Body | NotGiven + + @overload + def __init__( + self, + *, + url: URL, + ) -> None: ... + + @overload + def __init__( + self, + *, + params: Query, + ) -> None: ... + + @overload + def __init__( + self, + *, + json: Body, + ) -> None: ... + + def __init__( + self, + *, + url: URL | NotGiven = not_given, + json: Body | NotGiven = not_given, + params: Query | NotGiven = not_given, + ) -> None: + self.url = url + self.json = json + self.params = params + + @override + def __repr__(self) -> str: + if self.url: + return f"{self.__class__.__name__}(url={self.url})" + if self.json: + return f"{self.__class__.__name__}(json={self.json})" + return f"{self.__class__.__name__}(params={self.params})" + + +class BasePage(GenericModel, Generic[_T]): + """ + Defines the core interface for pagination. + + Type Args: + ModelT: The pydantic model that represents an item in the response. + + Methods: + has_next_page(): Check if there is another page available + next_page_info(): Get the necessary information to make a request for the next page + """ + + _options: FinalRequestOptions = PrivateAttr() + _model: Type[_T] = PrivateAttr() + # Per-operation cursor descriptor; set by the paginated method via _set_private_attributes and + # carried forward across pages so every page extracts items/cursor identically. None for pages + # that predate metadata-driven pagination. + _cursor_config: Optional[CursorPageConfig] = PrivateAttr(None) + + def has_next_page(self) -> bool: + items = self._get_page_items() + if not items: + return False + return self.next_page_info() is not None + + def next_page_info(self) -> Optional[PageInfo]: ... + + def _get_page_items(self) -> Iterable[_T]: # type: ignore[empty-body] + ... + + def _params_from_url(self, url: URL) -> httpx.QueryParams: + # TODO: do we have to preprocess params here? + return httpx.QueryParams(cast(Any, self._options.params)).merge(url.params) + + def _info_to_options(self, info: PageInfo) -> FinalRequestOptions: + options = model_copy(self._options) + options._strip_raw_response_header() + + if not isinstance(info.params, NotGiven): + options.params = {**options.params, **info.params} + return options + + if not isinstance(info.url, NotGiven): + params = self._params_from_url(info.url) + url = info.url.copy_with(params=params) + options.params = dict(url.params) + options.url = str(url) + return options + + if not isinstance(info.json, NotGiven): + if not is_mapping(info.json): + raise TypeError("Pagination is only supported with mappings") + + if not options.json_data: + options.json_data = {**info.json} + else: + if not is_mapping(options.json_data): + raise TypeError("Pagination is only supported with mappings") + + options.json_data = {**options.json_data, **info.json} + return options + + raise ValueError("Unexpected PageInfo state") + + +class BaseSyncPage(BasePage[_T], Generic[_T]): + _client: SyncAPIClient = pydantic.PrivateAttr() + + def _set_private_attributes( + self, + client: SyncAPIClient, + model: Type[_T], + options: FinalRequestOptions, + cursor_config: Optional[CursorPageConfig] = None, + ) -> None: + if (not PYDANTIC_V1) and getattr(self, "__pydantic_private__", None) is None: + self.__pydantic_private__ = {} + + self._model = model + self._client = client + self._options = options + self._cursor_config = cursor_config + + # Pydantic uses a custom `__iter__` method to support casting BaseModels + # to dictionaries. e.g. dict(model). + # As we want to support `for item in page`, this is inherently incompatible + # with the default pydantic behaviour. It is not possible to support both + # use cases at once. Fortunately, this is not a big deal as all other pydantic + # methods should continue to work as expected as there is an alternative method + # to cast a model to a dictionary, model.dict(), which is used internally + # by pydantic. + def __iter__(self) -> Iterator[_T]: # type: ignore + for page in self.iter_pages(): + for item in page._get_page_items(): + yield item + + def iter_pages(self: SyncPageT) -> Iterator[SyncPageT]: + page = self + while True: + yield page + if page.has_next_page(): + page = page.get_next_page() + else: + return + + def get_next_page(self: SyncPageT) -> SyncPageT: + info = self.next_page_info() + if not info: + raise RuntimeError( + "No next page expected; please check `.has_next_page()` before calling `.get_next_page()`." + ) + + options = self._info_to_options(info) + # Carry the cursor descriptor forward so the next page extracts items/cursor identically. + return self._client._request_api_list( + self._model, page=self.__class__, options=options, cursor_config=self._cursor_config + ) + + +class AsyncPaginator(Generic[_T, AsyncPageT]): + def __init__( + self, + client: AsyncAPIClient, + options: FinalRequestOptions, + page_cls: Type[AsyncPageT], + model: Type[_T], + cursor_config: Optional[CursorPageConfig] = None, + ) -> None: + self._model = model + self._client = client + self._options = options + self._page_cls = page_cls + self._cursor_config = cursor_config + + def __await__(self) -> Generator[Any, None, AsyncPageT]: + return self._get_page().__await__() + + async def _get_page(self) -> AsyncPageT: + def _parser(resp: AsyncPageT) -> AsyncPageT: + resp._set_private_attributes( + model=self._model, + options=self._options, + client=self._client, + cursor_config=self._cursor_config, + ) + return resp + + self._options.post_parser = _parser + + return await self._client.request(self._page_cls, self._options) + + async def __aiter__(self) -> AsyncIterator[_T]: + # https://github.com/microsoft/pyright/issues/3464 + page = cast( + AsyncPageT, + await self, # type: ignore + ) + async for item in page: + yield item + + +class BaseAsyncPage(BasePage[_T], Generic[_T]): + _client: AsyncAPIClient = pydantic.PrivateAttr() + + def _set_private_attributes( + self, + model: Type[_T], + client: AsyncAPIClient, + options: FinalRequestOptions, + cursor_config: Optional[CursorPageConfig] = None, + ) -> None: + if (not PYDANTIC_V1) and getattr(self, "__pydantic_private__", None) is None: + self.__pydantic_private__ = {} + + self._model = model + self._client = client + self._options = options + self._cursor_config = cursor_config + + async def __aiter__(self) -> AsyncIterator[_T]: + async for page in self.iter_pages(): + for item in page._get_page_items(): + yield item + + async def iter_pages(self: AsyncPageT) -> AsyncIterator[AsyncPageT]: + page = self + while True: + yield page + if page.has_next_page(): + page = await page.get_next_page() + else: + return + + async def get_next_page(self: AsyncPageT) -> AsyncPageT: + info = self.next_page_info() + if not info: + raise RuntimeError( + "No next page expected; please check `.has_next_page()` before calling `.get_next_page()`." + ) + + options = self._info_to_options(info) + # Carry the cursor descriptor forward so the next page extracts items/cursor identically. + return await self._client._request_api_list( + self._model, page=self.__class__, options=options, cursor_config=self._cursor_config + ) + + +_HttpxClientT = TypeVar("_HttpxClientT", bound=Union[httpx.Client, httpx.AsyncClient]) +_DefaultStreamT = TypeVar("_DefaultStreamT", bound=Union[Stream[Any], AsyncStream[Any]]) + + +class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]): + _client: _HttpxClientT + _version: str + _base_url: URL + max_retries: int + timeout: Union[float, Timeout, None] + _strict_response_validation: bool + _idempotency_header: str | None + _default_stream_cls: type[_DefaultStreamT] | None = None + + def __init__( + self, + *, + version: str, + base_url: str | URL, + _strict_response_validation: bool, + max_retries: int = DEFAULT_MAX_RETRIES, + timeout: float | Timeout | None = DEFAULT_TIMEOUT, + custom_headers: Mapping[str, str] | None = None, + custom_query: Mapping[str, object] | None = None, + ) -> None: + self._version = version + self._base_url = self._enforce_trailing_slash(URL(base_url)) + self.max_retries = max_retries + self.timeout = timeout + self._custom_headers = custom_headers or {} + self._custom_query = custom_query or {} + self._strict_response_validation = _strict_response_validation + self._idempotency_header = None + self._platform: Platform | None = None + + if max_retries is None: # pyright: ignore[reportUnnecessaryComparison] + raise TypeError( + "max_retries cannot be None. If you want to disable retries, pass `0`; if you want unlimited retries, pass `math.inf` or a very high number; if you want the default behavior, pass `gumlet.DEFAULT_MAX_RETRIES`" + ) + + def _enforce_trailing_slash(self, url: URL) -> URL: + if url.raw_path.endswith(b"/"): + return url + return url.copy_with(raw_path=url.raw_path + b"/") + + def _make_status_error_from_response( + self, + response: httpx.Response, + ) -> APIStatusError: + if response.is_closed and not response.is_stream_consumed: + # We can't read the response body as it has been closed + # before it was read. This can happen if an event hook + # raises a status error. + body = None + err_msg = f"Error code: {response.status_code}" + else: + err_text = response.text.strip() + body = err_text + + try: + body = json.loads(err_text) + err_msg = f"Error code: {response.status_code} - {body}" + except Exception: + err_msg = err_text or f"Error code: {response.status_code}" + + return self._make_status_error(err_msg, body=body, response=response) + + def _make_status_error( + self, + err_msg: str, + *, + body: object, + response: httpx.Response, + ) -> _exceptions.APIStatusError: + raise NotImplementedError() + + def _auth_query( + self, + security: SecurityOptions, # noqa: ARG002 + ) -> dict[str, str]: + return {} + + def _auth_cookies( + self, + security: SecurityOptions, # noqa: ARG002 + ) -> dict[str, str]: + return {} + + def _custom_auth( + self, + security: SecurityOptions, # noqa: ARG002 + ) -> httpx.Auth | None: + # Single hook for custom httpx auth on both the sync and async request paths. It defaults to + # the legacy public `custom_auth` property so subclasses that already override that property + # keep applying identically on both paths; override `_custom_auth` itself to layer + # per-operation, security-aware auth on top. + return self.custom_auth + + def _build_headers( + self, + options: FinalRequestOptions, + *, + params: Query, + cookies: Mapping[str, str], + retries_taken: int = 0, + ) -> httpx.Headers: + custom_headers = options.headers or {} + # Auth headers are folded into `default_headers` (matching the reference SDKs), so + # merging `default_headers` here already applies the configured credentials. + headers_dict = _merge_mappings(self.default_headers, custom_headers) + self._validate_headers(headers_dict, custom_headers, params, cookies) + + # headers are case-insensitive while dictionaries are not. + headers = httpx.Headers(headers_dict) + + idempotency_header = self._idempotency_header + if idempotency_header and options.idempotency_key and idempotency_header not in headers: + headers[idempotency_header] = options.idempotency_key + + # Don't set these headers if they were already set or removed by the caller. We check + # `custom_headers`, which can contain `Omit()`, instead of `headers` to account for the removal case. + lower_custom_headers = [header.lower() for header in custom_headers] + if "x-scalar-retry-count" not in lower_custom_headers: + headers["x-scalar-retry-count"] = str(retries_taken) + if "x-scalar-read-timeout" not in lower_custom_headers: + timeout = self.timeout if isinstance(options.timeout, NotGiven) else options.timeout + if isinstance(timeout, Timeout): + timeout = timeout.read + if timeout is not None: + headers["x-scalar-read-timeout"] = str(timeout) + + return headers + + def _prepare_url(self, url: str) -> URL: + """ + Merge a URL argument together with any 'base_url' on the client, + to create the URL used for the outgoing request. + """ + # Copied from httpx's `_merge_url` method. + merge_url = URL(url) + if merge_url.is_relative_url: + merge_raw_path = self.base_url.raw_path + merge_url.raw_path.lstrip(b"/") + return self.base_url.copy_with(raw_path=merge_raw_path) + + return merge_url + + def _make_sse_decoder(self) -> SSEDecoder | SSEBytesDecoder: + return SSEDecoder() + + def _build_request( + self, + options: FinalRequestOptions, + *, + retries_taken: int = 0, + ) -> httpx.Request: + if log.isEnabledFor(logging.DEBUG): + log.debug( + "Request options: %s", + model_dump( + options, + exclude_unset=True, + # Pydantic v1 can't dump every type we support in content, so we exclude it for now. + exclude={ + "content", + } + if PYDANTIC_V1 + else {}, + ), + ) + kwargs: dict[str, Any] = {} + + json_data = options.json_data + if options.extra_json is not None: + if json_data is None: + json_data = cast(Body, options.extra_json) + elif is_mapping(json_data): + json_data = _merge_mappings(json_data, options.extra_json) + else: + raise RuntimeError(f"Unexpected JSON data type, {type(json_data)}, cannot merge with `extra_body`") + + params = _merge_mappings({**self._auth_query(options.security), **self.default_query}, options.params) + cookies = self._auth_cookies(options.security) + headers = self._build_headers(options, retries_taken=retries_taken, params=params, cookies=cookies) + content_type = headers.get("Content-Type") + files = options.files + + # If the given Content-Type header is multipart/form-data then it + # has to be removed so that httpx can generate the header with + # additional information for us as it has to be in this form + # for the server to be able to correctly parse the request: + # multipart/form-data; boundary=---abc-- + if content_type is not None and content_type.startswith("multipart/form-data"): + if "boundary" not in content_type: + # only remove the header if the boundary hasn't been explicitly set + # as the caller doesn't want httpx to come up with their own boundary + headers.pop("Content-Type") + + # As we are now sending multipart/form-data instead of application/json + # we need to tell httpx to use it, https://www.python-httpx.org/advanced/clients/#multipart-file-encoding + if json_data: + if not is_dict(json_data): + raise TypeError( + f"Expected query input to be a dictionary for multipart requests but got {type(json_data)} instead." + ) + kwargs["data"] = self._serialize_multipartform(json_data) + + # httpx determines whether or not to send a "multipart/form-data" + # request based on the truthiness of the "files" argument. + # This gets around that issue by generating a dict value that + # evaluates to true. + # + # https://github.com/encode/httpx/discussions/2399#discussioncomment-3814186 + if not files: + files = cast(HttpxRequestFiles, ForceMultipartDict()) + + prepared_url = self._prepare_url(options.url) + # preserve hard-coded query params from the url + if params and prepared_url.query: + params = {**dict(prepared_url.params.items()), **params} + prepared_url = prepared_url.copy_with(raw_path=prepared_url.raw_path.split(b"?", 1)[0]) + if "_" in prepared_url.host: + # work around https://github.com/encode/httpx/discussions/2880 + kwargs["extensions"] = {"sni_hostname": prepared_url.host.replace("_", "-")} + + is_body_allowed = options.method.lower() != "get" + + if is_body_allowed: + if options.content is not None and json_data is not None: + raise TypeError("Passing both `content` and `json_data` is not supported") + if options.content is not None and files is not None: + raise TypeError("Passing both `content` and `files` is not supported") + if options.content is not None: + kwargs["content"] = options.content + elif isinstance(json_data, bytes): + kwargs["content"] = json_data + elif not files: + # Don't set content when JSON is sent as multipart/form-data, + # since httpx's content param overrides other body arguments + kwargs["content"] = openapi_dumps(json_data) if is_given(json_data) and json_data is not None else None + kwargs["files"] = files + else: + headers.pop("Content-Type", None) + kwargs.pop("data", None) + + # TODO: report this error to httpx + return self._client.build_request( # pyright: ignore[reportUnknownMemberType] + headers=headers, + timeout=self.timeout if isinstance(options.timeout, NotGiven) else options.timeout, + method=options.method, + url=prepared_url, + # the `Query` type that we use is incompatible with qs' + # `Params` type as it needs to be typed as `Mapping[str, object]` + # so that passing a `TypedDict` doesn't cause an error. + # https://github.com/microsoft/pyright/issues/3526#event-6715453066 + params=self.qs.stringify(cast(Mapping[str, Any], params)) if params else None, + cookies=cookies or None, + **kwargs, + ) + + def _serialize_multipartform(self, data: Mapping[object, object]) -> dict[str, object]: + items = self.qs.stringify_items( + # TODO: type ignore is required as stringify_items is well typed but we can't be + # well typed without heavy validation. + data, # type: ignore + array_format="brackets", + ) + serialized: dict[str, object] = {} + for key, value in items: + existing = serialized.get(key) + + if not existing: + serialized[key] = value + continue + + # If a value has already been set for this key then that + # means we're sending data like `array[]=[1, 2, 3]` and we + # need to tell httpx that we want to send multiple values with + # the same key which is done by using a list or a tuple. + # + # Note: 2d arrays should never result in the same key at both + # levels so it's safe to assume that if the value is a list, + # it was because we changed it to be a list. + if is_list(existing): + existing.append(value) + else: + serialized[key] = [existing, value] + + return serialized + + def _maybe_override_cast_to(self, cast_to: type[ResponseT], options: FinalRequestOptions) -> type[ResponseT]: + if not is_given(options.headers): + return cast_to + + # make a copy of the headers so we don't mutate user-input + headers = dict(options.headers) + + # we internally support defining a temporary header to override the + # default `cast_to` type for use with `.with_raw_response` and `.with_streaming_response` + # see _response.py for implementation details + override_cast_to = headers.pop(OVERRIDE_CAST_TO_HEADER, not_given) + if is_given(override_cast_to): + options.headers = headers + return cast(Type[ResponseT], override_cast_to) + + return cast_to + + def _should_stream_response_body(self, request: httpx.Request) -> bool: + return request.headers.get(RAW_RESPONSE_HEADER) == "stream" # type: ignore[no-any-return] + + def _process_response_data( + self, + *, + data: object, + cast_to: type[ResponseT], + response: httpx.Response, + ) -> ResponseT: + if data is None: + return cast(ResponseT, None) + + if cast_to is object: + return cast(ResponseT, data) + + try: + if inspect.isclass(cast_to) and issubclass(cast_to, ModelBuilderProtocol): + return cast(ResponseT, cast_to.build(response=response, data=data)) + + if self._strict_response_validation: + return cast(ResponseT, validate_type(type_=cast_to, value=data)) + + return cast(ResponseT, construct_type(type_=cast_to, value=data)) + except pydantic.ValidationError as err: + raise APIResponseValidationError(response=response, body=data) from err + + @property + def qs(self) -> Querystring: + return Querystring() + + @property + def custom_auth(self) -> httpx.Auth | None: + return None + + @property + def auth_headers(self) -> dict[str, str]: + return {} + + @property + def default_headers(self) -> dict[str, str | Omit]: + return { + "Accept": "application/json", + "Content-Type": "application/json", + "User-Agent": self.user_agent, + **self.platform_headers(), + **self.auth_headers, + **self._custom_headers, + } + + @property + def default_query(self) -> dict[str, object]: + return { + **self._custom_query, + } + + def _validate_headers( + self, + headers: Headers, # noqa: ARG002 + custom_headers: Headers, # noqa: ARG002 + params: Query, # noqa: ARG002 + cookies: Mapping[str, str], # noqa: ARG002 + ) -> None: + """Validate merged auth-bearing headers, query params, and cookies. + + Does nothing by default. + """ + return + + @property + def user_agent(self) -> str: + return f"{self.__class__.__name__}/Python {self._version}" + + @property + def base_url(self) -> URL: + return self._base_url + + @base_url.setter + def base_url(self, url: URL | str) -> None: + self._base_url = self._enforce_trailing_slash(url if isinstance(url, URL) else URL(url)) + + def platform_headers(self) -> Dict[str, str]: + # the actual implementation is in a separate `lru_cache` decorated + # function because adding `lru_cache` to methods will leak memory + # https://github.com/python/cpython/issues/88476 + return platform_headers(self._version, platform=self._platform) + + def _parse_retry_after_header(self, response_headers: Optional[httpx.Headers] = None) -> float | None: + """Returns a float of the number of seconds (not milliseconds) to wait after retrying, or None if unspecified. + + About the Retry-After header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After + See also https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After#syntax + """ + if response_headers is None: + return None + + # First, try the non-standard `retry-after-ms` header for milliseconds, + # which is more precise than integer-seconds `retry-after` + try: + retry_ms_header = response_headers.get("retry-after-ms", None) + return float(retry_ms_header) / 1000 + except (TypeError, ValueError): + pass + + # Next, try parsing `retry-after` header as seconds (allowing nonstandard floats). + retry_header = response_headers.get("retry-after") + try: + # note: the spec indicates that this should only ever be an integer + # but if someone sends a float there's no reason for us to not respect it + return float(retry_header) + except (TypeError, ValueError): + pass + + # Last, try parsing `retry-after` as a date. + retry_date_tuple = email.utils.parsedate_tz(retry_header) + if retry_date_tuple is None: + return None + + retry_date = email.utils.mktime_tz(retry_date_tuple) + return float(retry_date - time.time()) + + def _calculate_retry_timeout( + self, + remaining_retries: int, + options: FinalRequestOptions, + response_headers: Optional[httpx.Headers] = None, + ) -> float: + max_retries = options.get_max_retries(self.max_retries) + + # If the API asks us to wait a certain amount of time (and it's a reasonable amount), just do what it says. + retry_after = self._parse_retry_after_header(response_headers) + if retry_after is not None and 0 < retry_after <= 60: + return retry_after + + # Also cap retry count to 1000 to avoid any potential overflows with `pow` + nb_retries = min(max_retries - remaining_retries, 1000) + + # Apply exponential backoff, but not more than the max. + sleep_seconds = min(INITIAL_RETRY_DELAY * pow(2.0, nb_retries), MAX_RETRY_DELAY) + + # Apply some jitter, plus-or-minus half a second. + jitter = 1 - 0.25 * random() + timeout = sleep_seconds * jitter + return timeout if timeout >= 0 else 0 + + def _should_retry(self, response: httpx.Response) -> bool: + # Note: this is not a standard header + should_retry_header = response.headers.get("x-should-retry") + + # If the server explicitly says whether or not to retry, obey. + if should_retry_header == "true": + log.debug("Retrying as header `x-should-retry` is set to `true`") + return True + if should_retry_header == "false": + log.debug("Not retrying as header `x-should-retry` is set to `false`") + return False + + # Retry on request timeouts. + if response.status_code == 408: + log.debug("Retrying due to status code %i", response.status_code) + return True + + # Retry on lock timeouts. + if response.status_code == 409: + log.debug("Retrying due to status code %i", response.status_code) + return True + + # Retry on rate limits. + if response.status_code == 429: + log.debug("Retrying due to status code %i", response.status_code) + return True + + # Retry internal errors. + if response.status_code >= 500: + log.debug("Retrying due to status code %i", response.status_code) + return True + + log.debug("Not retrying") + return False + + def _idempotency_key(self) -> str: + return f"scalar-python-retry-{uuid.uuid4()}" + + +class _DefaultHttpxClient(httpx.Client): + def __init__(self, **kwargs: Any) -> None: + kwargs.setdefault("timeout", DEFAULT_TIMEOUT) + kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS) + kwargs.setdefault("follow_redirects", True) + super().__init__(**kwargs) + + +if TYPE_CHECKING: + DefaultHttpxClient = httpx.Client + """An alias to `httpx.Client` that provides the same defaults that this SDK + uses internally. + + This is useful because overriding the `http_client` with your own instance of + `httpx.Client` will result in httpx's defaults being used, not ours. + """ +else: + DefaultHttpxClient = _DefaultHttpxClient + + +class SyncHttpxClientWrapper(DefaultHttpxClient): + def __del__(self) -> None: + if self.is_closed: + return + + try: + self.close() + except Exception: + pass + + +class SyncAPIClient(BaseClient[httpx.Client, Stream[Any]]): + _client: httpx.Client + _default_stream_cls: type[Stream[Any]] | None = None + + def __init__( + self, + *, + version: str, + base_url: str | URL, + max_retries: int = DEFAULT_MAX_RETRIES, + timeout: float | Timeout | None | NotGiven = not_given, + http_client: httpx.Client | None = None, + custom_headers: Mapping[str, str] | None = None, + custom_query: Mapping[str, object] | None = None, + _strict_response_validation: bool, + **kwargs: Any, + ) -> None: + if not is_given(timeout): + # if the user passed in a custom http client with a non-default + # timeout set then we use that timeout. + # + # note: there is an edge case here where the user passes in a client + # where they've explicitly set the timeout to match the default timeout + # as this check is structural, meaning that we'll think they didn't + # pass in a timeout and will ignore it + if http_client and http_client.timeout != HTTPX_DEFAULT_TIMEOUT: + timeout = http_client.timeout + else: + timeout = DEFAULT_TIMEOUT + + if http_client is not None and not isinstance(http_client, httpx.Client): # pyright: ignore[reportUnnecessaryIsInstance] + raise TypeError( + f"Invalid `http_client` argument; Expected an instance of `httpx.Client` but got {type(http_client)}" + ) + + super().__init__( + version=version, + # cast to a valid type because mypy doesn't understand our type narrowing + timeout=cast(Timeout, timeout), + base_url=base_url, + max_retries=max_retries, + custom_query=custom_query, + custom_headers=custom_headers, + _strict_response_validation=_strict_response_validation, + ) + self._client = http_client or SyncHttpxClientWrapper( + base_url=base_url, + # cast to a valid type because mypy doesn't understand our type narrowing + timeout=cast(Timeout, timeout), + # Used by generated `with_options(_extra_kwargs=...)` for HTTPX client configuration. + **kwargs, + ) + + def is_closed(self) -> bool: + return self._client.is_closed + + def close(self) -> None: + """Close the underlying HTTPX client. + + The client will *not* be usable after this. + """ + # If an error is thrown while constructing a client, self._client + # may not be present + if hasattr(self, "_client"): + self._client.close() + + def __enter__(self: _T) -> _T: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self.close() + + def _prepare_options( + self, + options: FinalRequestOptions, # noqa: ARG002 + ) -> FinalRequestOptions: + """Hook for mutating the given options""" + return options + + def _prepare_request( + self, + request: httpx.Request, # noqa: ARG002 + ) -> None: + """This method is used as a callback for mutating the `Request` object + after it has been constructed. + This is useful for cases where you want to add certain headers based off of + the request properties, e.g. `url`, `method` etc. + """ + return None + + @overload + def request( + self, + cast_to: Type[ResponseT], + options: FinalRequestOptions, + *, + stream: Literal[True], + stream_cls: Type[_StreamT], + ) -> _StreamT: ... + + @overload + def request( + self, + cast_to: Type[ResponseT], + options: FinalRequestOptions, + *, + stream: Literal[False] = False, + ) -> ResponseT: ... + + @overload + def request( + self, + cast_to: Type[ResponseT], + options: FinalRequestOptions, + *, + stream: bool = False, + stream_cls: Type[_StreamT] | None = None, + ) -> ResponseT | _StreamT: ... + + def request( + self, + cast_to: Type[ResponseT], + options: FinalRequestOptions, + *, + stream: bool = False, + stream_cls: type[_StreamT] | None = None, + ) -> ResponseT | _StreamT: + cast_to = self._maybe_override_cast_to(cast_to, options) + + # create a copy of the options we were given so that if the + # options are mutated later & we then retry, the retries are + # given the original options + input_options = model_copy(options) + if input_options.idempotency_key is None and input_options.method.lower() != "get": + # ensure the idempotency key is reused between requests + input_options.idempotency_key = self._idempotency_key() + + response: httpx.Response | None = None + max_retries = input_options.get_max_retries(self.max_retries) + + retries_taken = 0 + for retries_taken in range(max_retries + 1): + options = model_copy(input_options) + options = self._prepare_options(options) + + remaining_retries = max_retries - retries_taken + request = self._build_request(options, retries_taken=retries_taken) + self._prepare_request(request) + + kwargs: HttpxSendArgs = {} + custom_auth = self._custom_auth(options.security) + if custom_auth is not None: + kwargs["auth"] = custom_auth + + if options.follow_redirects is not None: + kwargs["follow_redirects"] = options.follow_redirects + + log.debug("Sending HTTP Request: %s %s", request.method, request.url) + + response = None + try: + response = self._client.send( + request, + stream=stream or self._should_stream_response_body(request=request), + **kwargs, + ) + except httpx.TimeoutException as err: + log.debug("Encountered httpx.TimeoutException", exc_info=True) + + if remaining_retries > 0: + self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=None, + ) + continue + + log.debug("Raising timeout error") + raise APITimeoutError(request=request) from err + except Exception as err: + log.debug("Encountered Exception", exc_info=True) + + if remaining_retries > 0: + self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=None, + ) + continue + + log.debug("Raising connection error") + raise APIConnectionError(request=request) from err + + log.debug( + 'HTTP Response: %s %s "%i %s" %s', + request.method, + request.url, + response.status_code, + response.reason_phrase, + response.headers, + ) + + try: + response.raise_for_status() + except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code + log.debug("Encountered httpx.HTTPStatusError", exc_info=True) + + if remaining_retries > 0 and self._should_retry(err.response): + err.response.close() + self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=response, + ) + continue + + # If the response is streamed then we need to explicitly read the response + # to completion before attempting to access the response text. + if not err.response.is_closed: + err.response.read() + + log.debug("Re-raising status error") + raise self._make_status_error_from_response(err.response) from None + + break + + assert response is not None, "could not resolve response (should never happen)" + return self._process_response( + cast_to=cast_to, + options=options, + response=response, + stream=stream, + stream_cls=stream_cls, + retries_taken=retries_taken, + ) + + def _sleep_for_retry( + self, *, retries_taken: int, max_retries: int, options: FinalRequestOptions, response: httpx.Response | None + ) -> None: + remaining_retries = max_retries - retries_taken + if remaining_retries == 1: + log.debug("1 retry left") + else: + log.debug("%i retries left", remaining_retries) + + timeout = self._calculate_retry_timeout(remaining_retries, options, response.headers if response else None) + log.info("Retrying request to %s in %f seconds", options.url, timeout) + + time.sleep(timeout) + + def _process_response( + self, + *, + cast_to: Type[ResponseT], + options: FinalRequestOptions, + response: httpx.Response, + stream: bool, + stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None, + retries_taken: int = 0, + ) -> ResponseT: + origin = get_origin(cast_to) or cast_to + + if ( + inspect.isclass(origin) + and issubclass(origin, BaseAPIResponse) + # we only want to actually return the custom BaseAPIResponse class if we're + # returning the raw response, or if we're not streaming SSE, as if we're streaming + # SSE then `cast_to` doesn't actively reflect the type we need to parse into + and (not stream or bool(response.request.headers.get(RAW_RESPONSE_HEADER))) + ): + if not issubclass(origin, APIResponse): + raise TypeError(f"API Response types must subclass {APIResponse}; Received {origin}") + + response_cls = cast("type[BaseAPIResponse[Any]]", cast_to) + return cast( + ResponseT, + response_cls( + raw=response, + client=self, + cast_to=extract_response_type(response_cls), + stream=stream, + stream_cls=stream_cls, + options=options, + retries_taken=retries_taken, + ), + ) + + if cast_to == httpx.Response: + return cast(ResponseT, response) + + api_response = APIResponse( + raw=response, + client=self, + cast_to=cast("type[ResponseT]", cast_to), # pyright: ignore[reportUnnecessaryCast] + stream=stream, + stream_cls=stream_cls, + options=options, + retries_taken=retries_taken, + ) + if bool(response.request.headers.get(RAW_RESPONSE_HEADER)): + return cast(ResponseT, api_response) + + return api_response.parse() + + def _request_api_list( + self, + model: Type[object], + page: Type[SyncPageT], + options: FinalRequestOptions, + cursor_config: Optional[CursorPageConfig] = None, + ) -> SyncPageT: + def _parser(resp: SyncPageT) -> SyncPageT: + resp._set_private_attributes( + client=self, + model=model, + options=options, + cursor_config=cursor_config, + ) + return resp + + options.post_parser = _parser + + return self.request(page, options, stream=False) + + @overload + def get( + self, + path: str, + *, + cast_to: Type[ResponseT], + options: RequestOptions = {}, + stream: Literal[False] = False, + ) -> ResponseT: ... + + @overload + def get( + self, + path: str, + *, + cast_to: Type[ResponseT], + options: RequestOptions = {}, + stream: Literal[True], + stream_cls: type[_StreamT], + ) -> _StreamT: ... + + @overload + def get( + self, + path: str, + *, + cast_to: Type[ResponseT], + options: RequestOptions = {}, + stream: bool, + stream_cls: type[_StreamT] | None = None, + ) -> ResponseT | _StreamT: ... + + def get( + self, + path: str, + *, + cast_to: Type[ResponseT], + options: RequestOptions = {}, + stream: bool = False, + stream_cls: type[_StreamT] | None = None, + ) -> ResponseT | _StreamT: + opts = FinalRequestOptions.construct(method="get", url=path, **options) + # cast is required because mypy complains about returning Any even though + # it understands the type variables + return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls)) + + @overload + def post( + self, + path: str, + *, + cast_to: Type[ResponseT], + body: Body | None = None, + content: BinaryTypes | None = None, + options: RequestOptions = {}, + files: RequestFiles | None = None, + stream: Literal[False] = False, + ) -> ResponseT: ... + + @overload + def post( + self, + path: str, + *, + cast_to: Type[ResponseT], + body: Body | None = None, + content: BinaryTypes | None = None, + options: RequestOptions = {}, + files: RequestFiles | None = None, + stream: Literal[True], + stream_cls: type[_StreamT], + ) -> _StreamT: ... + + @overload + def post( + self, + path: str, + *, + cast_to: Type[ResponseT], + body: Body | None = None, + content: BinaryTypes | None = None, + options: RequestOptions = {}, + files: RequestFiles | None = None, + stream: bool, + stream_cls: type[_StreamT] | None = None, + ) -> ResponseT | _StreamT: ... + + def post( + self, + path: str, + *, + cast_to: Type[ResponseT], + body: Body | None = None, + content: BinaryTypes | None = None, + options: RequestOptions = {}, + files: RequestFiles | None = None, + stream: bool = False, + stream_cls: type[_StreamT] | None = None, + ) -> ResponseT | _StreamT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) + opts = FinalRequestOptions.construct( + method="post", url=path, json_data=body, content=content, files=to_httpx_files(files), **options + ) + return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls)) + + def patch( + self, + path: str, + *, + cast_to: Type[ResponseT], + body: Body | None = None, + content: BinaryTypes | None = None, + files: RequestFiles | None = None, + options: RequestOptions = {}, + ) -> ResponseT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) + opts = FinalRequestOptions.construct( + method="patch", url=path, json_data=body, content=content, files=to_httpx_files(files), **options + ) + return self.request(cast_to, opts) + + def put( + self, + path: str, + *, + cast_to: Type[ResponseT], + body: Body | None = None, + content: BinaryTypes | None = None, + files: RequestFiles | None = None, + options: RequestOptions = {}, + ) -> ResponseT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) + opts = FinalRequestOptions.construct( + method="put", url=path, json_data=body, content=content, files=to_httpx_files(files), **options + ) + return self.request(cast_to, opts) + + def delete( + self, + path: str, + *, + cast_to: Type[ResponseT], + body: Body | None = None, + content: BinaryTypes | None = None, + options: RequestOptions = {}, + ) -> ResponseT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) + opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, content=content, **options) + return self.request(cast_to, opts) + + def get_api_list( + self, + path: str, + *, + model: Type[object], + page: Type[SyncPageT], + body: Body | None = None, + files: RequestFiles | None = None, + options: RequestOptions = {}, + method: str = "get", + cursor_config: Optional[CursorPageConfig] = None, + ) -> SyncPageT: + opts = FinalRequestOptions.construct( + method=method, url=path, json_data=body, files=to_httpx_files(files), **options + ) + return self._request_api_list(model, page, opts, cursor_config=cursor_config) + + +class _DefaultAsyncHttpxClient(httpx.AsyncClient): + def __init__(self, **kwargs: Any) -> None: + kwargs.setdefault("timeout", DEFAULT_TIMEOUT) + kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS) + kwargs.setdefault("follow_redirects", True) + super().__init__(**kwargs) + + +try: + import httpx_aiohttp +except ImportError: + + class _DefaultAioHttpClient(httpx.AsyncClient): + def __init__(self, **_kwargs: Any) -> None: + raise RuntimeError("To use the aiohttp client you must have installed the package with the `aiohttp` extra") +else: + + class _DefaultAioHttpClient(httpx_aiohttp.HttpxAiohttpClient): # type: ignore + def __init__(self, **kwargs: Any) -> None: + kwargs.setdefault("timeout", DEFAULT_TIMEOUT) + kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS) + kwargs.setdefault("follow_redirects", True) + + super().__init__(**kwargs) + + +if TYPE_CHECKING: + DefaultAsyncHttpxClient = httpx.AsyncClient + """An alias to `httpx.AsyncClient` that provides the same defaults that this SDK + uses internally. + + This is useful because overriding the `http_client` with your own instance of + `httpx.AsyncClient` will result in httpx's defaults being used, not ours. + """ + + DefaultAioHttpClient = httpx.AsyncClient + """An alias to `httpx.AsyncClient` that changes the default HTTP transport to `aiohttp`.""" +else: + DefaultAsyncHttpxClient = _DefaultAsyncHttpxClient + DefaultAioHttpClient = _DefaultAioHttpClient + + +class AsyncHttpxClientWrapper(DefaultAsyncHttpxClient): + def __del__(self) -> None: + if self.is_closed: + return + + try: + # TODO(someday): support non asyncio runtimes here + asyncio.get_running_loop().create_task(self.aclose()) + except Exception: + pass + + +class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]): + _client: httpx.AsyncClient + _default_stream_cls: type[AsyncStream[Any]] | None = None + + def __init__( + self, + *, + version: str, + base_url: str | URL, + _strict_response_validation: bool, + max_retries: int = DEFAULT_MAX_RETRIES, + timeout: float | Timeout | None | NotGiven = not_given, + http_client: httpx.AsyncClient | None = None, + custom_headers: Mapping[str, str] | None = None, + custom_query: Mapping[str, object] | None = None, + **kwargs: Any, + ) -> None: + if not is_given(timeout): + # if the user passed in a custom http client with a non-default + # timeout set then we use that timeout. + # + # note: there is an edge case here where the user passes in a client + # where they've explicitly set the timeout to match the default timeout + # as this check is structural, meaning that we'll think they didn't + # pass in a timeout and will ignore it + if http_client and http_client.timeout != HTTPX_DEFAULT_TIMEOUT: + timeout = http_client.timeout + else: + timeout = DEFAULT_TIMEOUT + + if http_client is not None and not isinstance(http_client, httpx.AsyncClient): # pyright: ignore[reportUnnecessaryIsInstance] + raise TypeError( + f"Invalid `http_client` argument; Expected an instance of `httpx.AsyncClient` but got {type(http_client)}" + ) + + super().__init__( + version=version, + base_url=base_url, + # cast to a valid type because mypy doesn't understand our type narrowing + timeout=cast(Timeout, timeout), + max_retries=max_retries, + custom_query=custom_query, + custom_headers=custom_headers, + _strict_response_validation=_strict_response_validation, + ) + self._client = http_client or AsyncHttpxClientWrapper( + base_url=base_url, + # cast to a valid type because mypy doesn't understand our type narrowing + timeout=cast(Timeout, timeout), + # Used by generated `with_options(_extra_kwargs=...)` for HTTPX client configuration. + **kwargs, + ) + + def is_closed(self) -> bool: + return self._client.is_closed + + async def close(self) -> None: + """Close the underlying HTTPX client. + + The client will *not* be usable after this. + """ + await self._client.aclose() + + async def __aenter__(self: _T) -> _T: + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + await self.close() + + async def _prepare_options( + self, + options: FinalRequestOptions, # noqa: ARG002 + ) -> FinalRequestOptions: + """Hook for mutating the given options""" + return options + + async def _prepare_request( + self, + request: httpx.Request, # noqa: ARG002 + ) -> None: + """This method is used as a callback for mutating the `Request` object + after it has been constructed. + This is useful for cases where you want to add certain headers based off of + the request properties, e.g. `url`, `method` etc. + """ + return None + + @overload + async def request( + self, + cast_to: Type[ResponseT], + options: FinalRequestOptions, + *, + stream: Literal[False] = False, + ) -> ResponseT: ... + + @overload + async def request( + self, + cast_to: Type[ResponseT], + options: FinalRequestOptions, + *, + stream: Literal[True], + stream_cls: type[_AsyncStreamT], + ) -> _AsyncStreamT: ... + + @overload + async def request( + self, + cast_to: Type[ResponseT], + options: FinalRequestOptions, + *, + stream: bool, + stream_cls: type[_AsyncStreamT] | None = None, + ) -> ResponseT | _AsyncStreamT: ... + + async def request( + self, + cast_to: Type[ResponseT], + options: FinalRequestOptions, + *, + stream: bool = False, + stream_cls: type[_AsyncStreamT] | None = None, + ) -> ResponseT | _AsyncStreamT: + if self._platform is None: + # `get_platform` can make blocking IO calls so we + # execute it earlier while we are in an async context + self._platform = await asyncify(get_platform)() + + cast_to = self._maybe_override_cast_to(cast_to, options) + + # create a copy of the options we were given so that if the + # options are mutated later & we then retry, the retries are + # given the original options + input_options = model_copy(options) + if input_options.idempotency_key is None and input_options.method.lower() != "get": + # ensure the idempotency key is reused between requests + input_options.idempotency_key = self._idempotency_key() + + response: httpx.Response | None = None + max_retries = input_options.get_max_retries(self.max_retries) + + retries_taken = 0 + for retries_taken in range(max_retries + 1): + options = model_copy(input_options) + options = await self._prepare_options(options) + + remaining_retries = max_retries - retries_taken + request = self._build_request(options, retries_taken=retries_taken) + await self._prepare_request(request) + + kwargs: HttpxSendArgs = {} + custom_auth = self._custom_auth(options.security) + if custom_auth is not None: + kwargs["auth"] = custom_auth + + if options.follow_redirects is not None: + kwargs["follow_redirects"] = options.follow_redirects + + log.debug("Sending HTTP Request: %s %s", request.method, request.url) + + response = None + try: + response = await self._client.send( + request, + stream=stream or self._should_stream_response_body(request=request), + **kwargs, + ) + except httpx.TimeoutException as err: + log.debug("Encountered httpx.TimeoutException", exc_info=True) + + if remaining_retries > 0: + await self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=None, + ) + continue + + log.debug("Raising timeout error") + raise APITimeoutError(request=request) from err + except Exception as err: + log.debug("Encountered Exception", exc_info=True) + + if remaining_retries > 0: + await self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=None, + ) + continue + + log.debug("Raising connection error") + raise APIConnectionError(request=request) from err + + log.debug( + 'HTTP Response: %s %s "%i %s" %s', + request.method, + request.url, + response.status_code, + response.reason_phrase, + response.headers, + ) + + try: + response.raise_for_status() + except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code + log.debug("Encountered httpx.HTTPStatusError", exc_info=True) + + if remaining_retries > 0 and self._should_retry(err.response): + await err.response.aclose() + await self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=response, + ) + continue + + # If the response is streamed then we need to explicitly read the response + # to completion before attempting to access the response text. + if not err.response.is_closed: + await err.response.aread() + + log.debug("Re-raising status error") + raise self._make_status_error_from_response(err.response) from None + + break + + assert response is not None, "could not resolve response (should never happen)" + return await self._process_response( + cast_to=cast_to, + options=options, + response=response, + stream=stream, + stream_cls=stream_cls, + retries_taken=retries_taken, + ) + + async def _sleep_for_retry( + self, *, retries_taken: int, max_retries: int, options: FinalRequestOptions, response: httpx.Response | None + ) -> None: + remaining_retries = max_retries - retries_taken + if remaining_retries == 1: + log.debug("1 retry left") + else: + log.debug("%i retries left", remaining_retries) + + timeout = self._calculate_retry_timeout(remaining_retries, options, response.headers if response else None) + log.info("Retrying request to %s in %f seconds", options.url, timeout) + + await anyio.sleep(timeout) + + async def _process_response( + self, + *, + cast_to: Type[ResponseT], + options: FinalRequestOptions, + response: httpx.Response, + stream: bool, + stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None, + retries_taken: int = 0, + ) -> ResponseT: + origin = get_origin(cast_to) or cast_to + + if ( + inspect.isclass(origin) + and issubclass(origin, BaseAPIResponse) + # we only want to actually return the custom BaseAPIResponse class if we're + # returning the raw response, or if we're not streaming SSE, as if we're streaming + # SSE then `cast_to` doesn't actively reflect the type we need to parse into + and (not stream or bool(response.request.headers.get(RAW_RESPONSE_HEADER))) + ): + if not issubclass(origin, AsyncAPIResponse): + raise TypeError(f"API Response types must subclass {AsyncAPIResponse}; Received {origin}") + + response_cls = cast("type[BaseAPIResponse[Any]]", cast_to) + return cast( + "ResponseT", + response_cls( + raw=response, + client=self, + cast_to=extract_response_type(response_cls), + stream=stream, + stream_cls=stream_cls, + options=options, + retries_taken=retries_taken, + ), + ) + + if cast_to == httpx.Response: + return cast(ResponseT, response) + + api_response = AsyncAPIResponse( + raw=response, + client=self, + cast_to=cast("type[ResponseT]", cast_to), # pyright: ignore[reportUnnecessaryCast] + stream=stream, + stream_cls=stream_cls, + options=options, + retries_taken=retries_taken, + ) + if bool(response.request.headers.get(RAW_RESPONSE_HEADER)): + return cast(ResponseT, api_response) + + return await api_response.parse() + + def _request_api_list( + self, + model: Type[_T], + page: Type[AsyncPageT], + options: FinalRequestOptions, + cursor_config: Optional[CursorPageConfig] = None, + ) -> AsyncPaginator[_T, AsyncPageT]: + return AsyncPaginator(client=self, options=options, page_cls=page, model=model, cursor_config=cursor_config) + + @overload + async def get( + self, + path: str, + *, + cast_to: Type[ResponseT], + options: RequestOptions = {}, + stream: Literal[False] = False, + ) -> ResponseT: ... + + @overload + async def get( + self, + path: str, + *, + cast_to: Type[ResponseT], + options: RequestOptions = {}, + stream: Literal[True], + stream_cls: type[_AsyncStreamT], + ) -> _AsyncStreamT: ... + + @overload + async def get( + self, + path: str, + *, + cast_to: Type[ResponseT], + options: RequestOptions = {}, + stream: bool, + stream_cls: type[_AsyncStreamT] | None = None, + ) -> ResponseT | _AsyncStreamT: ... + + async def get( + self, + path: str, + *, + cast_to: Type[ResponseT], + options: RequestOptions = {}, + stream: bool = False, + stream_cls: type[_AsyncStreamT] | None = None, + ) -> ResponseT | _AsyncStreamT: + opts = FinalRequestOptions.construct(method="get", url=path, **options) + return await self.request(cast_to, opts, stream=stream, stream_cls=stream_cls) + + @overload + async def post( + self, + path: str, + *, + cast_to: Type[ResponseT], + body: Body | None = None, + content: AsyncBinaryTypes | None = None, + files: RequestFiles | None = None, + options: RequestOptions = {}, + stream: Literal[False] = False, + ) -> ResponseT: ... + + @overload + async def post( + self, + path: str, + *, + cast_to: Type[ResponseT], + body: Body | None = None, + content: AsyncBinaryTypes | None = None, + files: RequestFiles | None = None, + options: RequestOptions = {}, + stream: Literal[True], + stream_cls: type[_AsyncStreamT], + ) -> _AsyncStreamT: ... + + @overload + async def post( + self, + path: str, + *, + cast_to: Type[ResponseT], + body: Body | None = None, + content: AsyncBinaryTypes | None = None, + files: RequestFiles | None = None, + options: RequestOptions = {}, + stream: bool, + stream_cls: type[_AsyncStreamT] | None = None, + ) -> ResponseT | _AsyncStreamT: ... + + async def post( + self, + path: str, + *, + cast_to: Type[ResponseT], + body: Body | None = None, + content: AsyncBinaryTypes | None = None, + files: RequestFiles | None = None, + options: RequestOptions = {}, + stream: bool = False, + stream_cls: type[_AsyncStreamT] | None = None, + ) -> ResponseT | _AsyncStreamT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) + opts = FinalRequestOptions.construct( + method="post", url=path, json_data=body, content=content, files=await async_to_httpx_files(files), **options + ) + return await self.request(cast_to, opts, stream=stream, stream_cls=stream_cls) + + async def patch( + self, + path: str, + *, + cast_to: Type[ResponseT], + body: Body | None = None, + content: AsyncBinaryTypes | None = None, + files: RequestFiles | None = None, + options: RequestOptions = {}, + ) -> ResponseT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) + opts = FinalRequestOptions.construct( + method="patch", + url=path, + json_data=body, + content=content, + files=await async_to_httpx_files(files), + **options, + ) + return await self.request(cast_to, opts) + + async def put( + self, + path: str, + *, + cast_to: Type[ResponseT], + body: Body | None = None, + content: AsyncBinaryTypes | None = None, + files: RequestFiles | None = None, + options: RequestOptions = {}, + ) -> ResponseT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) + opts = FinalRequestOptions.construct( + method="put", url=path, json_data=body, content=content, files=await async_to_httpx_files(files), **options + ) + return await self.request(cast_to, opts) + + async def delete( + self, + path: str, + *, + cast_to: Type[ResponseT], + body: Body | None = None, + content: AsyncBinaryTypes | None = None, + options: RequestOptions = {}, + ) -> ResponseT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) + opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, content=content, **options) + return await self.request(cast_to, opts) + + def get_api_list( + self, + path: str, + *, + model: Type[_T], + page: Type[AsyncPageT], + body: Body | None = None, + files: RequestFiles | None = None, + options: RequestOptions = {}, + method: str = "get", + cursor_config: Optional[CursorPageConfig] = None, + ) -> AsyncPaginator[_T, AsyncPageT]: + opts = FinalRequestOptions.construct( + method=method, url=path, json_data=body, files=to_httpx_files(files), **options + ) + return self._request_api_list(model, page, opts, cursor_config=cursor_config) + + +def make_request_options( + *, + query: Query | None = None, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + idempotency_key: str | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + post_parser: PostParser | NotGiven = not_given, + security: SecurityOptions | None = None, +) -> RequestOptions: + """Create a dict of type RequestOptions without keys of NotGiven values.""" + options: RequestOptions = {} + if extra_headers is not None: + options["headers"] = extra_headers + + if extra_body is not None: + options["extra_json"] = cast(AnyMapping, extra_body) + + if query is not None: + options["params"] = query + + if extra_query is not None: + options["params"] = {**options.get("params", {}), **extra_query} + + if not isinstance(timeout, NotGiven): + options["timeout"] = timeout + + if idempotency_key is not None: + options["idempotency_key"] = idempotency_key + + if is_given(post_parser): + # internal + options["post_parser"] = post_parser # type: ignore + + if security is not None: + options["security"] = security + + return options + + +class ForceMultipartDict(Dict[str, None]): + def __bool__(self) -> bool: + return True + + +class OtherPlatform: + def __init__(self, name: str) -> None: + self.name = name + + @override + def __str__(self) -> str: + return f"Other:{self.name}" + + +Platform = Union[ + OtherPlatform, + Literal[ + "MacOS", + "Linux", + "Windows", + "FreeBSD", + "OpenBSD", + "iOS", + "Android", + "Unknown", + ], +] + + +def get_platform() -> Platform: + try: + system = platform.system().lower() + platform_name = platform.platform().lower() + except Exception: + return "Unknown" + + if "iphone" in platform_name or "ipad" in platform_name: + # Tested using Python3IDE on an iPhone 11 and Pythonista on an iPad 7 + # system is Darwin and platform_name is a string like: + # - Darwin-21.6.0-iPhone12,1-64bit + # - Darwin-21.6.0-iPad7,11-64bit + return "iOS" + + if system == "darwin": + return "MacOS" + + if system == "windows": + return "Windows" + + if "android" in platform_name: + # Tested using Pydroid 3 + # system is Linux and platform_name is a string like 'Linux-5.10.81-android12-9-00001-geba40aecb3b7-ab8534902-aarch64-with-libc' + return "Android" + + if system == "linux": + # https://distro.readthedocs.io/en/latest/#distro.id + distro_id = distro.id() + if distro_id == "freebsd": + return "FreeBSD" + + if distro_id == "openbsd": + return "OpenBSD" + + return "Linux" + + if platform_name: + return OtherPlatform(platform_name) + + return "Unknown" + + +@lru_cache(maxsize=None) +def platform_headers(version: str, *, platform: Platform | None) -> Dict[str, str]: + return { + "X-Scalar-Lang": "python", + "X-Scalar-Package-Version": version, + "X-Scalar-OS": str(platform or get_platform()), + "X-Scalar-Arch": str(get_architecture()), + "X-Scalar-Runtime": get_python_runtime(), + "X-Scalar-Runtime-Version": get_python_version(), + } + + +class OtherArch: + def __init__(self, name: str) -> None: + self.name = name + + @override + def __str__(self) -> str: + return f"other:{self.name}" + + +Arch = Union[OtherArch, Literal["x32", "x64", "arm", "arm64", "unknown"]] + + +def get_python_runtime() -> str: + try: + return platform.python_implementation() + except Exception: + return "unknown" + + +def get_python_version() -> str: + try: + return platform.python_version() + except Exception: + return "unknown" + + +def get_architecture() -> Arch: + try: + machine = platform.machine().lower() + except Exception: + return "unknown" + + if machine in ("arm64", "aarch64"): + return "arm64" + + # TODO: untested + if machine == "arm": + return "arm" + + if machine == "x86_64": + return "x64" + + # TODO: untested + if sys.maxsize <= 2**32: + return "x32" + + if machine: + return OtherArch(machine) + + return "unknown" + + +def _merge_mappings( + obj1: Mapping[_T_co, Union[_T, Omit]], + obj2: Mapping[_T_co, Union[_T, Omit]], +) -> Dict[_T_co, _T]: + """Merge two mappings of the same type, removing any values that are instances of `Omit`. + + In cases with duplicate keys the second mapping takes precedence. + """ + merged = {**obj1, **obj2} + return {key: value for key, value in merged.items() if not isinstance(value, Omit)} diff --git a/src/gumlet/_client.py b/src/gumlet/_client.py new file mode 100644 index 0000000..0a3a7b2 --- /dev/null +++ b/src/gumlet/_client.py @@ -0,0 +1,1256 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +import os +import threading +from typing import TYPE_CHECKING, Any, Mapping +from typing_extensions import Self, override + +import httpx + +from . import _exceptions +from ._qs import Querystring +from ._types import ( + Omit, + Headers, + Timeout, + NotGiven, + Transport, + ProxiesTypes, + RequestOptions, + not_given, +) +from ._utils import is_given, is_mapping_t, get_async_library +from ._compat import cached_property +from ._exceptions import APIStatusError, GumletError +from ._base_client import ( + DEFAULT_MAX_RETRIES, + SyncAPIClient, + AsyncAPIClient, +) +from ._streaming import Stream as Stream, AsyncStream as AsyncStream +from ._version import __version__ + +if TYPE_CHECKING: + from .resources import ( + video_assets, + subtitle_upload, + audio_upload, + video_usage_analytics, + multipart_upload, + video_profiles, + video_playlists, + webhooks, + image_sources, + image_usage_analytics, + live_stream_assets, + recycle_bin, + video_workspaces, + folders, + channel_viewers, + video_analytics, + organization_data, + user_data, + audit_logs, + billing, + live_stream_workspaces, + ) + from .resources.video_assets import VideoAssetsResource, AsyncVideoAssetsResource + from .resources.subtitle_upload import SubtitleUploadResource, AsyncSubtitleUploadResource + from .resources.audio_upload import AudioUploadResource, AsyncAudioUploadResource + from .resources.video_usage_analytics import VideoUsageAnalyticsResource, AsyncVideoUsageAnalyticsResource + from .resources.multipart_upload import MultipartUploadResource, AsyncMultipartUploadResource + from .resources.video_profiles import VideoProfilesResource, AsyncVideoProfilesResource + from .resources.video_playlists import VideoPlaylistsResource, AsyncVideoPlaylistsResource + from .resources.webhooks import WebhooksResource, AsyncWebhooksResource + from .resources.image_sources import ImageSourcesResource, AsyncImageSourcesResource + from .resources.image_usage_analytics import ImageUsageAnalyticsResource, AsyncImageUsageAnalyticsResource + from .resources.live_stream_assets import LiveStreamAssetsResource, AsyncLiveStreamAssetsResource + from .resources.recycle_bin import RecycleBinResource, AsyncRecycleBinResource + from .resources.video_workspaces import VideoWorkspacesResource, AsyncVideoWorkspacesResource + from .resources.folders import FoldersResource, AsyncFoldersResource + from .resources.channel_viewers import ChannelViewersResource, AsyncChannelViewersResource + from .resources.video_analytics import VideoAnalyticsResource, AsyncVideoAnalyticsResource + from .resources.organization_data import OrganizationDataResource, AsyncOrganizationDataResource + from .resources.user_data import UserDataResource, AsyncUserDataResource + from .resources.audit_logs import AuditLogsResource, AsyncAuditLogsResource + from .resources.billing import BillingResource, AsyncBillingResource + from .resources.live_stream_workspaces import LiveStreamWorkspacesResource, AsyncLiveStreamWorkspacesResource + +# Serializes lazy resource imports so concurrent cold access from multiple +# threads cannot deadlock on CPython import locks (see CPython 3.14). +_RESOURCE_IMPORT_LOCK = threading.RLock() + +__all__ = ["Gumlet", "AsyncGumlet", "Client", "AsyncClient", "Timeout", "Transport", "ProxiesTypes", "RequestOptions"] + + +class Gumlet(SyncAPIClient): + # client options + api_key: str + + def __init__( + self, + *, + api_key: str | None = None, + base_url: str | httpx.URL | None = None, + timeout: float | Timeout | None | NotGiven = not_given, + max_retries: int = DEFAULT_MAX_RETRIES, + default_headers: Mapping[str, str] | None = None, + default_query: Mapping[str, object] | None = None, + # Configure a custom httpx client. + # We provide a `DefaultHttpxClient` class that you can pass to retain the default values we use for `limits`, `timeout` & `follow_redirects`. + # See the [httpx documentation](https://www.python-httpx.org/api/#client) for more details. + http_client: httpx.Client | None = None, + # Enable or disable schema validation for data returned by the API. + # When enabled an error APIResponseValidationError is raised + # if the API responds with invalid data for the expected schema. + # + # This parameter may be removed or changed in the future. + # If you rely on this feature, please open a GitHub issue + # outlining your use-case to help us decide if it should be + # part of our public interface in the future. + _strict_response_validation: bool = False, + ) -> None: + """Construct a new synchronous Gumlet client instance. + + This automatically infers the following arguments from their corresponding environment variables if they are not provided: + - `api_key` from `API_KEY` + """ + if api_key is None: + api_key = os.environ.get("API_KEY") + if api_key is None: + raise GumletError( + "The api_key client option must be set either by passing api_key to the client or by setting the API_KEY environment variable" + ) + self.api_key = api_key + if base_url is None: + base_url = os.environ.get("GUMLET_BASE_URL") + if base_url is None: + base_url = "https://api.gumlet.com/v1" + custom_headers_env = os.environ.get("GUMLET_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, + max_retries=max_retries, + timeout=timeout, + http_client=http_client, + custom_headers=default_headers, + custom_query=default_query, + _strict_response_validation=_strict_response_validation, + ) + self._idempotency_header = None + self._default_stream_cls = Stream + + @cached_property + def video_assets(self) -> "VideoAssetsResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.video_assets import VideoAssetsResource + return VideoAssetsResource(self) + + @cached_property + def subtitle_upload(self) -> "SubtitleUploadResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.subtitle_upload import SubtitleUploadResource + return SubtitleUploadResource(self) + + @cached_property + def audio_upload(self) -> "AudioUploadResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.audio_upload import AudioUploadResource + return AudioUploadResource(self) + + @cached_property + def video_usage_analytics(self) -> "VideoUsageAnalyticsResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.video_usage_analytics import VideoUsageAnalyticsResource + return VideoUsageAnalyticsResource(self) + + @cached_property + def multipart_upload(self) -> "MultipartUploadResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.multipart_upload import MultipartUploadResource + return MultipartUploadResource(self) + + @cached_property + def video_profiles(self) -> "VideoProfilesResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.video_profiles import VideoProfilesResource + return VideoProfilesResource(self) + + @cached_property + def video_playlists(self) -> "VideoPlaylistsResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.video_playlists import VideoPlaylistsResource + return VideoPlaylistsResource(self) + + @cached_property + def webhooks(self) -> "WebhooksResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.webhooks import WebhooksResource + return WebhooksResource(self) + + @cached_property + def image_sources(self) -> "ImageSourcesResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.image_sources import ImageSourcesResource + return ImageSourcesResource(self) + + @cached_property + def image_usage_analytics(self) -> "ImageUsageAnalyticsResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.image_usage_analytics import ImageUsageAnalyticsResource + return ImageUsageAnalyticsResource(self) + + @cached_property + def live_stream_assets(self) -> "LiveStreamAssetsResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.live_stream_assets import LiveStreamAssetsResource + return LiveStreamAssetsResource(self) + + @cached_property + def recycle_bin(self) -> "RecycleBinResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.recycle_bin import RecycleBinResource + return RecycleBinResource(self) + + @cached_property + def video_workspaces(self) -> "VideoWorkspacesResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.video_workspaces import VideoWorkspacesResource + return VideoWorkspacesResource(self) + + @cached_property + def folders(self) -> "FoldersResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.folders import FoldersResource + return FoldersResource(self) + + @cached_property + def channel_viewers(self) -> "ChannelViewersResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.channel_viewers import ChannelViewersResource + return ChannelViewersResource(self) + + @cached_property + def video_analytics(self) -> "VideoAnalyticsResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.video_analytics import VideoAnalyticsResource + return VideoAnalyticsResource(self) + + @cached_property + def organization_data(self) -> "OrganizationDataResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.organization_data import OrganizationDataResource + return OrganizationDataResource(self) + + @cached_property + def user_data(self) -> "UserDataResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.user_data import UserDataResource + return UserDataResource(self) + + @cached_property + def audit_logs(self) -> "AuditLogsResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.audit_logs import AuditLogsResource + return AuditLogsResource(self) + + @cached_property + def billing(self) -> "BillingResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.billing import BillingResource + return BillingResource(self) + + @cached_property + def live_stream_workspaces(self) -> "LiveStreamWorkspacesResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.live_stream_workspaces import LiveStreamWorkspacesResource + return LiveStreamWorkspacesResource(self) + + @cached_property + def with_raw_response(self) -> GumletWithRawResponse: + return GumletWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> GumletWithStreamedResponse: + return GumletWithStreamedResponse(self) + + @property + @override + def qs(self) -> Querystring: + return Querystring(array_format="comma") + + @property + @override + def auth_headers(self) -> dict[str, str]: + return { + **self._api_key_header_auth, + } + + @override + def _auth_query(self, security: dict[str, bool]) -> dict[str, str]: + _ = security + return {} + + @override + def _auth_cookies(self, security: dict[str, bool]) -> dict[str, str]: + _ = security + return {} + + @property + def _api_key_header_auth(self) -> dict[str, str]: + value = self.api_key + if value is None: + return {} + return {"Authorization": f"Bearer {value}"} + + @property + @override + def default_headers(self) -> dict[str, str | Omit]: + return { + **super().default_headers, + "X-Scalar-Async": "false", + **self._custom_headers, + } + + @override + def _validate_headers( + self, + headers: Headers, + custom_headers: Headers, + params: Mapping[str, object], + cookies: Mapping[str, str], + ) -> None: + if headers.get("Authorization"): + return + if isinstance(custom_headers.get("Authorization"), Omit): + return + raise TypeError( + '"Could not resolve authentication method. Expected the api_key to be set. Or for the `Authorization` headers to be explicitly omitted"' + ) + + def copy( + self, + *, + api_key: str | None = None, + base_url: str | httpx.URL | None = None, + timeout: float | Timeout | None | NotGiven = not_given, + http_client: httpx.Client | None = None, + max_retries: int | NotGiven = not_given, + default_headers: Mapping[str, str] | None = None, + set_default_headers: Mapping[str, str] | None = None, + default_query: Mapping[str, object] | None = None, + set_default_query: Mapping[str, object] | None = None, + _extra_kwargs: Mapping[str, Any] = {}, + ) -> Self: + """Create a new client reusing this client's options with optional overrides.""" + if default_headers is not None and set_default_headers is not None: + raise ValueError("The `default_headers` and `set_default_headers` arguments are mutually exclusive") + if default_query is not None and set_default_query is not None: + raise ValueError("The `default_query` and `set_default_query` arguments are mutually exclusive") + headers = self._custom_headers + if default_headers is not None: + headers = {**headers, **default_headers} + elif set_default_headers is not None: + headers = set_default_headers + params = self._custom_query + if default_query is not None: + params = {**params, **default_query} + elif set_default_query is not None: + params = set_default_query + http_client = http_client or self._client + return self.__class__( + api_key=api_key or self.api_key, + base_url=base_url or self.base_url, + timeout=self.timeout if isinstance(timeout, NotGiven) else timeout, + http_client=http_client, + max_retries=max_retries if is_given(max_retries) else self.max_retries, + default_headers=headers, + default_query=params, + _strict_response_validation=self._strict_response_validation, + **_extra_kwargs, + ) + + with_options = copy + + @override + def _make_status_error(self, err_msg: str, *, body: object, response: httpx.Response) -> APIStatusError: + if response.status_code == 400: + return _exceptions.BadRequestError(err_msg, response=response, body=body) + if response.status_code == 401: + return _exceptions.AuthenticationError(err_msg, response=response, body=body) + if response.status_code == 403: + return _exceptions.PermissionDeniedError(err_msg, response=response, body=body) + if response.status_code == 404: + return _exceptions.NotFoundError(err_msg, response=response, body=body) + if response.status_code == 409: + return _exceptions.ConflictError(err_msg, response=response, body=body) + if response.status_code == 422: + return _exceptions.UnprocessableEntityError(err_msg, response=response, body=body) + if response.status_code == 429: + return _exceptions.RateLimitError(err_msg, response=response, body=body) + if response.status_code >= 500: + return _exceptions.InternalServerError(err_msg, response=response, body=body) + return APIStatusError(err_msg, response=response, body=body) + + +class AsyncGumlet(AsyncAPIClient): + # client options + api_key: str + + def __init__( + self, + *, + api_key: str | None = None, + base_url: str | httpx.URL | None = None, + timeout: float | Timeout | None | NotGiven = not_given, + max_retries: int = DEFAULT_MAX_RETRIES, + default_headers: Mapping[str, str] | None = None, + default_query: Mapping[str, object] | None = None, + # Configure a custom httpx client. + # We provide a `DefaultAsyncHttpxClient` class that you can pass to retain the default values we use for `limits`, `timeout` & `follow_redirects`. + # See the [httpx documentation](https://www.python-httpx.org/api/#asyncclient) for more details. + http_client: httpx.AsyncClient | None = None, + # Enable or disable schema validation for data returned by the API. + # When enabled an error APIResponseValidationError is raised + # if the API responds with invalid data for the expected schema. + # + # This parameter may be removed or changed in the future. + # If you rely on this feature, please open a GitHub issue + # outlining your use-case to help us decide if it should be + # part of our public interface in the future. + _strict_response_validation: bool = False, + ) -> None: + """Construct a new async AsyncGumlet client instance. + + This automatically infers the following arguments from their corresponding environment variables if they are not provided: + - `api_key` from `API_KEY` + """ + if api_key is None: + api_key = os.environ.get("API_KEY") + if api_key is None: + raise GumletError( + "The api_key client option must be set either by passing api_key to the client or by setting the API_KEY environment variable" + ) + self.api_key = api_key + if base_url is None: + base_url = os.environ.get("GUMLET_BASE_URL") + if base_url is None: + base_url = "https://api.gumlet.com/v1" + custom_headers_env = os.environ.get("GUMLET_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, + max_retries=max_retries, + timeout=timeout, + http_client=http_client, + custom_headers=default_headers, + custom_query=default_query, + _strict_response_validation=_strict_response_validation, + ) + self._idempotency_header = None + self._default_stream_cls = AsyncStream + + @cached_property + def video_assets(self) -> "AsyncVideoAssetsResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.video_assets import AsyncVideoAssetsResource + return AsyncVideoAssetsResource(self) + + @cached_property + def subtitle_upload(self) -> "AsyncSubtitleUploadResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.subtitle_upload import AsyncSubtitleUploadResource + return AsyncSubtitleUploadResource(self) + + @cached_property + def audio_upload(self) -> "AsyncAudioUploadResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.audio_upload import AsyncAudioUploadResource + return AsyncAudioUploadResource(self) + + @cached_property + def video_usage_analytics(self) -> "AsyncVideoUsageAnalyticsResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.video_usage_analytics import AsyncVideoUsageAnalyticsResource + return AsyncVideoUsageAnalyticsResource(self) + + @cached_property + def multipart_upload(self) -> "AsyncMultipartUploadResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.multipart_upload import AsyncMultipartUploadResource + return AsyncMultipartUploadResource(self) + + @cached_property + def video_profiles(self) -> "AsyncVideoProfilesResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.video_profiles import AsyncVideoProfilesResource + return AsyncVideoProfilesResource(self) + + @cached_property + def video_playlists(self) -> "AsyncVideoPlaylistsResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.video_playlists import AsyncVideoPlaylistsResource + return AsyncVideoPlaylistsResource(self) + + @cached_property + def webhooks(self) -> "AsyncWebhooksResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.webhooks import AsyncWebhooksResource + return AsyncWebhooksResource(self) + + @cached_property + def image_sources(self) -> "AsyncImageSourcesResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.image_sources import AsyncImageSourcesResource + return AsyncImageSourcesResource(self) + + @cached_property + def image_usage_analytics(self) -> "AsyncImageUsageAnalyticsResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.image_usage_analytics import AsyncImageUsageAnalyticsResource + return AsyncImageUsageAnalyticsResource(self) + + @cached_property + def live_stream_assets(self) -> "AsyncLiveStreamAssetsResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.live_stream_assets import AsyncLiveStreamAssetsResource + return AsyncLiveStreamAssetsResource(self) + + @cached_property + def recycle_bin(self) -> "AsyncRecycleBinResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.recycle_bin import AsyncRecycleBinResource + return AsyncRecycleBinResource(self) + + @cached_property + def video_workspaces(self) -> "AsyncVideoWorkspacesResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.video_workspaces import AsyncVideoWorkspacesResource + return AsyncVideoWorkspacesResource(self) + + @cached_property + def folders(self) -> "AsyncFoldersResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.folders import AsyncFoldersResource + return AsyncFoldersResource(self) + + @cached_property + def channel_viewers(self) -> "AsyncChannelViewersResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.channel_viewers import AsyncChannelViewersResource + return AsyncChannelViewersResource(self) + + @cached_property + def video_analytics(self) -> "AsyncVideoAnalyticsResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.video_analytics import AsyncVideoAnalyticsResource + return AsyncVideoAnalyticsResource(self) + + @cached_property + def organization_data(self) -> "AsyncOrganizationDataResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.organization_data import AsyncOrganizationDataResource + return AsyncOrganizationDataResource(self) + + @cached_property + def user_data(self) -> "AsyncUserDataResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.user_data import AsyncUserDataResource + return AsyncUserDataResource(self) + + @cached_property + def audit_logs(self) -> "AsyncAuditLogsResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.audit_logs import AsyncAuditLogsResource + return AsyncAuditLogsResource(self) + + @cached_property + def billing(self) -> "AsyncBillingResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.billing import AsyncBillingResource + return AsyncBillingResource(self) + + @cached_property + def live_stream_workspaces(self) -> "AsyncLiveStreamWorkspacesResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.live_stream_workspaces import AsyncLiveStreamWorkspacesResource + return AsyncLiveStreamWorkspacesResource(self) + + @cached_property + def with_raw_response(self) -> AsyncGumletWithRawResponse: + return AsyncGumletWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncGumletWithStreamedResponse: + return AsyncGumletWithStreamedResponse(self) + + @property + @override + def qs(self) -> Querystring: + return Querystring(array_format="comma") + + @property + @override + def auth_headers(self) -> dict[str, str]: + return { + **self._api_key_header_auth, + } + + @override + def _auth_query(self, security: dict[str, bool]) -> dict[str, str]: + _ = security + return {} + + @override + def _auth_cookies(self, security: dict[str, bool]) -> dict[str, str]: + _ = security + return {} + + @property + def _api_key_header_auth(self) -> dict[str, str]: + value = self.api_key + if value is None: + return {} + return {"Authorization": f"Bearer {value}"} + + @property + @override + def default_headers(self) -> dict[str, str | Omit]: + return { + **super().default_headers, + "X-Scalar-Async": f"async:{get_async_library()}", + **self._custom_headers, + } + + @override + def _validate_headers( + self, + headers: Headers, + custom_headers: Headers, + params: Mapping[str, object], + cookies: Mapping[str, str], + ) -> None: + if headers.get("Authorization"): + return + if isinstance(custom_headers.get("Authorization"), Omit): + return + raise TypeError( + '"Could not resolve authentication method. Expected the api_key to be set. Or for the `Authorization` headers to be explicitly omitted"' + ) + + def copy( + self, + *, + api_key: str | None = None, + base_url: str | httpx.URL | None = None, + timeout: float | Timeout | None | NotGiven = not_given, + http_client: httpx.AsyncClient | None = None, + max_retries: int | NotGiven = not_given, + default_headers: Mapping[str, str] | None = None, + set_default_headers: Mapping[str, str] | None = None, + default_query: Mapping[str, object] | None = None, + set_default_query: Mapping[str, object] | None = None, + _extra_kwargs: Mapping[str, Any] = {}, + ) -> Self: + """Create a new client reusing this client's options with optional overrides.""" + if default_headers is not None and set_default_headers is not None: + raise ValueError("The `default_headers` and `set_default_headers` arguments are mutually exclusive") + if default_query is not None and set_default_query is not None: + raise ValueError("The `default_query` and `set_default_query` arguments are mutually exclusive") + headers = self._custom_headers + if default_headers is not None: + headers = {**headers, **default_headers} + elif set_default_headers is not None: + headers = set_default_headers + params = self._custom_query + if default_query is not None: + params = {**params, **default_query} + elif set_default_query is not None: + params = set_default_query + http_client = http_client or self._client + return self.__class__( + api_key=api_key or self.api_key, + base_url=base_url or self.base_url, + timeout=self.timeout if isinstance(timeout, NotGiven) else timeout, + http_client=http_client, + max_retries=max_retries if is_given(max_retries) else self.max_retries, + default_headers=headers, + default_query=params, + _strict_response_validation=self._strict_response_validation, + **_extra_kwargs, + ) + + with_options = copy + + @override + def _make_status_error(self, err_msg: str, *, body: object, response: httpx.Response) -> APIStatusError: + if response.status_code == 400: + return _exceptions.BadRequestError(err_msg, response=response, body=body) + if response.status_code == 401: + return _exceptions.AuthenticationError(err_msg, response=response, body=body) + if response.status_code == 403: + return _exceptions.PermissionDeniedError(err_msg, response=response, body=body) + if response.status_code == 404: + return _exceptions.NotFoundError(err_msg, response=response, body=body) + if response.status_code == 409: + return _exceptions.ConflictError(err_msg, response=response, body=body) + if response.status_code == 422: + return _exceptions.UnprocessableEntityError(err_msg, response=response, body=body) + if response.status_code == 429: + return _exceptions.RateLimitError(err_msg, response=response, body=body) + if response.status_code >= 500: + return _exceptions.InternalServerError(err_msg, response=response, body=body) + return APIStatusError(err_msg, response=response, body=body) + + +class GumletWithRawResponse: + _client: Gumlet + + def __init__(self, client: Gumlet) -> None: + self._client = client + + @cached_property + def video_assets(self) -> video_assets.VideoAssetsResourceWithRawResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.video_assets import VideoAssetsResourceWithRawResponse + return VideoAssetsResourceWithRawResponse(self._client.video_assets) + + @cached_property + def subtitle_upload(self) -> subtitle_upload.SubtitleUploadResourceWithRawResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.subtitle_upload import SubtitleUploadResourceWithRawResponse + return SubtitleUploadResourceWithRawResponse(self._client.subtitle_upload) + + @cached_property + def audio_upload(self) -> audio_upload.AudioUploadResourceWithRawResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.audio_upload import AudioUploadResourceWithRawResponse + return AudioUploadResourceWithRawResponse(self._client.audio_upload) + + @cached_property + def video_usage_analytics(self) -> video_usage_analytics.VideoUsageAnalyticsResourceWithRawResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.video_usage_analytics import VideoUsageAnalyticsResourceWithRawResponse + return VideoUsageAnalyticsResourceWithRawResponse(self._client.video_usage_analytics) + + @cached_property + def multipart_upload(self) -> multipart_upload.MultipartUploadResourceWithRawResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.multipart_upload import MultipartUploadResourceWithRawResponse + return MultipartUploadResourceWithRawResponse(self._client.multipart_upload) + + @cached_property + def video_profiles(self) -> video_profiles.VideoProfilesResourceWithRawResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.video_profiles import VideoProfilesResourceWithRawResponse + return VideoProfilesResourceWithRawResponse(self._client.video_profiles) + + @cached_property + def video_playlists(self) -> video_playlists.VideoPlaylistsResourceWithRawResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.video_playlists import VideoPlaylistsResourceWithRawResponse + return VideoPlaylistsResourceWithRawResponse(self._client.video_playlists) + + @cached_property + def webhooks(self) -> webhooks.WebhooksResourceWithRawResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.webhooks import WebhooksResourceWithRawResponse + return WebhooksResourceWithRawResponse(self._client.webhooks) + + @cached_property + def image_sources(self) -> image_sources.ImageSourcesResourceWithRawResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.image_sources import ImageSourcesResourceWithRawResponse + return ImageSourcesResourceWithRawResponse(self._client.image_sources) + + @cached_property + def image_usage_analytics(self) -> image_usage_analytics.ImageUsageAnalyticsResourceWithRawResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.image_usage_analytics import ImageUsageAnalyticsResourceWithRawResponse + return ImageUsageAnalyticsResourceWithRawResponse(self._client.image_usage_analytics) + + @cached_property + def live_stream_assets(self) -> live_stream_assets.LiveStreamAssetsResourceWithRawResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.live_stream_assets import LiveStreamAssetsResourceWithRawResponse + return LiveStreamAssetsResourceWithRawResponse(self._client.live_stream_assets) + + @cached_property + def recycle_bin(self) -> recycle_bin.RecycleBinResourceWithRawResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.recycle_bin import RecycleBinResourceWithRawResponse + return RecycleBinResourceWithRawResponse(self._client.recycle_bin) + + @cached_property + def video_workspaces(self) -> video_workspaces.VideoWorkspacesResourceWithRawResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.video_workspaces import VideoWorkspacesResourceWithRawResponse + return VideoWorkspacesResourceWithRawResponse(self._client.video_workspaces) + + @cached_property + def folders(self) -> folders.FoldersResourceWithRawResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.folders import FoldersResourceWithRawResponse + return FoldersResourceWithRawResponse(self._client.folders) + + @cached_property + def channel_viewers(self) -> channel_viewers.ChannelViewersResourceWithRawResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.channel_viewers import ChannelViewersResourceWithRawResponse + return ChannelViewersResourceWithRawResponse(self._client.channel_viewers) + + @cached_property + def video_analytics(self) -> video_analytics.VideoAnalyticsResourceWithRawResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.video_analytics import VideoAnalyticsResourceWithRawResponse + return VideoAnalyticsResourceWithRawResponse(self._client.video_analytics) + + @cached_property + def organization_data(self) -> organization_data.OrganizationDataResourceWithRawResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.organization_data import OrganizationDataResourceWithRawResponse + return OrganizationDataResourceWithRawResponse(self._client.organization_data) + + @cached_property + def user_data(self) -> user_data.UserDataResourceWithRawResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.user_data import UserDataResourceWithRawResponse + return UserDataResourceWithRawResponse(self._client.user_data) + + @cached_property + def audit_logs(self) -> audit_logs.AuditLogsResourceWithRawResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.audit_logs import AuditLogsResourceWithRawResponse + return AuditLogsResourceWithRawResponse(self._client.audit_logs) + + @cached_property + def billing(self) -> billing.BillingResourceWithRawResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.billing import BillingResourceWithRawResponse + return BillingResourceWithRawResponse(self._client.billing) + + @cached_property + def live_stream_workspaces(self) -> live_stream_workspaces.LiveStreamWorkspacesResourceWithRawResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.live_stream_workspaces import LiveStreamWorkspacesResourceWithRawResponse + return LiveStreamWorkspacesResourceWithRawResponse(self._client.live_stream_workspaces) + + +class AsyncGumletWithRawResponse: + _client: AsyncGumlet + + def __init__(self, client: AsyncGumlet) -> None: + self._client = client + + @cached_property + def video_assets(self) -> video_assets.AsyncVideoAssetsResourceWithRawResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.video_assets import AsyncVideoAssetsResourceWithRawResponse + return AsyncVideoAssetsResourceWithRawResponse(self._client.video_assets) + + @cached_property + def subtitle_upload(self) -> subtitle_upload.AsyncSubtitleUploadResourceWithRawResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.subtitle_upload import AsyncSubtitleUploadResourceWithRawResponse + return AsyncSubtitleUploadResourceWithRawResponse(self._client.subtitle_upload) + + @cached_property + def audio_upload(self) -> audio_upload.AsyncAudioUploadResourceWithRawResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.audio_upload import AsyncAudioUploadResourceWithRawResponse + return AsyncAudioUploadResourceWithRawResponse(self._client.audio_upload) + + @cached_property + def video_usage_analytics(self) -> video_usage_analytics.AsyncVideoUsageAnalyticsResourceWithRawResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.video_usage_analytics import AsyncVideoUsageAnalyticsResourceWithRawResponse + return AsyncVideoUsageAnalyticsResourceWithRawResponse(self._client.video_usage_analytics) + + @cached_property + def multipart_upload(self) -> multipart_upload.AsyncMultipartUploadResourceWithRawResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.multipart_upload import AsyncMultipartUploadResourceWithRawResponse + return AsyncMultipartUploadResourceWithRawResponse(self._client.multipart_upload) + + @cached_property + def video_profiles(self) -> video_profiles.AsyncVideoProfilesResourceWithRawResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.video_profiles import AsyncVideoProfilesResourceWithRawResponse + return AsyncVideoProfilesResourceWithRawResponse(self._client.video_profiles) + + @cached_property + def video_playlists(self) -> video_playlists.AsyncVideoPlaylistsResourceWithRawResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.video_playlists import AsyncVideoPlaylistsResourceWithRawResponse + return AsyncVideoPlaylistsResourceWithRawResponse(self._client.video_playlists) + + @cached_property + def webhooks(self) -> webhooks.AsyncWebhooksResourceWithRawResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.webhooks import AsyncWebhooksResourceWithRawResponse + return AsyncWebhooksResourceWithRawResponse(self._client.webhooks) + + @cached_property + def image_sources(self) -> image_sources.AsyncImageSourcesResourceWithRawResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.image_sources import AsyncImageSourcesResourceWithRawResponse + return AsyncImageSourcesResourceWithRawResponse(self._client.image_sources) + + @cached_property + def image_usage_analytics(self) -> image_usage_analytics.AsyncImageUsageAnalyticsResourceWithRawResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.image_usage_analytics import AsyncImageUsageAnalyticsResourceWithRawResponse + return AsyncImageUsageAnalyticsResourceWithRawResponse(self._client.image_usage_analytics) + + @cached_property + def live_stream_assets(self) -> live_stream_assets.AsyncLiveStreamAssetsResourceWithRawResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.live_stream_assets import AsyncLiveStreamAssetsResourceWithRawResponse + return AsyncLiveStreamAssetsResourceWithRawResponse(self._client.live_stream_assets) + + @cached_property + def recycle_bin(self) -> recycle_bin.AsyncRecycleBinResourceWithRawResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.recycle_bin import AsyncRecycleBinResourceWithRawResponse + return AsyncRecycleBinResourceWithRawResponse(self._client.recycle_bin) + + @cached_property + def video_workspaces(self) -> video_workspaces.AsyncVideoWorkspacesResourceWithRawResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.video_workspaces import AsyncVideoWorkspacesResourceWithRawResponse + return AsyncVideoWorkspacesResourceWithRawResponse(self._client.video_workspaces) + + @cached_property + def folders(self) -> folders.AsyncFoldersResourceWithRawResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.folders import AsyncFoldersResourceWithRawResponse + return AsyncFoldersResourceWithRawResponse(self._client.folders) + + @cached_property + def channel_viewers(self) -> channel_viewers.AsyncChannelViewersResourceWithRawResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.channel_viewers import AsyncChannelViewersResourceWithRawResponse + return AsyncChannelViewersResourceWithRawResponse(self._client.channel_viewers) + + @cached_property + def video_analytics(self) -> video_analytics.AsyncVideoAnalyticsResourceWithRawResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.video_analytics import AsyncVideoAnalyticsResourceWithRawResponse + return AsyncVideoAnalyticsResourceWithRawResponse(self._client.video_analytics) + + @cached_property + def organization_data(self) -> organization_data.AsyncOrganizationDataResourceWithRawResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.organization_data import AsyncOrganizationDataResourceWithRawResponse + return AsyncOrganizationDataResourceWithRawResponse(self._client.organization_data) + + @cached_property + def user_data(self) -> user_data.AsyncUserDataResourceWithRawResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.user_data import AsyncUserDataResourceWithRawResponse + return AsyncUserDataResourceWithRawResponse(self._client.user_data) + + @cached_property + def audit_logs(self) -> audit_logs.AsyncAuditLogsResourceWithRawResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.audit_logs import AsyncAuditLogsResourceWithRawResponse + return AsyncAuditLogsResourceWithRawResponse(self._client.audit_logs) + + @cached_property + def billing(self) -> billing.AsyncBillingResourceWithRawResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.billing import AsyncBillingResourceWithRawResponse + return AsyncBillingResourceWithRawResponse(self._client.billing) + + @cached_property + def live_stream_workspaces(self) -> live_stream_workspaces.AsyncLiveStreamWorkspacesResourceWithRawResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.live_stream_workspaces import AsyncLiveStreamWorkspacesResourceWithRawResponse + return AsyncLiveStreamWorkspacesResourceWithRawResponse(self._client.live_stream_workspaces) + + +class GumletWithStreamedResponse: + _client: Gumlet + + def __init__(self, client: Gumlet) -> None: + self._client = client + + @cached_property + def video_assets(self) -> video_assets.VideoAssetsResourceWithStreamingResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.video_assets import VideoAssetsResourceWithStreamingResponse + return VideoAssetsResourceWithStreamingResponse(self._client.video_assets) + + @cached_property + def subtitle_upload(self) -> subtitle_upload.SubtitleUploadResourceWithStreamingResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.subtitle_upload import SubtitleUploadResourceWithStreamingResponse + return SubtitleUploadResourceWithStreamingResponse(self._client.subtitle_upload) + + @cached_property + def audio_upload(self) -> audio_upload.AudioUploadResourceWithStreamingResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.audio_upload import AudioUploadResourceWithStreamingResponse + return AudioUploadResourceWithStreamingResponse(self._client.audio_upload) + + @cached_property + def video_usage_analytics(self) -> video_usage_analytics.VideoUsageAnalyticsResourceWithStreamingResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.video_usage_analytics import VideoUsageAnalyticsResourceWithStreamingResponse + return VideoUsageAnalyticsResourceWithStreamingResponse(self._client.video_usage_analytics) + + @cached_property + def multipart_upload(self) -> multipart_upload.MultipartUploadResourceWithStreamingResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.multipart_upload import MultipartUploadResourceWithStreamingResponse + return MultipartUploadResourceWithStreamingResponse(self._client.multipart_upload) + + @cached_property + def video_profiles(self) -> video_profiles.VideoProfilesResourceWithStreamingResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.video_profiles import VideoProfilesResourceWithStreamingResponse + return VideoProfilesResourceWithStreamingResponse(self._client.video_profiles) + + @cached_property + def video_playlists(self) -> video_playlists.VideoPlaylistsResourceWithStreamingResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.video_playlists import VideoPlaylistsResourceWithStreamingResponse + return VideoPlaylistsResourceWithStreamingResponse(self._client.video_playlists) + + @cached_property + def webhooks(self) -> webhooks.WebhooksResourceWithStreamingResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.webhooks import WebhooksResourceWithStreamingResponse + return WebhooksResourceWithStreamingResponse(self._client.webhooks) + + @cached_property + def image_sources(self) -> image_sources.ImageSourcesResourceWithStreamingResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.image_sources import ImageSourcesResourceWithStreamingResponse + return ImageSourcesResourceWithStreamingResponse(self._client.image_sources) + + @cached_property + def image_usage_analytics(self) -> image_usage_analytics.ImageUsageAnalyticsResourceWithStreamingResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.image_usage_analytics import ImageUsageAnalyticsResourceWithStreamingResponse + return ImageUsageAnalyticsResourceWithStreamingResponse(self._client.image_usage_analytics) + + @cached_property + def live_stream_assets(self) -> live_stream_assets.LiveStreamAssetsResourceWithStreamingResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.live_stream_assets import LiveStreamAssetsResourceWithStreamingResponse + return LiveStreamAssetsResourceWithStreamingResponse(self._client.live_stream_assets) + + @cached_property + def recycle_bin(self) -> recycle_bin.RecycleBinResourceWithStreamingResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.recycle_bin import RecycleBinResourceWithStreamingResponse + return RecycleBinResourceWithStreamingResponse(self._client.recycle_bin) + + @cached_property + def video_workspaces(self) -> video_workspaces.VideoWorkspacesResourceWithStreamingResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.video_workspaces import VideoWorkspacesResourceWithStreamingResponse + return VideoWorkspacesResourceWithStreamingResponse(self._client.video_workspaces) + + @cached_property + def folders(self) -> folders.FoldersResourceWithStreamingResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.folders import FoldersResourceWithStreamingResponse + return FoldersResourceWithStreamingResponse(self._client.folders) + + @cached_property + def channel_viewers(self) -> channel_viewers.ChannelViewersResourceWithStreamingResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.channel_viewers import ChannelViewersResourceWithStreamingResponse + return ChannelViewersResourceWithStreamingResponse(self._client.channel_viewers) + + @cached_property + def video_analytics(self) -> video_analytics.VideoAnalyticsResourceWithStreamingResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.video_analytics import VideoAnalyticsResourceWithStreamingResponse + return VideoAnalyticsResourceWithStreamingResponse(self._client.video_analytics) + + @cached_property + def organization_data(self) -> organization_data.OrganizationDataResourceWithStreamingResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.organization_data import OrganizationDataResourceWithStreamingResponse + return OrganizationDataResourceWithStreamingResponse(self._client.organization_data) + + @cached_property + def user_data(self) -> user_data.UserDataResourceWithStreamingResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.user_data import UserDataResourceWithStreamingResponse + return UserDataResourceWithStreamingResponse(self._client.user_data) + + @cached_property + def audit_logs(self) -> audit_logs.AuditLogsResourceWithStreamingResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.audit_logs import AuditLogsResourceWithStreamingResponse + return AuditLogsResourceWithStreamingResponse(self._client.audit_logs) + + @cached_property + def billing(self) -> billing.BillingResourceWithStreamingResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.billing import BillingResourceWithStreamingResponse + return BillingResourceWithStreamingResponse(self._client.billing) + + @cached_property + def live_stream_workspaces(self) -> live_stream_workspaces.LiveStreamWorkspacesResourceWithStreamingResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.live_stream_workspaces import LiveStreamWorkspacesResourceWithStreamingResponse + return LiveStreamWorkspacesResourceWithStreamingResponse(self._client.live_stream_workspaces) + + +class AsyncGumletWithStreamedResponse: + _client: AsyncGumlet + + def __init__(self, client: AsyncGumlet) -> None: + self._client = client + + @cached_property + def video_assets(self) -> video_assets.AsyncVideoAssetsResourceWithStreamingResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.video_assets import AsyncVideoAssetsResourceWithStreamingResponse + return AsyncVideoAssetsResourceWithStreamingResponse(self._client.video_assets) + + @cached_property + def subtitle_upload(self) -> subtitle_upload.AsyncSubtitleUploadResourceWithStreamingResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.subtitle_upload import AsyncSubtitleUploadResourceWithStreamingResponse + return AsyncSubtitleUploadResourceWithStreamingResponse(self._client.subtitle_upload) + + @cached_property + def audio_upload(self) -> audio_upload.AsyncAudioUploadResourceWithStreamingResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.audio_upload import AsyncAudioUploadResourceWithStreamingResponse + return AsyncAudioUploadResourceWithStreamingResponse(self._client.audio_upload) + + @cached_property + def video_usage_analytics(self) -> video_usage_analytics.AsyncVideoUsageAnalyticsResourceWithStreamingResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.video_usage_analytics import AsyncVideoUsageAnalyticsResourceWithStreamingResponse + return AsyncVideoUsageAnalyticsResourceWithStreamingResponse(self._client.video_usage_analytics) + + @cached_property + def multipart_upload(self) -> multipart_upload.AsyncMultipartUploadResourceWithStreamingResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.multipart_upload import AsyncMultipartUploadResourceWithStreamingResponse + return AsyncMultipartUploadResourceWithStreamingResponse(self._client.multipart_upload) + + @cached_property + def video_profiles(self) -> video_profiles.AsyncVideoProfilesResourceWithStreamingResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.video_profiles import AsyncVideoProfilesResourceWithStreamingResponse + return AsyncVideoProfilesResourceWithStreamingResponse(self._client.video_profiles) + + @cached_property + def video_playlists(self) -> video_playlists.AsyncVideoPlaylistsResourceWithStreamingResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.video_playlists import AsyncVideoPlaylistsResourceWithStreamingResponse + return AsyncVideoPlaylistsResourceWithStreamingResponse(self._client.video_playlists) + + @cached_property + def webhooks(self) -> webhooks.AsyncWebhooksResourceWithStreamingResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.webhooks import AsyncWebhooksResourceWithStreamingResponse + return AsyncWebhooksResourceWithStreamingResponse(self._client.webhooks) + + @cached_property + def image_sources(self) -> image_sources.AsyncImageSourcesResourceWithStreamingResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.image_sources import AsyncImageSourcesResourceWithStreamingResponse + return AsyncImageSourcesResourceWithStreamingResponse(self._client.image_sources) + + @cached_property + def image_usage_analytics(self) -> image_usage_analytics.AsyncImageUsageAnalyticsResourceWithStreamingResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.image_usage_analytics import AsyncImageUsageAnalyticsResourceWithStreamingResponse + return AsyncImageUsageAnalyticsResourceWithStreamingResponse(self._client.image_usage_analytics) + + @cached_property + def live_stream_assets(self) -> live_stream_assets.AsyncLiveStreamAssetsResourceWithStreamingResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.live_stream_assets import AsyncLiveStreamAssetsResourceWithStreamingResponse + return AsyncLiveStreamAssetsResourceWithStreamingResponse(self._client.live_stream_assets) + + @cached_property + def recycle_bin(self) -> recycle_bin.AsyncRecycleBinResourceWithStreamingResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.recycle_bin import AsyncRecycleBinResourceWithStreamingResponse + return AsyncRecycleBinResourceWithStreamingResponse(self._client.recycle_bin) + + @cached_property + def video_workspaces(self) -> video_workspaces.AsyncVideoWorkspacesResourceWithStreamingResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.video_workspaces import AsyncVideoWorkspacesResourceWithStreamingResponse + return AsyncVideoWorkspacesResourceWithStreamingResponse(self._client.video_workspaces) + + @cached_property + def folders(self) -> folders.AsyncFoldersResourceWithStreamingResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.folders import AsyncFoldersResourceWithStreamingResponse + return AsyncFoldersResourceWithStreamingResponse(self._client.folders) + + @cached_property + def channel_viewers(self) -> channel_viewers.AsyncChannelViewersResourceWithStreamingResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.channel_viewers import AsyncChannelViewersResourceWithStreamingResponse + return AsyncChannelViewersResourceWithStreamingResponse(self._client.channel_viewers) + + @cached_property + def video_analytics(self) -> video_analytics.AsyncVideoAnalyticsResourceWithStreamingResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.video_analytics import AsyncVideoAnalyticsResourceWithStreamingResponse + return AsyncVideoAnalyticsResourceWithStreamingResponse(self._client.video_analytics) + + @cached_property + def organization_data(self) -> organization_data.AsyncOrganizationDataResourceWithStreamingResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.organization_data import AsyncOrganizationDataResourceWithStreamingResponse + return AsyncOrganizationDataResourceWithStreamingResponse(self._client.organization_data) + + @cached_property + def user_data(self) -> user_data.AsyncUserDataResourceWithStreamingResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.user_data import AsyncUserDataResourceWithStreamingResponse + return AsyncUserDataResourceWithStreamingResponse(self._client.user_data) + + @cached_property + def audit_logs(self) -> audit_logs.AsyncAuditLogsResourceWithStreamingResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.audit_logs import AsyncAuditLogsResourceWithStreamingResponse + return AsyncAuditLogsResourceWithStreamingResponse(self._client.audit_logs) + + @cached_property + def billing(self) -> billing.AsyncBillingResourceWithStreamingResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.billing import AsyncBillingResourceWithStreamingResponse + return AsyncBillingResourceWithStreamingResponse(self._client.billing) + + @cached_property + def live_stream_workspaces(self) -> live_stream_workspaces.AsyncLiveStreamWorkspacesResourceWithStreamingResponse: + with _RESOURCE_IMPORT_LOCK: + from .resources.live_stream_workspaces import AsyncLiveStreamWorkspacesResourceWithStreamingResponse + return AsyncLiveStreamWorkspacesResourceWithStreamingResponse(self._client.live_stream_workspaces) + + +# Alias names for the documented `Client` / `AsyncClient` symbols. +Client = Gumlet +AsyncClient = AsyncGumlet diff --git a/src/gumlet/_compat.py b/src/gumlet/_compat.py new file mode 100644 index 0000000..e6690a4 --- /dev/null +++ b/src/gumlet/_compat.py @@ -0,0 +1,226 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Union, Generic, TypeVar, Callable, cast, overload +from datetime import date, datetime +from typing_extensions import Self, Literal, TypedDict + +import pydantic +from pydantic.fields import FieldInfo + +from ._types import IncEx, StrBytesIntFloat + +_T = TypeVar("_T") +_ModelT = TypeVar("_ModelT", bound=pydantic.BaseModel) + +# --------------- Pydantic v2, v3 compatibility --------------- + +# Pyright incorrectly reports some of our functions as overriding a method when they don't +# pyright: reportIncompatibleMethodOverride=false + +PYDANTIC_V1 = pydantic.VERSION.startswith("1.") + +if TYPE_CHECKING: + + def parse_date(value: date | StrBytesIntFloat) -> date: # noqa: ARG001 + ... + + def parse_datetime(value: Union[datetime, StrBytesIntFloat]) -> datetime: # noqa: ARG001 + ... + + def get_args(t: type[Any]) -> tuple[Any, ...]: # noqa: ARG001 + ... + + def is_union(tp: type[Any] | None) -> bool: # noqa: ARG001 + ... + + def get_origin(t: type[Any]) -> type[Any] | None: # noqa: ARG001 + ... + + def is_literal_type(type_: type[Any]) -> bool: # noqa: ARG001 + ... + + def is_typeddict(type_: type[Any]) -> bool: # noqa: ARG001 + ... + +else: + # v1 re-exports + if PYDANTIC_V1: + from pydantic.typing import ( + get_args as get_args, + is_union as is_union, + get_origin as get_origin, + is_typeddict as is_typeddict, + is_literal_type as is_literal_type, + ) + from pydantic.datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime + else: + from ._utils import ( + get_args as get_args, + is_union as is_union, + get_origin as get_origin, + parse_date as parse_date, + is_typeddict as is_typeddict, + parse_datetime as parse_datetime, + is_literal_type as is_literal_type, + ) + + +# refactored config +if TYPE_CHECKING: + from pydantic import ConfigDict as ConfigDict +else: + if PYDANTIC_V1: + # TODO: provide an error message here? + ConfigDict = None + else: + from pydantic import ConfigDict as ConfigDict + + +# renamed methods / properties +def parse_obj(model: type[_ModelT], value: object) -> _ModelT: + if PYDANTIC_V1: + return cast(_ModelT, model.parse_obj(value)) # pyright: ignore[reportDeprecated, reportUnnecessaryCast] + else: + return model.model_validate(value) + + +def field_is_required(field: FieldInfo) -> bool: + if PYDANTIC_V1: + return field.required # type: ignore + return field.is_required() + + +def field_get_default(field: FieldInfo) -> Any: + value = field.get_default() + if PYDANTIC_V1: + return value + from pydantic_core import PydanticUndefined + + if value == PydanticUndefined: + return None + return value + + +def field_outer_type(field: FieldInfo) -> Any: + if PYDANTIC_V1: + return field.outer_type_ # type: ignore + return field.annotation + + +def get_model_config(model: type[pydantic.BaseModel]) -> Any: + if PYDANTIC_V1: + return model.__config__ # type: ignore + return model.model_config + + +def get_model_fields(model: type[pydantic.BaseModel]) -> dict[str, FieldInfo]: + if PYDANTIC_V1: + return model.__fields__ # type: ignore + return model.model_fields + + +def model_copy(model: _ModelT, *, deep: bool = False) -> _ModelT: + if PYDANTIC_V1: + return model.copy(deep=deep) # type: ignore + return model.model_copy(deep=deep) + + +def model_json(model: pydantic.BaseModel, *, indent: int | None = None) -> str: + if PYDANTIC_V1: + return model.json(indent=indent) # type: ignore + return model.model_dump_json(indent=indent) + + +class _ModelDumpKwargs(TypedDict, total=False): + by_alias: bool + + +def model_dump( + model: pydantic.BaseModel, + *, + exclude: IncEx | None = None, + exclude_unset: bool = False, + exclude_defaults: bool = False, + warnings: bool = True, + mode: Literal["json", "python"] = "python", + by_alias: bool | None = None, +) -> dict[str, Any]: + if (not PYDANTIC_V1) or hasattr(model, "model_dump"): + kwargs: _ModelDumpKwargs = {} + if by_alias is not None: + kwargs["by_alias"] = by_alias + return model.model_dump( + mode=mode, + exclude=exclude, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + # warnings are not supported in Pydantic v1 + warnings=True if PYDANTIC_V1 else warnings, + **kwargs, + ) + return cast( + "dict[str, Any]", + model.dict( # pyright: ignore[reportDeprecated, reportUnnecessaryCast] + exclude=exclude, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, by_alias=bool(by_alias) + ), + ) + + +def model_parse(model: type[_ModelT], data: Any) -> _ModelT: + if PYDANTIC_V1: + return model.parse_obj(data) # pyright: ignore[reportDeprecated] + return model.model_validate(data) + + +# generic models +if TYPE_CHECKING: + + class GenericModel(pydantic.BaseModel): ... + +else: + if PYDANTIC_V1: + import pydantic.generics + + class GenericModel(pydantic.generics.GenericModel, pydantic.BaseModel): ... + else: + # there no longer needs to be a distinction in v2 but + # we still have to create our own subclass to avoid + # inconsistent MRO ordering errors + class GenericModel(pydantic.BaseModel): ... + + +# cached properties +if TYPE_CHECKING: + cached_property = property + + # we define a separate type (copied from typeshed) + # that represents that `cached_property` is `set`able + # at runtime, which differs from `@property`. + # + # this is a separate type as editors likely special case + # `@property` and we don't want to cause issues just to have + # more helpful internal types. + + class typed_cached_property(Generic[_T]): + func: Callable[[Any], _T] + attrname: str | None + + def __init__(self, func: Callable[[Any], _T]) -> None: ... + + @overload + def __get__(self, instance: None, owner: type[Any] | None = None) -> Self: ... + + @overload + def __get__(self, instance: object, owner: type[Any] | None = None) -> _T: ... + + def __get__(self, instance: object, owner: type[Any] | None = None) -> _T | Self: + raise NotImplementedError() + + def __set_name__(self, owner: type[Any], name: str) -> None: ... + + # __set__ is not defined at runtime, but @cached_property is designed to be settable + def __set__(self, instance: object, value: _T) -> None: ... +else: + from functools import cached_property as cached_property + + typed_cached_property = cached_property diff --git a/src/gumlet/_constants.py b/src/gumlet/_constants.py new file mode 100644 index 0000000..969d075 --- /dev/null +++ b/src/gumlet/_constants.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Scalar. See CONTRIBUTING.md for details. + +import httpx + +RAW_RESPONSE_HEADER = "X-Scalar-Raw-Response" +OVERRIDE_CAST_TO_HEADER = "____scalar_override_cast_to" + +# default timeout is 1 minute +DEFAULT_TIMEOUT = httpx.Timeout(timeout=60, connect=5.0) +DEFAULT_MAX_RETRIES = 2 +DEFAULT_CONNECTION_LIMITS = httpx.Limits(max_connections=100, max_keepalive_connections=20) + +INITIAL_RETRY_DELAY = 0.5 +MAX_RETRY_DELAY = 8.0 diff --git a/src/gumlet/_event_handler.py b/src/gumlet/_event_handler.py new file mode 100644 index 0000000..d84a24d --- /dev/null +++ b/src/gumlet/_event_handler.py @@ -0,0 +1,85 @@ +# File generated from our OpenAPI spec by Scalar. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import threading +from typing import Any, Callable + +EventHandler = Callable[..., Any] + + +class EventHandlerRegistry: + """Thread-safe (optional) registry of event handlers.""" + + def __init__(self, *, use_lock: bool = False) -> None: + self._handlers: dict[str, list[EventHandler]] = {} + self._once_ids: set[int] = set() + self._lock: threading.Lock | None = threading.Lock() if use_lock else None + + def _acquire(self) -> None: + if self._lock is not None: + self._lock.acquire() + + def _release(self) -> None: + if self._lock is not None: + self._lock.release() + + def add(self, event_type: str, handler: EventHandler, *, once: bool = False) -> None: + self._acquire() + try: + handlers = self._handlers.setdefault(event_type, []) + handlers.append(handler) + if once: + self._once_ids.add(id(handler)) + finally: + self._release() + + def remove(self, event_type: str, handler: EventHandler) -> None: + self._acquire() + try: + handlers = self._handlers.get(event_type) + if handlers is not None: + try: + handlers.remove(handler) + except ValueError: + pass + self._once_ids.discard(id(handler)) + finally: + self._release() + + def get_handlers(self, event_type: str) -> list[EventHandler]: + """Return a snapshot of handlers for the given event type, removing once-handlers.""" + self._acquire() + try: + handlers = self._handlers.get(event_type) + if not handlers: + return [] + result = list(handlers) + to_remove = [h for h in result if id(h) in self._once_ids] + for h in to_remove: + handlers.remove(h) + self._once_ids.discard(id(h)) + return result + finally: + self._release() + + def has_handlers(self, event_type: str) -> bool: + self._acquire() + try: + handlers = self._handlers.get(event_type) + return bool(handlers) + finally: + self._release() + + def merge_into(self, target: EventHandlerRegistry) -> None: + """Move all handlers from this registry into *target*, then clear self.""" + self._acquire() + try: + for event_type, handlers in self._handlers.items(): + for handler in handlers: + once = id(handler) in self._once_ids + target.add(event_type, handler, once=once) + self._handlers.clear() + self._once_ids.clear() + finally: + self._release() diff --git a/src/gumlet/_exceptions.py b/src/gumlet/_exceptions.py new file mode 100644 index 0000000..cdb19b5 --- /dev/null +++ b/src/gumlet/_exceptions.py @@ -0,0 +1,126 @@ +# File generated from our OpenAPI spec by Scalar. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal + +import httpx + +__all__ = [ + "BadRequestError", + "AuthenticationError", + "PermissionDeniedError", + "NotFoundError", + "ConflictError", + "UnprocessableEntityError", + "RateLimitError", + "InternalServerError", + "WebSocketConnectionClosedError", + "WebSocketQueueFullError", +] + + +class GumletError(Exception): + pass + + +class APIError(GumletError): + message: str + request: httpx.Request + + body: object | None + """The API response body. + + If the API responded with a valid JSON structure then this property will be the + decoded result. + + If it isn't a valid JSON structure then this will be the raw response. + + If there was no response associated with this error then it will be `None`. + """ + + def __init__(self, message: str, request: httpx.Request, *, body: object | None) -> None: # noqa: ARG002 + super().__init__(message) + self.request = request + self.message = message + self.body = body + + +class APIResponseValidationError(APIError): + response: httpx.Response + status_code: int + + def __init__(self, response: httpx.Response, body: object | None, *, message: str | None = None) -> None: + super().__init__(message or "Data returned by API invalid for expected schema.", response.request, body=body) + self.response = response + self.status_code = response.status_code + + +class APIStatusError(APIError): + """Raised when an API response has a status code of 4xx or 5xx.""" + + response: httpx.Response + status_code: int + + def __init__(self, message: str, *, response: httpx.Response, body: object | None) -> None: + super().__init__(message, response.request, body=body) + self.response = response + self.status_code = response.status_code + + +class APIConnectionError(APIError): + def __init__(self, *, message: str = "Connection error.", request: httpx.Request) -> None: + super().__init__(message, request, body=None) + + +class APITimeoutError(APIConnectionError): + def __init__(self, request: httpx.Request) -> None: + super().__init__(message="Request timed out.", request=request) + + +class BadRequestError(APIStatusError): + status_code: Literal[400] = 400 # pyright: ignore[reportIncompatibleVariableOverride] + + +class AuthenticationError(APIStatusError): + status_code: Literal[401] = 401 # pyright: ignore[reportIncompatibleVariableOverride] + + +class PermissionDeniedError(APIStatusError): + status_code: Literal[403] = 403 # pyright: ignore[reportIncompatibleVariableOverride] + + +class NotFoundError(APIStatusError): + status_code: Literal[404] = 404 # pyright: ignore[reportIncompatibleVariableOverride] + + +class ConflictError(APIStatusError): + status_code: Literal[409] = 409 # pyright: ignore[reportIncompatibleVariableOverride] + + +class UnprocessableEntityError(APIStatusError): + status_code: Literal[422] = 422 # pyright: ignore[reportIncompatibleVariableOverride] + + +class RateLimitError(APIStatusError): + status_code: Literal[429] = 429 # pyright: ignore[reportIncompatibleVariableOverride] + + +class InternalServerError(APIStatusError): + pass + + +class WebSocketConnectionClosedError(GumletError): + """Raised when a WebSocket connection closes with unsent messages.""" + + unsent_messages: list[str] + + def __init__(self, message: str, *, unsent_messages: list[str]) -> None: + super().__init__(message) + self.unsent_messages = unsent_messages + + +class WebSocketQueueFullError(GumletError): + """Raised when the outgoing WebSocket message queue exceeds its byte-size limit.""" + + pass diff --git a/src/gumlet/_files.py b/src/gumlet/_files.py new file mode 100644 index 0000000..76da9e0 --- /dev/null +++ b/src/gumlet/_files.py @@ -0,0 +1,173 @@ +from __future__ import annotations + +import io +import os +import pathlib +from typing import Sequence, cast, overload +from typing_extensions import TypeVar, TypeGuard + +import anyio + +from ._types import ( + FileTypes, + FileContent, + RequestFiles, + HttpxFileTypes, + Base64FileInput, + HttpxFileContent, + HttpxRequestFiles, +) +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]: + return isinstance(obj, io.IOBase) or isinstance(obj, os.PathLike) + + +def is_file_content(obj: object) -> TypeGuard[FileContent]: + return ( + isinstance(obj, bytes) or isinstance(obj, tuple) or isinstance(obj, io.IOBase) or isinstance(obj, os.PathLike) + ) + + +def assert_is_file_content(obj: object, *, key: str | None = None) -> None: + if not is_file_content(obj): + prefix = f"Expected entry at `{key}`" if key is not None else f"Expected file input `{obj!r}`" + raise RuntimeError( + f"{prefix} to be bytes, an io.IOBase instance, PathLike or a tuple but received {type(obj)} instead." + ) from None + + +@overload +def to_httpx_files(files: None) -> None: ... + + +@overload +def to_httpx_files(files: RequestFiles) -> HttpxRequestFiles: ... + + +def to_httpx_files(files: RequestFiles | None) -> HttpxRequestFiles | None: + if files is None: + return None + + if is_mapping_t(files): + files = {key: _transform_file(file) for key, file in files.items()} + elif is_sequence_t(files): + files = [(key, _transform_file(file)) for key, file in files] + else: + raise TypeError(f"Unexpected file type input {type(files)}, expected mapping or sequence") + + return files + + +def _transform_file(file: FileTypes) -> HttpxFileTypes: + if is_file_content(file): + if isinstance(file, os.PathLike): + path = pathlib.Path(file) + return (path.name, path.read_bytes()) + + return file + + if is_tuple_t(file): + return (file[0], read_file_content(file[1]), *file[2:]) + + raise TypeError(f"Expected file types input to be a FileContent type or to be a tuple") + + +def read_file_content(file: FileContent) -> HttpxFileContent: + if isinstance(file, os.PathLike): + return pathlib.Path(file).read_bytes() + return file + + +@overload +async def async_to_httpx_files(files: None) -> None: ... + + +@overload +async def async_to_httpx_files(files: RequestFiles) -> HttpxRequestFiles: ... + + +async def async_to_httpx_files(files: RequestFiles | None) -> HttpxRequestFiles | None: + if files is None: + return None + + if is_mapping_t(files): + files = {key: await _async_transform_file(file) for key, file in files.items()} + elif is_sequence_t(files): + files = [(key, await _async_transform_file(file)) for key, file in files] + else: + raise TypeError(f"Unexpected file type input {type(files)}, expected mapping or sequence") + + return files + + +async def _async_transform_file(file: FileTypes) -> HttpxFileTypes: + if is_file_content(file): + if isinstance(file, os.PathLike): + path = anyio.Path(file) + return (path.name, await path.read_bytes()) + + return file + + if is_tuple_t(file): + return (file[0], await async_read_file_content(file[1]), *file[2:]) + + raise TypeError(f"Expected file types input to be a FileContent type or to be a tuple") + + +async def async_read_file_content(file: FileContent) -> HttpxFileContent: + if isinstance(file, os.PathLike): + 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/gumlet/_models.py b/src/gumlet/_models.py new file mode 100644 index 0000000..ce728eb --- /dev/null +++ b/src/gumlet/_models.py @@ -0,0 +1,962 @@ +from __future__ import annotations + +import os +import inspect +import weakref +from typing import ( + IO, + TYPE_CHECKING, + Any, + Type, + Union, + Generic, + TypeVar, + Callable, + Iterable, + Optional, + AsyncIterable, + cast, +) +from datetime import date, datetime +from typing_extensions import ( + List, + Unpack, + Literal, + ClassVar, + Protocol, + Required, + Annotated, + ParamSpec, + TypeAlias, + TypedDict, + TypeGuard, + final, + override, + runtime_checkable, +) + +import pydantic +from pydantic.fields import FieldInfo + +from ._types import ( + Body, + IncEx, + Query, + ModelT, + Headers, + Timeout, + NotGiven, + AnyMapping, + HttpxRequestFiles, +) +from ._utils import ( + PropertyInfo, + is_list, + is_given, + json_safe, + lru_cache, + is_mapping, + parse_date, + coerce_boolean, + parse_datetime, + strip_not_given, + extract_type_arg, + is_annotated_type, + is_type_alias_type, + strip_annotated_type, +) +from ._compat import ( + PYDANTIC_V1, + ConfigDict, + GenericModel as BaseGenericModel, + get_args, + is_union, + parse_obj, + get_origin, + is_literal_type, + get_model_config, + get_model_fields, + field_get_default, +) +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"] + +_T = TypeVar("_T") +_BaseModelT = TypeVar("_BaseModelT", bound="BaseModel") + +P = ParamSpec("P") + + +@runtime_checkable +class _ConfigProtocol(Protocol): + allow_population_by_field_name: bool + + +class BaseModel(pydantic.BaseModel): + if PYDANTIC_V1: + + @property + @override + def model_fields_set(self) -> set[str]: + # a forwards-compat shim for pydantic v2 + return self.__fields_set__ # type: ignore + + class Config(pydantic.BaseConfig): # pyright: ignore[reportDeprecated] + extra: Any = pydantic.Extra.allow # type: ignore + else: + model_config: ClassVar[ConfigDict] = ConfigDict( + extra="allow", defer_build=coerce_boolean(os.environ.get("DEFER_PYDANTIC_BUILD", "true")) + ) + + def to_dict( + self, + *, + mode: Literal["json", "python"] = "python", + use_api_names: bool = True, + exclude_unset: bool = True, + exclude_defaults: bool = False, + exclude_none: bool = False, + warnings: bool = True, + ) -> dict[str, object]: + """Recursively generate a dictionary representation of the model, optionally specifying which fields to include or exclude. + + By default, fields that were not set by the API will not be included, + and keys will match the API response, *not* the property names from the model. + + For example, if the API responds with `"fooBar": true` but we've defined a `foo_bar: bool` property, + the output will use the `"fooBar"` key (unless `use_api_names=False` is passed). + + Args: + mode: + If mode is 'json', the dictionary will only contain JSON serializable types. e.g. `datetime` will be turned into a string, `"2024-3-22T18:11:19.117000Z"`. + If mode is 'python', the dictionary may contain any Python objects. e.g. `datetime(2024, 3, 22)` + + use_api_names: Whether to use the key that the API responded with or the property name. Defaults to `True`. + exclude_unset: Whether to exclude fields that have not been explicitly set. + exclude_defaults: Whether to exclude fields that are set to their default value from the output. + exclude_none: Whether to exclude fields that have a value of `None` from the output. + warnings: Whether to log warnings when invalid fields are encountered. This is only supported in Pydantic v2. + """ + return self.model_dump( + mode=mode, + by_alias=use_api_names, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + warnings=warnings, + ) + + def to_json( + self, + *, + indent: int | None = 2, + use_api_names: bool = True, + exclude_unset: bool = True, + exclude_defaults: bool = False, + exclude_none: bool = False, + warnings: bool = True, + ) -> str: + """Generates a JSON string representing this model as it would be received from or sent to the API (but with indentation). + + By default, fields that were not set by the API will not be included, + and keys will match the API response, *not* the property names from the model. + + For example, if the API responds with `"fooBar": true` but we've defined a `foo_bar: bool` property, + the output will use the `"fooBar"` key (unless `use_api_names=False` is passed). + + Args: + indent: Indentation to use in the JSON output. If `None` is passed, the output will be compact. Defaults to `2` + use_api_names: Whether to use the key that the API responded with or the property name. Defaults to `True`. + exclude_unset: Whether to exclude fields that have not been explicitly set. + exclude_defaults: Whether to exclude fields that have the default value. + exclude_none: Whether to exclude fields that have a value of `None`. + warnings: Whether to show any warnings that occurred during serialization. This is only supported in Pydantic v2. + """ + return self.model_dump_json( + indent=indent, + by_alias=use_api_names, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + warnings=warnings, + ) + + @override + def __str__(self) -> str: + # mypy complains about an invalid self arg + return f"{self.__repr_name__()}({self.__repr_str__(', ')})" # type: ignore[misc] + + # Override the 'construct' method in a way that supports recursive parsing without validation. + # Based on https://github.com/samuelcolvin/pydantic/issues/1168#issuecomment-817742836. + @classmethod + @override + def construct( # pyright: ignore[reportIncompatibleMethodOverride] + __cls: Type[ModelT], + _fields_set: set[str] | None = None, + **values: object, + ) -> ModelT: + m = __cls.__new__(__cls) + fields_values: dict[str, object] = {} + + config = get_model_config(__cls) + populate_by_name = ( + config.allow_population_by_field_name + if isinstance(config, _ConfigProtocol) + else config.get("populate_by_name") + ) + + if _fields_set is None: + _fields_set = set() + + model_fields = get_model_fields(__cls) + for name, field in model_fields.items(): + key = field.alias + if key is None or (key not in values and populate_by_name): + key = name + + if key in values: + fields_values[name] = _construct_field(value=values[key], field=field, key=key) + _fields_set.add(name) + else: + fields_values[name] = field_get_default(field) + + extra_field_type = _get_extra_fields_type(__cls) + + _extra = {} + for key, value in values.items(): + if key not in model_fields: + parsed = construct_type(value=value, type_=extra_field_type) if extra_field_type is not None else value + + if PYDANTIC_V1: + _fields_set.add(key) + fields_values[key] = parsed + else: + _extra[key] = parsed + + object.__setattr__(m, "__dict__", fields_values) + + if PYDANTIC_V1: + # init_private_attributes() does not exist in v2 + m._init_private_attributes() # type: ignore + + # copied from Pydantic v1's `construct()` method + object.__setattr__(m, "__fields_set__", _fields_set) + else: + # these properties are copied from Pydantic's `model_construct()` method + object.__setattr__(m, "__pydantic_private__", None) + object.__setattr__(m, "__pydantic_extra__", _extra) + object.__setattr__(m, "__pydantic_fields_set__", _fields_set) + + return m + + if not TYPE_CHECKING: + # type checkers incorrectly complain about this assignment + # because the type signatures are technically different + # although not in practice + model_construct = construct + + if PYDANTIC_V1: + # we define aliases for some of the new pydantic v2 methods so + # that we can just document these methods without having to specify + # a specific pydantic version as some users may not know which + # pydantic version they are currently using + + @override + def model_dump( + self, + *, + mode: Literal["json", "python"] | str = "python", + include: IncEx | None = None, + exclude: IncEx | None = None, + context: Any | None = None, + by_alias: bool | None = None, + exclude_unset: bool = False, + exclude_defaults: bool = False, + exclude_none: bool = False, + exclude_computed_fields: bool = False, + round_trip: bool = False, + warnings: bool | Literal["none", "warn", "error"] = True, + fallback: Callable[[Any], Any] | None = None, + serialize_as_any: bool = False, + ) -> dict[str, Any]: + """Usage docs: https://docs.pydantic.dev/2.4/concepts/serialization/#modelmodel_dump + + Generate a dictionary representation of the model, optionally specifying which fields to include or exclude. + + Args: + mode: The mode in which `to_python` should run. + If mode is 'json', the output will only contain JSON serializable types. + If mode is 'python', the output may contain non-JSON-serializable Python objects. + include: A set of fields to include in the output. + exclude: A set of fields to exclude from the output. + context: Additional context to pass to the serializer. + by_alias: Whether to use the field's alias in the dictionary key if defined. + exclude_unset: Whether to exclude fields that have not been explicitly set. + exclude_defaults: Whether to exclude fields that are set to their default value. + exclude_none: Whether to exclude fields that have a value of `None`. + exclude_computed_fields: Whether to exclude computed fields. + While this can be useful for round-tripping, it is usually recommended to use the dedicated + `round_trip` parameter instead. + round_trip: If True, dumped values should be valid as input for non-idempotent types such as Json[T]. + warnings: How to handle serialization errors. False/"none" ignores them, True/"warn" logs errors, + "error" raises a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError]. + fallback: A function to call when an unknown value is encountered. If not provided, + a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError] error is raised. + serialize_as_any: Whether to serialize fields with duck-typing serialization behavior. + + Returns: + A dictionary representation of the model. + """ + if mode not in {"json", "python"}: + raise ValueError("mode must be either 'json' or 'python'") + if round_trip != False: + raise ValueError("round_trip is only supported in Pydantic v2") + if warnings != True: + raise ValueError("warnings is only supported in Pydantic v2") + if context is not None: + raise ValueError("context is only supported in Pydantic v2") + if serialize_as_any != False: + raise ValueError("serialize_as_any is only supported in Pydantic v2") + if fallback is not None: + raise ValueError("fallback is only supported in Pydantic v2") + if exclude_computed_fields != False: + raise ValueError("exclude_computed_fields is only supported in Pydantic v2") + dumped = super().dict( # pyright: ignore[reportDeprecated] + include=include, + exclude=exclude, + by_alias=by_alias if by_alias is not None else False, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + ) + + return cast("dict[str, Any]", json_safe(dumped)) if mode == "json" else dumped + + @override + def model_dump_json( + self, + *, + indent: int | None = None, + ensure_ascii: bool = False, + include: IncEx | None = None, + exclude: IncEx | None = None, + context: Any | None = None, + by_alias: bool | None = None, + exclude_unset: bool = False, + exclude_defaults: bool = False, + exclude_none: bool = False, + exclude_computed_fields: bool = False, + round_trip: bool = False, + warnings: bool | Literal["none", "warn", "error"] = True, + fallback: Callable[[Any], Any] | None = None, + serialize_as_any: bool = False, + ) -> str: + """Usage docs: https://docs.pydantic.dev/2.4/concepts/serialization/#modelmodel_dump_json + + Generates a JSON representation of the model using Pydantic's `to_json` method. + + Args: + indent: Indentation to use in the JSON output. If None is passed, the output will be compact. + include: Field(s) to include in the JSON output. Can take either a string or set of strings. + exclude: Field(s) to exclude from the JSON output. Can take either a string or set of strings. + by_alias: Whether to serialize using field aliases. + exclude_unset: Whether to exclude fields that have not been explicitly set. + exclude_defaults: Whether to exclude fields that have the default value. + exclude_none: Whether to exclude fields that have a value of `None`. + round_trip: Whether to use serialization/deserialization between JSON and class instance. + warnings: Whether to show any warnings that occurred during serialization. + + Returns: + A JSON string representation of the model. + """ + if round_trip != False: + raise ValueError("round_trip is only supported in Pydantic v2") + if warnings != True: + raise ValueError("warnings is only supported in Pydantic v2") + if context is not None: + raise ValueError("context is only supported in Pydantic v2") + if serialize_as_any != False: + raise ValueError("serialize_as_any is only supported in Pydantic v2") + if fallback is not None: + raise ValueError("fallback is only supported in Pydantic v2") + if ensure_ascii != False: + raise ValueError("ensure_ascii is only supported in Pydantic v2") + if exclude_computed_fields != False: + raise ValueError("exclude_computed_fields is only supported in Pydantic v2") + return super().json( # type: ignore[reportDeprecated] + indent=indent, + include=include, + exclude=exclude, + by_alias=by_alias if by_alias is not None else False, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + ) + + +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) + + if PYDANTIC_V1: + type_ = cast(type, field.outer_type_) # type: ignore + else: + type_ = field.annotation # type: ignore + + if type_ is None: + raise RuntimeError(f"Unexpected field type is None for {key}") + + return construct_type(value=value, type_=type_, metadata=getattr(field, "metadata", None)) + + +def _get_extra_fields_type(cls: type[pydantic.BaseModel]) -> type | None: + if PYDANTIC_V1: + # TODO + return None + + schema = cls.__pydantic_core_schema__ + if schema["type"] == "model": + fields = schema["schema"] + if fields["type"] == "model-fields": + extras = fields.get("extras_schema") + if extras and "cls" in extras: + # mypy can't narrow the type + return extras["cls"] # type: ignore[no-any-return] + + return None + + +def is_basemodel(type_: type) -> bool: + """Returns whether or not the given type is either a `BaseModel` or a union of `BaseModel`""" + if is_union(type_): + for variant in get_args(type_): + if is_basemodel(variant): + return True + + return False + + return is_basemodel_type(type_) + + +def is_basemodel_type(type_: type) -> TypeGuard[type[BaseModel] | type[GenericModel]]: + origin = get_origin(type_) or type_ + if not inspect.isclass(origin): + return False + return issubclass(origin, BaseModel) or issubclass(origin, GenericModel) + + +def build( + base_model_cls: Callable[P, _BaseModelT], + *args: P.args, + **kwargs: P.kwargs, +) -> _BaseModelT: + """Construct a BaseModel class without validation. + + This is useful for cases where you need to instantiate a `BaseModel` + from an API response as this provides type-safe params which isn't supported + by helpers like `construct_type()`. + + ```py + build(MyModel, my_field_a="foo", my_field_b=123) + ``` + """ + if args: + raise TypeError( + "Received positional arguments which are not supported; Keyword arguments must be used instead", + ) + + return cast(_BaseModelT, construct_type(type_=base_model_cls, value=kwargs)) + + +def construct_type_unchecked(*, value: object, type_: type[_T]) -> _T: + """Loose coercion to the expected type with construction of nested values. + + Note: the returned value from this function is not guaranteed to match the + given type. + """ + return cast(_T, construct_type(value=value, type_=type_)) + + +def construct_type(*, value: object, type_: object, metadata: Optional[List[Any]] = None) -> object: + """Loose coercion to the expected type with construction of nested values. + + If the given value does not match the expected type then it is returned as-is. + """ + + # store a reference to the original type we were given before we extract any inner + # types so that we can properly resolve forward references in `TypeAliasType` annotations + original_type = None + + # we allow `object` as the input type because otherwise, passing things like + # `Literal['value']` will be reported as a type error by type checkers + type_ = cast("type[object]", type_) + if is_type_alias_type(type_): + original_type = type_ # type: ignore[unreachable] + type_ = type_.__value__ # type: ignore[unreachable] + + # unwrap `Annotated[T, ...]` -> `T` + if metadata is not None and len(metadata) > 0: + meta: tuple[Any, ...] = tuple(metadata) + elif is_annotated_type(type_): + meta = get_args(type_)[1:] + type_ = extract_type_arg(type_, 0) + else: + meta = tuple() + + # we need to use the origin class for any types that are subscripted generics + # e.g. Dict[str, object] + origin = get_origin(type_) or type_ + args = get_args(type_) + + if is_union(origin): + try: + return validate_type(type_=cast("type[object]", original_type or type_), value=value) + except Exception: + pass + + # if the type is a discriminated union then we want to construct the right variant + # in the union, even if the data doesn't match exactly, otherwise we'd break code + # that relies on the constructed class types, e.g. + # + # class FooType: + # kind: Literal['foo'] + # value: str + # + # class BarType: + # kind: Literal['bar'] + # value: int + # + # without this block, if the data we get is something like `{'kind': 'bar', 'value': 'foo'}` then + # we'd end up constructing `FooType` when it should be `BarType`. + discriminator = _build_discriminated_union_meta(union=type_, meta_annotations=meta) + if discriminator and is_mapping(value): + variant_value = value.get(discriminator.field_alias_from or discriminator.field_name) + if variant_value and isinstance(variant_value, str): + variant_type = discriminator.mapping.get(variant_value) + if variant_type: + return construct_type(type_=variant_type, value=value) + + # if the data is not valid, use the first variant that doesn't fail while deserializing + for variant in args: + try: + return construct_type(value=value, type_=variant) + except Exception: + continue + + raise RuntimeError(f"Could not convert data into a valid instance of {type_}") + + if origin == dict: + if not is_mapping(value): + return value + + _, items_type = get_args(type_) # Dict[_, items_type] + return {key: construct_type(value=item, type_=items_type) for key, item in value.items()} + + if ( + not is_literal_type(type_) + and inspect.isclass(origin) + and (issubclass(origin, BaseModel) or issubclass(origin, GenericModel)) + ): + if is_list(value): + return [cast(Any, type_).construct(**entry) if is_mapping(entry) else entry for entry in value] + + if is_mapping(value): + if issubclass(type_, BaseModel): + return type_.construct(**value) # type: ignore[arg-type] + + return cast(Any, type_).construct(**value) + + if origin == list: + if not is_list(value): + return value + + inner_type = args[0] # List[inner_type] + return [construct_type(value=entry, type_=inner_type) for entry in value] + + if origin == float: + if isinstance(value, int): + coerced = float(value) + if coerced != value: + return value + return coerced + + return value + + if type_ == datetime: + try: + return parse_datetime(value) # type: ignore + except Exception: + return value + + if type_ == date: + try: + return parse_date(value) # type: ignore + except Exception: + return value + + return value + + +@runtime_checkable +class CachedDiscriminatorType(Protocol): + __discriminator__: DiscriminatorDetails + + +DISCRIMINATOR_CACHE: weakref.WeakKeyDictionary[type, DiscriminatorDetails] = weakref.WeakKeyDictionary() + + +class DiscriminatorDetails: + field_name: str + """The name of the discriminator field in the variant class, e.g. + + ```py + class Foo(BaseModel): + type: Literal['foo'] + ``` + + Will result in field_name='type' + """ + + field_alias_from: str | None + """The name of the discriminator field in the API response, e.g. + + ```py + class Foo(BaseModel): + type: Literal['foo'] = Field(alias='type_from_api') + ``` + + Will result in field_alias_from='type_from_api' + """ + + mapping: dict[str, type] + """Mapping of discriminator value to variant type, e.g. + + {'foo': FooVariant, 'bar': BarVariant} + """ + + def __init__( + self, + *, + mapping: dict[str, type], + discriminator_field: str, + discriminator_alias: str | None, + ) -> None: + self.mapping = mapping + self.field_name = discriminator_field + self.field_alias_from = discriminator_alias + + +def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any, ...]) -> DiscriminatorDetails | None: + cached = DISCRIMINATOR_CACHE.get(union) + if cached is not None: + return cached + + discriminator_field_name: str | None = None + + for annotation in meta_annotations: + if isinstance(annotation, PropertyInfo) and annotation.discriminator is not None: + discriminator_field_name = annotation.discriminator + break + + if not discriminator_field_name: + return None + + mapping: dict[str, type] = {} + discriminator_alias: str | None = None + + for variant in get_args(union): + variant = strip_annotated_type(variant) + if is_basemodel_type(variant): + if PYDANTIC_V1: + field_info = cast("dict[str, FieldInfo]", variant.__fields__).get(discriminator_field_name) # pyright: ignore[reportDeprecated, reportUnnecessaryCast] + if not field_info: + continue + + # Note: if one variant defines an alias then they all should + discriminator_alias = field_info.alias + + if (annotation := getattr(field_info, "annotation", None)) and is_literal_type(annotation): + for entry in get_args(annotation): + if isinstance(entry, str): + mapping[entry] = variant + else: + field = _extract_field_schema_pv2(variant, discriminator_field_name) + if not field: + continue + + # Note: if one variant defines an alias then they all should + discriminator_alias = field.get("serialization_alias") + + field_schema = field["schema"] + + if field_schema["type"] == "literal": + for entry in cast("LiteralSchema", field_schema)["expected"]: + if isinstance(entry, str): + mapping[entry] = variant + + if not mapping: + return None + + details = DiscriminatorDetails( + mapping=mapping, + discriminator_field=discriminator_field_name, + discriminator_alias=discriminator_alias, + ) + DISCRIMINATOR_CACHE.setdefault(union, details) + return details + + +def _extract_field_schema_pv2(model: type[BaseModel], field_name: str) -> ModelField | None: + schema = model.__pydantic_core_schema__ + if schema["type"] == "definitions": + schema = schema["schema"] + + if schema["type"] != "model": + return None + + schema = cast("ModelSchema", schema) + fields_schema = schema["schema"] + if fields_schema["type"] != "model-fields": + return None + + fields_schema = cast("ModelFieldsSchema", fields_schema) + field = fields_schema["fields"].get(field_name) + if not field: + return None + + return cast("ModelField", field) # pyright: ignore[reportUnnecessaryCast] + + +def validate_type(*, type_: type[_T], value: object) -> _T: + """Strict validation that the given value matches the expected type""" + if inspect.isclass(type_) and issubclass(type_, pydantic.BaseModel): + return cast(_T, parse_obj(type_, value)) + + return cast(_T, _validate_non_model_type(type_=type_, value=value)) + + +def set_pydantic_config(typ: Any, config: pydantic.ConfigDict) -> None: + """Add a pydantic config for the given type. + + Note: this is a no-op on Pydantic v1. + """ + setattr(typ, "__pydantic_config__", config) # noqa: B010 + + +# our use of subclassing here causes weirdness for type checkers, +# so we just pretend that we don't subclass +if TYPE_CHECKING: + GenericModel = BaseModel +else: + + class GenericModel(BaseGenericModel, BaseModel): + pass + + +if not PYDANTIC_V1: + from pydantic import TypeAdapter as _TypeAdapter + + _CachedTypeAdapter = cast("TypeAdapter[object]", lru_cache(maxsize=None)(_TypeAdapter)) + + if TYPE_CHECKING: + from pydantic import TypeAdapter + else: + TypeAdapter = _CachedTypeAdapter + + def _validate_non_model_type(*, type_: type[_T], value: object) -> _T: + return TypeAdapter(type_).validate_python(value) + +elif not TYPE_CHECKING: # TODO: condition is weird + + class RootModel(GenericModel, Generic[_T]): + """Used as a placeholder to easily convert runtime types to a Pydantic format + to provide validation. + + For example: + ```py + validated = RootModel[int](__root__="5").__root__ + # validated: 5 + ``` + """ + + __root__: _T + + def _validate_non_model_type(*, type_: type[_T], value: object) -> _T: + model = _create_pydantic_model(type_).validate(value) + return cast(_T, model.__root__) + + def _create_pydantic_model(type_: _T) -> Type[RootModel[_T]]: + return RootModel[type_] # type: ignore + + +class SecurityOptions(TypedDict, total=False): + api_key_auth: bool + bearer_auth: bool + + +class FinalRequestOptionsInput(TypedDict, total=False): + method: Required[str] + url: Required[str] + params: Query + headers: Headers + max_retries: int + timeout: float | Timeout | None + files: HttpxRequestFiles | None + idempotency_key: str + content: Union[bytes, bytearray, IO[bytes], Iterable[bytes], AsyncIterable[bytes], None] + json_data: Body + extra_json: AnyMapping + follow_redirects: bool + security: SecurityOptions + + +@final +class FinalRequestOptions(pydantic.BaseModel): + method: str + url: str + params: Query = {} + headers: Union[Headers, NotGiven] = NotGiven() + max_retries: Union[int, NotGiven] = NotGiven() + timeout: Union[float, Timeout, None, NotGiven] = NotGiven() + files: Union[HttpxRequestFiles, None] = None + idempotency_key: Union[str, None] = None + post_parser: Union[Callable[[Any], Any], NotGiven] = NotGiven() + follow_redirects: Union[bool, None] = None + security: SecurityOptions = { + "api_key_auth": True, + "bearer_auth": True, + } + + content: Union[bytes, bytearray, IO[bytes], Iterable[bytes], AsyncIterable[bytes], None] = None + # It should be noted that we cannot use `json` here as that would override + # a BaseModel method in an incompatible fashion. + json_data: Union[Body, None] = None + extra_json: Union[AnyMapping, None] = None + + if PYDANTIC_V1: + + class Config(pydantic.BaseConfig): # pyright: ignore[reportDeprecated] + arbitrary_types_allowed: bool = True + else: + model_config: ClassVar[ConfigDict] = ConfigDict(arbitrary_types_allowed=True) + + def get_max_retries(self, max_retries: int) -> int: + if isinstance(self.max_retries, NotGiven): + return max_retries + return self.max_retries + + def _strip_raw_response_header(self) -> None: + if not is_given(self.headers): + return + + if self.headers.get(RAW_RESPONSE_HEADER): + self.headers = {**self.headers} + self.headers.pop(RAW_RESPONSE_HEADER) + + # override the `construct` method so that we can run custom transformations. + # this is necessary as we don't want to do any actual runtime type checking + # (which means we can't use validators) but we do want to ensure that `NotGiven` + # values are not present + # + # type ignore required because we're adding explicit types to `**values` + @classmethod + def construct( # type: ignore + cls, + _fields_set: set[str] | None = None, + **values: Unpack[FinalRequestOptionsInput], + ) -> FinalRequestOptions: + kwargs: dict[str, Any] = { + # we unconditionally call `strip_not_given` on any value + # as it will just ignore any non-mapping types + key: strip_not_given(value) + for key, value in values.items() + } + if PYDANTIC_V1: + return cast(FinalRequestOptions, super().construct(_fields_set, **kwargs)) # pyright: ignore[reportDeprecated] + return super().model_construct(_fields_set, **kwargs) + + if not TYPE_CHECKING: + # type checkers incorrectly complain about this assignment + model_construct = construct diff --git a/src/gumlet/_qs.py b/src/gumlet/_qs.py new file mode 100644 index 0000000..4127c19 --- /dev/null +++ b/src/gumlet/_qs.py @@ -0,0 +1,149 @@ +from __future__ import annotations + +from typing import Any, List, Tuple, Union, Mapping, TypeVar +from urllib.parse import parse_qs, urlencode +from typing_extensions import get_args + +from ._types import NotGiven, ArrayFormat, NestedFormat, not_given +from ._utils import flatten + +_T = TypeVar("_T") + +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 +Data = Union[PrimitiveData, List[Any], Tuple[Any], "Mapping[str, Any]"] +Params = Mapping[str, Data] + + +class Querystring: + array_format: ArrayFormat + nested_format: NestedFormat + + def __init__( + self, + *, + array_format: ArrayFormat = "repeat", + nested_format: NestedFormat = "brackets", + ) -> None: + self.array_format = array_format + self.nested_format = nested_format + + def parse(self, query: str) -> Mapping[str, object]: + # Note: custom format syntax is not supported yet + return parse_qs(query) + + def stringify( + self, + params: Params, + *, + array_format: ArrayFormat | NotGiven = not_given, + nested_format: NestedFormat | NotGiven = not_given, + ) -> str: + return urlencode( + self.stringify_items( + params, + array_format=array_format, + nested_format=nested_format, + ) + ) + + def stringify_items( + self, + params: Params, + *, + array_format: ArrayFormat | NotGiven = not_given, + nested_format: NestedFormat | NotGiven = not_given, + ) -> list[tuple[str, str]]: + opts = Options( + qs=self, + array_format=array_format, + nested_format=nested_format, + ) + return flatten([self._stringify_item(key, value, opts) for key, value in params.items()]) + + def _stringify_item( + self, + key: str, + value: Data, + opts: Options, + ) -> list[tuple[str, str]]: + if isinstance(value, Mapping): + items: list[tuple[str, str]] = [] + nested_format = opts.nested_format + for subkey, subvalue in value.items(): + items.extend( + self._stringify_item( + # TODO: error if unknown format + f"{key}.{subkey}" if nested_format == "dots" else f"{key}[{subkey}]", + subvalue, + opts, + ) + ) + return items + + if isinstance(value, (list, tuple)): + array_format = opts.array_format + if array_format == "comma": + return [ + ( + key, + ",".join(self._primitive_value_to_str(item) for item in value if item is not None), + ), + ] + elif array_format == "repeat": + items = [] + for item in value: + items.extend(self._stringify_item(key, item, opts)) + return items + elif array_format == "indices": + items = [] + for i, item in enumerate(value): + items.extend(self._stringify_item(f"{key}[{i}]", item, opts)) + return items + elif array_format == "brackets": + items = [] + key = key + "[]" + for item in value: + items.extend(self._stringify_item(key, item, opts)) + return items + else: + raise NotImplementedError( + f"Unknown array_format value: {array_format}, choose from {', '.join(get_args(ArrayFormat))}" + ) + + serialised = self._primitive_value_to_str(value) + if not serialised: + return [] + return [(key, serialised)] + + def _primitive_value_to_str(self, value: PrimitiveData) -> str: + # copied from httpx + if value is True: + return "true" + elif value is False: + return "false" + elif value is None: + return "" + return str(value) + + +_qs = Querystring() +parse = _qs.parse +stringify = _qs.stringify +stringify_items = _qs.stringify_items + + +class Options: + array_format: ArrayFormat + nested_format: NestedFormat + + def __init__( + self, + qs: Querystring = _qs, + *, + array_format: ArrayFormat | NotGiven = not_given, + nested_format: NestedFormat | NotGiven = not_given, + ) -> None: + self.array_format = qs.array_format if isinstance(array_format, NotGiven) else array_format + self.nested_format = qs.nested_format if isinstance(nested_format, NotGiven) else nested_format diff --git a/src/gumlet/_resource.py b/src/gumlet/_resource.py new file mode 100644 index 0000000..49f2e1b --- /dev/null +++ b/src/gumlet/_resource.py @@ -0,0 +1,43 @@ +# File generated from our OpenAPI spec by Scalar. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import time +from typing import TYPE_CHECKING + +import anyio + +if TYPE_CHECKING: + from ._client import Gumlet, AsyncGumlet + + +class SyncAPIResource: + _client: Gumlet + + def __init__(self, client: Gumlet) -> None: + self._client = client + self._get = client.get + self._post = client.post + self._patch = client.patch + self._put = client.put + self._delete = client.delete + self._get_api_list = client.get_api_list + + def _sleep(self, seconds: float) -> None: + time.sleep(seconds) + + +class AsyncAPIResource: + _client: AsyncGumlet + + def __init__(self, client: AsyncGumlet) -> None: + self._client = client + self._get = client.get + self._post = client.post + self._patch = client.patch + self._put = client.put + self._delete = client.delete + self._get_api_list = client.get_api_list + + async def _sleep(self, seconds: float) -> None: + await anyio.sleep(seconds) diff --git a/src/gumlet/_response.py b/src/gumlet/_response.py new file mode 100644 index 0000000..b541ca5 --- /dev/null +++ b/src/gumlet/_response.py @@ -0,0 +1,840 @@ +from __future__ import annotations + +import os +import inspect +import logging +import datetime +import functools +from types import TracebackType +from typing import ( + TYPE_CHECKING, + Any, + Union, + Generic, + TypeVar, + Callable, + Iterator, + AsyncIterator, + cast, + overload, +) +from typing_extensions import Awaitable, ParamSpec, override, get_origin + +import anyio +import httpx +import pydantic + +from ._types import NoneType +from ._utils import ( + is_given, + is_union_type, + extract_type_arg, + is_annotated_type, + is_type_alias_type, + extract_type_var_from_base, +) +from ._models import BaseModel, is_basemodel +from ._constants import RAW_RESPONSE_HEADER, OVERRIDE_CAST_TO_HEADER +from ._streaming import Stream, AsyncStream, is_stream_class_type, extract_stream_chunk_type +from ._exceptions import GumletError, APIResponseValidationError + +if TYPE_CHECKING: + from ._models import FinalRequestOptions + from ._base_client import BaseClient + + +P = ParamSpec("P") +R = TypeVar("R") +_T = TypeVar("_T") +_APIResponseT = TypeVar("_APIResponseT", bound="APIResponse[Any]") +_AsyncAPIResponseT = TypeVar("_AsyncAPIResponseT", bound="AsyncAPIResponse[Any]") + +log: logging.Logger = logging.getLogger(__name__) + + +class BaseAPIResponse(Generic[R]): + _cast_to: type[R] + _client: BaseClient[Any, Any] + _parsed_by_type: dict[type[Any], Any] + _is_sse_stream: bool + _stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None + _options: FinalRequestOptions + + http_response: httpx.Response + + retries_taken: int + """The number of retries made. If no retries happened this will be `0`""" + + def __init__( + self, + *, + raw: httpx.Response, + cast_to: type[R], + client: BaseClient[Any, Any], + stream: bool, + stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None, + options: FinalRequestOptions, + retries_taken: int = 0, + ) -> None: + self._cast_to = cast_to + self._client = client + self._parsed_by_type = {} + self._is_sse_stream = stream + self._stream_cls = stream_cls + self._options = options + self.http_response = raw + self.retries_taken = retries_taken + + @property + def headers(self) -> httpx.Headers: + return self.http_response.headers + + @property + def http_request(self) -> httpx.Request: + """Returns the httpx Request instance associated with the current response.""" + return self.http_response.request + + @property + def status_code(self) -> int: + return self.http_response.status_code + + @property + def url(self) -> httpx.URL: + """Returns the URL for which the request was made.""" + return self.http_response.url + + @property + def method(self) -> str: + return self.http_request.method + + @property + def http_version(self) -> str: + return self.http_response.http_version + + @property + def elapsed(self) -> datetime.timedelta: + """The time taken for the complete request/response cycle to complete.""" + return self.http_response.elapsed + + @property + def is_closed(self) -> bool: + """Whether or not the response body has been closed. + + If this is False then there is response data that has not been read yet. + You must either fully consume the response body or call `.close()` + before discarding the response to prevent resource leaks. + """ + return self.http_response.is_closed + + @override + def __repr__(self) -> str: + return ( + f"<{self.__class__.__name__} [{self.status_code} {self.http_response.reason_phrase}] type={self._cast_to}>" + ) + + def _parse(self, *, to: type[_T] | None = None) -> R | _T: + cast_to = to if to is not None else self._cast_to + + # unwrap `TypeAlias('Name', T)` -> `T` + if is_type_alias_type(cast_to): + cast_to = cast_to.__value__ # type: ignore[unreachable] + + # unwrap `Annotated[T, ...]` -> `T` + if cast_to and is_annotated_type(cast_to): + cast_to = extract_type_arg(cast_to, 0) + + origin = get_origin(cast_to) or cast_to + + if self._is_sse_stream: + if to: + if not is_stream_class_type(to): + raise TypeError(f"Expected custom parse type to be a subclass of {Stream} or {AsyncStream}") + + return cast( + _T, + to( + cast_to=extract_stream_chunk_type( + to, + failure_message="Expected custom stream type to be passed with a type argument, e.g. Stream[ChunkType]", + ), + response=self.http_response, + client=cast(Any, self._client), + options=self._options, + ), + ) + + if self._stream_cls: + return cast( + R, + self._stream_cls( + cast_to=extract_stream_chunk_type(self._stream_cls), + response=self.http_response, + client=cast(Any, self._client), + options=self._options, + ), + ) + + stream_cls = cast("type[Stream[Any]] | type[AsyncStream[Any]] | None", self._client._default_stream_cls) + if stream_cls is None: + raise MissingStreamClassError() + + return cast( + R, + stream_cls( + cast_to=cast_to, + response=self.http_response, + client=cast(Any, self._client), + options=self._options, + ), + ) + + if cast_to is None or cast_to is NoneType: + return cast(R, None) + + response = self.http_response + if cast_to == str: + return cast(R, response.text) + + if cast_to == bytes: + return cast(R, response.content) + + if cast_to == int: + return cast(R, int(response.text)) + + if cast_to == float: + return cast(R, float(response.text)) + + if cast_to == bool: + return cast(R, response.text.lower() == "true") + + if origin == APIResponse: + raise RuntimeError("Unexpected state - cast_to is `APIResponse`") + + if inspect.isclass(origin) and issubclass(origin, httpx.Response): + # Because of the invariance of our ResponseT TypeVar, users can subclass httpx.Response + # and pass that class to our request functions. We cannot change the variance to be either + # covariant or contravariant as that makes our usage of ResponseT illegal. We could construct + # the response class ourselves but that is something that should be supported directly in httpx + # as it would be easy to incorrectly construct the Response object due to the multitude of arguments. + if cast_to != httpx.Response: + raise ValueError(f"Subclasses of httpx.Response cannot be passed to `cast_to`") + return cast(R, response) + + if ( + inspect.isclass( + origin # pyright: ignore[reportUnknownArgumentType] + ) + and not issubclass(origin, BaseModel) + and issubclass(origin, pydantic.BaseModel) + ): + raise TypeError("Pydantic models must subclass our base model type, e.g. `from gumlet import BaseModel`") + + if ( + cast_to is not object + and not origin is list + and not origin is dict + and not is_union_type(cast_to) + and not (inspect.isclass(origin) and issubclass(origin, BaseModel)) + ): + raise RuntimeError( + f"Unsupported type, expected {cast_to} to be a subclass of {BaseModel}, {dict}, {list}, {Union}, {NoneType}, {str} or {httpx.Response}." + ) + + # split is required to handle cases where additional information is included + # in the response, e.g. application/json; charset=utf-8 + content_type, *_ = response.headers.get("content-type", "*").split(";") + if not content_type.endswith("json"): + if is_basemodel(cast_to): + try: + data = response.json() + except Exception as exc: + log.debug("Could not read JSON from response data due to %s - %s", type(exc), exc) + else: + return self._client._process_response_data( + data=data, + cast_to=cast_to, # type: ignore + response=response, + ) + + if self._client._strict_response_validation: + raise APIResponseValidationError( + response=response, + message=f"Expected Content-Type response header to be `application/json` but received `{content_type}` instead.", + body=response.text, + ) + + # If the API responds with content that isn't JSON then we just return + # the (decoded) text without performing any parsing so that you can still + # handle the response however you need to. + return response.text # type: ignore + + data = response.json() + + return self._client._process_response_data( + data=data, + cast_to=cast_to, # type: ignore + response=response, + ) + + +class APIResponse(BaseAPIResponse[R]): + @overload + def parse(self, *, to: type[_T]) -> _T: ... + + @overload + def parse(self) -> R: ... + + def parse(self, *, to: type[_T] | None = None) -> R | _T: + """Returns the rich python representation of this response's data. + + For lower-level control, see `.read()`, `.json()`, `.iter_bytes()`. + + You can customise the type that the response is parsed into through + the `to` argument, e.g. + + ```py + from gumlet import BaseModel + + + class MyModel(BaseModel): + foo: str + + + obj = response.parse(to=MyModel) + print(obj.foo) + ``` + + We support parsing: + - `BaseModel` + - `dict` + - `list` + - `Union` + - `str` + - `int` + - `float` + - `httpx.Response` + """ + cache_key = to if to is not None else self._cast_to + cached = self._parsed_by_type.get(cache_key) + if cached is not None: + return cached # type: ignore[no-any-return] + + if not self._is_sse_stream: + self.read() + + parsed = self._parse(to=to) + if is_given(self._options.post_parser): + parsed = self._options.post_parser(parsed) + + self._parsed_by_type[cache_key] = parsed + return parsed + + def read(self) -> bytes: + """Read and return the binary response content.""" + try: + return self.http_response.read() + except httpx.StreamConsumed as exc: + # The default error raised by httpx isn't very + # helpful in our case so we re-raise it with + # a different error message. + raise StreamAlreadyConsumed() from exc + + def text(self) -> str: + """Read and decode the response content into a string.""" + self.read() + return self.http_response.text + + def json(self) -> object: + """Read and decode the JSON response content.""" + self.read() + return self.http_response.json() + + def close(self) -> None: + """Close the response and release the connection. + + Automatically called if the response body is read to completion. + """ + self.http_response.close() + + def iter_bytes(self, chunk_size: int | None = None) -> Iterator[bytes]: + """ + A byte-iterator over the decoded response content. + + This automatically handles gzip, deflate and brotli encoded responses. + """ + for chunk in self.http_response.iter_bytes(chunk_size): + yield chunk + + def iter_text(self, chunk_size: int | None = None) -> Iterator[str]: + """A str-iterator over the decoded response content + that handles both gzip, deflate, etc but also detects the content's + string encoding. + """ + for chunk in self.http_response.iter_text(chunk_size): + yield chunk + + def iter_lines(self) -> Iterator[str]: + """Like `iter_text()` but will only yield chunks for each line""" + for chunk in self.http_response.iter_lines(): + yield chunk + + +class AsyncAPIResponse(BaseAPIResponse[R]): + @overload + async def parse(self, *, to: type[_T]) -> _T: ... + + @overload + async def parse(self) -> R: ... + + async def parse(self, *, to: type[_T] | None = None) -> R | _T: + """Returns the rich python representation of this response's data. + + For lower-level control, see `.read()`, `.json()`, `.iter_bytes()`. + + You can customise the type that the response is parsed into through + the `to` argument, e.g. + + ```py + from gumlet import BaseModel + + + class MyModel(BaseModel): + foo: str + + + obj = response.parse(to=MyModel) + print(obj.foo) + ``` + + We support parsing: + - `BaseModel` + - `dict` + - `list` + - `Union` + - `str` + - `httpx.Response` + """ + cache_key = to if to is not None else self._cast_to + cached = self._parsed_by_type.get(cache_key) + if cached is not None: + return cached # type: ignore[no-any-return] + + if not self._is_sse_stream: + await self.read() + + parsed = self._parse(to=to) + if is_given(self._options.post_parser): + parsed = self._options.post_parser(parsed) + + self._parsed_by_type[cache_key] = parsed + return parsed + + async def read(self) -> bytes: + """Read and return the binary response content.""" + try: + return await self.http_response.aread() + except httpx.StreamConsumed as exc: + # the default error raised by httpx isn't very + # helpful in our case so we re-raise it with + # a different error message + raise StreamAlreadyConsumed() from exc + + async def text(self) -> str: + """Read and decode the response content into a string.""" + await self.read() + return self.http_response.text + + async def json(self) -> object: + """Read and decode the JSON response content.""" + await self.read() + return self.http_response.json() + + async def close(self) -> None: + """Close the response and release the connection. + + Automatically called if the response body is read to completion. + """ + await self.http_response.aclose() + + async def iter_bytes(self, chunk_size: int | None = None) -> AsyncIterator[bytes]: + """ + A byte-iterator over the decoded response content. + + This automatically handles gzip, deflate and brotli encoded responses. + """ + async for chunk in self.http_response.aiter_bytes(chunk_size): + yield chunk + + async def iter_text(self, chunk_size: int | None = None) -> AsyncIterator[str]: + """A str-iterator over the decoded response content + that handles both gzip, deflate, etc but also detects the content's + string encoding. + """ + async for chunk in self.http_response.aiter_text(chunk_size): + yield chunk + + async def iter_lines(self) -> AsyncIterator[str]: + """Like `iter_text()` but will only yield chunks for each line""" + async for chunk in self.http_response.aiter_lines(): + yield chunk + + +class BinaryAPIResponse(APIResponse[bytes]): + """Subclass of APIResponse providing helpers for dealing with binary data. + + Note: If you want to stream the response data instead of eagerly reading it + all at once then you should use `.with_streaming_response` when making + the API request, e.g. `.with_streaming_response.get_binary_response()` + """ + + def write_to_file( + self, + file: str | os.PathLike[str], + ) -> None: + """Write the output to the given file. + + Accepts a filename or any path-like object, e.g. pathlib.Path + + Note: if you want to stream the data to the file instead of writing + all at once then you should use `.with_streaming_response` when making + the API request, e.g. `.with_streaming_response.get_binary_response()` + """ + with open(file, mode="wb") as f: + for data in self.iter_bytes(): + f.write(data) + + +class AsyncBinaryAPIResponse(AsyncAPIResponse[bytes]): + """Subclass of APIResponse providing helpers for dealing with binary data. + + Note: If you want to stream the response data instead of eagerly reading it + all at once then you should use `.with_streaming_response` when making + the API request, e.g. `.with_streaming_response.get_binary_response()` + """ + + async def write_to_file( + self, + file: str | os.PathLike[str], + ) -> None: + """Write the output to the given file. + + Accepts a filename or any path-like object, e.g. pathlib.Path + + Note: if you want to stream the data to the file instead of writing + all at once then you should use `.with_streaming_response` when making + the API request, e.g. `.with_streaming_response.get_binary_response()` + """ + path = anyio.Path(file) + async with await path.open(mode="wb") as f: + async for data in self.iter_bytes(): + await f.write(data) + + +class StreamedBinaryAPIResponse(APIResponse[bytes]): + def stream_to_file( + self, + file: str | os.PathLike[str], + *, + chunk_size: int | None = None, + ) -> None: + """Streams the output to the given file. + + Accepts a filename or any path-like object, e.g. pathlib.Path + """ + with open(file, mode="wb") as f: + for data in self.iter_bytes(chunk_size): + f.write(data) + + +class AsyncStreamedBinaryAPIResponse(AsyncAPIResponse[bytes]): + async def stream_to_file( + self, + file: str | os.PathLike[str], + *, + chunk_size: int | None = None, + ) -> None: + """Streams the output to the given file. + + Accepts a filename or any path-like object, e.g. pathlib.Path + """ + path = anyio.Path(file) + async with await path.open(mode="wb") as f: + async for data in self.iter_bytes(chunk_size): + await f.write(data) + + +class MissingStreamClassError(TypeError): + def __init__(self) -> None: + super().__init__( + "The `stream` argument was set to `True` but the `stream_cls` argument was not given. See `gumlet._streaming` for reference", + ) + + +class StreamAlreadyConsumed(GumletError): + """ + Attempted to read or stream content, but the content has already + been streamed. + + This can happen if you use a method like `.iter_lines()` and then attempt + to read th entire response body afterwards, e.g. + + ```py + response = await client.post(...) + async for line in response.iter_lines(): + ... # do something with `line` + + content = await response.read() + # ^ error + ``` + + If you want this behaviour you'll need to either manually accumulate the response + content or call `await response.read()` before iterating over the stream. + """ + + def __init__(self) -> None: + message = ( + "Attempted to read or stream some content, but the content has " + "already been streamed. " + "This could be due to attempting to stream the response " + "content more than once." + "\n\n" + "You can fix this by manually accumulating the response content while streaming " + "or by calling `.read()` before starting to stream." + ) + super().__init__(message) + + +class ResponseContextManager(Generic[_APIResponseT]): + """Context manager for ensuring that a request is not made + until it is entered and that the response will always be closed + when the context manager exits + """ + + def __init__(self, request_func: Callable[[], _APIResponseT]) -> None: + self._request_func = request_func + self.__response: _APIResponseT | None = None + + def __enter__(self) -> _APIResponseT: + self.__response = self._request_func() + return self.__response + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + if self.__response is not None: + self.__response.close() + + +class AsyncResponseContextManager(Generic[_AsyncAPIResponseT]): + """Context manager for ensuring that a request is not made + until it is entered and that the response will always be closed + when the context manager exits + """ + + def __init__(self, api_request: Awaitable[_AsyncAPIResponseT]) -> None: + self._api_request = api_request + self.__response: _AsyncAPIResponseT | None = None + + async def __aenter__(self) -> _AsyncAPIResponseT: + self.__response = await self._api_request + return self.__response + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + if self.__response is not None: + await self.__response.close() + + +def to_streamed_response_wrapper(func: Callable[P, R]) -> Callable[P, ResponseContextManager[APIResponse[R]]]: + """Higher order function that takes one of our bound API methods and wraps it + to support streaming and returning the raw `APIResponse` object directly. + """ + + @functools.wraps(func) + def wrapped(*args: P.args, **kwargs: P.kwargs) -> ResponseContextManager[APIResponse[R]]: + extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers[RAW_RESPONSE_HEADER] = "stream" + + kwargs["extra_headers"] = extra_headers + + make_request = functools.partial(func, *args, **kwargs) + + return ResponseContextManager(cast(Callable[[], APIResponse[R]], make_request)) + + return wrapped + + +def async_to_streamed_response_wrapper( + func: Callable[P, Awaitable[R]], +) -> Callable[P, AsyncResponseContextManager[AsyncAPIResponse[R]]]: + """Higher order function that takes one of our bound API methods and wraps it + to support streaming and returning the raw `APIResponse` object directly. + """ + + @functools.wraps(func) + def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncResponseContextManager[AsyncAPIResponse[R]]: + extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers[RAW_RESPONSE_HEADER] = "stream" + + kwargs["extra_headers"] = extra_headers + + make_request = func(*args, **kwargs) + + return AsyncResponseContextManager(cast(Awaitable[AsyncAPIResponse[R]], make_request)) + + return wrapped + + +def to_custom_streamed_response_wrapper( + func: Callable[P, object], + response_cls: type[_APIResponseT], +) -> Callable[P, ResponseContextManager[_APIResponseT]]: + """Higher order function that takes one of our bound API methods and an `APIResponse` class + and wraps the method to support streaming and returning the given response class directly. + + Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])` + """ + + @functools.wraps(func) + def wrapped(*args: P.args, **kwargs: P.kwargs) -> ResponseContextManager[_APIResponseT]: + extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers[RAW_RESPONSE_HEADER] = "stream" + extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls + + kwargs["extra_headers"] = extra_headers + + make_request = functools.partial(func, *args, **kwargs) + + return ResponseContextManager(cast(Callable[[], _APIResponseT], make_request)) + + return wrapped + + +def async_to_custom_streamed_response_wrapper( + func: Callable[P, Awaitable[object]], + response_cls: type[_AsyncAPIResponseT], +) -> Callable[P, AsyncResponseContextManager[_AsyncAPIResponseT]]: + """Higher order function that takes one of our bound API methods and an `APIResponse` class + and wraps the method to support streaming and returning the given response class directly. + + Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])` + """ + + @functools.wraps(func) + def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncResponseContextManager[_AsyncAPIResponseT]: + extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers[RAW_RESPONSE_HEADER] = "stream" + extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls + + kwargs["extra_headers"] = extra_headers + + make_request = func(*args, **kwargs) + + return AsyncResponseContextManager(cast(Awaitable[_AsyncAPIResponseT], make_request)) + + return wrapped + + +def to_raw_response_wrapper(func: Callable[P, R]) -> Callable[P, APIResponse[R]]: + """Higher order function that takes one of our bound API methods and wraps it + to support returning the raw `APIResponse` object directly. + """ + + @functools.wraps(func) + def wrapped(*args: P.args, **kwargs: P.kwargs) -> APIResponse[R]: + extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers[RAW_RESPONSE_HEADER] = "raw" + + kwargs["extra_headers"] = extra_headers + + return cast(APIResponse[R], func(*args, **kwargs)) + + return wrapped + + +def async_to_raw_response_wrapper(func: Callable[P, Awaitable[R]]) -> Callable[P, Awaitable[AsyncAPIResponse[R]]]: + """Higher order function that takes one of our bound API methods and wraps it + to support returning the raw `APIResponse` object directly. + """ + + @functools.wraps(func) + async def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncAPIResponse[R]: + extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers[RAW_RESPONSE_HEADER] = "raw" + + kwargs["extra_headers"] = extra_headers + + return cast(AsyncAPIResponse[R], await func(*args, **kwargs)) + + return wrapped + + +def to_custom_raw_response_wrapper( + func: Callable[P, object], + response_cls: type[_APIResponseT], +) -> Callable[P, _APIResponseT]: + """Higher order function that takes one of our bound API methods and an `APIResponse` class + and wraps the method to support returning the given response class directly. + + Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])` + """ + + @functools.wraps(func) + def wrapped(*args: P.args, **kwargs: P.kwargs) -> _APIResponseT: + extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers[RAW_RESPONSE_HEADER] = "raw" + extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls + + kwargs["extra_headers"] = extra_headers + + return cast(_APIResponseT, func(*args, **kwargs)) + + return wrapped + + +def async_to_custom_raw_response_wrapper( + func: Callable[P, Awaitable[object]], + response_cls: type[_AsyncAPIResponseT], +) -> Callable[P, Awaitable[_AsyncAPIResponseT]]: + """Higher order function that takes one of our bound API methods and an `APIResponse` class + and wraps the method to support returning the given response class directly. + + Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])` + """ + + @functools.wraps(func) + def wrapped(*args: P.args, **kwargs: P.kwargs) -> Awaitable[_AsyncAPIResponseT]: + extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers[RAW_RESPONSE_HEADER] = "raw" + extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls + + kwargs["extra_headers"] = extra_headers + + return cast(Awaitable[_AsyncAPIResponseT], func(*args, **kwargs)) + + return wrapped + + +def extract_response_type(typ: type[BaseAPIResponse[Any]]) -> type: + """Given a type like `APIResponse[T]`, returns the generic type variable `T`. + + This also handles the case where a concrete subclass is given, e.g. + ```py + class MyResponse(APIResponse[bytes]): + ... + + extract_response_type(MyResponse) -> bytes + ``` + """ + return extract_type_var_from_base( + typ, + generic_bases=cast("tuple[type, ...]", (BaseAPIResponse, APIResponse, AsyncAPIResponse)), + index=0, + ) diff --git a/src/gumlet/_send_queue.py b/src/gumlet/_send_queue.py new file mode 100644 index 0000000..3140d45 --- /dev/null +++ b/src/gumlet/_send_queue.py @@ -0,0 +1,90 @@ +# File generated from our OpenAPI spec by Scalar. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import typing +import threading + +from ._exceptions import WebSocketQueueFullError + + +class SendQueue: + """Bounded byte-size queue for outgoing WebSocket messages. + + Messages are stored as pre-serialized strings. The queue enforces a + maximum byte budget so that unbounded buffering cannot occur during + reconnection windows. + """ + + def __init__(self, max_bytes: int = 1_048_576) -> None: + self._queue: list[tuple[str, int]] = [] # (data, byte_length) + self._bytes: int = 0 + self._max_bytes = max_bytes + self._lock = threading.Lock() + + def enqueue(self, data: str) -> None: + """Append *data* to the queue. + + Raises :class:`WebSocketQueueFullError` if the message would + exceed the byte-size limit. + """ + byte_length = len(data.encode("utf-8")) + with self._lock: + if self._bytes + byte_length > self._max_bytes: + raise WebSocketQueueFullError("send queue is full, message discarded") + self._queue.append((data, byte_length)) + self._bytes += byte_length + + def flush_sync(self, send: typing.Callable[[str], object]) -> None: + """Send every queued message via *send*. + + If *send* raises, the failing message and all subsequent messages + are re-queued and the error is re-raised. + """ + with self._lock: + pending = list(self._queue) + self._queue.clear() + self._bytes = 0 + + for i, (data, _byte_length) in enumerate(pending): + try: + send(data) + except Exception: + with self._lock: + remaining = pending[i:] + self._queue = remaining + self._queue + self._bytes = sum(bl for _, bl in self._queue) + raise + + async def flush_async(self, send: typing.Callable[[str], typing.Awaitable[object]]) -> None: + """Async variant of :meth:`flush_sync`.""" + with self._lock: + pending = list(self._queue) + self._queue.clear() + self._bytes = 0 + + for i, (data, _byte_length) in enumerate(pending): + try: + await send(data) + except Exception: + with self._lock: + remaining = pending[i:] + self._queue = remaining + self._queue + self._bytes = sum(bl for _, bl in self._queue) + raise + + def drain(self) -> list[str]: + """Remove and return all queued messages.""" + with self._lock: + items = [data for data, _ in self._queue] + self._queue.clear() + self._bytes = 0 + return items + + def __len__(self) -> int: + with self._lock: + return len(self._queue) + + def __bool__(self) -> bool: + with self._lock: + return len(self._queue) > 0 diff --git a/src/gumlet/_streaming.py b/src/gumlet/_streaming.py new file mode 100644 index 0000000..c1e7590 --- /dev/null +++ b/src/gumlet/_streaming.py @@ -0,0 +1,350 @@ +# Note: initially copied from https://github.com/florimondmanca/httpx-sse/blob/master/src/httpx_sse/_decoders.py +from __future__ import annotations + +import json +import inspect +from types import TracebackType +from typing import TYPE_CHECKING, Any, Generic, TypeVar, Iterator, Optional, AsyncIterator, cast +from typing_extensions import Self, Protocol, TypeGuard, override, get_origin, runtime_checkable + +import httpx + +from ._utils import extract_type_var_from_base + +if TYPE_CHECKING: + from ._client import Gumlet, AsyncGumlet + from ._models import FinalRequestOptions + + +_T = TypeVar("_T") + + +class Stream(Generic[_T]): + """Provides the core interface to iterate over a synchronous stream response.""" + + response: httpx.Response + _options: Optional[FinalRequestOptions] = None + _decoder: SSEBytesDecoder + + def __init__( + self, + *, + cast_to: type[_T], + response: httpx.Response, + client: Gumlet, + options: Optional[FinalRequestOptions] = None, + ) -> None: + self.response = response + self._cast_to = cast_to + self._client = client + self._options = options + self._decoder = client._make_sse_decoder() + self._iterator = self.__stream__() + + def __next__(self) -> _T: + return self._iterator.__next__() + + def __iter__(self) -> Iterator[_T]: + for item in self._iterator: + yield item + + def _iter_events(self) -> Iterator[ServerSentEvent]: + yield from self._decoder.iter_bytes(self.response.iter_bytes()) + + def __stream__(self) -> Iterator[_T]: + cast_to = cast(Any, self._cast_to) + response = self.response + process_data = self._client._process_response_data + iterator = self._iter_events() + + try: + for sse in iterator: + if not sse.data: + continue + try: + payload = sse.json() + except Exception: + payload = sse.data + yield process_data(data=payload, cast_to=cast_to, response=response) + finally: + # Ensure the response is closed even if the consumer doesn't read all data + response.close() + + def __enter__(self) -> Self: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self.close() + + def close(self) -> None: + """ + Close the response and release the connection. + + Automatically called if the response body is read to completion. + """ + self.response.close() + + +class AsyncStream(Generic[_T]): + """Provides the core interface to iterate over an asynchronous stream response.""" + + response: httpx.Response + _options: Optional[FinalRequestOptions] = None + _decoder: SSEDecoder | SSEBytesDecoder + + def __init__( + self, + *, + cast_to: type[_T], + response: httpx.Response, + client: AsyncGumlet, + options: Optional[FinalRequestOptions] = None, + ) -> None: + self.response = response + self._cast_to = cast_to + self._client = client + self._options = options + self._decoder = client._make_sse_decoder() + self._iterator = self.__stream__() + + async def __anext__(self) -> _T: + return await self._iterator.__anext__() + + async def __aiter__(self) -> AsyncIterator[_T]: + async for item in self._iterator: + yield item + + async def _iter_events(self) -> AsyncIterator[ServerSentEvent]: + async for sse in self._decoder.aiter_bytes(self.response.aiter_bytes()): + yield sse + + async def __stream__(self) -> AsyncIterator[_T]: + cast_to = cast(Any, self._cast_to) + response = self.response + process_data = self._client._process_response_data + iterator = self._iter_events() + + try: + async for sse in iterator: + if not sse.data: + continue + try: + payload = sse.json() + except Exception: + payload = sse.data + yield process_data(data=payload, cast_to=cast_to, response=response) + finally: + # Ensure the response is closed even if the consumer doesn't read all data + await response.aclose() + + async def __aenter__(self) -> Self: + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + await self.close() + + async def close(self) -> None: + """ + Close the response and release the connection. + + Automatically called if the response body is read to completion. + """ + await self.response.aclose() + + +class ServerSentEvent: + def __init__( + self, + *, + event: str | None = None, + data: str | None = None, + id: str | None = None, + retry: int | None = None, + ) -> None: + if data is None: + data = "" + + self._id = id + self._data = data + self._event = event or None + self._retry = retry + + @property + def event(self) -> str | None: + return self._event + + @property + def id(self) -> str | None: + return self._id + + @property + def retry(self) -> int | None: + return self._retry + + @property + def data(self) -> str: + return self._data + + def json(self) -> Any: + return json.loads(self.data) + + @override + def __repr__(self) -> str: + return f"ServerSentEvent(event={self.event}, data={self.data}, id={self.id}, retry={self.retry})" + + +class SSEDecoder: + _data: list[str] + _event: str | None + _retry: int | None + _last_event_id: str | None + + def __init__(self) -> None: + self._event = None + self._data = [] + self._last_event_id = None + self._retry = None + + def iter_bytes(self, iterator: Iterator[bytes]) -> Iterator[ServerSentEvent]: + """Given an iterator that yields raw binary data, iterate over it & yield every event encountered""" + for chunk in self._iter_chunks(iterator): + # Split before decoding so splitlines() only uses \r and \n + for raw_line in chunk.splitlines(): + line = raw_line.decode("utf-8") + sse = self.decode(line) + if sse: + yield sse + + def _iter_chunks(self, iterator: Iterator[bytes]) -> Iterator[bytes]: + """Given an iterator that yields raw binary data, iterate over it and yield individual SSE chunks""" + data = b"" + for chunk in iterator: + for line in chunk.splitlines(keepends=True): + data += line + if data.endswith((b"\r\r", b"\n\n", b"\r\n\r\n")): + yield data + data = b"" + if data: + yield data + + async def aiter_bytes(self, iterator: AsyncIterator[bytes]) -> AsyncIterator[ServerSentEvent]: + """Given an iterator that yields raw binary data, iterate over it & yield every event encountered""" + async for chunk in self._aiter_chunks(iterator): + # Split before decoding so splitlines() only uses \r and \n + for raw_line in chunk.splitlines(): + line = raw_line.decode("utf-8") + sse = self.decode(line) + if sse: + yield sse + + async def _aiter_chunks(self, iterator: AsyncIterator[bytes]) -> AsyncIterator[bytes]: + """Given an iterator that yields raw binary data, iterate over it and yield individual SSE chunks""" + data = b"" + async for chunk in iterator: + for line in chunk.splitlines(keepends=True): + data += line + if data.endswith((b"\r\r", b"\n\n", b"\r\n\r\n")): + yield data + data = b"" + if data: + yield data + + def decode(self, line: str) -> ServerSentEvent | None: + # See: https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation # noqa: E501 + + if not line: + if not self._event and not self._data and not self._last_event_id and self._retry is None: + return None + + sse = ServerSentEvent( + event=self._event, + data="\n".join(self._data), + id=self._last_event_id, + retry=self._retry, + ) + + # NOTE: as per the SSE spec, do not reset last_event_id. + self._event = None + self._data = [] + self._retry = None + + return sse + + if line.startswith(":"): + return None + + fieldname, _, value = line.partition(":") + + if value.startswith(" "): + value = value[1:] + + if fieldname == "event": + self._event = value + elif fieldname == "data": + self._data.append(value) + elif fieldname == "id": + if "\0" in value: + pass + else: + self._last_event_id = value + elif fieldname == "retry": + try: + self._retry = int(value) + except (TypeError, ValueError): + pass + else: + pass # Field is ignored. + + return None + + +@runtime_checkable +class SSEBytesDecoder(Protocol): + def iter_bytes(self, iterator: Iterator[bytes]) -> Iterator[ServerSentEvent]: + """Given an iterator that yields raw binary data, iterate over it & yield every event encountered""" + ... + + def aiter_bytes(self, iterator: AsyncIterator[bytes]) -> AsyncIterator[ServerSentEvent]: + """Given an async iterator that yields raw binary data, iterate over it & yield every event encountered""" + ... + + +def is_stream_class_type(typ: type) -> TypeGuard[type[Stream[object]] | type[AsyncStream[object]]]: + """TypeGuard for determining whether or not the given type is a subclass of `Stream` / `AsyncStream`""" + origin = get_origin(typ) or typ + return inspect.isclass(origin) and issubclass(origin, (Stream, AsyncStream)) + + +def extract_stream_chunk_type( + stream_cls: type, + *, + failure_message: str | None = None, +) -> type: + """Given a type like `Stream[T]`, returns the generic type variable `T`. + + This also handles the case where a concrete subclass is given, e.g. + ```py + class MyStream(Stream[bytes]): + ... + + extract_stream_chunk_type(MyStream) -> bytes + ``` + """ + from ._base_client import Stream, AsyncStream + + return extract_type_var_from_base( + stream_cls, + index=0, + generic_bases=cast("tuple[type, ...]", (Stream, AsyncStream)), + failure_message=failure_message, + ) diff --git a/src/gumlet/_types.py b/src/gumlet/_types.py new file mode 100644 index 0000000..3a1d634 --- /dev/null +++ b/src/gumlet/_types.py @@ -0,0 +1,274 @@ +from __future__ import annotations + +from os import PathLike +from typing import ( + IO, + TYPE_CHECKING, + Any, + Dict, + List, + Type, + Tuple, + Union, + Mapping, + TypeVar, + Callable, + Iterable, + Iterator, + Optional, + Sequence, + AsyncIterable, +) +from typing_extensions import ( + Set, + Literal, + Protocol, + TypeAlias, + TypedDict, + SupportsIndex, + overload, + override, + runtime_checkable, +) + +import httpx +import pydantic +from httpx import URL, Proxy, Timeout, Response, BaseTransport, AsyncBaseTransport + +if TYPE_CHECKING: + from ._models import BaseModel, SecurityOptions + from ._response import APIResponse, AsyncAPIResponse + +Transport = BaseTransport +AsyncTransport = AsyncBaseTransport +Query = Mapping[str, object] +Body = object +AnyMapping = Mapping[str, object] +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 +ProxiesDict = Dict["str | URL", Union[None, str, URL, Proxy]] +ProxiesTypes = Union[str, Proxy, ProxiesDict] +if TYPE_CHECKING: + Base64FileInput = Union[IO[bytes], PathLike[str]] + FileContent = Union[IO[bytes], bytes, PathLike[str]] +else: + Base64FileInput = Union[IO[bytes], PathLike] + FileContent = Union[IO[bytes], bytes, PathLike] # PathLike is not subscriptable in Python 3.8. + + +# Used for sending raw binary data / streaming data in request bodies +# e.g. for file uploads without multipart encoding +BinaryTypes = Union[bytes, bytearray, IO[bytes], Iterable[bytes]] +AsyncBinaryTypes = Union[bytes, bytearray, IO[bytes], AsyncIterable[bytes]] + +FileTypes = Union[ + # file (or bytes) + FileContent, + # (filename, file (or bytes)) + Tuple[Optional[str], FileContent], + # (filename, file (or bytes), content_type) + Tuple[Optional[str], FileContent, Optional[str]], + # (filename, file (or bytes), content_type, headers) + Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], +] +RequestFiles = Union[Mapping[str, FileTypes], Sequence[Tuple[str, FileTypes]]] + +# duplicate of the above but without our custom file support +HttpxFileContent = Union[IO[bytes], bytes] +HttpxFileTypes = Union[ + # file (or bytes) + HttpxFileContent, + # (filename, file (or bytes)) + Tuple[Optional[str], HttpxFileContent], + # (filename, file (or bytes), content_type) + Tuple[Optional[str], HttpxFileContent, Optional[str]], + # (filename, file (or bytes), content_type, headers) + Tuple[Optional[str], HttpxFileContent, Optional[str], Mapping[str, str]], +] +HttpxRequestFiles = Union[Mapping[str, HttpxFileTypes], Sequence[Tuple[str, HttpxFileTypes]]] + +# Workaround to support (cast_to: Type[ResponseT]) -> ResponseT +# where ResponseT includes `None`. In order to support directly +# passing `None`, overloads would have to be defined for every +# method that uses `ResponseT` which would lead to an unacceptable +# amount of code duplication and make it unreadable. See _base_client.py +# for example usage. +# +# This unfortunately means that you will either have +# to import this type and pass it explicitly: +# +# from gumlet import NoneType +# client.get('/foo', cast_to=NoneType) +# +# or build it yourself: +# +# client.get('/foo', cast_to=type(None)) +if TYPE_CHECKING: + NoneType: Type[None] +else: + NoneType = type(None) + + +class RequestOptions(TypedDict, total=False): + headers: Headers + max_retries: int + timeout: float | Timeout | None + params: Query + extra_json: AnyMapping + idempotency_key: str + follow_redirects: bool + security: SecurityOptions + + +# Sentinel class used until PEP 0661 is accepted +class NotGiven: + """ + For parameters with a meaningful None value, we need to distinguish between + the user explicitly passing None, and the user not passing the parameter at + all. + + User code shouldn't need to use not_given directly. + + For example: + + ```py + def create(timeout: Timeout | None | NotGiven = not_given): ... + + + create(timeout=1) # 1s timeout + create(timeout=None) # No timeout + create() # Default timeout behavior + ``` + """ + + def __bool__(self) -> Literal[False]: + return False + + @override + def __repr__(self) -> str: + return "NOT_GIVEN" + + +not_given = NotGiven() +# for backwards compatibility: +NOT_GIVEN = NotGiven() + + +class Omit: + """ + To explicitly omit something from being sent in a request, use `omit`. + + ```py + # as the default `Content-Type` header is `application/json` that will be sent + client.post("/upload/files", files={"file": b"my raw file content"}) + + # you can't explicitly override the header as it has to be dynamically generated + # to look something like: 'multipart/form-data; boundary=0d8382fcf5f8c3be01ca2e11002d2983' + client.post(..., headers={"Content-Type": "multipart/form-data"}) + + # instead you can remove the default `application/json` header by passing omit + client.post(..., headers={"Content-Type": omit}) + ``` + """ + + def __bool__(self) -> Literal[False]: + return False + + +omit = Omit() + + +@runtime_checkable +class ModelBuilderProtocol(Protocol): + @classmethod + def build( + cls: type[_T], + *, + response: Response, + data: object, + ) -> _T: ... + + +Headers = Mapping[str, Union[str, Omit]] + + +class HeadersLikeProtocol(Protocol): + def get(self, __key: str) -> str | None: ... + + +HeadersLike = Union[Headers, HeadersLikeProtocol] + +ResponseT = TypeVar( + "ResponseT", + bound=Union[ + object, + str, + None, + "BaseModel", + List[Any], + Dict[str, Any], + Response, + ModelBuilderProtocol, + "APIResponse[Any]", + "AsyncAPIResponse[Any]", + ], +) + +StrBytesIntFloat = Union[str, bytes, int, float] + +# Note: copied from Pydantic +# https://github.com/pydantic/pydantic/blob/6f31f8f68ef011f84357330186f603ff295312fd/pydantic/main.py#L79 +IncEx: TypeAlias = Union[Set[int], Set[str], Mapping[int, Union["IncEx", bool]], Mapping[str, Union["IncEx", bool]]] + +PostParser = Callable[[Any], Any] + + +@runtime_checkable +class InheritsGeneric(Protocol): + """Represents a type that has inherited from `Generic` + + The `__orig_bases__` property can be used to determine the resolved + type variable for a given base class. + """ + + __orig_bases__: tuple[_GenericAlias] + + +class _GenericAlias(Protocol): + __origin__: type[object] + + +class HttpxSendArgs(TypedDict, total=False): + auth: httpx.Auth + follow_redirects: bool + + +_T_co = TypeVar("_T_co", covariant=True) + + +if TYPE_CHECKING: + # This works because str.__contains__ does not accept object (either in typeshed or at runtime) + # https://github.com/hauntsaninja/useful_types/blob/5e9710f3875107d068e7679fd7fec9cfab0eff3b/useful_types/__init__.py#L285 + # + # Note: index() and count() methods are intentionally omitted to allow pyright to properly + # infer TypedDict types when dict literals are used in lists assigned to SequenceNotStr. + class SequenceNotStr(Protocol[_T_co]): + @overload + def __getitem__(self, index: SupportsIndex, /) -> _T_co: ... + @overload + def __getitem__(self, index: slice, /) -> Sequence[_T_co]: ... + def __contains__(self, value: object, /) -> bool: ... + def __len__(self) -> int: ... + def __iter__(self) -> Iterator[_T_co]: ... + def __reversed__(self) -> Iterator[_T_co]: ... +else: + # just point this to a normal `Sequence` at runtime to avoid having to special case + # deserializing our custom sequence type + SequenceNotStr = Sequence diff --git a/src/gumlet/_utils/__init__.py b/src/gumlet/_utils/__init__.py new file mode 100644 index 0000000..1c090e5 --- /dev/null +++ b/src/gumlet/_utils/__init__.py @@ -0,0 +1,64 @@ +from ._path import path_template as path_template +from ._sync import asyncify as asyncify +from ._proxy import LazyProxy as LazyProxy +from ._utils import ( + flatten as flatten, + is_dict as is_dict, + is_list as is_list, + is_given as is_given, + is_tuple as is_tuple, + json_safe as json_safe, + lru_cache as lru_cache, + is_mapping as is_mapping, + is_tuple_t as is_tuple_t, + is_iterable as is_iterable, + is_sequence as is_sequence, + coerce_float as coerce_float, + is_mapping_t as is_mapping_t, + removeprefix as removeprefix, + removesuffix as removesuffix, + extract_files as extract_files, + is_sequence_t as is_sequence_t, + required_args as required_args, + coerce_boolean as coerce_boolean, + coerce_integer as coerce_integer, + file_from_path as file_from_path, + strip_not_given as strip_not_given, + get_async_library as get_async_library, + maybe_coerce_float as maybe_coerce_float, + get_required_header as get_required_header, + maybe_coerce_boolean as maybe_coerce_boolean, + maybe_coerce_integer as maybe_coerce_integer, +) +from ._compat import ( + get_args as get_args, + is_union as is_union, + get_origin as get_origin, + is_typeddict as is_typeddict, + is_literal_type as is_literal_type, +) +from ._typing import ( + is_list_type as is_list_type, + is_union_type as is_union_type, + extract_type_arg as extract_type_arg, + is_iterable_type as is_iterable_type, + is_required_type as is_required_type, + is_sequence_type as is_sequence_type, + is_annotated_type as is_annotated_type, + is_type_alias_type as is_type_alias_type, + strip_annotated_type as strip_annotated_type, + extract_type_var_from_base as extract_type_var_from_base, +) +from ._streams import consume_sync_iterator as consume_sync_iterator, consume_async_iterator as consume_async_iterator +from ._transform import ( + PropertyInfo as PropertyInfo, + transform as transform, + async_transform as async_transform, + maybe_transform as maybe_transform, + async_maybe_transform as async_maybe_transform, +) +from ._reflection import ( + function_has_argument as function_has_argument, + assert_signatures_in_sync as assert_signatures_in_sync, +) +from ._datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime diff --git a/src/gumlet/_utils/_compat.py b/src/gumlet/_utils/_compat.py new file mode 100644 index 0000000..2c70b29 --- /dev/null +++ b/src/gumlet/_utils/_compat.py @@ -0,0 +1,45 @@ +from __future__ import annotations + +import sys +import typing_extensions +from typing import Any, Type, Union, Literal, Optional +from datetime import date, datetime +from typing_extensions import get_args as _get_args, get_origin as _get_origin + +from .._types import StrBytesIntFloat +from ._datetime_parse import parse_date as _parse_date, parse_datetime as _parse_datetime + +_LITERAL_TYPES = {Literal, typing_extensions.Literal} + + +def get_args(tp: type[Any]) -> tuple[Any, ...]: + return _get_args(tp) + + +def get_origin(tp: type[Any]) -> type[Any] | None: + return _get_origin(tp) + + +def is_union(tp: Optional[Type[Any]]) -> bool: + if sys.version_info < (3, 10): + return tp is Union # type: ignore[comparison-overlap] + else: + import types + + return tp is Union or tp is types.UnionType # type: ignore[comparison-overlap] + + +def is_typeddict(tp: Type[Any]) -> bool: + return typing_extensions.is_typeddict(tp) + + +def is_literal_type(tp: Type[Any]) -> bool: + return get_origin(tp) in _LITERAL_TYPES + + +def parse_date(value: Union[date, StrBytesIntFloat]) -> date: + return _parse_date(value) + + +def parse_datetime(value: Union[datetime, StrBytesIntFloat]) -> datetime: + return _parse_datetime(value) diff --git a/src/gumlet/_utils/_datetime_parse.py b/src/gumlet/_utils/_datetime_parse.py new file mode 100644 index 0000000..7cb9d9e --- /dev/null +++ b/src/gumlet/_utils/_datetime_parse.py @@ -0,0 +1,136 @@ +""" +This file contains code from https://github.com/pydantic/pydantic/blob/main/pydantic/v1/datetime_parse.py +without the Pydantic v1 specific errors. +""" + +from __future__ import annotations + +import re +from typing import Dict, Union, Optional +from datetime import date, datetime, timezone, timedelta + +from .._types import StrBytesIntFloat + +date_expr = r"(?P\d{4})-(?P\d{1,2})-(?P\d{1,2})" +time_expr = ( + r"(?P\d{1,2}):(?P\d{1,2})" + r"(?::(?P\d{1,2})(?:\.(?P\d{1,6})\d{0,6})?)?" + r"(?PZ|[+-]\d{2}(?::?\d{2})?)?$" +) + +date_re = re.compile(f"{date_expr}$") +datetime_re = re.compile(f"{date_expr}[T ]{time_expr}") + + +EPOCH = datetime(1970, 1, 1) +# if greater than this, the number is in ms, if less than or equal it's in seconds +# (in seconds this is 11th October 2603, in ms it's 20th August 1970) +MS_WATERSHED = int(2e10) +# slightly more than datetime.max in ns - (datetime.max - EPOCH).total_seconds() * 1e9 +MAX_NUMBER = int(3e20) + + +def _get_numeric(value: StrBytesIntFloat, native_expected_type: str) -> Union[None, int, float]: + if isinstance(value, (int, float)): + return value + try: + return float(value) + except ValueError: + return None + except TypeError: + raise TypeError(f"invalid type; expected {native_expected_type}, string, bytes, int or float") from None + + +def _from_unix_seconds(seconds: Union[int, float]) -> datetime: + if seconds > MAX_NUMBER: + return datetime.max + elif seconds < -MAX_NUMBER: + return datetime.min + + while abs(seconds) > MS_WATERSHED: + seconds /= 1000 + dt = EPOCH + timedelta(seconds=seconds) + return dt.replace(tzinfo=timezone.utc) + + +def _parse_timezone(value: Optional[str]) -> Union[None, int, timezone]: + if value == "Z": + return timezone.utc + elif value is not None: + offset_mins = int(value[-2:]) if len(value) > 3 else 0 + offset = 60 * int(value[1:3]) + offset_mins + if value[0] == "-": + offset = -offset + return timezone(timedelta(minutes=offset)) + else: + return None + + +def parse_datetime(value: Union[datetime, StrBytesIntFloat]) -> datetime: + """ + Parse a datetime/int/float/string and return a datetime.datetime. + + This function supports time zone offsets. When the input contains one, + the output uses a timezone with a fixed offset from UTC. + + Raise ValueError if the input is well formatted but not a valid datetime. + Raise ValueError if the input isn't well formatted. + """ + if isinstance(value, datetime): + return value + + number = _get_numeric(value, "datetime") + if number is not None: + return _from_unix_seconds(number) + + if isinstance(value, bytes): + value = value.decode() + + assert not isinstance(value, (float, int)) + + match = datetime_re.match(value) + if match is None: + raise ValueError("invalid datetime format") + + kw = match.groupdict() + if kw["microsecond"]: + kw["microsecond"] = kw["microsecond"].ljust(6, "0") + + tzinfo = _parse_timezone(kw.pop("tzinfo")) + kw_: Dict[str, Union[None, int, timezone]] = {k: int(v) for k, v in kw.items() if v is not None} + kw_["tzinfo"] = tzinfo + + return datetime(**kw_) # type: ignore + + +def parse_date(value: Union[date, StrBytesIntFloat]) -> date: + """ + Parse a date/int/float/string and return a datetime.date. + + Raise ValueError if the input is well formatted but not a valid date. + Raise ValueError if the input isn't well formatted. + """ + if isinstance(value, date): + if isinstance(value, datetime): + return value.date() + else: + return value + + number = _get_numeric(value, "date") + if number is not None: + return _from_unix_seconds(number).date() + + if isinstance(value, bytes): + value = value.decode() + + assert not isinstance(value, (float, int)) + match = date_re.match(value) + if match is None: + raise ValueError("invalid date format") + + kw = {k: int(v) for k, v in match.groupdict().items()} + + try: + return date(**kw) + except ValueError: + raise ValueError("invalid date format") from None diff --git a/src/gumlet/_utils/_json.py b/src/gumlet/_utils/_json.py new file mode 100644 index 0000000..6058421 --- /dev/null +++ b/src/gumlet/_utils/_json.py @@ -0,0 +1,35 @@ +import json +from typing import Any +from datetime import datetime +from typing_extensions import override + +import pydantic + +from .._compat import model_dump + + +def openapi_dumps(obj: Any) -> bytes: + """ + Serialize an object to UTF-8 encoded JSON bytes. + + Extends the standard json.dumps with support for additional types + commonly used in the SDK, such as `datetime`, `pydantic.BaseModel`, etc. + """ + return json.dumps( + obj, + cls=_CustomEncoder, + # Uses the same defaults as httpx's JSON serialization + ensure_ascii=False, + separators=(",", ":"), + allow_nan=False, + ).encode() + + +class _CustomEncoder(json.JSONEncoder): + @override + def default(self, o: Any) -> Any: + if isinstance(o, datetime): + return o.isoformat() + if isinstance(o, pydantic.BaseModel): + return model_dump(o, exclude_unset=True, mode="json", by_alias=True) + return super().default(o) diff --git a/src/gumlet/_utils/_logs.py b/src/gumlet/_utils/_logs.py new file mode 100644 index 0000000..1c057d4 --- /dev/null +++ b/src/gumlet/_utils/_logs.py @@ -0,0 +1,25 @@ +import os +import logging + +logger: logging.Logger = logging.getLogger("gumlet") +httpx_logger: logging.Logger = logging.getLogger("httpx") + + +def _basic_config() -> None: + # e.g. [2023-10-05 14:12:26 - gumlet._base_client:818 - DEBUG] HTTP Request: POST http://127.0.0.1:4010/foo/bar "200 OK" + logging.basicConfig( + format="[%(asctime)s - %(name)s:%(lineno)d - %(levelname)s] %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", + ) + + +def setup_logging() -> None: + env = os.environ.get("GUMLET_LOG") + if env == "debug": + _basic_config() + logger.setLevel(logging.DEBUG) + httpx_logger.setLevel(logging.DEBUG) + elif env == "info": + _basic_config() + logger.setLevel(logging.INFO) + httpx_logger.setLevel(logging.INFO) diff --git a/src/gumlet/_utils/_path.py b/src/gumlet/_utils/_path.py new file mode 100644 index 0000000..4d6e1e4 --- /dev/null +++ b/src/gumlet/_utils/_path.py @@ -0,0 +1,127 @@ +from __future__ import annotations + +import re +from typing import ( + Any, + Mapping, + Callable, +) +from urllib.parse import quote + +# Matches '.' or '..' where each dot is either literal or percent-encoded (%2e / %2E). +_DOT_SEGMENT_RE = re.compile(r"^(?:\.|%2[eE]){1,2}$") + +_PLACEHOLDER_RE = re.compile(r"\{(\w+)\}") + + +def _quote_path_segment_part(value: str) -> str: + """Percent-encode `value` for use in a URI path segment. + + Considers characters not in `pchar` set from RFC 3986 §3.3 to be unsafe. + https://datatracker.ietf.org/doc/html/rfc3986#section-3.3 + """ + # quote() already treats unreserved characters (letters, digits, and -._~) + # as safe, so we only need to add sub-delims, ':', and '@'. + # Notably, unlike the default `safe` for quote(), / is unsafe and must be quoted. + return quote(value, safe="!$&'()*+,;=:@") + + +def _quote_query_part(value: str) -> str: + """Percent-encode `value` for use in a URI query string. + + Considers &, = and characters not in `query` set from RFC 3986 §3.4 to be unsafe. + https://datatracker.ietf.org/doc/html/rfc3986#section-3.4 + """ + return quote(value, safe="!$'()*+,;:@/?") + + +def _quote_fragment_part(value: str) -> str: + """Percent-encode `value` for use in a URI fragment. + + Considers characters not in `fragment` set from RFC 3986 §3.5 to be unsafe. + https://datatracker.ietf.org/doc/html/rfc3986#section-3.5 + """ + return quote(value, safe="!$&'()*+,;=:@/?") + + +def _interpolate( + template: str, + values: Mapping[str, Any], + quoter: Callable[[str], str], +) -> str: + """Replace {name} placeholders in `template`, quoting each value with `quoter`. + + Placeholder names are looked up in `values`. + + Raises: + KeyError: If a placeholder is not found in `values`. + """ + # re.split with a capturing group returns alternating + # [text, name, text, name, ..., text] elements. + parts = _PLACEHOLDER_RE.split(template) + + for i in range(1, len(parts), 2): + name = parts[i] + if name not in values: + raise KeyError(f"a value for placeholder {{{name}}} was not provided") + val = values[name] + if val is None: + parts[i] = "null" + elif isinstance(val, bool): + parts[i] = "true" if val else "false" + else: + parts[i] = quoter(str(values[name])) + + return "".join(parts) + + +def path_template(template: str, /, **kwargs: Any) -> str: + """Interpolate {name} placeholders in `template` from keyword arguments. + + Args: + template: The template string containing {name} placeholders. + **kwargs: Keyword arguments to interpolate into the template. + + Returns: + The template with placeholders interpolated and percent-encoded. + + Safe characters for percent-encoding are dependent on the URI component. + Placeholders in path and fragment portions are percent-encoded where the `segment` + and `fragment` sets from RFC 3986 respectively are considered safe. + Placeholders in the query portion are percent-encoded where the `query` set from + RFC 3986 §3.3 is considered safe except for = and & characters. + + Raises: + KeyError: If a placeholder is not found in `kwargs`. + ValueError: If resulting path contains /./ or /../ segments (including percent-encoded dot-segments). + """ + # Split the template into path, query, and fragment portions. + fragment_template: str | None = None + query_template: str | None = None + + rest = template + if "#" in rest: + rest, fragment_template = rest.split("#", 1) + if "?" in rest: + rest, query_template = rest.split("?", 1) + path_template = rest + + # Interpolate each portion with the appropriate quoting rules. + path_result = _interpolate(path_template, kwargs, _quote_path_segment_part) + + # Reject dot-segments (. and ..) in the final assembled path. The check + # runs after interpolation so that adjacent placeholders or a mix of static + # text and placeholders that together form a dot-segment are caught. + # Also reject percent-encoded dot-segments to protect against incorrectly + # implemented normalization in servers/proxies. + for segment in path_result.split("/"): + if _DOT_SEGMENT_RE.match(segment): + raise ValueError(f"Constructed path {path_result!r} contains dot-segment {segment!r} which is not allowed") + + result = path_result + if query_template is not None: + result += "?" + _interpolate(query_template, kwargs, _quote_query_part) + if fragment_template is not None: + result += "#" + _interpolate(fragment_template, kwargs, _quote_fragment_part) + + return result diff --git a/src/gumlet/_utils/_proxy.py b/src/gumlet/_utils/_proxy.py new file mode 100644 index 0000000..0f239a3 --- /dev/null +++ b/src/gumlet/_utils/_proxy.py @@ -0,0 +1,65 @@ +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import Generic, TypeVar, Iterable, cast +from typing_extensions import override + +T = TypeVar("T") + + +class LazyProxy(Generic[T], ABC): + """Implements data methods to pretend that an instance is another instance. + + This includes forwarding attribute access and other methods. + """ + + # Note: we have to special case proxies that themselves return proxies + # to support using a proxy as a catch-all for any random access, e.g. `proxy.foo.bar.baz` + + def __getattr__(self, attr: str) -> object: + proxied = self.__get_proxied__() + if isinstance(proxied, LazyProxy): + return proxied # pyright: ignore + return getattr(proxied, attr) + + @override + def __repr__(self) -> str: + proxied = self.__get_proxied__() + if isinstance(proxied, LazyProxy): + return proxied.__class__.__name__ + return repr(self.__get_proxied__()) + + @override + def __str__(self) -> str: + proxied = self.__get_proxied__() + if isinstance(proxied, LazyProxy): + return proxied.__class__.__name__ + return str(proxied) + + @override + def __dir__(self) -> Iterable[str]: + proxied = self.__get_proxied__() + if isinstance(proxied, LazyProxy): + return [] + return proxied.__dir__() + + @property # type: ignore + @override + def __class__(self) -> type: # pyright: ignore + try: + proxied = self.__get_proxied__() + except Exception: + return type(self) + if issubclass(type(proxied), LazyProxy): + return type(proxied) + return proxied.__class__ + + def __get_proxied__(self) -> T: + return self.__load__() + + def __as_proxied__(self) -> T: + """Helper method that returns the current proxy, typed as the loaded object""" + return cast(T, self) + + @abstractmethod + def __load__(self) -> T: ... diff --git a/src/gumlet/_utils/_reflection.py b/src/gumlet/_utils/_reflection.py new file mode 100644 index 0000000..89aa712 --- /dev/null +++ b/src/gumlet/_utils/_reflection.py @@ -0,0 +1,42 @@ +from __future__ import annotations + +import inspect +from typing import Any, Callable + + +def function_has_argument(func: Callable[..., Any], arg_name: str) -> bool: + """Returns whether or not the given function has a specific parameter""" + sig = inspect.signature(func) + return arg_name in sig.parameters + + +def assert_signatures_in_sync( + source_func: Callable[..., Any], + check_func: Callable[..., Any], + *, + exclude_params: set[str] = set(), +) -> None: + """Ensure that the signature of the second function matches the first.""" + + check_sig = inspect.signature(check_func) + source_sig = inspect.signature(source_func) + + errors: list[str] = [] + + for name, source_param in source_sig.parameters.items(): + if name in exclude_params: + continue + + custom_param = check_sig.parameters.get(name) + if not custom_param: + errors.append(f"the `{name}` param is missing") + continue + + if custom_param.annotation != source_param.annotation: + errors.append( + f"types for the `{name}` param are do not match; source={repr(source_param.annotation)} checking={repr(custom_param.annotation)}" + ) + continue + + if errors: + raise AssertionError(f"{len(errors)} errors encountered when comparing signatures:\n\n" + "\n\n".join(errors)) diff --git a/src/gumlet/_utils/_resources_proxy.py b/src/gumlet/_utils/_resources_proxy.py new file mode 100644 index 0000000..a6d42f0 --- /dev/null +++ b/src/gumlet/_utils/_resources_proxy.py @@ -0,0 +1,24 @@ +from __future__ import annotations + +from typing import Any +from typing_extensions import override + +from ._proxy import LazyProxy + + +class ResourcesProxy(LazyProxy[Any]): + """A proxy for the `gumlet.resources` module. + + This is used so that we can lazily import `gumlet.resources` only when + needed *and* so that users can just import `gumlet` and reference `gumlet.resources` + """ + + @override + def __load__(self) -> Any: + import importlib + + mod = importlib.import_module("gumlet.resources") + return mod + + +resources = ResourcesProxy().__as_proxied__() diff --git a/src/gumlet/_utils/_streams.py b/src/gumlet/_utils/_streams.py new file mode 100644 index 0000000..f4a0208 --- /dev/null +++ b/src/gumlet/_utils/_streams.py @@ -0,0 +1,12 @@ +from typing import Any +from typing_extensions import Iterator, AsyncIterator + + +def consume_sync_iterator(iterator: Iterator[Any]) -> None: + for _ in iterator: + ... + + +async def consume_async_iterator(iterator: AsyncIterator[Any]) -> None: + async for _ in iterator: + ... diff --git a/src/gumlet/_utils/_sync.py b/src/gumlet/_utils/_sync.py new file mode 100644 index 0000000..f6027c1 --- /dev/null +++ b/src/gumlet/_utils/_sync.py @@ -0,0 +1,58 @@ +from __future__ import annotations + +import asyncio +import functools +from typing import TypeVar, Callable, Awaitable +from typing_extensions import ParamSpec + +import anyio +import sniffio +import anyio.to_thread + +T_Retval = TypeVar("T_Retval") +T_ParamSpec = ParamSpec("T_ParamSpec") + + +async def to_thread( + func: Callable[T_ParamSpec, T_Retval], /, *args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs +) -> T_Retval: + if sniffio.current_async_library() == "asyncio": + return await asyncio.to_thread(func, *args, **kwargs) + + return await anyio.to_thread.run_sync( + functools.partial(func, *args, **kwargs), + ) + + +# inspired by `asyncer`, https://github.com/tiangolo/asyncer +def asyncify(function: Callable[T_ParamSpec, T_Retval]) -> Callable[T_ParamSpec, Awaitable[T_Retval]]: + """ + Take a blocking function and create an async one that receives the same + positional and keyword arguments. + + Usage: + + ```python + def blocking_func(arg1, arg2, kwarg1=None): + # blocking code + return result + + + result = asyncify(blocking_function)(arg1, arg2, kwarg1=value1) + ``` + + ## Arguments + + `function`: a blocking regular callable (e.g. a function) + + ## Return + + An async function that takes the same positional and keyword arguments as the + original one, that when called runs the same original function in a thread worker + and returns the result. + """ + + async def wrapper(*args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs) -> T_Retval: + return await to_thread(function, *args, **kwargs) + + return wrapper diff --git a/src/gumlet/_utils/_transform.py b/src/gumlet/_utils/_transform.py new file mode 100644 index 0000000..5207549 --- /dev/null +++ b/src/gumlet/_utils/_transform.py @@ -0,0 +1,457 @@ +from __future__ import annotations + +import io +import base64 +import pathlib +from typing import Any, Mapping, TypeVar, cast +from datetime import date, datetime +from typing_extensions import Literal, get_args, override, get_type_hints as _get_type_hints + +import anyio +import pydantic + +from ._utils import ( + is_list, + is_given, + lru_cache, + is_mapping, + is_iterable, + is_sequence, +) +from .._files import is_base64_file_input +from ._compat import get_origin, is_typeddict +from ._typing import ( + is_list_type, + is_union_type, + extract_type_arg, + is_iterable_type, + is_required_type, + is_sequence_type, + is_annotated_type, + strip_annotated_type, +) + +_T = TypeVar("_T") + + +# TODO: support for drilling globals() and locals() +# TODO: ensure works correctly with forward references in all cases + + +PropertyFormat = Literal["iso8601", "base64", "custom"] + + +class PropertyInfo: + """Metadata class to be used in Annotated types to provide information about a given type. + + For example: + + class MyParams(TypedDict): + account_holder_name: Annotated[str, PropertyInfo(alias='accountHolderName')] + + This means that {'account_holder_name': 'Robert'} will be transformed to {'accountHolderName': 'Robert'} before being sent to the API. + """ + + alias: str | None + format: PropertyFormat | None + format_template: str | None + discriminator: str | None + + def __init__( + self, + *, + alias: str | None = None, + format: PropertyFormat | None = None, + format_template: str | None = None, + discriminator: str | None = None, + ) -> None: + self.alias = alias + self.format = format + self.format_template = format_template + self.discriminator = discriminator + + @override + def __repr__(self) -> str: + return f"{self.__class__.__name__}(alias='{self.alias}', format={self.format}, format_template='{self.format_template}', discriminator='{self.discriminator}')" + + +def maybe_transform( + data: object, + expected_type: object, +) -> Any | None: + """Wrapper over `transform()` that allows `None` to be passed. + + See `transform()` for more details. + """ + if data is None: + return None + return transform(data, expected_type) + + +# Wrapper over _transform_recursive providing fake types +def transform( + data: _T, + expected_type: object, +) -> _T: + """Transform dictionaries based off of type information from the given type, for example: + + ```py + class Params(TypedDict, total=False): + card_id: Required[Annotated[str, PropertyInfo(alias="cardID")]] + + + transformed = transform({"card_id": ""}, Params) + # {'cardID': ''} + ``` + + Any keys / data that does not have type information given will be included as is. + + It should be noted that the transformations that this function does are not represented in the type system. + """ + transformed = _transform_recursive(data, annotation=cast(type, expected_type)) + return cast(_T, transformed) + + +@lru_cache(maxsize=8096) +def _get_annotated_type(type_: type) -> type | None: + """If the given type is an `Annotated` type then it is returned, if not `None` is returned. + + This also unwraps the type when applicable, e.g. `Required[Annotated[T, ...]]` + """ + if is_required_type(type_): + # Unwrap `Required[Annotated[T, ...]]` to `Annotated[T, ...]` + type_ = get_args(type_)[0] + + if is_annotated_type(type_): + return type_ + + return None + + +def _maybe_transform_key(key: str, type_: type) -> str: + """Transform the given `data` based on the annotations provided in `type_`. + + Note: this function only looks at `Annotated` types that contain `PropertyInfo` metadata. + """ + annotated_type = _get_annotated_type(type_) + if annotated_type is None: + # no `Annotated` definition for this type, no transformation needed + return key + + # ignore the first argument as it is the actual type + annotations = get_args(annotated_type)[1:] + for annotation in annotations: + if isinstance(annotation, PropertyInfo) and annotation.alias is not None: + return annotation.alias + + return key + + +def _no_transform_needed(annotation: type) -> bool: + return annotation == float or annotation == int + + +def _transform_recursive( + data: object, + *, + annotation: type, + inner_type: type | None = None, +) -> object: + """Transform the given data against the expected type. + + Args: + annotation: The direct type annotation given to the particular piece of data. + This may or may not be wrapped in metadata types, e.g. `Required[T]`, `Annotated[T, ...]` etc + + inner_type: If applicable, this is the "inside" type. This is useful in certain cases where the outside type + is a container type such as `List[T]`. In that case `inner_type` should be set to `T` so that each entry in + the list can be transformed using the metadata from the container type. + + Defaults to the same value as the `annotation` argument. + """ + from .._compat import model_dump + + if inner_type is None: + inner_type = annotation + + stripped_type = strip_annotated_type(inner_type) + origin = get_origin(stripped_type) or stripped_type + if is_typeddict(stripped_type) and is_mapping(data): + return _transform_typeddict(data, stripped_type) + + if origin == dict and is_mapping(data): + items_type = get_args(stripped_type)[1] + return {key: _transform_recursive(value, annotation=items_type) for key, value in data.items()} + + if ( + # List[T] + (is_list_type(stripped_type) and is_list(data)) + # Iterable[T] + or (is_iterable_type(stripped_type) and is_iterable(data) and not isinstance(data, str)) + # Sequence[T] + or (is_sequence_type(stripped_type) and is_sequence(data) and not isinstance(data, str)) + ): + # dicts are technically iterable, but it is an iterable on the keys of the dict and is not usually + # intended as an iterable, so we don't transform it. + if isinstance(data, dict): + return cast(object, data) + + inner_type = extract_type_arg(stripped_type, 0) + if _no_transform_needed(inner_type): + # for some types there is no need to transform anything, so we can get a small + # perf boost from skipping that work. + # + # but we still need to convert to a list to ensure the data is json-serializable + if is_list(data): + return data + return list(data) + + return [_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data] + + if is_union_type(stripped_type): + # For union types we run the transformation against all subtypes to ensure that everything is transformed. + # + # TODO: there may be edge cases where the same normalized field name will transform to two different names + # in different subtypes. + for subtype in get_args(stripped_type): + data = _transform_recursive(data, annotation=annotation, inner_type=subtype) + return data + + if isinstance(data, pydantic.BaseModel): + return model_dump(data, exclude_unset=True, mode="json") + + annotated_type = _get_annotated_type(annotation) + if annotated_type is None: + return data + + # ignore the first argument as it is the actual type + annotations = get_args(annotated_type)[1:] + for annotation in annotations: + if isinstance(annotation, PropertyInfo) and annotation.format is not None: + return _format_data(data, annotation.format, annotation.format_template) + + return data + + +def _format_data(data: object, format_: PropertyFormat, format_template: str | None) -> object: + if isinstance(data, (date, datetime)): + if format_ == "iso8601": + return data.isoformat() + + if format_ == "custom" and format_template is not None: + return data.strftime(format_template) + + if format_ == "base64" and is_base64_file_input(data): + binary: str | bytes | None = None + + if isinstance(data, pathlib.Path): + binary = data.read_bytes() + elif isinstance(data, io.IOBase): + binary = data.read() + + if isinstance(binary, str): # type: ignore[unreachable] + binary = binary.encode() + + if not isinstance(binary, bytes): + raise RuntimeError(f"Could not read bytes from {data}; Received {type(binary)}") + + return base64.b64encode(binary).decode("ascii") + + return data + + +def _transform_typeddict( + data: Mapping[str, object], + expected_type: type, +) -> Mapping[str, object]: + result: dict[str, object] = {} + annotations = get_type_hints(expected_type, include_extras=True) + for key, value in data.items(): + if not is_given(value): + # we don't need to include omitted values here as they'll + # be stripped out before the request is sent anyway + continue + + type_ = annotations.get(key) + if type_ is None: + # we do not have a type annotation for this field, leave it as is + result[key] = value + else: + result[_maybe_transform_key(key, type_)] = _transform_recursive(value, annotation=type_) + return result + + +async def async_maybe_transform( + data: object, + expected_type: object, +) -> Any | None: + """Wrapper over `async_transform()` that allows `None` to be passed. + + See `async_transform()` for more details. + """ + if data is None: + return None + return await async_transform(data, expected_type) + + +async def async_transform( + data: _T, + expected_type: object, +) -> _T: + """Transform dictionaries based off of type information from the given type, for example: + + ```py + class Params(TypedDict, total=False): + card_id: Required[Annotated[str, PropertyInfo(alias="cardID")]] + + + transformed = transform({"card_id": ""}, Params) + # {'cardID': ''} + ``` + + Any keys / data that does not have type information given will be included as is. + + It should be noted that the transformations that this function does are not represented in the type system. + """ + transformed = await _async_transform_recursive(data, annotation=cast(type, expected_type)) + return cast(_T, transformed) + + +async def _async_transform_recursive( + data: object, + *, + annotation: type, + inner_type: type | None = None, +) -> object: + """Transform the given data against the expected type. + + Args: + annotation: The direct type annotation given to the particular piece of data. + This may or may not be wrapped in metadata types, e.g. `Required[T]`, `Annotated[T, ...]` etc + + inner_type: If applicable, this is the "inside" type. This is useful in certain cases where the outside type + is a container type such as `List[T]`. In that case `inner_type` should be set to `T` so that each entry in + the list can be transformed using the metadata from the container type. + + Defaults to the same value as the `annotation` argument. + """ + from .._compat import model_dump + + if inner_type is None: + inner_type = annotation + + stripped_type = strip_annotated_type(inner_type) + origin = get_origin(stripped_type) or stripped_type + if is_typeddict(stripped_type) and is_mapping(data): + return await _async_transform_typeddict(data, stripped_type) + + if origin == dict and is_mapping(data): + items_type = get_args(stripped_type)[1] + return {key: _transform_recursive(value, annotation=items_type) for key, value in data.items()} + + if ( + # List[T] + (is_list_type(stripped_type) and is_list(data)) + # Iterable[T] + or (is_iterable_type(stripped_type) and is_iterable(data) and not isinstance(data, str)) + # Sequence[T] + or (is_sequence_type(stripped_type) and is_sequence(data) and not isinstance(data, str)) + ): + # dicts are technically iterable, but it is an iterable on the keys of the dict and is not usually + # intended as an iterable, so we don't transform it. + if isinstance(data, dict): + return cast(object, data) + + inner_type = extract_type_arg(stripped_type, 0) + if _no_transform_needed(inner_type): + # for some types there is no need to transform anything, so we can get a small + # perf boost from skipping that work. + # + # but we still need to convert to a list to ensure the data is json-serializable + if is_list(data): + return data + return list(data) + + return [await _async_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data] + + if is_union_type(stripped_type): + # For union types we run the transformation against all subtypes to ensure that everything is transformed. + # + # TODO: there may be edge cases where the same normalized field name will transform to two different names + # in different subtypes. + for subtype in get_args(stripped_type): + data = await _async_transform_recursive(data, annotation=annotation, inner_type=subtype) + return data + + if isinstance(data, pydantic.BaseModel): + return model_dump(data, exclude_unset=True, mode="json") + + annotated_type = _get_annotated_type(annotation) + if annotated_type is None: + return data + + # ignore the first argument as it is the actual type + annotations = get_args(annotated_type)[1:] + for annotation in annotations: + if isinstance(annotation, PropertyInfo) and annotation.format is not None: + return await _async_format_data(data, annotation.format, annotation.format_template) + + return data + + +async def _async_format_data(data: object, format_: PropertyFormat, format_template: str | None) -> object: + if isinstance(data, (date, datetime)): + if format_ == "iso8601": + return data.isoformat() + + if format_ == "custom" and format_template is not None: + return data.strftime(format_template) + + if format_ == "base64" and is_base64_file_input(data): + binary: str | bytes | None = None + + if isinstance(data, pathlib.Path): + binary = await anyio.Path(data).read_bytes() + elif isinstance(data, io.IOBase): + binary = data.read() + + if isinstance(binary, str): # type: ignore[unreachable] + binary = binary.encode() + + if not isinstance(binary, bytes): + raise RuntimeError(f"Could not read bytes from {data}; Received {type(binary)}") + + return base64.b64encode(binary).decode("ascii") + + return data + + +async def _async_transform_typeddict( + data: Mapping[str, object], + expected_type: type, +) -> Mapping[str, object]: + result: dict[str, object] = {} + annotations = get_type_hints(expected_type, include_extras=True) + for key, value in data.items(): + if not is_given(value): + # we don't need to include omitted values here as they'll + # be stripped out before the request is sent anyway + continue + + type_ = annotations.get(key) + if type_ is None: + # we do not have a type annotation for this field, leave it as is + result[key] = value + else: + result[_maybe_transform_key(key, type_)] = await _async_transform_recursive(value, annotation=type_) + return result + + +@lru_cache(maxsize=8096) +def get_type_hints( + obj: Any, + globalns: dict[str, Any] | None = None, + localns: Mapping[str, Any] | None = None, + include_extras: bool = False, +) -> dict[str, Any]: + return _get_type_hints(obj, globalns=globalns, localns=localns, include_extras=include_extras) diff --git a/src/gumlet/_utils/_typing.py b/src/gumlet/_utils/_typing.py new file mode 100644 index 0000000..193109f --- /dev/null +++ b/src/gumlet/_utils/_typing.py @@ -0,0 +1,156 @@ +from __future__ import annotations + +import sys +import typing +import typing_extensions +from typing import Any, TypeVar, Iterable, cast +from collections import abc as _c_abc +from typing_extensions import ( + TypeIs, + Required, + Annotated, + get_args, + get_origin, +) + +from ._utils import lru_cache +from .._types import InheritsGeneric +from ._compat import is_union as _is_union + + +def is_annotated_type(typ: type) -> bool: + return get_origin(typ) == Annotated + + +def is_list_type(typ: type) -> bool: + return (get_origin(typ) or typ) == list + + +def is_sequence_type(typ: type) -> bool: + origin = get_origin(typ) or typ + return origin == typing_extensions.Sequence or origin == typing.Sequence or origin == _c_abc.Sequence + + +def is_iterable_type(typ: type) -> bool: + """If the given type is `typing.Iterable[T]`""" + origin = get_origin(typ) or typ + return origin == Iterable or origin == _c_abc.Iterable + + +def is_union_type(typ: type) -> bool: + return _is_union(get_origin(typ)) + + +def is_required_type(typ: type) -> bool: + return get_origin(typ) == Required + + +def is_typevar(typ: type) -> bool: + # type ignore is required because type checkers + # think this expression will always return False + return type(typ) == TypeVar # type: ignore + + +_TYPE_ALIAS_TYPES: tuple[type[typing_extensions.TypeAliasType], ...] = (typing_extensions.TypeAliasType,) +if sys.version_info >= (3, 12): + _TYPE_ALIAS_TYPES = (*_TYPE_ALIAS_TYPES, typing.TypeAliasType) + + +def is_type_alias_type(tp: Any, /) -> TypeIs[typing_extensions.TypeAliasType]: + """Return whether the provided argument is an instance of `TypeAliasType`. + + ```python + type Int = int + is_type_alias_type(Int) + # > True + Str = TypeAliasType("Str", str) + is_type_alias_type(Str) + # > True + ``` + """ + return isinstance(tp, _TYPE_ALIAS_TYPES) + + +# Extracts T from Annotated[T, ...] or from Required[Annotated[T, ...]] +@lru_cache(maxsize=8096) +def strip_annotated_type(typ: type) -> type: + if is_required_type(typ) or is_annotated_type(typ): + return strip_annotated_type(cast(type, get_args(typ)[0])) + + return typ + + +def extract_type_arg(typ: type, index: int) -> type: + args = get_args(typ) + try: + return cast(type, args[index]) + except IndexError as err: + raise RuntimeError(f"Expected type {typ} to have a type argument at index {index} but it did not") from err + + +def extract_type_var_from_base( + typ: type, + *, + generic_bases: tuple[type, ...], + index: int, + failure_message: str | None = None, +) -> type: + """Given a type like `Foo[T]`, returns the generic type variable `T`. + + This also handles the case where a concrete subclass is given, e.g. + ```py + class MyResponse(Foo[bytes]): + ... + + extract_type_var(MyResponse, bases=(Foo,), index=0) -> bytes + ``` + + And where a generic subclass is given: + ```py + _T = TypeVar('_T') + class MyResponse(Foo[_T]): + ... + + extract_type_var(MyResponse[bytes], bases=(Foo,), index=0) -> bytes + ``` + """ + cls = cast(object, get_origin(typ) or typ) + if cls in generic_bases: # pyright: ignore[reportUnnecessaryContains] + # we're given the class directly + return extract_type_arg(typ, index) + + # if a subclass is given + # --- + # this is needed as __orig_bases__ is not present in the typeshed stubs + # because it is intended to be for internal use only, however there does + # not seem to be a way to resolve generic TypeVars for inherited subclasses + # without using it. + if isinstance(cls, InheritsGeneric): + target_base_class: Any | None = None + for base in cls.__orig_bases__: + if base.__origin__ in generic_bases: + target_base_class = base + break + + if target_base_class is None: + raise RuntimeError( + "Could not find the generic base class;\n" + "This should never happen;\n" + f"Does {cls} inherit from one of {generic_bases} ?" + ) + + extracted = extract_type_arg(target_base_class, index) + if is_typevar(extracted): + # If the extracted type argument is itself a type variable + # then that means the subclass itself is generic, so we have + # to resolve the type argument from the class itself, not + # the base class. + # + # Note: if there is more than 1 type argument, the subclass could + # change the ordering of the type arguments, this is not currently + # supported. + return extract_type_arg(typ, index) + + return extracted + + raise RuntimeError(failure_message or f"Could not resolve inner type variable at index {index} for {typ}") diff --git a/src/gumlet/_utils/_utils.py b/src/gumlet/_utils/_utils.py new file mode 100644 index 0000000..8b051e2 --- /dev/null +++ b/src/gumlet/_utils/_utils.py @@ -0,0 +1,433 @@ +from __future__ import annotations + +import os +import re +import inspect +import functools +from typing import ( + Any, + Tuple, + Mapping, + TypeVar, + Callable, + Iterable, + Sequence, + cast, + overload, +) +from pathlib import Path +from datetime import date, datetime +from typing_extensions import TypeGuard, get_args + +import sniffio + +from .._types import Omit, NotGiven, FileTypes, ArrayFormat, HeadersLike + +_T = TypeVar("_T") +_TupleT = TypeVar("_TupleT", bound=Tuple[object, ...]) +_MappingT = TypeVar("_MappingT", bound=Mapping[str, object]) +_SequenceT = TypeVar("_SequenceT", bound=Sequence[object]) +CallableT = TypeVar("CallableT", bound=Callable[..., Any]) + + +def flatten(t: Iterable[Iterable[_T]]) -> list[_T]: + return [item for sublist in t for item in sublist] + + +def extract_files( + # TODO: this needs to take Dict but variance issues..... + # create protocol type ? + 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, 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] + except IndexError: + if not is_given(obj): + # no value was provided - we can safely ignore + return [] + + # cyclical import + from .._files import assert_is_file_content + + # We have exhausted the path, return the entry we found. + assert flattened_key is not None + + if is_list(obj): + files: list[tuple[str, FileTypes]] = [] + 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) + return [(flattened_key, cast(FileTypes, obj))] + + index += 1 + if is_dict(obj): + try: + # Remove the field if there are no more dict keys in the path, + # only "" traversal markers or end. + if all(p == "" for p in path[index:]): + item = obj.pop(key) + else: + item = obj[key] + except KeyError: + # Key was not present in the dictionary, this is not indicative of an error + # as the given path may not point to a required field. We also do not want + # to enforce required fields as the API may differ from the spec in some cases. + return [] + if flattened_key is None: + flattened_key = key + else: + flattened_key += f"[{key}]" + return _extract_items( + item, + path, + index=index, + flattened_key=flattened_key, + array_format=array_format, + ) + elif is_list(obj): + if key != "": + return [] + + return flatten( + [ + _extract_items( + item, + path, + index=index, + flattened_key=( + (flattened_key if flattened_key is not None else "") + _array_suffix(array_format, array_index) + ), + array_format=array_format, + ) + for array_index, item in enumerate(obj) + ] + ) + + # Something unexpected was passed, just ignore it. + return [] + + +def is_given(obj: _T | NotGiven | Omit) -> TypeGuard[_T]: + return not isinstance(obj, NotGiven) and not isinstance(obj, Omit) + + +# Type safe methods for narrowing types with TypeVars. +# The default narrowing for isinstance(obj, dict) is dict[unknown, unknown], +# however this cause Pyright to rightfully report errors. As we know we don't +# care about the contained types we can safely use `object` in its place. +# +# There are two separate functions defined, `is_*` and `is_*_t` for different use cases. +# `is_*` is for when you're dealing with an unknown input +# `is_*_t` is for when you're narrowing a known union type to a specific subset + + +def is_tuple(obj: object) -> TypeGuard[tuple[object, ...]]: + return isinstance(obj, tuple) + + +def is_tuple_t(obj: _TupleT | object) -> TypeGuard[_TupleT]: + return isinstance(obj, tuple) + + +def is_sequence(obj: object) -> TypeGuard[Sequence[object]]: + return isinstance(obj, Sequence) + + +def is_sequence_t(obj: _SequenceT | object) -> TypeGuard[_SequenceT]: + return isinstance(obj, Sequence) + + +def is_mapping(obj: object) -> TypeGuard[Mapping[str, object]]: + return isinstance(obj, Mapping) + + +def is_mapping_t(obj: _MappingT | object) -> TypeGuard[_MappingT]: + return isinstance(obj, Mapping) + + +def is_dict(obj: object) -> TypeGuard[dict[object, object]]: + return isinstance(obj, dict) + + +def is_list(obj: object) -> TypeGuard[list[object]]: + return isinstance(obj, list) + + +def is_iterable(obj: object) -> TypeGuard[Iterable[object]]: + return isinstance(obj, Iterable) + + +# copied from https://github.com/Rapptz/RoboDanny +def human_join(seq: Sequence[str], *, delim: str = ", ", final: str = "or") -> str: + size = len(seq) + if size == 0: + return "" + + if size == 1: + return seq[0] + + if size == 2: + return f"{seq[0]} {final} {seq[1]}" + + return delim.join(seq[:-1]) + f" {final} {seq[-1]}" + + +def quote(string: str) -> str: + """Add single quotation marks around the given string. Does *not* do any escaping.""" + return f"'{string}'" + + +def required_args(*variants: Sequence[str]) -> Callable[[CallableT], CallableT]: + """Decorator to enforce a given set of arguments or variants of arguments are passed to the decorated function. + + Useful for enforcing runtime validation of overloaded functions. + + Example usage: + ```py + @overload + def foo(*, a: str) -> str: ... + + + @overload + def foo(*, b: bool) -> str: ... + + + # This enforces the same constraints that a static type checker would + # i.e. that either a or b must be passed to the function + @required_args(["a"], ["b"]) + def foo(*, a: str | None = None, b: bool | None = None) -> str: ... + ``` + """ + + def inner(func: CallableT) -> CallableT: + params = inspect.signature(func).parameters + positional = [ + name + for name, param in params.items() + if param.kind + in { + param.POSITIONAL_ONLY, + param.POSITIONAL_OR_KEYWORD, + } + ] + + @functools.wraps(func) + def wrapper(*args: object, **kwargs: object) -> object: + given_params: set[str] = set() + for i, _ in enumerate(args): + try: + given_params.add(positional[i]) + except IndexError: + raise TypeError( + f"{func.__name__}() takes {len(positional)} argument(s) but {len(args)} were given" + ) from None + + for key in kwargs.keys(): + given_params.add(key) + + for variant in variants: + matches = all((param in given_params for param in variant)) + if matches: + break + else: # no break + if len(variants) > 1: + variations = human_join( + ["(" + human_join([quote(arg) for arg in variant], final="and") + ")" for variant in variants] + ) + msg = f"Missing required arguments; Expected either {variations} arguments to be given" + else: + assert len(variants) > 0 + + # TODO: this error message is not deterministic + missing = list(set(variants[0]) - given_params) + if len(missing) > 1: + msg = f"Missing required arguments: {human_join([quote(arg) for arg in missing])}" + else: + msg = f"Missing required argument: {quote(missing[0])}" + raise TypeError(msg) + return func(*args, **kwargs) + + return wrapper # type: ignore + + return inner + + +_K = TypeVar("_K") +_V = TypeVar("_V") + + +@overload +def strip_not_given(obj: None) -> None: ... + + +@overload +def strip_not_given(obj: Mapping[_K, _V | NotGiven]) -> dict[_K, _V]: ... + + +@overload +def strip_not_given(obj: object) -> object: ... + + +def strip_not_given(obj: object | None) -> object: + """Remove all top-level keys where their values are instances of `NotGiven`""" + if obj is None: + return None + + if not is_mapping(obj): + return obj + + return {key: value for key, value in obj.items() if not isinstance(value, NotGiven)} + + +def coerce_integer(val: str) -> int: + return int(val, base=10) + + +def coerce_float(val: str) -> float: + return float(val) + + +def coerce_boolean(val: str) -> bool: + return val == "true" or val == "1" or val == "on" + + +def maybe_coerce_integer(val: str | None) -> int | None: + if val is None: + return None + return coerce_integer(val) + + +def maybe_coerce_float(val: str | None) -> float | None: + if val is None: + return None + return coerce_float(val) + + +def maybe_coerce_boolean(val: str | None) -> bool | None: + if val is None: + return None + return coerce_boolean(val) + + +def removeprefix(string: str, prefix: str) -> str: + """Remove a prefix from a string. + + Backport of `str.removeprefix` for Python < 3.9 + """ + if string.startswith(prefix): + return string[len(prefix) :] + return string + + +def removesuffix(string: str, suffix: str) -> str: + """Remove a suffix from a string. + + Backport of `str.removesuffix` for Python < 3.9 + """ + if string.endswith(suffix): + return string[: -len(suffix)] + return string + + +def file_from_path(path: str) -> FileTypes: + contents = Path(path).read_bytes() + file_name = os.path.basename(path) + return (file_name, contents) + + +def get_required_header(headers: HeadersLike, header: str) -> str: + lower_header = header.lower() + if is_mapping_t(headers): + # mypy doesn't understand the type narrowing here + for k, v in headers.items(): # type: ignore + if k.lower() == lower_header and isinstance(v, str): + return v + + # to deal with the case where the header looks like Scalar-Event-Id + intercaps_header = re.sub(r"([^\w])(\w)", lambda pat: pat.group(1) + pat.group(2).upper(), header.capitalize()) + + for normalized_header in [header, lower_header, header.upper(), intercaps_header]: + value = headers.get(normalized_header) + if value: + return value + + raise ValueError(f"Could not find {header} header") + + +def get_async_library() -> str: + try: + return sniffio.current_async_library() + except Exception: + return "false" + + +def lru_cache(*, maxsize: int | None = 128) -> Callable[[CallableT], CallableT]: + """A version of functools.lru_cache that retains the type signature + for the wrapped function arguments. + """ + wrapper = functools.lru_cache( # noqa: TID251 + maxsize=maxsize, + ) + return cast(Any, wrapper) # type: ignore[no-any-return] + + +def json_safe(data: object) -> object: + """Translates a mapping / sequence recursively in the same fashion + as `pydantic` v2's `model_dump(mode="json")`. + """ + if is_mapping(data): + return {json_safe(key): json_safe(value) for key, value in data.items()} + + if is_iterable(data) and not isinstance(data, (str, bytes, bytearray)): + return [json_safe(item) for item in data] + + if isinstance(data, (datetime, date)): + return data.isoformat() + + return data diff --git a/src/gumlet/_version.py b/src/gumlet/_version.py new file mode 100644 index 0000000..486250a --- /dev/null +++ b/src/gumlet/_version.py @@ -0,0 +1,4 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +__title__ = "gumlet" +__version__ = "0.2.0" # x-release-please-version diff --git a/src/gumlet/py.typed b/src/gumlet/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/src/gumlet/resources/__init__.py b/src/gumlet/resources/__init__.py new file mode 100644 index 0000000..37cf0ec --- /dev/null +++ b/src/gumlet/resources/__init__.py @@ -0,0 +1,299 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from .video_assets import ( + VideoAssetsResource, + AsyncVideoAssetsResource, + VideoAssetsResourceWithRawResponse, + AsyncVideoAssetsResourceWithRawResponse, + VideoAssetsResourceWithStreamingResponse, + AsyncVideoAssetsResourceWithStreamingResponse, +) +from .subtitle_upload import ( + SubtitleUploadResource, + AsyncSubtitleUploadResource, + SubtitleUploadResourceWithRawResponse, + AsyncSubtitleUploadResourceWithRawResponse, + SubtitleUploadResourceWithStreamingResponse, + AsyncSubtitleUploadResourceWithStreamingResponse, +) +from .audio_upload import ( + AudioUploadResource, + AsyncAudioUploadResource, + AudioUploadResourceWithRawResponse, + AsyncAudioUploadResourceWithRawResponse, + AudioUploadResourceWithStreamingResponse, + AsyncAudioUploadResourceWithStreamingResponse, +) +from .video_usage_analytics import ( + VideoUsageAnalyticsResource, + AsyncVideoUsageAnalyticsResource, + VideoUsageAnalyticsResourceWithRawResponse, + AsyncVideoUsageAnalyticsResourceWithRawResponse, + VideoUsageAnalyticsResourceWithStreamingResponse, + AsyncVideoUsageAnalyticsResourceWithStreamingResponse, +) +from .multipart_upload import ( + MultipartUploadResource, + AsyncMultipartUploadResource, + MultipartUploadResourceWithRawResponse, + AsyncMultipartUploadResourceWithRawResponse, + MultipartUploadResourceWithStreamingResponse, + AsyncMultipartUploadResourceWithStreamingResponse, +) +from .video_profiles import ( + VideoProfilesResource, + AsyncVideoProfilesResource, + VideoProfilesResourceWithRawResponse, + AsyncVideoProfilesResourceWithRawResponse, + VideoProfilesResourceWithStreamingResponse, + AsyncVideoProfilesResourceWithStreamingResponse, +) +from .video_playlists import ( + VideoPlaylistsResource, + AsyncVideoPlaylistsResource, + VideoPlaylistsResourceWithRawResponse, + AsyncVideoPlaylistsResourceWithRawResponse, + VideoPlaylistsResourceWithStreamingResponse, + AsyncVideoPlaylistsResourceWithStreamingResponse, +) +from .webhooks import ( + WebhooksResource, + AsyncWebhooksResource, + WebhooksResourceWithRawResponse, + AsyncWebhooksResourceWithRawResponse, + WebhooksResourceWithStreamingResponse, + AsyncWebhooksResourceWithStreamingResponse, +) +from .image_sources import ( + ImageSourcesResource, + AsyncImageSourcesResource, + ImageSourcesResourceWithRawResponse, + AsyncImageSourcesResourceWithRawResponse, + ImageSourcesResourceWithStreamingResponse, + AsyncImageSourcesResourceWithStreamingResponse, +) +from .image_usage_analytics import ( + ImageUsageAnalyticsResource, + AsyncImageUsageAnalyticsResource, + ImageUsageAnalyticsResourceWithRawResponse, + AsyncImageUsageAnalyticsResourceWithRawResponse, + ImageUsageAnalyticsResourceWithStreamingResponse, + AsyncImageUsageAnalyticsResourceWithStreamingResponse, +) +from .live_stream_assets import ( + LiveStreamAssetsResource, + AsyncLiveStreamAssetsResource, + LiveStreamAssetsResourceWithRawResponse, + AsyncLiveStreamAssetsResourceWithRawResponse, + LiveStreamAssetsResourceWithStreamingResponse, + AsyncLiveStreamAssetsResourceWithStreamingResponse, +) +from .recycle_bin import ( + RecycleBinResource, + AsyncRecycleBinResource, + RecycleBinResourceWithRawResponse, + AsyncRecycleBinResourceWithRawResponse, + RecycleBinResourceWithStreamingResponse, + AsyncRecycleBinResourceWithStreamingResponse, +) +from .video_workspaces import ( + VideoWorkspacesResource, + AsyncVideoWorkspacesResource, + VideoWorkspacesResourceWithRawResponse, + AsyncVideoWorkspacesResourceWithRawResponse, + VideoWorkspacesResourceWithStreamingResponse, + AsyncVideoWorkspacesResourceWithStreamingResponse, +) +from .folders import ( + FoldersResource, + AsyncFoldersResource, + FoldersResourceWithRawResponse, + AsyncFoldersResourceWithRawResponse, + FoldersResourceWithStreamingResponse, + AsyncFoldersResourceWithStreamingResponse, +) +from .channel_viewers import ( + ChannelViewersResource, + AsyncChannelViewersResource, + ChannelViewersResourceWithRawResponse, + AsyncChannelViewersResourceWithRawResponse, + ChannelViewersResourceWithStreamingResponse, + AsyncChannelViewersResourceWithStreamingResponse, +) +from .video_analytics import ( + VideoAnalyticsResource, + AsyncVideoAnalyticsResource, + VideoAnalyticsResourceWithRawResponse, + AsyncVideoAnalyticsResourceWithRawResponse, + VideoAnalyticsResourceWithStreamingResponse, + AsyncVideoAnalyticsResourceWithStreamingResponse, +) +from .organization_data import ( + OrganizationDataResource, + AsyncOrganizationDataResource, + OrganizationDataResourceWithRawResponse, + AsyncOrganizationDataResourceWithRawResponse, + OrganizationDataResourceWithStreamingResponse, + AsyncOrganizationDataResourceWithStreamingResponse, +) +from .user_data import ( + UserDataResource, + AsyncUserDataResource, + UserDataResourceWithRawResponse, + AsyncUserDataResourceWithRawResponse, + UserDataResourceWithStreamingResponse, + AsyncUserDataResourceWithStreamingResponse, +) +from .audit_logs import ( + AuditLogsResource, + AsyncAuditLogsResource, + AuditLogsResourceWithRawResponse, + AsyncAuditLogsResourceWithRawResponse, + AuditLogsResourceWithStreamingResponse, + AsyncAuditLogsResourceWithStreamingResponse, +) +from .billing import ( + BillingResource, + AsyncBillingResource, + BillingResourceWithRawResponse, + AsyncBillingResourceWithRawResponse, + BillingResourceWithStreamingResponse, + AsyncBillingResourceWithStreamingResponse, +) +from .live_stream_workspaces import ( + LiveStreamWorkspacesResource, + AsyncLiveStreamWorkspacesResource, + LiveStreamWorkspacesResourceWithRawResponse, + AsyncLiveStreamWorkspacesResourceWithRawResponse, + LiveStreamWorkspacesResourceWithStreamingResponse, + AsyncLiveStreamWorkspacesResourceWithStreamingResponse, +) + +__all__ = [ + "VideoAssetsResource", + "AsyncVideoAssetsResource", + "VideoAssetsResourceWithRawResponse", + "AsyncVideoAssetsResourceWithRawResponse", + "VideoAssetsResourceWithStreamingResponse", + "AsyncVideoAssetsResourceWithStreamingResponse", + "SubtitleUploadResource", + "AsyncSubtitleUploadResource", + "SubtitleUploadResourceWithRawResponse", + "AsyncSubtitleUploadResourceWithRawResponse", + "SubtitleUploadResourceWithStreamingResponse", + "AsyncSubtitleUploadResourceWithStreamingResponse", + "AudioUploadResource", + "AsyncAudioUploadResource", + "AudioUploadResourceWithRawResponse", + "AsyncAudioUploadResourceWithRawResponse", + "AudioUploadResourceWithStreamingResponse", + "AsyncAudioUploadResourceWithStreamingResponse", + "VideoUsageAnalyticsResource", + "AsyncVideoUsageAnalyticsResource", + "VideoUsageAnalyticsResourceWithRawResponse", + "AsyncVideoUsageAnalyticsResourceWithRawResponse", + "VideoUsageAnalyticsResourceWithStreamingResponse", + "AsyncVideoUsageAnalyticsResourceWithStreamingResponse", + "MultipartUploadResource", + "AsyncMultipartUploadResource", + "MultipartUploadResourceWithRawResponse", + "AsyncMultipartUploadResourceWithRawResponse", + "MultipartUploadResourceWithStreamingResponse", + "AsyncMultipartUploadResourceWithStreamingResponse", + "VideoProfilesResource", + "AsyncVideoProfilesResource", + "VideoProfilesResourceWithRawResponse", + "AsyncVideoProfilesResourceWithRawResponse", + "VideoProfilesResourceWithStreamingResponse", + "AsyncVideoProfilesResourceWithStreamingResponse", + "VideoPlaylistsResource", + "AsyncVideoPlaylistsResource", + "VideoPlaylistsResourceWithRawResponse", + "AsyncVideoPlaylistsResourceWithRawResponse", + "VideoPlaylistsResourceWithStreamingResponse", + "AsyncVideoPlaylistsResourceWithStreamingResponse", + "WebhooksResource", + "AsyncWebhooksResource", + "WebhooksResourceWithRawResponse", + "AsyncWebhooksResourceWithRawResponse", + "WebhooksResourceWithStreamingResponse", + "AsyncWebhooksResourceWithStreamingResponse", + "ImageSourcesResource", + "AsyncImageSourcesResource", + "ImageSourcesResourceWithRawResponse", + "AsyncImageSourcesResourceWithRawResponse", + "ImageSourcesResourceWithStreamingResponse", + "AsyncImageSourcesResourceWithStreamingResponse", + "ImageUsageAnalyticsResource", + "AsyncImageUsageAnalyticsResource", + "ImageUsageAnalyticsResourceWithRawResponse", + "AsyncImageUsageAnalyticsResourceWithRawResponse", + "ImageUsageAnalyticsResourceWithStreamingResponse", + "AsyncImageUsageAnalyticsResourceWithStreamingResponse", + "LiveStreamAssetsResource", + "AsyncLiveStreamAssetsResource", + "LiveStreamAssetsResourceWithRawResponse", + "AsyncLiveStreamAssetsResourceWithRawResponse", + "LiveStreamAssetsResourceWithStreamingResponse", + "AsyncLiveStreamAssetsResourceWithStreamingResponse", + "RecycleBinResource", + "AsyncRecycleBinResource", + "RecycleBinResourceWithRawResponse", + "AsyncRecycleBinResourceWithRawResponse", + "RecycleBinResourceWithStreamingResponse", + "AsyncRecycleBinResourceWithStreamingResponse", + "VideoWorkspacesResource", + "AsyncVideoWorkspacesResource", + "VideoWorkspacesResourceWithRawResponse", + "AsyncVideoWorkspacesResourceWithRawResponse", + "VideoWorkspacesResourceWithStreamingResponse", + "AsyncVideoWorkspacesResourceWithStreamingResponse", + "FoldersResource", + "AsyncFoldersResource", + "FoldersResourceWithRawResponse", + "AsyncFoldersResourceWithRawResponse", + "FoldersResourceWithStreamingResponse", + "AsyncFoldersResourceWithStreamingResponse", + "ChannelViewersResource", + "AsyncChannelViewersResource", + "ChannelViewersResourceWithRawResponse", + "AsyncChannelViewersResourceWithRawResponse", + "ChannelViewersResourceWithStreamingResponse", + "AsyncChannelViewersResourceWithStreamingResponse", + "VideoAnalyticsResource", + "AsyncVideoAnalyticsResource", + "VideoAnalyticsResourceWithRawResponse", + "AsyncVideoAnalyticsResourceWithRawResponse", + "VideoAnalyticsResourceWithStreamingResponse", + "AsyncVideoAnalyticsResourceWithStreamingResponse", + "OrganizationDataResource", + "AsyncOrganizationDataResource", + "OrganizationDataResourceWithRawResponse", + "AsyncOrganizationDataResourceWithRawResponse", + "OrganizationDataResourceWithStreamingResponse", + "AsyncOrganizationDataResourceWithStreamingResponse", + "UserDataResource", + "AsyncUserDataResource", + "UserDataResourceWithRawResponse", + "AsyncUserDataResourceWithRawResponse", + "UserDataResourceWithStreamingResponse", + "AsyncUserDataResourceWithStreamingResponse", + "AuditLogsResource", + "AsyncAuditLogsResource", + "AuditLogsResourceWithRawResponse", + "AsyncAuditLogsResourceWithRawResponse", + "AuditLogsResourceWithStreamingResponse", + "AsyncAuditLogsResourceWithStreamingResponse", + "BillingResource", + "AsyncBillingResource", + "BillingResourceWithRawResponse", + "AsyncBillingResourceWithRawResponse", + "BillingResourceWithStreamingResponse", + "AsyncBillingResourceWithStreamingResponse", + "LiveStreamWorkspacesResource", + "AsyncLiveStreamWorkspacesResource", + "LiveStreamWorkspacesResourceWithRawResponse", + "AsyncLiveStreamWorkspacesResourceWithRawResponse", + "LiveStreamWorkspacesResourceWithStreamingResponse", + "AsyncLiveStreamWorkspacesResourceWithStreamingResponse", +] diff --git a/src/gumlet/resources/audio_upload.py b/src/gumlet/resources/audio_upload.py new file mode 100644 index 0000000..0df9998 --- /dev/null +++ b/src/gumlet/resources/audio_upload.py @@ -0,0 +1,289 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +import httpx + +from typing import Iterable +from .._types import SequenceNotStr + +from .._types import Body, Omit, Query, Headers, NotGiven, 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.audio_upload_upload_response import AudioUploadUploadResponse +from ..types import audio_upload_upload_params, audio_upload_complete_params +from ..types.audio_upload_complete_response import AudioUploadCompleteResponse + +__all__ = ["AudioUploadResource", "AsyncAudioUploadResource"] + + +class AudioUploadResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> AudioUploadResourceWithRawResponse: + return AudioUploadResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AudioUploadResourceWithStreamingResponse: + return AudioUploadResourceWithStreamingResponse(self) + + def upload( + self, + asset_id: str, + *, + language_codes: 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, + ) -> AudioUploadUploadResponse: + """ + Add any audio file to the video asset. + The response of this API call gives `upload_url` for each language specified. You need to send a `PUT` request of the audio files to those URLs. Once that's done, you need to call the audio upload complete API. Only after that will Gumlet add audio to the asset. + + Args: + asset_id: An asset id for the previously created asset. + language_codes: List of language Code to upload audio file (use ISO 639-1 Language Codes) + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + AudioUploadUploadResponse: 200 + + Example: + ```python + audio_upload = client.audio_upload.upload( + asset_id="assetId", + language_codes=["en"], + ) + ``` + """ + if asset_id is None or (isinstance(asset_id, str) and not asset_id): + raise ValueError(f"Expected a non-empty value for `asset_id` but received {asset_id!r}") + return self._post( + path_template("/video/assets/{asset_ID}/audio/upload", **{"asset_ID": asset_id}), + body=maybe_transform( + {"language_codes": language_codes}, + audio_upload_upload_params.AudioUploadUploadParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AudioUploadUploadResponse, + ) + + def complete( + self, + asset_id: str, + *, + upload_responses: Iterable[audio_upload_complete_params.UploadResponse] | 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, + ) -> AudioUploadCompleteResponse: + """ + This API must be called after adding audio(s); The add audio call gives you URLs to upload, and you complete a `PUT` request to those URLs. + Once that is done, calling this initiates the process to actually add the subtitle to the video. + + Args: + asset_id: An asset id for the previously created asset. + upload_responses: Body parameter. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + AudioUploadCompleteResponse: 200 + + Example: + ```python + audio_upload = client.audio_upload.complete( + asset_id="assetId", + upload_responses=[{"language_codes": ["en"], "uploaded": True}], + ) + ``` + """ + if asset_id is None or (isinstance(asset_id, str) and not asset_id): + raise ValueError(f"Expected a non-empty value for `asset_id` but received {asset_id!r}") + return self._post( + path_template("/video/assets/{asset_ID}/audio/upload/event", **{"asset_ID": asset_id}), + body=maybe_transform( + {"upload_responses": upload_responses}, + audio_upload_complete_params.AudioUploadCompleteParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AudioUploadCompleteResponse, + ) + + +class AsyncAudioUploadResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncAudioUploadResourceWithRawResponse: + return AsyncAudioUploadResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncAudioUploadResourceWithStreamingResponse: + return AsyncAudioUploadResourceWithStreamingResponse(self) + + async def upload( + self, + asset_id: str, + *, + language_codes: 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, + ) -> AudioUploadUploadResponse: + """ + Add any audio file to the video asset. + The response of this API call gives `upload_url` for each language specified. You need to send a `PUT` request of the audio files to those URLs. Once that's done, you need to call the audio upload complete API. Only after that will Gumlet add audio to the asset. + + Args: + asset_id: An asset id for the previously created asset. + language_codes: List of language Code to upload audio file (use ISO 639-1 Language Codes) + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + AudioUploadUploadResponse: 200 + + Example: + ```python + audio_upload = await client.audio_upload.upload( + asset_id="assetId", + language_codes=["en"], + ) + ``` + """ + if asset_id is None or (isinstance(asset_id, str) and not asset_id): + raise ValueError(f"Expected a non-empty value for `asset_id` but received {asset_id!r}") + return await self._post( + path_template("/video/assets/{asset_ID}/audio/upload", **{"asset_ID": asset_id}), + body=await async_maybe_transform( + {"language_codes": language_codes}, + audio_upload_upload_params.AudioUploadUploadParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AudioUploadUploadResponse, + ) + + async def complete( + self, + asset_id: str, + *, + upload_responses: Iterable[audio_upload_complete_params.UploadResponse] | 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, + ) -> AudioUploadCompleteResponse: + """ + This API must be called after adding audio(s); The add audio call gives you URLs to upload, and you complete a `PUT` request to those URLs. + Once that is done, calling this initiates the process to actually add the subtitle to the video. + + Args: + asset_id: An asset id for the previously created asset. + upload_responses: Body parameter. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + AudioUploadCompleteResponse: 200 + + Example: + ```python + audio_upload = await client.audio_upload.complete( + asset_id="assetId", + upload_responses=[{"language_codes": ["en"], "uploaded": True}], + ) + ``` + """ + if asset_id is None or (isinstance(asset_id, str) and not asset_id): + raise ValueError(f"Expected a non-empty value for `asset_id` but received {asset_id!r}") + return await self._post( + path_template("/video/assets/{asset_ID}/audio/upload/event", **{"asset_ID": asset_id}), + body=await async_maybe_transform( + {"upload_responses": upload_responses}, + audio_upload_complete_params.AudioUploadCompleteParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AudioUploadCompleteResponse, + ) + + +class AudioUploadResourceWithRawResponse: + def __init__(self, audio_upload: AudioUploadResource) -> None: + self._audio_upload = audio_upload + + self.upload = to_raw_response_wrapper( + audio_upload.upload, + ) + self.complete = to_raw_response_wrapper( + audio_upload.complete, + ) + + +class AsyncAudioUploadResourceWithRawResponse: + def __init__(self, audio_upload: AsyncAudioUploadResource) -> None: + self._audio_upload = audio_upload + + self.upload = async_to_raw_response_wrapper( + audio_upload.upload, + ) + self.complete = async_to_raw_response_wrapper( + audio_upload.complete, + ) + + +class AudioUploadResourceWithStreamingResponse: + def __init__(self, audio_upload: AudioUploadResource) -> None: + self._audio_upload = audio_upload + + self.upload = to_streamed_response_wrapper( + audio_upload.upload, + ) + self.complete = to_streamed_response_wrapper( + audio_upload.complete, + ) + + +class AsyncAudioUploadResourceWithStreamingResponse: + def __init__(self, audio_upload: AsyncAudioUploadResource) -> None: + self._audio_upload = audio_upload + + self.upload = async_to_streamed_response_wrapper( + audio_upload.upload, + ) + self.complete = async_to_streamed_response_wrapper( + audio_upload.complete, + ) diff --git a/src/gumlet/resources/audit_logs.py b/src/gumlet/resources/audit_logs.py new file mode 100644 index 0000000..ffb2ddb --- /dev/null +++ b/src/gumlet/resources/audit_logs.py @@ -0,0 +1,291 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +import httpx + +from typing import List +from typing_extensions import Literal +from .._types import SequenceNotStr + +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.audit_log_fetch_response import AuditLogFetchResponse +from ..types import audit_log_fetch_params + +__all__ = ["AuditLogsResource", "AsyncAuditLogsResource"] + + +class AuditLogsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> AuditLogsResourceWithRawResponse: + return AuditLogsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AuditLogsResourceWithStreamingResponse: + return AuditLogsResourceWithStreamingResponse(self) + + def fetch( + self, + *, + date_range: audit_log_fetch_params.DateRange, + page_number: int | Omit = omit, + page_size: int | Omit = omit, + user_email: SequenceNotStr[str] | Omit = omit, + activity_type: List[ + Literal[ + "workspace_created", + "video_uploaded", + "video_details_updated", + "video_deleted", + "thumbnail_updated", + "subtitles_updated", + "audio_updated", + "processing_settings_updated", + "player_settings_updated", + "workspace_updated", + "team_member_invited", + "team_member_removed", + "import_started", + "playlist_created", + "playlist_updated", + "playlist_asset_changed", + "security_settings_updated", + "channel_updated", + "video_alert_changed", + "video_report_changed", + "live_stream_created", + "live_stream_started", + "live_stream_thumbnail_updated", + "live_stream_completed", + "live_stream_collection_created", + "live_stream_collection_updated", + "live_stream_deleted", + "image_source_created", + "image_source_updated", + "image_report_changed", + "image_alert_changed", + "user_profile_updated", + "api_key_changed", + "webhook_created", + "webhook_updated", + "webhook_deleted", + "drm_credentials_changed", + "plan_changed", + "billing_details_updated", + "playlist_deleted", + "payment_method_added", + "billing_alert_changed", + ] + ] + | 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, + ) -> AuditLogFetchResponse: + """ + Get audit logs for the user activity in your organisation. Please note that this endpoint can only be accessed by `owner` and `admin` role users. + + Args: + date_range: Body parameter. + page_number: Page number of results. + page_size: Body parameter. + user_email: Array of user emails to filter the activity logs. + activity_type: Array of activity types to filter the audit logs + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + AuditLogFetchResponse: Successful response + + Example: + ```python + audit_log = client.audit_logs.fetch( + date_range={"start_at": "2026-08-25", "end_at": "2026-08-29"}, + page_number=1, + page_size=100, + ) + ``` + """ + return self._post( + "/user/audit-log", + body=maybe_transform( + { + "date_range": date_range, + "page_number": page_number, + "page_size": page_size, + "user_email": user_email, + "activity_type": activity_type, + }, + audit_log_fetch_params.AuditLogFetchParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AuditLogFetchResponse, + ) + + +class AsyncAuditLogsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncAuditLogsResourceWithRawResponse: + return AsyncAuditLogsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncAuditLogsResourceWithStreamingResponse: + return AsyncAuditLogsResourceWithStreamingResponse(self) + + async def fetch( + self, + *, + date_range: audit_log_fetch_params.DateRange, + page_number: int | Omit = omit, + page_size: int | Omit = omit, + user_email: SequenceNotStr[str] | Omit = omit, + activity_type: List[ + Literal[ + "workspace_created", + "video_uploaded", + "video_details_updated", + "video_deleted", + "thumbnail_updated", + "subtitles_updated", + "audio_updated", + "processing_settings_updated", + "player_settings_updated", + "workspace_updated", + "team_member_invited", + "team_member_removed", + "import_started", + "playlist_created", + "playlist_updated", + "playlist_asset_changed", + "security_settings_updated", + "channel_updated", + "video_alert_changed", + "video_report_changed", + "live_stream_created", + "live_stream_started", + "live_stream_thumbnail_updated", + "live_stream_completed", + "live_stream_collection_created", + "live_stream_collection_updated", + "live_stream_deleted", + "image_source_created", + "image_source_updated", + "image_report_changed", + "image_alert_changed", + "user_profile_updated", + "api_key_changed", + "webhook_created", + "webhook_updated", + "webhook_deleted", + "drm_credentials_changed", + "plan_changed", + "billing_details_updated", + "playlist_deleted", + "payment_method_added", + "billing_alert_changed", + ] + ] + | 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, + ) -> AuditLogFetchResponse: + """ + Get audit logs for the user activity in your organisation. Please note that this endpoint can only be accessed by `owner` and `admin` role users. + + Args: + date_range: Body parameter. + page_number: Page number of results. + page_size: Body parameter. + user_email: Array of user emails to filter the activity logs. + activity_type: Array of activity types to filter the audit logs + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + AuditLogFetchResponse: Successful response + + Example: + ```python + audit_log = await client.audit_logs.fetch( + date_range={"start_at": "2026-08-25", "end_at": "2026-08-29"}, + page_number=1, + page_size=100, + ) + ``` + """ + return await self._post( + "/user/audit-log", + body=await async_maybe_transform( + { + "date_range": date_range, + "page_number": page_number, + "page_size": page_size, + "user_email": user_email, + "activity_type": activity_type, + }, + audit_log_fetch_params.AuditLogFetchParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AuditLogFetchResponse, + ) + + +class AuditLogsResourceWithRawResponse: + def __init__(self, audit_logs: AuditLogsResource) -> None: + self._audit_logs = audit_logs + + self.fetch = to_raw_response_wrapper( + audit_logs.fetch, + ) + + +class AsyncAuditLogsResourceWithRawResponse: + def __init__(self, audit_logs: AsyncAuditLogsResource) -> None: + self._audit_logs = audit_logs + + self.fetch = async_to_raw_response_wrapper( + audit_logs.fetch, + ) + + +class AuditLogsResourceWithStreamingResponse: + def __init__(self, audit_logs: AuditLogsResource) -> None: + self._audit_logs = audit_logs + + self.fetch = to_streamed_response_wrapper( + audit_logs.fetch, + ) + + +class AsyncAuditLogsResourceWithStreamingResponse: + def __init__(self, audit_logs: AsyncAuditLogsResource) -> None: + self._audit_logs = audit_logs + + self.fetch = async_to_streamed_response_wrapper( + audit_logs.fetch, + ) diff --git a/src/gumlet/resources/billing.py b/src/gumlet/resources/billing.py new file mode 100644 index 0000000..bad31d3 --- /dev/null +++ b/src/gumlet/resources/billing.py @@ -0,0 +1,464 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +import httpx + +from .._types import Body, Query, Headers, NotGiven, 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.billing_list_invoices_response import BillingListInvoicesResponse +from ..types.billing_fetch_details_response import BillingFetchDetailsResponse +from ..types.billing_update_details_response import BillingUpdateDetailsResponse +from ..types import billing_update_details_params +from ..types.billing_fetch_upcoming_invoice_response import BillingFetchUpcomingInvoiceResponse + +__all__ = ["BillingResource", "AsyncBillingResource"] + + +class BillingResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> BillingResourceWithRawResponse: + return BillingResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> BillingResourceWithStreamingResponse: + return BillingResourceWithStreamingResponse(self) + + def list_invoices( + self, + *, + # 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, + ) -> BillingListInvoicesResponse: + """ + Liost all invoices that are generated so far. + + Args: + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + BillingListInvoicesResponse: Successful response + + Example: + ```python + billing = client.billing.list_invoices() + ``` + """ + return self._get( + "/mixed/billing/invoice/history", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=BillingListInvoicesResponse, + ) + + def fetch_details( + self, + *, + # 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, + ) -> BillingFetchDetailsResponse: + """ + Get billing details for this organization. + + Args: + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + BillingFetchDetailsResponse: Successful response + + Example: + ```python + billing = client.billing.fetch_details() + ``` + """ + return self._get( + "/mixed/billing/details", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=BillingFetchDetailsResponse, + ) + + def update_details( + self, + *, + address_line: str, + city: str, + company_name: str, + country_code: str, + gst_number: str, + postal: str, + state_code: 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, + ) -> BillingUpdateDetailsResponse: + """ + Update billing details + + Args: + address_line: Address line 1 + city: Name of the city + company_name: Company name + country_code: ISO country code + gst_number: GST / VAT details of the company + postal: Postal code of the company + state_code: ISO code of the state / region + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + BillingUpdateDetailsResponse: Successful response + + Example: + ```python + billing = client.billing.update_details( + address_line="", + city="", + company_name="", + country_code="", + gst_number="", + postal="", + state_code="", + ) + ``` + """ + return self._post( + "/mixed/billing/details", + body=maybe_transform( + { + "address_line": address_line, + "city": city, + "company_name": company_name, + "country_code": country_code, + "gst_number": gst_number, + "postal": postal, + "state_code": state_code, + }, + billing_update_details_params.BillingUpdateDetailsParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=BillingUpdateDetailsResponse, + ) + + def fetch_upcoming_invoice( + self, + *, + # 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, + ) -> BillingFetchUpcomingInvoiceResponse: + """ + Get details about upcoming invoice. + + Args: + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + BillingFetchUpcomingInvoiceResponse: Successful response + + Example: + ```python + billing = client.billing.fetch_upcoming_invoice() + ``` + """ + return self._get( + "/mixed/billing/invoice/upcoming", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=BillingFetchUpcomingInvoiceResponse, + ) + + +class AsyncBillingResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncBillingResourceWithRawResponse: + return AsyncBillingResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncBillingResourceWithStreamingResponse: + return AsyncBillingResourceWithStreamingResponse(self) + + async def list_invoices( + self, + *, + # 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, + ) -> BillingListInvoicesResponse: + """ + Liost all invoices that are generated so far. + + Args: + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + BillingListInvoicesResponse: Successful response + + Example: + ```python + billing = await client.billing.list_invoices() + ``` + """ + return await self._get( + "/mixed/billing/invoice/history", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=BillingListInvoicesResponse, + ) + + async def fetch_details( + self, + *, + # 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, + ) -> BillingFetchDetailsResponse: + """ + Get billing details for this organization. + + Args: + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + BillingFetchDetailsResponse: Successful response + + Example: + ```python + billing = await client.billing.fetch_details() + ``` + """ + return await self._get( + "/mixed/billing/details", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=BillingFetchDetailsResponse, + ) + + async def update_details( + self, + *, + address_line: str, + city: str, + company_name: str, + country_code: str, + gst_number: str, + postal: str, + state_code: 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, + ) -> BillingUpdateDetailsResponse: + """ + Update billing details + + Args: + address_line: Address line 1 + city: Name of the city + company_name: Company name + country_code: ISO country code + gst_number: GST / VAT details of the company + postal: Postal code of the company + state_code: ISO code of the state / region + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + BillingUpdateDetailsResponse: Successful response + + Example: + ```python + billing = await client.billing.update_details( + address_line="", + city="", + company_name="", + country_code="", + gst_number="", + postal="", + state_code="", + ) + ``` + """ + return await self._post( + "/mixed/billing/details", + body=await async_maybe_transform( + { + "address_line": address_line, + "city": city, + "company_name": company_name, + "country_code": country_code, + "gst_number": gst_number, + "postal": postal, + "state_code": state_code, + }, + billing_update_details_params.BillingUpdateDetailsParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=BillingUpdateDetailsResponse, + ) + + async def fetch_upcoming_invoice( + self, + *, + # 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, + ) -> BillingFetchUpcomingInvoiceResponse: + """ + Get details about upcoming invoice. + + Args: + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + BillingFetchUpcomingInvoiceResponse: Successful response + + Example: + ```python + billing = await client.billing.fetch_upcoming_invoice() + ``` + """ + return await self._get( + "/mixed/billing/invoice/upcoming", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=BillingFetchUpcomingInvoiceResponse, + ) + + +class BillingResourceWithRawResponse: + def __init__(self, billing: BillingResource) -> None: + self._billing = billing + + self.list_invoices = to_raw_response_wrapper( + billing.list_invoices, + ) + self.fetch_details = to_raw_response_wrapper( + billing.fetch_details, + ) + self.update_details = to_raw_response_wrapper( + billing.update_details, + ) + self.fetch_upcoming_invoice = to_raw_response_wrapper( + billing.fetch_upcoming_invoice, + ) + + +class AsyncBillingResourceWithRawResponse: + def __init__(self, billing: AsyncBillingResource) -> None: + self._billing = billing + + self.list_invoices = async_to_raw_response_wrapper( + billing.list_invoices, + ) + self.fetch_details = async_to_raw_response_wrapper( + billing.fetch_details, + ) + self.update_details = async_to_raw_response_wrapper( + billing.update_details, + ) + self.fetch_upcoming_invoice = async_to_raw_response_wrapper( + billing.fetch_upcoming_invoice, + ) + + +class BillingResourceWithStreamingResponse: + def __init__(self, billing: BillingResource) -> None: + self._billing = billing + + self.list_invoices = to_streamed_response_wrapper( + billing.list_invoices, + ) + self.fetch_details = to_streamed_response_wrapper( + billing.fetch_details, + ) + self.update_details = to_streamed_response_wrapper( + billing.update_details, + ) + self.fetch_upcoming_invoice = to_streamed_response_wrapper( + billing.fetch_upcoming_invoice, + ) + + +class AsyncBillingResourceWithStreamingResponse: + def __init__(self, billing: AsyncBillingResource) -> None: + self._billing = billing + + self.list_invoices = async_to_streamed_response_wrapper( + billing.list_invoices, + ) + self.fetch_details = async_to_streamed_response_wrapper( + billing.fetch_details, + ) + self.update_details = async_to_streamed_response_wrapper( + billing.update_details, + ) + self.fetch_upcoming_invoice = async_to_streamed_response_wrapper( + billing.fetch_upcoming_invoice, + ) diff --git a/src/gumlet/resources/channel_viewers.py b/src/gumlet/resources/channel_viewers.py new file mode 100644 index 0000000..56cc929 --- /dev/null +++ b/src/gumlet/resources/channel_viewers.py @@ -0,0 +1,545 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +import httpx + +from typing import Iterable, Mapping, cast +from .._types import FileTypes, SequenceNotStr + +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._files import deepcopy_with_paths +from .._utils import extract_files, 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.channel_viewer_invite_response import ChannelViewerInviteResponse +from ..types import ( + channel_viewer_invite_params, + channel_viewer_delete_params, + channel_viewer_invite_csv_params, + channel_viewer_list_subscribers_params, +) +from ..types.channel_viewer_delete_response import ChannelViewerDeleteResponse +from ..types.channel_viewer_invite_csv_response import ChannelViewerInviteCsvResponse +from ..types.channel_viewer_list_subscribers_response import ChannelViewerListSubscribersResponse + +__all__ = ["ChannelViewersResource", "AsyncChannelViewersResource"] + + +class ChannelViewersResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> ChannelViewersResourceWithRawResponse: + return ChannelViewersResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ChannelViewersResourceWithStreamingResponse: + return ChannelViewersResourceWithStreamingResponse(self) + + def invite( + self, + video_workspace_id: str, + *, + users: Iterable[channel_viewer_invite_params.User], + # 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, + ) -> ChannelViewerInviteResponse: + """ + Invite one or more viewers to a members-only channel. + + Args: + video_workspace_id: Gumlet workspace ID. You can get it on Gumlet dashboard or retrieve it using list workspace API. + users: List of viewers to invite. A maximum of 200 viewers can be invited in one request. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + ChannelViewerInviteResponse: 200 + + Example: + ```python + channel_viewer = client.channel_viewers.invite( + video_workspace_id="videoWorkspaceId", + users=[ + {"email": "test@gumlet.com", "name": "Test User-0"}, + {"email": "test+1@gumlet.com", "name": "Test User-1"}, + {"email": "test+2@gumlet.com", "name": "Test User-2"}, + ], + ) + ``` + """ + if video_workspace_id is None or (isinstance(video_workspace_id, str) and not video_workspace_id): + raise ValueError(f"Expected a non-empty value for `video_workspace_id` but received {video_workspace_id!r}") + return self._post( + path_template("/channel/{video_workspace_id}/viewers/invite", **{"video_workspace_id": video_workspace_id}), + body=maybe_transform( + {"users": users}, + channel_viewer_invite_params.ChannelViewerInviteParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ChannelViewerInviteResponse, + ) + + def delete( + self, + video_workspace_id: str, + *, + emails: SequenceNotStr[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, + ) -> ChannelViewerDeleteResponse: + """ + Remove one or more viewers from a channel by email address. + + Args: + video_workspace_id: Gumlet workspace ID. You can get it on Gumlet dashboard or retrieve it using list workspace API. + emails: Email addresses of viewers to remove. A maximum of 200 viewers can be removed in one request. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + ChannelViewerDeleteResponse: 200 + + Example: + ```python + channel_viewer = client.channel_viewers.delete( + video_workspace_id="videoWorkspaceId", + emails=["test@gumlet.com", "test+2@gumlet.com"], + ) + ``` + """ + if video_workspace_id is None or (isinstance(video_workspace_id, str) and not video_workspace_id): + raise ValueError(f"Expected a non-empty value for `video_workspace_id` but received {video_workspace_id!r}") + return self._post( + path_template("/channel/{video_workspace_id}/viewers/remove", **{"video_workspace_id": video_workspace_id}), + body=maybe_transform( + {"emails": emails}, + channel_viewer_delete_params.ChannelViewerDeleteParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ChannelViewerDeleteResponse, + ) + + def invite_csv( + self, + video_workspace_id: str, + *, + viewers_csv: FileTypes, + # 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, + ) -> ChannelViewerInviteCsvResponse: + """ + Invite viewers to a channel by uploading a CSV file. + + Args: + video_workspace_id: Gumlet workspace ID. You can get it on Gumlet dashboard or retrieve it using list workspace API. + viewers_csv: CSV file containing viewer rows. Required columns: email, name. Maximum 500 viewers. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + ChannelViewerInviteCsvResponse: 200 + + Example: + ```python + channel_viewer = client.channel_viewers.invite_csv( + video_workspace_id="videoWorkspaceId", + viewers_csv=b"viewers.csv", + ) + ``` + """ + if video_workspace_id is None or (isinstance(video_workspace_id, str) and not video_workspace_id): + raise ValueError(f"Expected a non-empty value for `video_workspace_id` but received {video_workspace_id!r}") + body = deepcopy_with_paths( + { + "viewers_csv": viewers_csv, + }, + [["viewers_csv"]], + ) + files = extract_files(cast(Mapping[str, object], body), paths=[["viewers_csv"]]) + extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} + return self._post( + path_template( + "/channel/{video_workspace_id}/viewers/invite/csv", **{"video_workspace_id": video_workspace_id} + ), + body=maybe_transform(body, channel_viewer_invite_csv_params.ChannelViewerInviteCsvParams), + files=files, + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ChannelViewerInviteCsvResponse, + ) + + def list_subscribers( + self, + workspace_id: str, + *, + page_number: int | Omit = omit, + page_size: int | 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, + ) -> ChannelViewerListSubscribersResponse: + """ + List all channel subscribers. + + Args: + workspace_id: Gumlet workspace ID. You can get it on Gumlet dashboard or retrieve it using list workspace API. + page_number: Page number to retrieve. Starts at 1. + page_size: Number of items to return per page + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + ChannelViewerListSubscribersResponse: Successful response + + Example: + ```python + channel_viewer = client.channel_viewers.list_subscribers( + workspace_id="workspaceId", + page_number=1, + page_size=10, + ) + ``` + """ + if workspace_id is None or (isinstance(workspace_id, str) and not workspace_id): + raise ValueError(f"Expected a non-empty value for `workspace_id` but received {workspace_id!r}") + return self._get( + path_template("/channel/{workspace_id}/viewers", **{"workspace_id": workspace_id}), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + {"page_number": page_number, "page_size": page_size}, + channel_viewer_list_subscribers_params.ChannelViewerListSubscribersParams, + ), + ), + cast_to=ChannelViewerListSubscribersResponse, + ) + + +class AsyncChannelViewersResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncChannelViewersResourceWithRawResponse: + return AsyncChannelViewersResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncChannelViewersResourceWithStreamingResponse: + return AsyncChannelViewersResourceWithStreamingResponse(self) + + async def invite( + self, + video_workspace_id: str, + *, + users: Iterable[channel_viewer_invite_params.User], + # 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, + ) -> ChannelViewerInviteResponse: + """ + Invite one or more viewers to a members-only channel. + + Args: + video_workspace_id: Gumlet workspace ID. You can get it on Gumlet dashboard or retrieve it using list workspace API. + users: List of viewers to invite. A maximum of 200 viewers can be invited in one request. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + ChannelViewerInviteResponse: 200 + + Example: + ```python + channel_viewer = await client.channel_viewers.invite( + video_workspace_id="videoWorkspaceId", + users=[ + {"email": "test@gumlet.com", "name": "Test User-0"}, + {"email": "test+1@gumlet.com", "name": "Test User-1"}, + {"email": "test+2@gumlet.com", "name": "Test User-2"}, + ], + ) + ``` + """ + if video_workspace_id is None or (isinstance(video_workspace_id, str) and not video_workspace_id): + raise ValueError(f"Expected a non-empty value for `video_workspace_id` but received {video_workspace_id!r}") + return await self._post( + path_template("/channel/{video_workspace_id}/viewers/invite", **{"video_workspace_id": video_workspace_id}), + body=await async_maybe_transform( + {"users": users}, + channel_viewer_invite_params.ChannelViewerInviteParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ChannelViewerInviteResponse, + ) + + async def delete( + self, + video_workspace_id: str, + *, + emails: SequenceNotStr[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, + ) -> ChannelViewerDeleteResponse: + """ + Remove one or more viewers from a channel by email address. + + Args: + video_workspace_id: Gumlet workspace ID. You can get it on Gumlet dashboard or retrieve it using list workspace API. + emails: Email addresses of viewers to remove. A maximum of 200 viewers can be removed in one request. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + ChannelViewerDeleteResponse: 200 + + Example: + ```python + channel_viewer = await client.channel_viewers.delete( + video_workspace_id="videoWorkspaceId", + emails=["test@gumlet.com", "test+2@gumlet.com"], + ) + ``` + """ + if video_workspace_id is None or (isinstance(video_workspace_id, str) and not video_workspace_id): + raise ValueError(f"Expected a non-empty value for `video_workspace_id` but received {video_workspace_id!r}") + return await self._post( + path_template("/channel/{video_workspace_id}/viewers/remove", **{"video_workspace_id": video_workspace_id}), + body=await async_maybe_transform( + {"emails": emails}, + channel_viewer_delete_params.ChannelViewerDeleteParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ChannelViewerDeleteResponse, + ) + + async def invite_csv( + self, + video_workspace_id: str, + *, + viewers_csv: FileTypes, + # 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, + ) -> ChannelViewerInviteCsvResponse: + """ + Invite viewers to a channel by uploading a CSV file. + + Args: + video_workspace_id: Gumlet workspace ID. You can get it on Gumlet dashboard or retrieve it using list workspace API. + viewers_csv: CSV file containing viewer rows. Required columns: email, name. Maximum 500 viewers. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + ChannelViewerInviteCsvResponse: 200 + + Example: + ```python + channel_viewer = await client.channel_viewers.invite_csv( + video_workspace_id="videoWorkspaceId", + viewers_csv=b"viewers.csv", + ) + ``` + """ + if video_workspace_id is None or (isinstance(video_workspace_id, str) and not video_workspace_id): + raise ValueError(f"Expected a non-empty value for `video_workspace_id` but received {video_workspace_id!r}") + body = deepcopy_with_paths( + { + "viewers_csv": viewers_csv, + }, + [["viewers_csv"]], + ) + files = extract_files(cast(Mapping[str, object], body), paths=[["viewers_csv"]]) + extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} + return await self._post( + path_template( + "/channel/{video_workspace_id}/viewers/invite/csv", **{"video_workspace_id": video_workspace_id} + ), + body=await async_maybe_transform(body, channel_viewer_invite_csv_params.ChannelViewerInviteCsvParams), + files=files, + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ChannelViewerInviteCsvResponse, + ) + + async def list_subscribers( + self, + workspace_id: str, + *, + page_number: int | Omit = omit, + page_size: int | 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, + ) -> ChannelViewerListSubscribersResponse: + """ + List all channel subscribers. + + Args: + workspace_id: Gumlet workspace ID. You can get it on Gumlet dashboard or retrieve it using list workspace API. + page_number: Page number to retrieve. Starts at 1. + page_size: Number of items to return per page + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + ChannelViewerListSubscribersResponse: Successful response + + Example: + ```python + channel_viewer = await client.channel_viewers.list_subscribers( + workspace_id="workspaceId", + page_number=1, + page_size=10, + ) + ``` + """ + if workspace_id is None or (isinstance(workspace_id, str) and not workspace_id): + raise ValueError(f"Expected a non-empty value for `workspace_id` but received {workspace_id!r}") + return await self._get( + path_template("/channel/{workspace_id}/viewers", **{"workspace_id": workspace_id}), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + {"page_number": page_number, "page_size": page_size}, + channel_viewer_list_subscribers_params.ChannelViewerListSubscribersParams, + ), + ), + cast_to=ChannelViewerListSubscribersResponse, + ) + + +class ChannelViewersResourceWithRawResponse: + def __init__(self, channel_viewers: ChannelViewersResource) -> None: + self._channel_viewers = channel_viewers + + self.invite = to_raw_response_wrapper( + channel_viewers.invite, + ) + self.delete = to_raw_response_wrapper( + channel_viewers.delete, + ) + self.invite_csv = to_raw_response_wrapper( + channel_viewers.invite_csv, + ) + self.list_subscribers = to_raw_response_wrapper( + channel_viewers.list_subscribers, + ) + + +class AsyncChannelViewersResourceWithRawResponse: + def __init__(self, channel_viewers: AsyncChannelViewersResource) -> None: + self._channel_viewers = channel_viewers + + self.invite = async_to_raw_response_wrapper( + channel_viewers.invite, + ) + self.delete = async_to_raw_response_wrapper( + channel_viewers.delete, + ) + self.invite_csv = async_to_raw_response_wrapper( + channel_viewers.invite_csv, + ) + self.list_subscribers = async_to_raw_response_wrapper( + channel_viewers.list_subscribers, + ) + + +class ChannelViewersResourceWithStreamingResponse: + def __init__(self, channel_viewers: ChannelViewersResource) -> None: + self._channel_viewers = channel_viewers + + self.invite = to_streamed_response_wrapper( + channel_viewers.invite, + ) + self.delete = to_streamed_response_wrapper( + channel_viewers.delete, + ) + self.invite_csv = to_streamed_response_wrapper( + channel_viewers.invite_csv, + ) + self.list_subscribers = to_streamed_response_wrapper( + channel_viewers.list_subscribers, + ) + + +class AsyncChannelViewersResourceWithStreamingResponse: + def __init__(self, channel_viewers: AsyncChannelViewersResource) -> None: + self._channel_viewers = channel_viewers + + self.invite = async_to_streamed_response_wrapper( + channel_viewers.invite, + ) + self.delete = async_to_streamed_response_wrapper( + channel_viewers.delete, + ) + self.invite_csv = async_to_streamed_response_wrapper( + channel_viewers.invite_csv, + ) + self.list_subscribers = async_to_streamed_response_wrapper( + channel_viewers.list_subscribers, + ) diff --git a/src/gumlet/resources/folders.py b/src/gumlet/resources/folders.py new file mode 100644 index 0000000..0724b7f --- /dev/null +++ b/src/gumlet/resources/folders.py @@ -0,0 +1,842 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +import httpx + +from typing import Optional, Union +from typing_extensions import overload +from .._types import SequenceNotStr + +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import path_template, maybe_transform, async_maybe_transform, required_args +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.folder_create_response import FolderCreateResponse +from ..types import folder_create_params, folder_list_params, folder_update_params, folder_delete_assets_params +from ..types.folder_list_response import FolderListResponse +from ..types.folder_retrieve_response import FolderRetrieveResponse +from ..types.folder_update_response import FolderUpdateResponse +from ..types.folder_delete_response import FolderDeleteResponse +from ..types.folder_delete_assets_response import FolderDeleteAssetsResponse + +__all__ = ["FoldersResource", "AsyncFoldersResource"] + + +class FoldersResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> FoldersResourceWithRawResponse: + return FoldersResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> FoldersResourceWithStreamingResponse: + return FoldersResourceWithStreamingResponse(self) + + def create( + self, + workspace_id: str, + *, + name: str, + parent_id: Optional[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, + ) -> FolderCreateResponse: + """ + Create a folder inside a video workspace. Optionally provide `parent_id` to create a nested folder. + + Args: + workspace_id: Video workspace id. + name: Folder name. + parent_id: Parent folder id. Send `null` or omit it to create a root-level folder. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + FolderCreateResponse: 200 + + Example: + ```python + folder = client.folders.create( + workspace_id="workspaceId", + name="Course Assets", + parent_id="", + ) + ``` + """ + if workspace_id is None or (isinstance(workspace_id, str) and not workspace_id): + raise ValueError(f"Expected a non-empty value for `workspace_id` but received {workspace_id!r}") + return self._post( + path_template("/video/workspaces/{workspace_id}/folders", **{"workspace_id": workspace_id}), + body=maybe_transform( + { + "name": name, + "parent_id": parent_id, + }, + folder_create_params.FolderCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FolderCreateResponse, + ) + + def list( + self, + workspace_id: str, + *, + parent_id: 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, + ) -> FolderListResponse: + """ + List folders for a video workspace. Use `parent_id` to list only folders inside a specific parent folder. + + Args: + workspace_id: Video workspace id. + parent_id: Parent folder id. Send `null` to list root folders. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + FolderListResponse: 200 + + Example: + ```python + folder = client.folders.list( + workspace_id="workspaceId", + ) + ``` + """ + if workspace_id is None or (isinstance(workspace_id, str) and not workspace_id): + raise ValueError(f"Expected a non-empty value for `workspace_id` but received {workspace_id!r}") + return self._get( + path_template("/video/workspaces/{workspace_id}/folders", **{"workspace_id": workspace_id}), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform({"parent_id": parent_id}, folder_list_params.FolderListParams), + ), + cast_to=FolderListResponse, + ) + + def retrieve( + self, + folder_id: str, + *, + workspace_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, + ) -> FolderRetrieveResponse: + """ + Get a single folder by id. + + Args: + folder_id: Folder id. + workspace_id: Video workspace id. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + FolderRetrieveResponse: 200 + + Example: + ```python + folder = client.folders.retrieve( + workspace_id="workspaceId", + folder_id="folderId", + ) + ``` + """ + if workspace_id is None or (isinstance(workspace_id, str) and not workspace_id): + raise ValueError(f"Expected a non-empty value for `workspace_id` but received {workspace_id!r}") + if folder_id is None or (isinstance(folder_id, str) and not folder_id): + raise ValueError(f"Expected a non-empty value for `folder_id` but received {folder_id!r}") + return self._get( + path_template( + "/video/workspaces/{workspace_id}/folders/{folder_id}", + **{"workspace_id": workspace_id, "folder_id": folder_id}, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FolderRetrieveResponse, + ) + + @overload + def update( + self, + folder_id: str, + *, + workspace_id: str, + name: str, + parent_id: Optional[str] | Omit = omit, + asset_ids: 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, + ) -> FolderUpdateResponse: ... + + @overload + def update( + self, + folder_id: str, + *, + workspace_id: str, + name: str | Omit = omit, + parent_id: str | Omit = omit, + asset_ids: SequenceNotStr[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, + ) -> FolderUpdateResponse: ... + + @required_args(["name"], ["asset_ids"]) + def update( + self, + folder_id: str, + *, + workspace_id: str, + name: str | Omit = omit, + parent_id: Union[Optional[str], str] | Omit = omit, + asset_ids: 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, + ) -> FolderUpdateResponse: + """ + Rename a folder, move it to another parent folder, or move assets into the folder by sending `asset_ids`. + + Args: + folder_id: Folder id. + workspace_id: Video workspace id. + name: New folder name. + parent_id: New parent folder id. Send `null` to move the folder to the root level. + asset_ids: Asset ids to move into this folder. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + FolderUpdateResponse: 200 + + Example: + ```python + folder = client.folders.update( + workspace_id="workspaceId", + folder_id="folderId", + name="Course Assets Updated", + ) + ``` + """ + if workspace_id is None or (isinstance(workspace_id, str) and not workspace_id): + raise ValueError(f"Expected a non-empty value for `workspace_id` but received {workspace_id!r}") + if folder_id is None or (isinstance(folder_id, str) and not folder_id): + raise ValueError(f"Expected a non-empty value for `folder_id` but received {folder_id!r}") + return self._post( + path_template( + "/video/workspaces/{workspace_id}/folders/{folder_id}", + **{"workspace_id": workspace_id, "folder_id": folder_id}, + ), + body=maybe_transform( + { + "name": name, + "parent_id": parent_id, + "asset_ids": asset_ids, + }, + folder_update_params.FolderUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FolderUpdateResponse, + ) + + def delete( + self, + folder_id: str, + *, + workspace_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, + ) -> FolderDeleteResponse: + """ + Delete a folder. Descendant folders and assets inside them are deleted by the backend workflow. + + Args: + folder_id: Folder id. + workspace_id: Video workspace id. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + FolderDeleteResponse: 200 + + Example: + ```python + folder = client.folders.delete( + workspace_id="workspaceId", + folder_id="folderId", + ) + ``` + """ + if workspace_id is None or (isinstance(workspace_id, str) and not workspace_id): + raise ValueError(f"Expected a non-empty value for `workspace_id` but received {workspace_id!r}") + if folder_id is None or (isinstance(folder_id, str) and not folder_id): + raise ValueError(f"Expected a non-empty value for `folder_id` but received {folder_id!r}") + return self._delete( + path_template( + "/video/workspaces/{workspace_id}/folders/{folder_id}", + **{"workspace_id": workspace_id, "folder_id": folder_id}, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FolderDeleteResponse, + ) + + def delete_assets( + self, + workspace_id: str, + *, + asset_ids: SequenceNotStr[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, + ) -> FolderDeleteAssetsResponse: + """ + Remove one or more assets from their current folder assignment inside the workspace. + + Args: + workspace_id: Video workspace id. + asset_ids: Body parameter. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + FolderDeleteAssetsResponse: 200 + + Example: + ```python + folder = client.folders.delete_assets( + workspace_id="workspaceId", + asset_ids=["67e4f2b4403562dbea654301", "67e4f2bb403562dbea654302"], + ) + ``` + """ + if workspace_id is None or (isinstance(workspace_id, str) and not workspace_id): + raise ValueError(f"Expected a non-empty value for `workspace_id` but received {workspace_id!r}") + return self._post( + path_template( + "/video/workspaces/{workspace_id}/remove-assets-from-folder", **{"workspace_id": workspace_id} + ), + body=maybe_transform( + {"asset_ids": asset_ids}, + folder_delete_assets_params.FolderDeleteAssetsParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FolderDeleteAssetsResponse, + ) + + +class AsyncFoldersResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncFoldersResourceWithRawResponse: + return AsyncFoldersResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncFoldersResourceWithStreamingResponse: + return AsyncFoldersResourceWithStreamingResponse(self) + + async def create( + self, + workspace_id: str, + *, + name: str, + parent_id: Optional[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, + ) -> FolderCreateResponse: + """ + Create a folder inside a video workspace. Optionally provide `parent_id` to create a nested folder. + + Args: + workspace_id: Video workspace id. + name: Folder name. + parent_id: Parent folder id. Send `null` or omit it to create a root-level folder. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + FolderCreateResponse: 200 + + Example: + ```python + folder = await client.folders.create( + workspace_id="workspaceId", + name="Course Assets", + parent_id="", + ) + ``` + """ + if workspace_id is None or (isinstance(workspace_id, str) and not workspace_id): + raise ValueError(f"Expected a non-empty value for `workspace_id` but received {workspace_id!r}") + return await self._post( + path_template("/video/workspaces/{workspace_id}/folders", **{"workspace_id": workspace_id}), + body=await async_maybe_transform( + { + "name": name, + "parent_id": parent_id, + }, + folder_create_params.FolderCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FolderCreateResponse, + ) + + async def list( + self, + workspace_id: str, + *, + parent_id: 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, + ) -> FolderListResponse: + """ + List folders for a video workspace. Use `parent_id` to list only folders inside a specific parent folder. + + Args: + workspace_id: Video workspace id. + parent_id: Parent folder id. Send `null` to list root folders. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + FolderListResponse: 200 + + Example: + ```python + folder = await client.folders.list( + workspace_id="workspaceId", + ) + ``` + """ + if workspace_id is None or (isinstance(workspace_id, str) and not workspace_id): + raise ValueError(f"Expected a non-empty value for `workspace_id` but received {workspace_id!r}") + return await self._get( + path_template("/video/workspaces/{workspace_id}/folders", **{"workspace_id": workspace_id}), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform({"parent_id": parent_id}, folder_list_params.FolderListParams), + ), + cast_to=FolderListResponse, + ) + + async def retrieve( + self, + folder_id: str, + *, + workspace_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, + ) -> FolderRetrieveResponse: + """ + Get a single folder by id. + + Args: + folder_id: Folder id. + workspace_id: Video workspace id. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + FolderRetrieveResponse: 200 + + Example: + ```python + folder = await client.folders.retrieve( + workspace_id="workspaceId", + folder_id="folderId", + ) + ``` + """ + if workspace_id is None or (isinstance(workspace_id, str) and not workspace_id): + raise ValueError(f"Expected a non-empty value for `workspace_id` but received {workspace_id!r}") + if folder_id is None or (isinstance(folder_id, str) and not folder_id): + raise ValueError(f"Expected a non-empty value for `folder_id` but received {folder_id!r}") + return await self._get( + path_template( + "/video/workspaces/{workspace_id}/folders/{folder_id}", + **{"workspace_id": workspace_id, "folder_id": folder_id}, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FolderRetrieveResponse, + ) + + @overload + async def update( + self, + folder_id: str, + *, + workspace_id: str, + name: str, + parent_id: Optional[str] | Omit = omit, + asset_ids: 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, + ) -> FolderUpdateResponse: ... + + @overload + async def update( + self, + folder_id: str, + *, + workspace_id: str, + name: str | Omit = omit, + parent_id: str | Omit = omit, + asset_ids: SequenceNotStr[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, + ) -> FolderUpdateResponse: ... + + @required_args(["name"], ["asset_ids"]) + async def update( + self, + folder_id: str, + *, + workspace_id: str, + name: str | Omit = omit, + parent_id: Union[Optional[str], str] | Omit = omit, + asset_ids: 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, + ) -> FolderUpdateResponse: + """ + Rename a folder, move it to another parent folder, or move assets into the folder by sending `asset_ids`. + + Args: + folder_id: Folder id. + workspace_id: Video workspace id. + name: New folder name. + parent_id: New parent folder id. Send `null` to move the folder to the root level. + asset_ids: Asset ids to move into this folder. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + FolderUpdateResponse: 200 + + Example: + ```python + folder = await client.folders.update( + workspace_id="workspaceId", + folder_id="folderId", + name="Course Assets Updated", + ) + ``` + """ + if workspace_id is None or (isinstance(workspace_id, str) and not workspace_id): + raise ValueError(f"Expected a non-empty value for `workspace_id` but received {workspace_id!r}") + if folder_id is None or (isinstance(folder_id, str) and not folder_id): + raise ValueError(f"Expected a non-empty value for `folder_id` but received {folder_id!r}") + return await self._post( + path_template( + "/video/workspaces/{workspace_id}/folders/{folder_id}", + **{"workspace_id": workspace_id, "folder_id": folder_id}, + ), + body=await async_maybe_transform( + { + "name": name, + "parent_id": parent_id, + "asset_ids": asset_ids, + }, + folder_update_params.FolderUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FolderUpdateResponse, + ) + + async def delete( + self, + folder_id: str, + *, + workspace_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, + ) -> FolderDeleteResponse: + """ + Delete a folder. Descendant folders and assets inside them are deleted by the backend workflow. + + Args: + folder_id: Folder id. + workspace_id: Video workspace id. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + FolderDeleteResponse: 200 + + Example: + ```python + folder = await client.folders.delete( + workspace_id="workspaceId", + folder_id="folderId", + ) + ``` + """ + if workspace_id is None or (isinstance(workspace_id, str) and not workspace_id): + raise ValueError(f"Expected a non-empty value for `workspace_id` but received {workspace_id!r}") + if folder_id is None or (isinstance(folder_id, str) and not folder_id): + raise ValueError(f"Expected a non-empty value for `folder_id` but received {folder_id!r}") + return await self._delete( + path_template( + "/video/workspaces/{workspace_id}/folders/{folder_id}", + **{"workspace_id": workspace_id, "folder_id": folder_id}, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FolderDeleteResponse, + ) + + async def delete_assets( + self, + workspace_id: str, + *, + asset_ids: SequenceNotStr[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, + ) -> FolderDeleteAssetsResponse: + """ + Remove one or more assets from their current folder assignment inside the workspace. + + Args: + workspace_id: Video workspace id. + asset_ids: Body parameter. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + FolderDeleteAssetsResponse: 200 + + Example: + ```python + folder = await client.folders.delete_assets( + workspace_id="workspaceId", + asset_ids=["67e4f2b4403562dbea654301", "67e4f2bb403562dbea654302"], + ) + ``` + """ + if workspace_id is None or (isinstance(workspace_id, str) and not workspace_id): + raise ValueError(f"Expected a non-empty value for `workspace_id` but received {workspace_id!r}") + return await self._post( + path_template( + "/video/workspaces/{workspace_id}/remove-assets-from-folder", **{"workspace_id": workspace_id} + ), + body=await async_maybe_transform( + {"asset_ids": asset_ids}, + folder_delete_assets_params.FolderDeleteAssetsParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FolderDeleteAssetsResponse, + ) + + +class FoldersResourceWithRawResponse: + def __init__(self, folders: FoldersResource) -> None: + self._folders = folders + + self.create = to_raw_response_wrapper( + folders.create, + ) + self.list = to_raw_response_wrapper( + folders.list, + ) + self.retrieve = to_raw_response_wrapper( + folders.retrieve, + ) + self.update = to_raw_response_wrapper( + folders.update, + ) + self.delete = to_raw_response_wrapper( + folders.delete, + ) + self.delete_assets = to_raw_response_wrapper( + folders.delete_assets, + ) + + +class AsyncFoldersResourceWithRawResponse: + def __init__(self, folders: AsyncFoldersResource) -> None: + self._folders = folders + + self.create = async_to_raw_response_wrapper( + folders.create, + ) + self.list = async_to_raw_response_wrapper( + folders.list, + ) + self.retrieve = async_to_raw_response_wrapper( + folders.retrieve, + ) + self.update = async_to_raw_response_wrapper( + folders.update, + ) + self.delete = async_to_raw_response_wrapper( + folders.delete, + ) + self.delete_assets = async_to_raw_response_wrapper( + folders.delete_assets, + ) + + +class FoldersResourceWithStreamingResponse: + def __init__(self, folders: FoldersResource) -> None: + self._folders = folders + + self.create = to_streamed_response_wrapper( + folders.create, + ) + self.list = to_streamed_response_wrapper( + folders.list, + ) + self.retrieve = to_streamed_response_wrapper( + folders.retrieve, + ) + self.update = to_streamed_response_wrapper( + folders.update, + ) + self.delete = to_streamed_response_wrapper( + folders.delete, + ) + self.delete_assets = to_streamed_response_wrapper( + folders.delete_assets, + ) + + +class AsyncFoldersResourceWithStreamingResponse: + def __init__(self, folders: AsyncFoldersResource) -> None: + self._folders = folders + + self.create = async_to_streamed_response_wrapper( + folders.create, + ) + self.list = async_to_streamed_response_wrapper( + folders.list, + ) + self.retrieve = async_to_streamed_response_wrapper( + folders.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + folders.update, + ) + self.delete = async_to_streamed_response_wrapper( + folders.delete, + ) + self.delete_assets = async_to_streamed_response_wrapper( + folders.delete_assets, + ) diff --git a/src/gumlet/resources/image_sources.py b/src/gumlet/resources/image_sources.py new file mode 100644 index 0000000..e88599c --- /dev/null +++ b/src/gumlet/resources/image_sources.py @@ -0,0 +1,1077 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +import httpx + +from typing import Iterable +from typing_extensions import Literal +from .._types import SequenceNotStr + +from .._types import Body, Omit, Query, Headers, NotGiven, 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.image_source_create_response import ImageSourceCreateResponse +from ..types import ( + image_source_create_params, + image_source_list_params, + image_source_update_params, + image_source_purge_cache_params, + image_source_purge_params, +) +from ..types.image_source_list_response import ImageSourceListResponse +from ..types.image_source_retrieve_response import ImageSourceRetrieveResponse +from ..types.image_source_update_response import ImageSourceUpdateResponse +from ..types.image_source_delete_response import ImageSourceDeleteResponse +from ..types.image_source_purge_cache_response import ImageSourcePurgeCacheResponse +from ..types.image_source_purge_response import ImageSourcePurgeResponse + +__all__ = ["ImageSourcesResource", "AsyncImageSourcesResource"] + + +class ImageSourcesResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> ImageSourcesResourceWithRawResponse: + return ImageSourcesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ImageSourcesResourceWithStreamingResponse: + return ImageSourcesResourceWithStreamingResponse(self) + + def create( + self, + *, + namespace: str, + type: Literal[ + "proxy", + "webfolder", + "aws", + "gcs", + "dostorage", + "wasabi", + "cloudinary", + "azure", + "linode", + "backblaze", + "cloudflare", + "zoom", + ], + aws: image_source_create_params.Aws | Omit = omit, + proxy: image_source_create_params.Proxy | Omit = omit, + gcs: image_source_create_params.Gcs | Omit = omit, + dostorage: image_source_create_params.Dostorage | Omit = omit, + wasabi: image_source_create_params.Wasabi | Omit = omit, + cloudinary: image_source_create_params.Cloudinary | Omit = omit, + azure: image_source_create_params.Azure | Omit = omit, + linode: image_source_create_params.Linode | Omit = omit, + backblaze: image_source_create_params.Backblaze | Omit = omit, + cloudflare: image_source_create_params.Cloudflare | Omit = omit, + webfolder: image_source_create_params.Webfolder | 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, + ) -> ImageSourceCreateResponse: + """ + This endpoint allows users to create image source. + + Args: + namespace: unique subdomain associated with the image source + type: Body parameter. + aws: This is a required field if source type is aws. + proxy: This is a required field if source type is proxy. + gcs: This is a required field if source type is gcs. + dostorage: This is a required field if source type is dostorage. + wasabi: This is a required field if source type is wasabi. + cloudinary: This is a required field if source type is cloudinary. + azure: This is a required field if source type is azure. + linode: This is a required field if source type is linode. + backblaze: This is a required field if source type is backblaze. + cloudflare: This is a required field if source type is cloudflare. + webfolder: Body parameter. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + ImageSourceCreateResponse: 200 + + Example: + ```python + image_source = client.image_sources.create( + namespace="google-demo", + type="webfolder", + webfolder={"base_url": "https://www.google.com"}, + ) + ``` + """ + return self._post( + "/image/sources", + body=maybe_transform( + { + "namespace": namespace, + "type": type, + "aws": aws, + "proxy": proxy, + "gcs": gcs, + "dostorage": dostorage, + "wasabi": wasabi, + "cloudinary": cloudinary, + "azure": azure, + "linode": linode, + "backblaze": backblaze, + "cloudflare": cloudflare, + "webfolder": webfolder, + }, + image_source_create_params.ImageSourceCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ImageSourceCreateResponse, + ) + + def list( + self, + *, + offset: int | Omit = omit, + size: int | 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, + ) -> ImageSourceListResponse: + """ + This endpoint list image sources which are assigned to the user or token. + + Args: + offset: Skip number of items. Helpful for pagination. + size: Results per page. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + ImageSourceListResponse: List all image sources + + Example: + ```python + image_source = client.image_sources.list( + offset=0, + size=20, + ) + ``` + """ + return self._get( + "/image/sources", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform({"offset": offset, "size": size}, image_source_list_params.ImageSourceListParams), + ), + cast_to=ImageSourceListResponse, + ) + + def retrieve( + self, + image_source_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, + ) -> ImageSourceRetrieveResponse: + """ + Get all details about image source. + + Args: + image_source_id: Image source id. You can get it on Gumlet dashboard or using list sources API endpoint. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + ImageSourceRetrieveResponse + + Example: + ```python + image_source = client.image_sources.retrieve( + image_source_id="imageSourceId", + ) + ``` + """ + if image_source_id is None or (isinstance(image_source_id, str) and not image_source_id): + raise ValueError(f"Expected a non-empty value for `image_source_id` but received {image_source_id!r}") + return self._get( + path_template("/image/sources/{image_source_id}", **{"image_source_id": image_source_id}), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ImageSourceRetrieveResponse, + ) + + def update( + self, + image_source_id: str, + *, + type: Literal[ + "proxy", + "direct-upload", + "webfolder", + "aws", + "gcs", + "dostorage", + "wasabi", + "cloudinary", + "azure", + "linode", + "backblaze", + "cloudflare", + ] + | Omit = omit, + webfolder: image_source_update_params.Webfolder | Omit = omit, + aws: image_source_update_params.Aws | Omit = omit, + proxy: image_source_update_params.Proxy | Omit = omit, + gcs: image_source_update_params.Gcs | Omit = omit, + dostorage: image_source_update_params.Dostorage | Omit = omit, + wasabi: image_source_update_params.Wasabi | Omit = omit, + linode: image_source_update_params.Linode | Omit = omit, + backblaze: image_source_update_params.Backblaze | Omit = omit, + cloudflare: image_source_update_params.Cloudflare | Omit = omit, + cloudinary: image_source_update_params.Cloudinary | Omit = omit, + azure: image_source_update_params.Azure | Omit = omit, + default_params: object | Omit = omit, + error_image: str | Omit = omit, + request_headers: Iterable[object] | Omit = omit, + response_headers: Iterable[object] | Omit = omit, + temp_cname: SequenceNotStr[str] | Omit = omit, + browser_cache_time: int | Omit = omit, + cdn_cache_time: int | Omit = omit, + is_active: bool | Omit = omit, + cname: SequenceNotStr[str] | Omit = omit, + fallback_origins: Iterable[image_source_update_params.FallbackOrigin] | 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, + ) -> ImageSourceUpdateResponse: + """ + This endpoint allows users to update image source that has previously been created. + + Args: + image_source_id: image source id which you want to update + type: Body parameter. + webfolder: This is a required field if source type is webfolder. + aws: This is a required field if source type is aws. + proxy: This is a required field if source type is proxy. + gcs: This is a required field if source type is gcs. + dostorage: This is a required field if source type is dostorage. + wasabi: This is a required field if source type is wasabi. + linode: This is a required field if source type is linode. + backblaze: This is a required field if source type is backblaze. + cloudflare: This is a required field if source type is cloudflare. + cloudinary: This is a required field if source type is cloudinary. + azure: This is a required field if source type is azure. + default_params: Body parameter. + error_image: URL for error image to display when we get broken image from your origin. + request_headers: Body parameter. + response_headers: Body parameter. + temp_cname: Body parameter. + browser_cache_time: Browser cache time in seconds. For example setting this to 3600 caches the image in browser for 3600 seconds (1 hour) + cdn_cache_time: CDN cache time in seconds. + is_active: Enable / disable source. + cname: List of verified CNAMEs + fallback_origins: List of fallback origins + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + ImageSourceUpdateResponse: 200 + + Example: + ```python + image_source = client.image_sources.update( + image_source_id="imageSourceId", + type="aws", + aws={ + "bucket_name": "my-bucket-test", + "bucket_region": "ap-southeast-1", + "access_key": "BQUA6QFXVWHAAB6IO2X1", + "secret": "aws_secret", + }, + ) + ``` + """ + if image_source_id is None or (isinstance(image_source_id, str) and not image_source_id): + raise ValueError(f"Expected a non-empty value for `image_source_id` but received {image_source_id!r}") + return self._post( + path_template("/image/sources/{image_source_id}", **{"image_source_id": image_source_id}), + body=maybe_transform( + { + "type": type, + "webfolder": webfolder, + "aws": aws, + "proxy": proxy, + "gcs": gcs, + "dostorage": dostorage, + "wasabi": wasabi, + "linode": linode, + "backblaze": backblaze, + "cloudflare": cloudflare, + "cloudinary": cloudinary, + "azure": azure, + "default_params": default_params, + "error_image": error_image, + "request_headers": request_headers, + "response_headers": response_headers, + "temp_cname": temp_cname, + "browser_cache_time": browser_cache_time, + "cdn_cache_time": cdn_cache_time, + "is_active": is_active, + "cname": cname, + "fallback_origins": fallback_origins, + }, + image_source_update_params.ImageSourceUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ImageSourceUpdateResponse, + ) + + def delete( + self, + image_source_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, + ) -> ImageSourceDeleteResponse: + """ + This endpoint removes a image source. All image delivery using this subdomain will be stopped. + + Args: + image_source_id: Image source ID to delete. You can get this on Gumlet dashboard or in list source API. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + ImageSourceDeleteResponse + + Example: + ```python + image_source = client.image_sources.delete( + image_source_id="imageSourceId", + ) + ``` + """ + if image_source_id is None or (isinstance(image_source_id, str) and not image_source_id): + raise ValueError(f"Expected a non-empty value for `image_source_id` but received {image_source_id!r}") + return self._delete( + path_template("/image/sources/{image_source_id}", **{"image_source_id": image_source_id}), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ImageSourceDeleteResponse, + ) + + def purge_cache( + self, + subdomain: str, + *, + paths: 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, + ) -> ImageSourcePurgeCacheResponse: + """ + You can purge cache for any image by using our cache purge API. + + Args: + subdomain: Subdomain is same subdomain you created while creating source. If you serve image from example.gumlet.com, please enter only 'example' for this parameter. + paths: An array of path of images to purge. It should be provided without any query parameters. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + ImageSourcePurgeCacheResponse: 200 + + Example: + ```python + image_source = client.image_sources.purge_cache( + subdomain="subdomain", + paths=["image.jpeg", "image2.png"], + ) + ``` + + Deprecated: this method is deprecated. + """ + if subdomain is None or (isinstance(subdomain, str) and not subdomain): + raise ValueError(f"Expected a non-empty value for `subdomain` but received {subdomain!r}") + return self._post( + path_template("/purge/{subdomain}", **{"subdomain": subdomain}), + body=maybe_transform( + {"paths": paths}, + image_source_purge_cache_params.ImageSourcePurgeCacheParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ImageSourcePurgeCacheResponse, + ) + + def purge( + self, + source_id: str, + *, + paths: SequenceNotStr[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, + ) -> ImageSourcePurgeResponse: + """ + You can purge the cache for any image path by using this cache purge API. + + Args: + source_id: Image Source ID + paths: An array of path of images to purge. It should be provided without any query parameters. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + ImageSourcePurgeResponse: Successful response + + Example: + ```python + image_source = client.image_sources.purge( + source_id="sourceId", + paths=["image.jpeg", "image2.png"], + ) + ``` + """ + if source_id is None or (isinstance(source_id, str) and not source_id): + raise ValueError(f"Expected a non-empty value for `source_id` but received {source_id!r}") + return self._post( + path_template("/image/purge/{source_id}", **{"source_id": source_id}), + body=maybe_transform( + {"paths": paths}, + image_source_purge_params.ImageSourcePurgeParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ImageSourcePurgeResponse, + ) + + +class AsyncImageSourcesResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncImageSourcesResourceWithRawResponse: + return AsyncImageSourcesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncImageSourcesResourceWithStreamingResponse: + return AsyncImageSourcesResourceWithStreamingResponse(self) + + async def create( + self, + *, + namespace: str, + type: Literal[ + "proxy", + "webfolder", + "aws", + "gcs", + "dostorage", + "wasabi", + "cloudinary", + "azure", + "linode", + "backblaze", + "cloudflare", + "zoom", + ], + aws: image_source_create_params.Aws | Omit = omit, + proxy: image_source_create_params.Proxy | Omit = omit, + gcs: image_source_create_params.Gcs | Omit = omit, + dostorage: image_source_create_params.Dostorage | Omit = omit, + wasabi: image_source_create_params.Wasabi | Omit = omit, + cloudinary: image_source_create_params.Cloudinary | Omit = omit, + azure: image_source_create_params.Azure | Omit = omit, + linode: image_source_create_params.Linode | Omit = omit, + backblaze: image_source_create_params.Backblaze | Omit = omit, + cloudflare: image_source_create_params.Cloudflare | Omit = omit, + webfolder: image_source_create_params.Webfolder | 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, + ) -> ImageSourceCreateResponse: + """ + This endpoint allows users to create image source. + + Args: + namespace: unique subdomain associated with the image source + type: Body parameter. + aws: This is a required field if source type is aws. + proxy: This is a required field if source type is proxy. + gcs: This is a required field if source type is gcs. + dostorage: This is a required field if source type is dostorage. + wasabi: This is a required field if source type is wasabi. + cloudinary: This is a required field if source type is cloudinary. + azure: This is a required field if source type is azure. + linode: This is a required field if source type is linode. + backblaze: This is a required field if source type is backblaze. + cloudflare: This is a required field if source type is cloudflare. + webfolder: Body parameter. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + ImageSourceCreateResponse: 200 + + Example: + ```python + image_source = await client.image_sources.create( + namespace="google-demo", + type="webfolder", + webfolder={"base_url": "https://www.google.com"}, + ) + ``` + """ + return await self._post( + "/image/sources", + body=await async_maybe_transform( + { + "namespace": namespace, + "type": type, + "aws": aws, + "proxy": proxy, + "gcs": gcs, + "dostorage": dostorage, + "wasabi": wasabi, + "cloudinary": cloudinary, + "azure": azure, + "linode": linode, + "backblaze": backblaze, + "cloudflare": cloudflare, + "webfolder": webfolder, + }, + image_source_create_params.ImageSourceCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ImageSourceCreateResponse, + ) + + async def list( + self, + *, + offset: int | Omit = omit, + size: int | 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, + ) -> ImageSourceListResponse: + """ + This endpoint list image sources which are assigned to the user or token. + + Args: + offset: Skip number of items. Helpful for pagination. + size: Results per page. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + ImageSourceListResponse: List all image sources + + Example: + ```python + image_source = await client.image_sources.list( + offset=0, + size=20, + ) + ``` + """ + return await self._get( + "/image/sources", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + {"offset": offset, "size": size}, image_source_list_params.ImageSourceListParams + ), + ), + cast_to=ImageSourceListResponse, + ) + + async def retrieve( + self, + image_source_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, + ) -> ImageSourceRetrieveResponse: + """ + Get all details about image source. + + Args: + image_source_id: Image source id. You can get it on Gumlet dashboard or using list sources API endpoint. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + ImageSourceRetrieveResponse + + Example: + ```python + image_source = await client.image_sources.retrieve( + image_source_id="imageSourceId", + ) + ``` + """ + if image_source_id is None or (isinstance(image_source_id, str) and not image_source_id): + raise ValueError(f"Expected a non-empty value for `image_source_id` but received {image_source_id!r}") + return await self._get( + path_template("/image/sources/{image_source_id}", **{"image_source_id": image_source_id}), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ImageSourceRetrieveResponse, + ) + + async def update( + self, + image_source_id: str, + *, + type: Literal[ + "proxy", + "direct-upload", + "webfolder", + "aws", + "gcs", + "dostorage", + "wasabi", + "cloudinary", + "azure", + "linode", + "backblaze", + "cloudflare", + ] + | Omit = omit, + webfolder: image_source_update_params.Webfolder | Omit = omit, + aws: image_source_update_params.Aws | Omit = omit, + proxy: image_source_update_params.Proxy | Omit = omit, + gcs: image_source_update_params.Gcs | Omit = omit, + dostorage: image_source_update_params.Dostorage | Omit = omit, + wasabi: image_source_update_params.Wasabi | Omit = omit, + linode: image_source_update_params.Linode | Omit = omit, + backblaze: image_source_update_params.Backblaze | Omit = omit, + cloudflare: image_source_update_params.Cloudflare | Omit = omit, + cloudinary: image_source_update_params.Cloudinary | Omit = omit, + azure: image_source_update_params.Azure | Omit = omit, + default_params: object | Omit = omit, + error_image: str | Omit = omit, + request_headers: Iterable[object] | Omit = omit, + response_headers: Iterable[object] | Omit = omit, + temp_cname: SequenceNotStr[str] | Omit = omit, + browser_cache_time: int | Omit = omit, + cdn_cache_time: int | Omit = omit, + is_active: bool | Omit = omit, + cname: SequenceNotStr[str] | Omit = omit, + fallback_origins: Iterable[image_source_update_params.FallbackOrigin] | 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, + ) -> ImageSourceUpdateResponse: + """ + This endpoint allows users to update image source that has previously been created. + + Args: + image_source_id: image source id which you want to update + type: Body parameter. + webfolder: This is a required field if source type is webfolder. + aws: This is a required field if source type is aws. + proxy: This is a required field if source type is proxy. + gcs: This is a required field if source type is gcs. + dostorage: This is a required field if source type is dostorage. + wasabi: This is a required field if source type is wasabi. + linode: This is a required field if source type is linode. + backblaze: This is a required field if source type is backblaze. + cloudflare: This is a required field if source type is cloudflare. + cloudinary: This is a required field if source type is cloudinary. + azure: This is a required field if source type is azure. + default_params: Body parameter. + error_image: URL for error image to display when we get broken image from your origin. + request_headers: Body parameter. + response_headers: Body parameter. + temp_cname: Body parameter. + browser_cache_time: Browser cache time in seconds. For example setting this to 3600 caches the image in browser for 3600 seconds (1 hour) + cdn_cache_time: CDN cache time in seconds. + is_active: Enable / disable source. + cname: List of verified CNAMEs + fallback_origins: List of fallback origins + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + ImageSourceUpdateResponse: 200 + + Example: + ```python + image_source = await client.image_sources.update( + image_source_id="imageSourceId", + type="aws", + aws={ + "bucket_name": "my-bucket-test", + "bucket_region": "ap-southeast-1", + "access_key": "BQUA6QFXVWHAAB6IO2X1", + "secret": "aws_secret", + }, + ) + ``` + """ + if image_source_id is None or (isinstance(image_source_id, str) and not image_source_id): + raise ValueError(f"Expected a non-empty value for `image_source_id` but received {image_source_id!r}") + return await self._post( + path_template("/image/sources/{image_source_id}", **{"image_source_id": image_source_id}), + body=await async_maybe_transform( + { + "type": type, + "webfolder": webfolder, + "aws": aws, + "proxy": proxy, + "gcs": gcs, + "dostorage": dostorage, + "wasabi": wasabi, + "linode": linode, + "backblaze": backblaze, + "cloudflare": cloudflare, + "cloudinary": cloudinary, + "azure": azure, + "default_params": default_params, + "error_image": error_image, + "request_headers": request_headers, + "response_headers": response_headers, + "temp_cname": temp_cname, + "browser_cache_time": browser_cache_time, + "cdn_cache_time": cdn_cache_time, + "is_active": is_active, + "cname": cname, + "fallback_origins": fallback_origins, + }, + image_source_update_params.ImageSourceUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ImageSourceUpdateResponse, + ) + + async def delete( + self, + image_source_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, + ) -> ImageSourceDeleteResponse: + """ + This endpoint removes a image source. All image delivery using this subdomain will be stopped. + + Args: + image_source_id: Image source ID to delete. You can get this on Gumlet dashboard or in list source API. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + ImageSourceDeleteResponse + + Example: + ```python + image_source = await client.image_sources.delete( + image_source_id="imageSourceId", + ) + ``` + """ + if image_source_id is None or (isinstance(image_source_id, str) and not image_source_id): + raise ValueError(f"Expected a non-empty value for `image_source_id` but received {image_source_id!r}") + return await self._delete( + path_template("/image/sources/{image_source_id}", **{"image_source_id": image_source_id}), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ImageSourceDeleteResponse, + ) + + async def purge_cache( + self, + subdomain: str, + *, + paths: 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, + ) -> ImageSourcePurgeCacheResponse: + """ + You can purge cache for any image by using our cache purge API. + + Args: + subdomain: Subdomain is same subdomain you created while creating source. If you serve image from example.gumlet.com, please enter only 'example' for this parameter. + paths: An array of path of images to purge. It should be provided without any query parameters. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + ImageSourcePurgeCacheResponse: 200 + + Example: + ```python + image_source = await client.image_sources.purge_cache( + subdomain="subdomain", + paths=["image.jpeg", "image2.png"], + ) + ``` + + Deprecated: this method is deprecated. + """ + if subdomain is None or (isinstance(subdomain, str) and not subdomain): + raise ValueError(f"Expected a non-empty value for `subdomain` but received {subdomain!r}") + return await self._post( + path_template("/purge/{subdomain}", **{"subdomain": subdomain}), + body=await async_maybe_transform( + {"paths": paths}, + image_source_purge_cache_params.ImageSourcePurgeCacheParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ImageSourcePurgeCacheResponse, + ) + + async def purge( + self, + source_id: str, + *, + paths: SequenceNotStr[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, + ) -> ImageSourcePurgeResponse: + """ + You can purge the cache for any image path by using this cache purge API. + + Args: + source_id: Image Source ID + paths: An array of path of images to purge. It should be provided without any query parameters. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + ImageSourcePurgeResponse: Successful response + + Example: + ```python + image_source = await client.image_sources.purge( + source_id="sourceId", + paths=["image.jpeg", "image2.png"], + ) + ``` + """ + if source_id is None or (isinstance(source_id, str) and not source_id): + raise ValueError(f"Expected a non-empty value for `source_id` but received {source_id!r}") + return await self._post( + path_template("/image/purge/{source_id}", **{"source_id": source_id}), + body=await async_maybe_transform( + {"paths": paths}, + image_source_purge_params.ImageSourcePurgeParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ImageSourcePurgeResponse, + ) + + +class ImageSourcesResourceWithRawResponse: + def __init__(self, image_sources: ImageSourcesResource) -> None: + self._image_sources = image_sources + + self.create = to_raw_response_wrapper( + image_sources.create, + ) + self.list = to_raw_response_wrapper( + image_sources.list, + ) + self.retrieve = to_raw_response_wrapper( + image_sources.retrieve, + ) + self.update = to_raw_response_wrapper( + image_sources.update, + ) + self.delete = to_raw_response_wrapper( + image_sources.delete, + ) + self.purge_cache = to_raw_response_wrapper( + image_sources.purge_cache, + ) + self.purge = to_raw_response_wrapper( + image_sources.purge, + ) + + +class AsyncImageSourcesResourceWithRawResponse: + def __init__(self, image_sources: AsyncImageSourcesResource) -> None: + self._image_sources = image_sources + + self.create = async_to_raw_response_wrapper( + image_sources.create, + ) + self.list = async_to_raw_response_wrapper( + image_sources.list, + ) + self.retrieve = async_to_raw_response_wrapper( + image_sources.retrieve, + ) + self.update = async_to_raw_response_wrapper( + image_sources.update, + ) + self.delete = async_to_raw_response_wrapper( + image_sources.delete, + ) + self.purge_cache = async_to_raw_response_wrapper( + image_sources.purge_cache, + ) + self.purge = async_to_raw_response_wrapper( + image_sources.purge, + ) + + +class ImageSourcesResourceWithStreamingResponse: + def __init__(self, image_sources: ImageSourcesResource) -> None: + self._image_sources = image_sources + + self.create = to_streamed_response_wrapper( + image_sources.create, + ) + self.list = to_streamed_response_wrapper( + image_sources.list, + ) + self.retrieve = to_streamed_response_wrapper( + image_sources.retrieve, + ) + self.update = to_streamed_response_wrapper( + image_sources.update, + ) + self.delete = to_streamed_response_wrapper( + image_sources.delete, + ) + self.purge_cache = to_streamed_response_wrapper( + image_sources.purge_cache, + ) + self.purge = to_streamed_response_wrapper( + image_sources.purge, + ) + + +class AsyncImageSourcesResourceWithStreamingResponse: + def __init__(self, image_sources: AsyncImageSourcesResource) -> None: + self._image_sources = image_sources + + self.create = async_to_streamed_response_wrapper( + image_sources.create, + ) + self.list = async_to_streamed_response_wrapper( + image_sources.list, + ) + self.retrieve = async_to_streamed_response_wrapper( + image_sources.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + image_sources.update, + ) + self.delete = async_to_streamed_response_wrapper( + image_sources.delete, + ) + self.purge_cache = async_to_streamed_response_wrapper( + image_sources.purge_cache, + ) + self.purge = async_to_streamed_response_wrapper( + image_sources.purge, + ) diff --git a/src/gumlet/resources/image_usage_analytics.py b/src/gumlet/resources/image_usage_analytics.py new file mode 100644 index 0000000..d72c3ae --- /dev/null +++ b/src/gumlet/resources/image_usage_analytics.py @@ -0,0 +1,228 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +import httpx + +from typing import List +from typing_extensions import Literal + +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.image_usage_analytic_retrieve_response import ImageUsageAnalyticRetrieveResponse +from ..types import image_usage_analytic_retrieve_params + +__all__ = ["ImageUsageAnalyticsResource", "AsyncImageUsageAnalyticsResource"] + + +class ImageUsageAnalyticsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> ImageUsageAnalyticsResourceWithRawResponse: + return ImageUsageAnalyticsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ImageUsageAnalyticsResourceWithStreamingResponse: + return ImageUsageAnalyticsResourceWithStreamingResponse(self) + + def retrieve( + self, + *, + metrics: List[ + Literal[ + "bandwidth_consumption", + "requests_count", + "transformations_count", + "bandwidth_savings", + "origin_hit_rate", + "avg_transformation_response_time", + "status_4xx", + "status_5xx", + "cdn_hit_rate", + "content_type", + "status_2xx", + "avg_response_time", + "top_assets", + "bandwidth_consumption_by_source", + "ai_credit_usage", + ] + ], + date_range: image_usage_analytic_retrieve_params.DateRange, + group_by: Literal["daily", "weekly", "monthly", "hourly"] | Omit = omit, + filters: image_usage_analytic_retrieve_params.Filters | 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, + ) -> ImageUsageAnalyticRetrieveResponse: + """ + This endpoint helps you get image analytics data like bandwidth consumption, request count, CDN hit ratio, etc. + + Args: + metrics: Define the metric you need the data for, currently we support "bandwidth_consumption", "requests_count","status_4xx","status_5xx","avg_response_time"" + date_range: The timeframe to get the data for. Currently we only support a maximum of 30 days between `start_at` and `end_at`. + group_by: Body parameter. + filters: Body parameter. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + ImageUsageAnalyticRetrieveResponse: 200 + + Example: + ```python + image_usage_analytic = client.image_usage_analytics.retrieve( + metrics=["bandwidth_consumption"], + date_range={}, + group_by="daily", + ) + ``` + """ + return self._post( + "/image/analytics", + body=maybe_transform( + { + "metrics": metrics, + "date_range": date_range, + "group_by": group_by, + "filters": filters, + }, + image_usage_analytic_retrieve_params.ImageUsageAnalyticRetrieveParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ImageUsageAnalyticRetrieveResponse, + ) + + +class AsyncImageUsageAnalyticsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncImageUsageAnalyticsResourceWithRawResponse: + return AsyncImageUsageAnalyticsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncImageUsageAnalyticsResourceWithStreamingResponse: + return AsyncImageUsageAnalyticsResourceWithStreamingResponse(self) + + async def retrieve( + self, + *, + metrics: List[ + Literal[ + "bandwidth_consumption", + "requests_count", + "transformations_count", + "bandwidth_savings", + "origin_hit_rate", + "avg_transformation_response_time", + "status_4xx", + "status_5xx", + "cdn_hit_rate", + "content_type", + "status_2xx", + "avg_response_time", + "top_assets", + "bandwidth_consumption_by_source", + "ai_credit_usage", + ] + ], + date_range: image_usage_analytic_retrieve_params.DateRange, + group_by: Literal["daily", "weekly", "monthly", "hourly"] | Omit = omit, + filters: image_usage_analytic_retrieve_params.Filters | 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, + ) -> ImageUsageAnalyticRetrieveResponse: + """ + This endpoint helps you get image analytics data like bandwidth consumption, request count, CDN hit ratio, etc. + + Args: + metrics: Define the metric you need the data for, currently we support "bandwidth_consumption", "requests_count","status_4xx","status_5xx","avg_response_time"" + date_range: The timeframe to get the data for. Currently we only support a maximum of 30 days between `start_at` and `end_at`. + group_by: Body parameter. + filters: Body parameter. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + ImageUsageAnalyticRetrieveResponse: 200 + + Example: + ```python + image_usage_analytic = await client.image_usage_analytics.retrieve( + metrics=["bandwidth_consumption"], + date_range={}, + group_by="daily", + ) + ``` + """ + return await self._post( + "/image/analytics", + body=await async_maybe_transform( + { + "metrics": metrics, + "date_range": date_range, + "group_by": group_by, + "filters": filters, + }, + image_usage_analytic_retrieve_params.ImageUsageAnalyticRetrieveParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ImageUsageAnalyticRetrieveResponse, + ) + + +class ImageUsageAnalyticsResourceWithRawResponse: + def __init__(self, image_usage_analytics: ImageUsageAnalyticsResource) -> None: + self._image_usage_analytics = image_usage_analytics + + self.retrieve = to_raw_response_wrapper( + image_usage_analytics.retrieve, + ) + + +class AsyncImageUsageAnalyticsResourceWithRawResponse: + def __init__(self, image_usage_analytics: AsyncImageUsageAnalyticsResource) -> None: + self._image_usage_analytics = image_usage_analytics + + self.retrieve = async_to_raw_response_wrapper( + image_usage_analytics.retrieve, + ) + + +class ImageUsageAnalyticsResourceWithStreamingResponse: + def __init__(self, image_usage_analytics: ImageUsageAnalyticsResource) -> None: + self._image_usage_analytics = image_usage_analytics + + self.retrieve = to_streamed_response_wrapper( + image_usage_analytics.retrieve, + ) + + +class AsyncImageUsageAnalyticsResourceWithStreamingResponse: + def __init__(self, image_usage_analytics: AsyncImageUsageAnalyticsResource) -> None: + self._image_usage_analytics = image_usage_analytics + + self.retrieve = async_to_streamed_response_wrapper( + image_usage_analytics.retrieve, + ) diff --git a/src/gumlet/resources/live_stream_assets.py b/src/gumlet/resources/live_stream_assets.py new file mode 100644 index 0000000..60f0296 --- /dev/null +++ b/src/gumlet/resources/live_stream_assets.py @@ -0,0 +1,1025 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +import httpx + +from typing import List, Union +from datetime import datetime +from typing_extensions import Literal + +from .._types import Body, Omit, Query, Headers, NotGiven, NoneType, 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.live_stream_asset_create_response import LiveStreamAssetCreateResponse +from ..types import ( + live_stream_asset_create_params, + live_stream_asset_update_params, + live_stream_asset_filter_params, + live_stream_asset_upload_params, +) +from ..types.live_stream_asset_update_response import LiveStreamAssetUpdateResponse +from ..types.live_stream_asset_retrieve_status_response import LiveStreamAssetRetrieveStatusResponse +from ..types.live_stream_asset_delete_response import LiveStreamAssetDeleteResponse +from ..types.live_stream_asset_complete_response import LiveStreamAssetCompleteResponse +from ..types.live_stream_asset_filter_response import LiveStreamAssetFilterResponse +from ..types.live_stream_asset_upload_response import LiveStreamAssetUploadResponse +from ..types.live_stream_asset_status_history_response import LiveStreamAssetStatusHistoryResponse + +__all__ = ["LiveStreamAssetsResource", "AsyncLiveStreamAssetsResource"] + + +class LiveStreamAssetsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> LiveStreamAssetsResourceWithRawResponse: + return LiveStreamAssetsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> LiveStreamAssetsResourceWithStreamingResponse: + return LiveStreamAssetsResourceWithStreamingResponse(self) + + def create( + self, + *, + live_source_id: str, + resolution: str, + title: str | Omit = omit, + mp4_access: bool | Omit = omit, + orientation: Literal["landscape", "potrait"] | Omit = omit, + start_at: Union[str, datetime] | 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, + ) -> LiveStreamAssetCreateResponse: + """ + A live asset refers to a media content/video that is live-streamed through Gumlet. This endpoint creates a live streaming asset allowing users to live stream a video that will be pushed to Gumlet. + + Args: + live_source_id: Gumlet live video source/collection id. + resolution: Required resolutions in HLS delivery format for live stream. Can be an array of string out of the following values: `240p`, `360p`, `480p`, `540p`, `720p`, and `1080p`. Re-sized rendition will retain the input aspect ratio. + title: Your live stream asset title + mp4_access: Creates MP4 version for download purpose. + orientation: Body parameter. + start_at: Body parameter. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + LiveStreamAssetCreateResponse: 200 + + Example: + ```python + live_stream_asset = client.live_stream_assets.create( + live_source_id="", + resolution="", + ) + ``` + """ + return self._post( + "/video/live/assets", + body=maybe_transform( + { + "live_source_id": live_source_id, + "resolution": resolution, + "title": title, + "mp4_access": mp4_access, + "orientation": orientation, + "start_at": start_at, + }, + live_stream_asset_create_params.LiveStreamAssetCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=LiveStreamAssetCreateResponse, + ) + + def update( + self, + *, + live_asset_id: str, + title: str | Omit = omit, + start_at: Union[str, datetime] | 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, + ) -> LiveStreamAssetUpdateResponse: + """ + A live asset refers to a media content/video that is live-streamed through Gumlet. This endpoint allows user to update a live streaming asset. + + Args: + live_asset_id: Gumlet live video asset id. + title: Your live stream asset title + start_at: Body parameter. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + LiveStreamAssetUpdateResponse: 200 + + Example: + ```python + live_stream_asset = client.live_stream_assets.update( + live_asset_id="", + ) + ``` + """ + return self._post( + "/video/live/assets/update", + body=maybe_transform( + { + "live_asset_id": live_asset_id, + "title": title, + "start_at": start_at, + }, + live_stream_asset_update_params.LiveStreamAssetUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=LiveStreamAssetUpdateResponse, + ) + + def retrieve_status( + self, + live_asset_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, + ) -> LiveStreamAssetRetrieveStatusResponse: + """ + This endpoint retrieves the details of a live video asset that has previously been created. + + Args: + live_asset_id: An live asset id for the previously created asset. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + LiveStreamAssetRetrieveStatusResponse: 200 + + Example: + ```python + live_stream_asset = client.live_stream_assets.retrieve_status( + live_asset_id="liveAssetId", + ) + ``` + """ + if live_asset_id is None or (isinstance(live_asset_id, str) and not live_asset_id): + raise ValueError(f"Expected a non-empty value for `live_asset_id` but received {live_asset_id!r}") + return self._get( + path_template("/video/live/assets/{live_asset_id}", **{"live_asset_id": live_asset_id}), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=LiveStreamAssetRetrieveStatusResponse, + ) + + def delete( + self, + live_asset_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, + ) -> LiveStreamAssetDeleteResponse: + """ + This endpoint removes a live asset given its unique live asset id. The live asset will be removed from storage as well, associated URLs will be inaccessible. + + Args: + live_asset_id: Live asset id of the live asset which needs to be deleted. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + LiveStreamAssetDeleteResponse: 204 + + Example: + ```python + live_stream_asset = client.live_stream_assets.delete( + live_asset_id="liveAssetId", + ) + ``` + """ + if live_asset_id is None or (isinstance(live_asset_id, str) and not live_asset_id): + raise ValueError(f"Expected a non-empty value for `live_asset_id` but received {live_asset_id!r}") + return self._delete( + path_template("/video/live/assets/{live_asset_id}", **{"live_asset_id": live_asset_id}), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=LiveStreamAssetDeleteResponse, + ) + + def complete( + self, + live_asset_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, + ) -> LiveStreamAssetCompleteResponse: + """ + This endpoint allows marking live assets complete. Once the live asset is marked complete, it can no longer be used to ingest the live stream on Gumlet. + + Args: + live_asset_id: Live asset id of the live stream which needs to be completed. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + LiveStreamAssetCompleteResponse: 200 + + Example: + ```python + live_stream_asset = client.live_stream_assets.complete( + live_asset_id="liveAssetId", + ) + ``` + """ + if live_asset_id is None or (isinstance(live_asset_id, str) and not live_asset_id): + raise ValueError(f"Expected a non-empty value for `live_asset_id` but received {live_asset_id!r}") + return self._post( + path_template("/video/live/assets/{live_asset_id}/complete", **{"live_asset_id": live_asset_id}), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=LiveStreamAssetCompleteResponse, + ) + + def filter( + self, + live_source_id: str, + *, + status: str | Omit = omit, + offset: int | Omit = omit, + size: int | 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, + ) -> LiveStreamAssetFilterResponse: + """ + This endpoint lists live assets on the basis of `status` for the given `live_source_id`. + + Args: + live_source_id: Gumlet live source/collection id. + status: To filter live assets on the basis of their current status. Can be specified as a single status value string or comma-separated status values. The status value can be one of `created`, `active`, `complete`, `disconnected`, `errored`, and `deleted`. + offset: Offset value for a paginated list of assets. + size: Page size for the paginated list. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + LiveStreamAssetFilterResponse: 200 + + Example: + ```python + live_stream_asset = client.live_stream_assets.filter( + live_source_id="liveSourceId", + ) + ``` + """ + if live_source_id is None or (isinstance(live_source_id, str) and not live_source_id): + raise ValueError(f"Expected a non-empty value for `live_source_id` but received {live_source_id!r}") + return self._get( + path_template("/video/live/assets/list/{live_source_id}", **{"live_source_id": live_source_id}), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + {"status": status, "offset": offset, "size": size}, + live_stream_asset_filter_params.LiveStreamAssetFilterParams, + ), + ), + cast_to=LiveStreamAssetFilterResponse, + ) + + def start( + self, + live_asset_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, + ) -> None: + """ + Start a live stream. + + Args: + live_asset_id: List asset id for which the stream needs to start. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Example: + ```python + client.live_stream_assets.start( + live_asset_id="liveAssetId", + ) + ``` + """ + if live_asset_id is None or (isinstance(live_asset_id, str) and not live_asset_id): + raise ValueError(f"Expected a non-empty value for `live_asset_id` but received {live_asset_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return self._post( + path_template("/video/live/assets/{live_asset_id}/start", **{"live_asset_id": live_asset_id}), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + def upload( + self, + *, + live_asset_id: str, + statuses: Union[List[Literal["preparing", "disconnected", "end"]], 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, + ) -> LiveStreamAssetUploadResponse: + """ + Generate presigned upload URLs for live stream thumbnails. Supported thumbnail states are `preparing`, `disconnected`, and `end`. + + Args: + live_asset_id: Gumlet live video asset id. + statuses: Thumbnail states to upload. You can send an array or a comma-separated string. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + LiveStreamAssetUploadResponse: 200 + + Example: + ```python + live_stream_asset = client.live_stream_assets.upload( + live_asset_id="68c406b147f9ad0c0d584ce2", + statuses="preparing", + ) + ``` + """ + return self._post( + "/video/live/assets/thumbnail/upload", + body=maybe_transform( + { + "live_asset_id": live_asset_id, + "statuses": statuses, + }, + live_stream_asset_upload_params.LiveStreamAssetUploadParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=LiveStreamAssetUploadResponse, + ) + + def status_history( + self, + live_asset_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, + ) -> LiveStreamAssetStatusHistoryResponse: + """ + This endpoint retrieves the history of a live video asset that has previously been created. + + Args: + live_asset_id: An live asset id for the previously created asset. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + LiveStreamAssetStatusHistoryResponse: 200 + + Example: + ```python + live_stream_asset = client.live_stream_assets.status_history( + live_asset_id="liveAssetId", + ) + ``` + """ + if live_asset_id is None or (isinstance(live_asset_id, str) and not live_asset_id): + raise ValueError(f"Expected a non-empty value for `live_asset_id` but received {live_asset_id!r}") + return self._get( + path_template("/video/live/assets/{live_asset_id}/history", **{"live_asset_id": live_asset_id}), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=LiveStreamAssetStatusHistoryResponse, + ) + + +class AsyncLiveStreamAssetsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncLiveStreamAssetsResourceWithRawResponse: + return AsyncLiveStreamAssetsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncLiveStreamAssetsResourceWithStreamingResponse: + return AsyncLiveStreamAssetsResourceWithStreamingResponse(self) + + async def create( + self, + *, + live_source_id: str, + resolution: str, + title: str | Omit = omit, + mp4_access: bool | Omit = omit, + orientation: Literal["landscape", "potrait"] | Omit = omit, + start_at: Union[str, datetime] | 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, + ) -> LiveStreamAssetCreateResponse: + """ + A live asset refers to a media content/video that is live-streamed through Gumlet. This endpoint creates a live streaming asset allowing users to live stream a video that will be pushed to Gumlet. + + Args: + live_source_id: Gumlet live video source/collection id. + resolution: Required resolutions in HLS delivery format for live stream. Can be an array of string out of the following values: `240p`, `360p`, `480p`, `540p`, `720p`, and `1080p`. Re-sized rendition will retain the input aspect ratio. + title: Your live stream asset title + mp4_access: Creates MP4 version for download purpose. + orientation: Body parameter. + start_at: Body parameter. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + LiveStreamAssetCreateResponse: 200 + + Example: + ```python + live_stream_asset = await client.live_stream_assets.create( + live_source_id="", + resolution="", + ) + ``` + """ + return await self._post( + "/video/live/assets", + body=await async_maybe_transform( + { + "live_source_id": live_source_id, + "resolution": resolution, + "title": title, + "mp4_access": mp4_access, + "orientation": orientation, + "start_at": start_at, + }, + live_stream_asset_create_params.LiveStreamAssetCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=LiveStreamAssetCreateResponse, + ) + + async def update( + self, + *, + live_asset_id: str, + title: str | Omit = omit, + start_at: Union[str, datetime] | 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, + ) -> LiveStreamAssetUpdateResponse: + """ + A live asset refers to a media content/video that is live-streamed through Gumlet. This endpoint allows user to update a live streaming asset. + + Args: + live_asset_id: Gumlet live video asset id. + title: Your live stream asset title + start_at: Body parameter. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + LiveStreamAssetUpdateResponse: 200 + + Example: + ```python + live_stream_asset = await client.live_stream_assets.update( + live_asset_id="", + ) + ``` + """ + return await self._post( + "/video/live/assets/update", + body=await async_maybe_transform( + { + "live_asset_id": live_asset_id, + "title": title, + "start_at": start_at, + }, + live_stream_asset_update_params.LiveStreamAssetUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=LiveStreamAssetUpdateResponse, + ) + + async def retrieve_status( + self, + live_asset_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, + ) -> LiveStreamAssetRetrieveStatusResponse: + """ + This endpoint retrieves the details of a live video asset that has previously been created. + + Args: + live_asset_id: An live asset id for the previously created asset. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + LiveStreamAssetRetrieveStatusResponse: 200 + + Example: + ```python + live_stream_asset = await client.live_stream_assets.retrieve_status( + live_asset_id="liveAssetId", + ) + ``` + """ + if live_asset_id is None or (isinstance(live_asset_id, str) and not live_asset_id): + raise ValueError(f"Expected a non-empty value for `live_asset_id` but received {live_asset_id!r}") + return await self._get( + path_template("/video/live/assets/{live_asset_id}", **{"live_asset_id": live_asset_id}), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=LiveStreamAssetRetrieveStatusResponse, + ) + + async def delete( + self, + live_asset_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, + ) -> LiveStreamAssetDeleteResponse: + """ + This endpoint removes a live asset given its unique live asset id. The live asset will be removed from storage as well, associated URLs will be inaccessible. + + Args: + live_asset_id: Live asset id of the live asset which needs to be deleted. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + LiveStreamAssetDeleteResponse: 204 + + Example: + ```python + live_stream_asset = await client.live_stream_assets.delete( + live_asset_id="liveAssetId", + ) + ``` + """ + if live_asset_id is None or (isinstance(live_asset_id, str) and not live_asset_id): + raise ValueError(f"Expected a non-empty value for `live_asset_id` but received {live_asset_id!r}") + return await self._delete( + path_template("/video/live/assets/{live_asset_id}", **{"live_asset_id": live_asset_id}), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=LiveStreamAssetDeleteResponse, + ) + + async def complete( + self, + live_asset_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, + ) -> LiveStreamAssetCompleteResponse: + """ + This endpoint allows marking live assets complete. Once the live asset is marked complete, it can no longer be used to ingest the live stream on Gumlet. + + Args: + live_asset_id: Live asset id of the live stream which needs to be completed. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + LiveStreamAssetCompleteResponse: 200 + + Example: + ```python + live_stream_asset = await client.live_stream_assets.complete( + live_asset_id="liveAssetId", + ) + ``` + """ + if live_asset_id is None or (isinstance(live_asset_id, str) and not live_asset_id): + raise ValueError(f"Expected a non-empty value for `live_asset_id` but received {live_asset_id!r}") + return await self._post( + path_template("/video/live/assets/{live_asset_id}/complete", **{"live_asset_id": live_asset_id}), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=LiveStreamAssetCompleteResponse, + ) + + async def filter( + self, + live_source_id: str, + *, + status: str | Omit = omit, + offset: int | Omit = omit, + size: int | 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, + ) -> LiveStreamAssetFilterResponse: + """ + This endpoint lists live assets on the basis of `status` for the given `live_source_id`. + + Args: + live_source_id: Gumlet live source/collection id. + status: To filter live assets on the basis of their current status. Can be specified as a single status value string or comma-separated status values. The status value can be one of `created`, `active`, `complete`, `disconnected`, `errored`, and `deleted`. + offset: Offset value for a paginated list of assets. + size: Page size for the paginated list. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + LiveStreamAssetFilterResponse: 200 + + Example: + ```python + live_stream_asset = await client.live_stream_assets.filter( + live_source_id="liveSourceId", + ) + ``` + """ + if live_source_id is None or (isinstance(live_source_id, str) and not live_source_id): + raise ValueError(f"Expected a non-empty value for `live_source_id` but received {live_source_id!r}") + return await self._get( + path_template("/video/live/assets/list/{live_source_id}", **{"live_source_id": live_source_id}), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + {"status": status, "offset": offset, "size": size}, + live_stream_asset_filter_params.LiveStreamAssetFilterParams, + ), + ), + cast_to=LiveStreamAssetFilterResponse, + ) + + async def start( + self, + live_asset_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, + ) -> None: + """ + Start a live stream. + + Args: + live_asset_id: List asset id for which the stream needs to start. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Example: + ```python + await client.live_stream_assets.start( + live_asset_id="liveAssetId", + ) + ``` + """ + if live_asset_id is None or (isinstance(live_asset_id, str) and not live_asset_id): + raise ValueError(f"Expected a non-empty value for `live_asset_id` but received {live_asset_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return await self._post( + path_template("/video/live/assets/{live_asset_id}/start", **{"live_asset_id": live_asset_id}), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + async def upload( + self, + *, + live_asset_id: str, + statuses: Union[List[Literal["preparing", "disconnected", "end"]], 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, + ) -> LiveStreamAssetUploadResponse: + """ + Generate presigned upload URLs for live stream thumbnails. Supported thumbnail states are `preparing`, `disconnected`, and `end`. + + Args: + live_asset_id: Gumlet live video asset id. + statuses: Thumbnail states to upload. You can send an array or a comma-separated string. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + LiveStreamAssetUploadResponse: 200 + + Example: + ```python + live_stream_asset = await client.live_stream_assets.upload( + live_asset_id="68c406b147f9ad0c0d584ce2", + statuses="preparing", + ) + ``` + """ + return await self._post( + "/video/live/assets/thumbnail/upload", + body=await async_maybe_transform( + { + "live_asset_id": live_asset_id, + "statuses": statuses, + }, + live_stream_asset_upload_params.LiveStreamAssetUploadParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=LiveStreamAssetUploadResponse, + ) + + async def status_history( + self, + live_asset_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, + ) -> LiveStreamAssetStatusHistoryResponse: + """ + This endpoint retrieves the history of a live video asset that has previously been created. + + Args: + live_asset_id: An live asset id for the previously created asset. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + LiveStreamAssetStatusHistoryResponse: 200 + + Example: + ```python + live_stream_asset = await client.live_stream_assets.status_history( + live_asset_id="liveAssetId", + ) + ``` + """ + if live_asset_id is None or (isinstance(live_asset_id, str) and not live_asset_id): + raise ValueError(f"Expected a non-empty value for `live_asset_id` but received {live_asset_id!r}") + return await self._get( + path_template("/video/live/assets/{live_asset_id}/history", **{"live_asset_id": live_asset_id}), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=LiveStreamAssetStatusHistoryResponse, + ) + + +class LiveStreamAssetsResourceWithRawResponse: + def __init__(self, live_stream_assets: LiveStreamAssetsResource) -> None: + self._live_stream_assets = live_stream_assets + + self.create = to_raw_response_wrapper( + live_stream_assets.create, + ) + self.update = to_raw_response_wrapper( + live_stream_assets.update, + ) + self.retrieve_status = to_raw_response_wrapper( + live_stream_assets.retrieve_status, + ) + self.delete = to_raw_response_wrapper( + live_stream_assets.delete, + ) + self.complete = to_raw_response_wrapper( + live_stream_assets.complete, + ) + self.filter = to_raw_response_wrapper( + live_stream_assets.filter, + ) + self.start = to_raw_response_wrapper( + live_stream_assets.start, + ) + self.upload = to_raw_response_wrapper( + live_stream_assets.upload, + ) + self.status_history = to_raw_response_wrapper( + live_stream_assets.status_history, + ) + + +class AsyncLiveStreamAssetsResourceWithRawResponse: + def __init__(self, live_stream_assets: AsyncLiveStreamAssetsResource) -> None: + self._live_stream_assets = live_stream_assets + + self.create = async_to_raw_response_wrapper( + live_stream_assets.create, + ) + self.update = async_to_raw_response_wrapper( + live_stream_assets.update, + ) + self.retrieve_status = async_to_raw_response_wrapper( + live_stream_assets.retrieve_status, + ) + self.delete = async_to_raw_response_wrapper( + live_stream_assets.delete, + ) + self.complete = async_to_raw_response_wrapper( + live_stream_assets.complete, + ) + self.filter = async_to_raw_response_wrapper( + live_stream_assets.filter, + ) + self.start = async_to_raw_response_wrapper( + live_stream_assets.start, + ) + self.upload = async_to_raw_response_wrapper( + live_stream_assets.upload, + ) + self.status_history = async_to_raw_response_wrapper( + live_stream_assets.status_history, + ) + + +class LiveStreamAssetsResourceWithStreamingResponse: + def __init__(self, live_stream_assets: LiveStreamAssetsResource) -> None: + self._live_stream_assets = live_stream_assets + + self.create = to_streamed_response_wrapper( + live_stream_assets.create, + ) + self.update = to_streamed_response_wrapper( + live_stream_assets.update, + ) + self.retrieve_status = to_streamed_response_wrapper( + live_stream_assets.retrieve_status, + ) + self.delete = to_streamed_response_wrapper( + live_stream_assets.delete, + ) + self.complete = to_streamed_response_wrapper( + live_stream_assets.complete, + ) + self.filter = to_streamed_response_wrapper( + live_stream_assets.filter, + ) + self.start = to_streamed_response_wrapper( + live_stream_assets.start, + ) + self.upload = to_streamed_response_wrapper( + live_stream_assets.upload, + ) + self.status_history = to_streamed_response_wrapper( + live_stream_assets.status_history, + ) + + +class AsyncLiveStreamAssetsResourceWithStreamingResponse: + def __init__(self, live_stream_assets: AsyncLiveStreamAssetsResource) -> None: + self._live_stream_assets = live_stream_assets + + self.create = async_to_streamed_response_wrapper( + live_stream_assets.create, + ) + self.update = async_to_streamed_response_wrapper( + live_stream_assets.update, + ) + self.retrieve_status = async_to_streamed_response_wrapper( + live_stream_assets.retrieve_status, + ) + self.delete = async_to_streamed_response_wrapper( + live_stream_assets.delete, + ) + self.complete = async_to_streamed_response_wrapper( + live_stream_assets.complete, + ) + self.filter = async_to_streamed_response_wrapper( + live_stream_assets.filter, + ) + self.start = async_to_streamed_response_wrapper( + live_stream_assets.start, + ) + self.upload = async_to_streamed_response_wrapper( + live_stream_assets.upload, + ) + self.status_history = async_to_streamed_response_wrapper( + live_stream_assets.status_history, + ) diff --git a/src/gumlet/resources/live_stream_workspaces.py b/src/gumlet/resources/live_stream_workspaces.py new file mode 100644 index 0000000..e4aca69 --- /dev/null +++ b/src/gumlet/resources/live_stream_workspaces.py @@ -0,0 +1,462 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +import httpx + +from .._types import Body, Omit, Query, Headers, NotGiven, 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.live_stream_workspace_list_response import LiveStreamWorkspaceListResponse +from ..types.live_stream_workspace_create_response import LiveStreamWorkspaceCreateResponse +from ..types import live_stream_workspace_create_params, live_stream_workspace_update_params +from ..types.live_stream_workspace_update_response import LiveStreamWorkspaceUpdateResponse +from ..types.live_stream_workspace_delete_response import LiveStreamWorkspaceDeleteResponse + +__all__ = ["LiveStreamWorkspacesResource", "AsyncLiveStreamWorkspacesResource"] + + +class LiveStreamWorkspacesResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> LiveStreamWorkspacesResourceWithRawResponse: + return LiveStreamWorkspacesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> LiveStreamWorkspacesResourceWithStreamingResponse: + return LiveStreamWorkspacesResourceWithStreamingResponse(self) + + def list( + self, + *, + # 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, + ) -> LiveStreamWorkspaceListResponse: + """ + List all live stream workspaces. + + Args: + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + LiveStreamWorkspaceListResponse: Successful response + + Example: + ```python + live_stream_workspace = client.live_stream_workspaces.list() + ``` + """ + return self._get( + "/video/sources/live", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=LiveStreamWorkspaceListResponse, + ) + + def create( + self, + *, + name: 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, + ) -> LiveStreamWorkspaceCreateResponse: + """ + Create live stream workspace. + + Args: + name: Collection name + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + LiveStreamWorkspaceCreateResponse: Successful response + + Example: + ```python + live_stream_workspace = client.live_stream_workspaces.create( + name="", + ) + ``` + """ + return self._post( + "/video/sources/live", + body=maybe_transform( + {"name": name}, + live_stream_workspace_create_params.LiveStreamWorkspaceCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=LiveStreamWorkspaceCreateResponse, + ) + + def update( + self, + live_workspace_id: str, + *, + name: str | Omit = omit, + video_source_id: 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, + ) -> LiveStreamWorkspaceUpdateResponse: + """ + Update live stream workspace. + + Args: + live_workspace_id: Live stream workspace ID. + name: Live stream collection name + video_source_id: Video on demand workspace ID + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + LiveStreamWorkspaceUpdateResponse: Get updated collection details. + + Example: + ```python + live_stream_workspace = client.live_stream_workspaces.update( + live_workspace_id="liveWorkspaceId", + name="live-stream-collections", + video_source_id="67bea1d66ca0059a95bf7de9", + ) + ``` + """ + if live_workspace_id is None or (isinstance(live_workspace_id, str) and not live_workspace_id): + raise ValueError(f"Expected a non-empty value for `live_workspace_id` but received {live_workspace_id!r}") + return self._post( + path_template("/video/sources/live/{live_workspace_id}", **{"live_workspace_id": live_workspace_id}), + body=maybe_transform( + { + "name": name, + "video_source_id": video_source_id, + }, + live_stream_workspace_update_params.LiveStreamWorkspaceUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=LiveStreamWorkspaceUpdateResponse, + ) + + def delete( + self, + live_workspace_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, + ) -> LiveStreamWorkspaceDeleteResponse: + """ + Delete the live stream workspace. + + Args: + live_workspace_id: Live stream workspace ID. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + LiveStreamWorkspaceDeleteResponse: Successful response + + Example: + ```python + live_stream_workspace = client.live_stream_workspaces.delete( + live_workspace_id="liveWorkspaceId", + ) + ``` + """ + if live_workspace_id is None or (isinstance(live_workspace_id, str) and not live_workspace_id): + raise ValueError(f"Expected a non-empty value for `live_workspace_id` but received {live_workspace_id!r}") + return self._delete( + path_template("/video/sources/live/{live_workspace_id}", **{"live_workspace_id": live_workspace_id}), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=LiveStreamWorkspaceDeleteResponse, + ) + + +class AsyncLiveStreamWorkspacesResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncLiveStreamWorkspacesResourceWithRawResponse: + return AsyncLiveStreamWorkspacesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncLiveStreamWorkspacesResourceWithStreamingResponse: + return AsyncLiveStreamWorkspacesResourceWithStreamingResponse(self) + + async def list( + self, + *, + # 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, + ) -> LiveStreamWorkspaceListResponse: + """ + List all live stream workspaces. + + Args: + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + LiveStreamWorkspaceListResponse: Successful response + + Example: + ```python + live_stream_workspace = await client.live_stream_workspaces.list() + ``` + """ + return await self._get( + "/video/sources/live", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=LiveStreamWorkspaceListResponse, + ) + + async def create( + self, + *, + name: 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, + ) -> LiveStreamWorkspaceCreateResponse: + """ + Create live stream workspace. + + Args: + name: Collection name + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + LiveStreamWorkspaceCreateResponse: Successful response + + Example: + ```python + live_stream_workspace = await client.live_stream_workspaces.create( + name="", + ) + ``` + """ + return await self._post( + "/video/sources/live", + body=await async_maybe_transform( + {"name": name}, + live_stream_workspace_create_params.LiveStreamWorkspaceCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=LiveStreamWorkspaceCreateResponse, + ) + + async def update( + self, + live_workspace_id: str, + *, + name: str | Omit = omit, + video_source_id: 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, + ) -> LiveStreamWorkspaceUpdateResponse: + """ + Update live stream workspace. + + Args: + live_workspace_id: Live stream workspace ID. + name: Live stream collection name + video_source_id: Video on demand workspace ID + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + LiveStreamWorkspaceUpdateResponse: Get updated collection details. + + Example: + ```python + live_stream_workspace = await client.live_stream_workspaces.update( + live_workspace_id="liveWorkspaceId", + name="live-stream-collections", + video_source_id="67bea1d66ca0059a95bf7de9", + ) + ``` + """ + if live_workspace_id is None or (isinstance(live_workspace_id, str) and not live_workspace_id): + raise ValueError(f"Expected a non-empty value for `live_workspace_id` but received {live_workspace_id!r}") + return await self._post( + path_template("/video/sources/live/{live_workspace_id}", **{"live_workspace_id": live_workspace_id}), + body=await async_maybe_transform( + { + "name": name, + "video_source_id": video_source_id, + }, + live_stream_workspace_update_params.LiveStreamWorkspaceUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=LiveStreamWorkspaceUpdateResponse, + ) + + async def delete( + self, + live_workspace_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, + ) -> LiveStreamWorkspaceDeleteResponse: + """ + Delete the live stream workspace. + + Args: + live_workspace_id: Live stream workspace ID. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + LiveStreamWorkspaceDeleteResponse: Successful response + + Example: + ```python + live_stream_workspace = await client.live_stream_workspaces.delete( + live_workspace_id="liveWorkspaceId", + ) + ``` + """ + if live_workspace_id is None or (isinstance(live_workspace_id, str) and not live_workspace_id): + raise ValueError(f"Expected a non-empty value for `live_workspace_id` but received {live_workspace_id!r}") + return await self._delete( + path_template("/video/sources/live/{live_workspace_id}", **{"live_workspace_id": live_workspace_id}), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=LiveStreamWorkspaceDeleteResponse, + ) + + +class LiveStreamWorkspacesResourceWithRawResponse: + def __init__(self, live_stream_workspaces: LiveStreamWorkspacesResource) -> None: + self._live_stream_workspaces = live_stream_workspaces + + self.list = to_raw_response_wrapper( + live_stream_workspaces.list, + ) + self.create = to_raw_response_wrapper( + live_stream_workspaces.create, + ) + self.update = to_raw_response_wrapper( + live_stream_workspaces.update, + ) + self.delete = to_raw_response_wrapper( + live_stream_workspaces.delete, + ) + + +class AsyncLiveStreamWorkspacesResourceWithRawResponse: + def __init__(self, live_stream_workspaces: AsyncLiveStreamWorkspacesResource) -> None: + self._live_stream_workspaces = live_stream_workspaces + + self.list = async_to_raw_response_wrapper( + live_stream_workspaces.list, + ) + self.create = async_to_raw_response_wrapper( + live_stream_workspaces.create, + ) + self.update = async_to_raw_response_wrapper( + live_stream_workspaces.update, + ) + self.delete = async_to_raw_response_wrapper( + live_stream_workspaces.delete, + ) + + +class LiveStreamWorkspacesResourceWithStreamingResponse: + def __init__(self, live_stream_workspaces: LiveStreamWorkspacesResource) -> None: + self._live_stream_workspaces = live_stream_workspaces + + self.list = to_streamed_response_wrapper( + live_stream_workspaces.list, + ) + self.create = to_streamed_response_wrapper( + live_stream_workspaces.create, + ) + self.update = to_streamed_response_wrapper( + live_stream_workspaces.update, + ) + self.delete = to_streamed_response_wrapper( + live_stream_workspaces.delete, + ) + + +class AsyncLiveStreamWorkspacesResourceWithStreamingResponse: + def __init__(self, live_stream_workspaces: AsyncLiveStreamWorkspacesResource) -> None: + self._live_stream_workspaces = live_stream_workspaces + + self.list = async_to_streamed_response_wrapper( + live_stream_workspaces.list, + ) + self.create = async_to_streamed_response_wrapper( + live_stream_workspaces.create, + ) + self.update = async_to_streamed_response_wrapper( + live_stream_workspaces.update, + ) + self.delete = async_to_streamed_response_wrapper( + live_stream_workspaces.delete, + ) diff --git a/src/gumlet/resources/multipart_upload.py b/src/gumlet/resources/multipart_upload.py new file mode 100644 index 0000000..a657add --- /dev/null +++ b/src/gumlet/resources/multipart_upload.py @@ -0,0 +1,284 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +import httpx + +from typing import Iterable + +from .._types import Body, Omit, Query, Headers, NotGiven, 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.multipart_upload_retrieve_part_url_response import MultipartUploadRetrievePartURLResponse +from ..types.multipart_upload_complete_response import MultipartUploadCompleteResponse +from ..types import multipart_upload_complete_params + +__all__ = ["MultipartUploadResource", "AsyncMultipartUploadResource"] + + +class MultipartUploadResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> MultipartUploadResourceWithRawResponse: + return MultipartUploadResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> MultipartUploadResourceWithStreamingResponse: + return MultipartUploadResourceWithStreamingResponse(self) + + def retrieve_part_url( + self, + part_number: str, + *, + asset_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, + ) -> MultipartUploadRetrievePartURLResponse: + """ + Use this endpoint to retrieve a pre-signed upload URL for the given part number. + + Args: + part_number: Part number of multiple parts of the original video which you you are uploading + asset_id: An asset id of the created asset for which you are uploading parts + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + MultipartUploadRetrievePartURLResponse: 200 + + Example: + ```python + multipart_upload = client.multipart_upload.retrieve_part_url( + asset_id="assetId", + part_number="partNumber", + ) + ``` + """ + if asset_id is None or (isinstance(asset_id, str) and not asset_id): + raise ValueError(f"Expected a non-empty value for `asset_id` but received {asset_id!r}") + if part_number is None or (isinstance(part_number, str) and not part_number): + raise ValueError(f"Expected a non-empty value for `part_number` but received {part_number!r}") + return self._get( + path_template( + "/video/assets/{asset_id}/multipartupload/{part_number}/sign", + **{"asset_id": asset_id, "part_number": part_number}, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=MultipartUploadRetrievePartURLResponse, + ) + + def complete( + self, + asset_id: str, + *, + parts: Iterable[multipart_upload_complete_params.Part] | 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, + ) -> MultipartUploadCompleteResponse: + """ + Once you upload all parts to S3 bucket via pre-signed URL, use this endpoint to complete the multipart upload. + + Args: + asset_id: An asset id for which you are uploading original video via multipart + parts: List of object containing part number with ETag received as a response header while uploading each part + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + MultipartUploadCompleteResponse: 200 + + Example: + ```python + multipart_upload = client.multipart_upload.complete( + asset_id="assetId", + ) + ``` + """ + if asset_id is None or (isinstance(asset_id, str) and not asset_id): + raise ValueError(f"Expected a non-empty value for `asset_id` but received {asset_id!r}") + return self._post( + path_template("/video/assets/{asset_id}/multipartupload/complete", **{"asset_id": asset_id}), + body=maybe_transform( + {"parts": parts}, + multipart_upload_complete_params.MultipartUploadCompleteParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=MultipartUploadCompleteResponse, + ) + + +class AsyncMultipartUploadResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncMultipartUploadResourceWithRawResponse: + return AsyncMultipartUploadResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncMultipartUploadResourceWithStreamingResponse: + return AsyncMultipartUploadResourceWithStreamingResponse(self) + + async def retrieve_part_url( + self, + part_number: str, + *, + asset_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, + ) -> MultipartUploadRetrievePartURLResponse: + """ + Use this endpoint to retrieve a pre-signed upload URL for the given part number. + + Args: + part_number: Part number of multiple parts of the original video which you you are uploading + asset_id: An asset id of the created asset for which you are uploading parts + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + MultipartUploadRetrievePartURLResponse: 200 + + Example: + ```python + multipart_upload = await client.multipart_upload.retrieve_part_url( + asset_id="assetId", + part_number="partNumber", + ) + ``` + """ + if asset_id is None or (isinstance(asset_id, str) and not asset_id): + raise ValueError(f"Expected a non-empty value for `asset_id` but received {asset_id!r}") + if part_number is None or (isinstance(part_number, str) and not part_number): + raise ValueError(f"Expected a non-empty value for `part_number` but received {part_number!r}") + return await self._get( + path_template( + "/video/assets/{asset_id}/multipartupload/{part_number}/sign", + **{"asset_id": asset_id, "part_number": part_number}, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=MultipartUploadRetrievePartURLResponse, + ) + + async def complete( + self, + asset_id: str, + *, + parts: Iterable[multipart_upload_complete_params.Part] | 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, + ) -> MultipartUploadCompleteResponse: + """ + Once you upload all parts to S3 bucket via pre-signed URL, use this endpoint to complete the multipart upload. + + Args: + asset_id: An asset id for which you are uploading original video via multipart + parts: List of object containing part number with ETag received as a response header while uploading each part + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + MultipartUploadCompleteResponse: 200 + + Example: + ```python + multipart_upload = await client.multipart_upload.complete( + asset_id="assetId", + ) + ``` + """ + if asset_id is None or (isinstance(asset_id, str) and not asset_id): + raise ValueError(f"Expected a non-empty value for `asset_id` but received {asset_id!r}") + return await self._post( + path_template("/video/assets/{asset_id}/multipartupload/complete", **{"asset_id": asset_id}), + body=await async_maybe_transform( + {"parts": parts}, + multipart_upload_complete_params.MultipartUploadCompleteParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=MultipartUploadCompleteResponse, + ) + + +class MultipartUploadResourceWithRawResponse: + def __init__(self, multipart_upload: MultipartUploadResource) -> None: + self._multipart_upload = multipart_upload + + self.retrieve_part_url = to_raw_response_wrapper( + multipart_upload.retrieve_part_url, + ) + self.complete = to_raw_response_wrapper( + multipart_upload.complete, + ) + + +class AsyncMultipartUploadResourceWithRawResponse: + def __init__(self, multipart_upload: AsyncMultipartUploadResource) -> None: + self._multipart_upload = multipart_upload + + self.retrieve_part_url = async_to_raw_response_wrapper( + multipart_upload.retrieve_part_url, + ) + self.complete = async_to_raw_response_wrapper( + multipart_upload.complete, + ) + + +class MultipartUploadResourceWithStreamingResponse: + def __init__(self, multipart_upload: MultipartUploadResource) -> None: + self._multipart_upload = multipart_upload + + self.retrieve_part_url = to_streamed_response_wrapper( + multipart_upload.retrieve_part_url, + ) + self.complete = to_streamed_response_wrapper( + multipart_upload.complete, + ) + + +class AsyncMultipartUploadResourceWithStreamingResponse: + def __init__(self, multipart_upload: AsyncMultipartUploadResource) -> None: + self._multipart_upload = multipart_upload + + self.retrieve_part_url = async_to_streamed_response_wrapper( + multipart_upload.retrieve_part_url, + ) + self.complete = async_to_streamed_response_wrapper( + multipart_upload.complete, + ) diff --git a/src/gumlet/resources/organization_data.py b/src/gumlet/resources/organization_data.py new file mode 100644 index 0000000..03d5d21 --- /dev/null +++ b/src/gumlet/resources/organization_data.py @@ -0,0 +1,145 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +import httpx + +from .._types import Body, Query, Headers, NotGiven, not_given +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.organization_data_fetch_org_response import OrganizationDataFetchOrgResponse + +__all__ = ["OrganizationDataResource", "AsyncOrganizationDataResource"] + + +class OrganizationDataResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> OrganizationDataResourceWithRawResponse: + return OrganizationDataResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> OrganizationDataResourceWithStreamingResponse: + return OrganizationDataResourceWithStreamingResponse(self) + + def fetch_org( + self, + *, + # 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, + ) -> OrganizationDataFetchOrgResponse: + """ + You can get organization data using this API. + + Args: + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + OrganizationDataFetchOrgResponse: Successful response + + Example: + ```python + organization_data = client.organization_data.fetch_org() + ``` + """ + return self._get( + "/org/data", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=OrganizationDataFetchOrgResponse, + ) + + +class AsyncOrganizationDataResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncOrganizationDataResourceWithRawResponse: + return AsyncOrganizationDataResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncOrganizationDataResourceWithStreamingResponse: + return AsyncOrganizationDataResourceWithStreamingResponse(self) + + async def fetch_org( + self, + *, + # 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, + ) -> OrganizationDataFetchOrgResponse: + """ + You can get organization data using this API. + + Args: + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + OrganizationDataFetchOrgResponse: Successful response + + Example: + ```python + organization_data = await client.organization_data.fetch_org() + ``` + """ + return await self._get( + "/org/data", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=OrganizationDataFetchOrgResponse, + ) + + +class OrganizationDataResourceWithRawResponse: + def __init__(self, organization_data: OrganizationDataResource) -> None: + self._organization_data = organization_data + + self.fetch_org = to_raw_response_wrapper( + organization_data.fetch_org, + ) + + +class AsyncOrganizationDataResourceWithRawResponse: + def __init__(self, organization_data: AsyncOrganizationDataResource) -> None: + self._organization_data = organization_data + + self.fetch_org = async_to_raw_response_wrapper( + organization_data.fetch_org, + ) + + +class OrganizationDataResourceWithStreamingResponse: + def __init__(self, organization_data: OrganizationDataResource) -> None: + self._organization_data = organization_data + + self.fetch_org = to_streamed_response_wrapper( + organization_data.fetch_org, + ) + + +class AsyncOrganizationDataResourceWithStreamingResponse: + def __init__(self, organization_data: AsyncOrganizationDataResource) -> None: + self._organization_data = organization_data + + self.fetch_org = async_to_streamed_response_wrapper( + organization_data.fetch_org, + ) diff --git a/src/gumlet/resources/recycle_bin.py b/src/gumlet/resources/recycle_bin.py new file mode 100644 index 0000000..192e823 --- /dev/null +++ b/src/gumlet/resources/recycle_bin.py @@ -0,0 +1,273 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +import httpx + +from .._types import Body, Omit, Query, Headers, NotGiven, NoneType, 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 import recycle_bin_recover_params, recycle_bin_list_params +from ..types.recycle_bin_list_response import RecycleBinListResponse + +__all__ = ["RecycleBinResource", "AsyncRecycleBinResource"] + + +class RecycleBinResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> RecycleBinResourceWithRawResponse: + return RecycleBinResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> RecycleBinResourceWithStreamingResponse: + return RecycleBinResourceWithStreamingResponse(self) + + def recover( + self, + *, + asset_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, + ) -> None: + """ + Recovers a deleted asset from the recycle bin. + + Args: + asset_id: Gumlet Video Asset Id which needs to be recovered. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Example: + ```python + client.recycle_bin.recover( + asset_id="", + ) + ``` + """ + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return self._post( + "/video/asset/recover", + body=maybe_transform( + {"asset_id": asset_id}, + recycle_bin_recover_params.RecycleBinRecoverParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + def list( + self, + *, + offset: int | Omit = omit, + size: int | Omit = omit, + workspace_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, + ) -> RecycleBinListResponse: + """ + List all assets in a recycle bin for a given workspace. The deleted assets are available for 30 days. After that, assets are permanently deleted. + + Args: + offset: Number of items to skip from start of page response. + size: Number of items to return for a single page. + workspace_id: ID of workspace for which you want to list the recycle bin items. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + RecycleBinListResponse: Successful response + + Example: + ```python + recycle_bin = client.recycle_bin.list( + size=20, + workspace_id="workspace_id", + ) + ``` + """ + return self._get( + "/video/asset/recoverable/list", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + {"offset": offset, "size": size, "workspace_id": workspace_id}, + recycle_bin_list_params.RecycleBinListParams, + ), + ), + cast_to=RecycleBinListResponse, + ) + + +class AsyncRecycleBinResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncRecycleBinResourceWithRawResponse: + return AsyncRecycleBinResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncRecycleBinResourceWithStreamingResponse: + return AsyncRecycleBinResourceWithStreamingResponse(self) + + async def recover( + self, + *, + asset_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, + ) -> None: + """ + Recovers a deleted asset from the recycle bin. + + Args: + asset_id: Gumlet Video Asset Id which needs to be recovered. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Example: + ```python + await client.recycle_bin.recover( + asset_id="", + ) + ``` + """ + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return await self._post( + "/video/asset/recover", + body=await async_maybe_transform( + {"asset_id": asset_id}, + recycle_bin_recover_params.RecycleBinRecoverParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + async def list( + self, + *, + offset: int | Omit = omit, + size: int | Omit = omit, + workspace_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, + ) -> RecycleBinListResponse: + """ + List all assets in a recycle bin for a given workspace. The deleted assets are available for 30 days. After that, assets are permanently deleted. + + Args: + offset: Number of items to skip from start of page response. + size: Number of items to return for a single page. + workspace_id: ID of workspace for which you want to list the recycle bin items. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + RecycleBinListResponse: Successful response + + Example: + ```python + recycle_bin = await client.recycle_bin.list( + size=20, + workspace_id="workspace_id", + ) + ``` + """ + return await self._get( + "/video/asset/recoverable/list", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + {"offset": offset, "size": size, "workspace_id": workspace_id}, + recycle_bin_list_params.RecycleBinListParams, + ), + ), + cast_to=RecycleBinListResponse, + ) + + +class RecycleBinResourceWithRawResponse: + def __init__(self, recycle_bin: RecycleBinResource) -> None: + self._recycle_bin = recycle_bin + + self.recover = to_raw_response_wrapper( + recycle_bin.recover, + ) + self.list = to_raw_response_wrapper( + recycle_bin.list, + ) + + +class AsyncRecycleBinResourceWithRawResponse: + def __init__(self, recycle_bin: AsyncRecycleBinResource) -> None: + self._recycle_bin = recycle_bin + + self.recover = async_to_raw_response_wrapper( + recycle_bin.recover, + ) + self.list = async_to_raw_response_wrapper( + recycle_bin.list, + ) + + +class RecycleBinResourceWithStreamingResponse: + def __init__(self, recycle_bin: RecycleBinResource) -> None: + self._recycle_bin = recycle_bin + + self.recover = to_streamed_response_wrapper( + recycle_bin.recover, + ) + self.list = to_streamed_response_wrapper( + recycle_bin.list, + ) + + +class AsyncRecycleBinResourceWithStreamingResponse: + def __init__(self, recycle_bin: AsyncRecycleBinResource) -> None: + self._recycle_bin = recycle_bin + + self.recover = async_to_streamed_response_wrapper( + recycle_bin.recover, + ) + self.list = async_to_streamed_response_wrapper( + recycle_bin.list, + ) diff --git a/src/gumlet/resources/subtitle_upload.py b/src/gumlet/resources/subtitle_upload.py new file mode 100644 index 0000000..d105bc0 --- /dev/null +++ b/src/gumlet/resources/subtitle_upload.py @@ -0,0 +1,287 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +import httpx + +from typing import Iterable +from .._types import SequenceNotStr + +from .._types import Body, Omit, Query, Headers, NotGiven, 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.subtitle_upload_upload_response import SubtitleUploadUploadResponse +from ..types import subtitle_upload_upload_params, subtitle_upload_complete_params +from ..types.subtitle_upload_complete_response import SubtitleUploadCompleteResponse + +__all__ = ["SubtitleUploadResource", "AsyncSubtitleUploadResource"] + + +class SubtitleUploadResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> SubtitleUploadResourceWithRawResponse: + return SubtitleUploadResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> SubtitleUploadResourceWithStreamingResponse: + return SubtitleUploadResourceWithStreamingResponse(self) + + def upload( + self, + asset_id: str, + *, + language_codes: 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, + ) -> SubtitleUploadUploadResponse: + """ + Upload `.srt` or `.vtt` file to the video asset. The response of this API call gives `upload_url` for each language specified. You need to send a `PUT` request of the subtitle files to those URLs. Once that's done, you need to call the subtitle upload complete API. Only after that, Gumlet will add subtitles to asset. + + Args: + asset_id: An asset id for the previously created asset. + language_codes: List of language Code to upload subtitle file (use ISO 639-1 Language Codes) + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + SubtitleUploadUploadResponse: 200 + + Example: + ```python + subtitle_upload = client.subtitle_upload.upload( + asset_id="assetId", + language_codes=["en"], + ) + ``` + """ + if asset_id is None or (isinstance(asset_id, str) and not asset_id): + raise ValueError(f"Expected a non-empty value for `asset_id` but received {asset_id!r}") + return self._post( + path_template("/video/assets/{asset_ID}/subtitle/upload", **{"asset_ID": asset_id}), + body=maybe_transform( + {"language_codes": language_codes}, + subtitle_upload_upload_params.SubtitleUploadUploadParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=SubtitleUploadUploadResponse, + ) + + def complete( + self, + asset_id: str, + *, + upload_responses: Iterable[subtitle_upload_complete_params.UploadResponse] | 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, + ) -> SubtitleUploadCompleteResponse: + """ + This API must be called after adding subtitles; the add subtitle call gives you URLs to upload, and you complete a `PUT` request to those URLs. + Once that is done, calling this initiates the process to actually add the subtitle to the video. + + Args: + asset_id: An asset id for the previously created asset. + upload_responses: Body parameter. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + SubtitleUploadCompleteResponse: 200 + + Example: + ```python + subtitle_upload = client.subtitle_upload.complete( + asset_id="assetId", + upload_responses=[{"language_code": "en", "uploaded": True}], + ) + ``` + """ + if asset_id is None or (isinstance(asset_id, str) and not asset_id): + raise ValueError(f"Expected a non-empty value for `asset_id` but received {asset_id!r}") + return self._post( + path_template("/video/assets/{asset_ID}/subtitle/upload/event", **{"asset_ID": asset_id}), + body=maybe_transform( + {"upload_responses": upload_responses}, + subtitle_upload_complete_params.SubtitleUploadCompleteParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=SubtitleUploadCompleteResponse, + ) + + +class AsyncSubtitleUploadResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncSubtitleUploadResourceWithRawResponse: + return AsyncSubtitleUploadResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncSubtitleUploadResourceWithStreamingResponse: + return AsyncSubtitleUploadResourceWithStreamingResponse(self) + + async def upload( + self, + asset_id: str, + *, + language_codes: 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, + ) -> SubtitleUploadUploadResponse: + """ + Upload `.srt` or `.vtt` file to the video asset. The response of this API call gives `upload_url` for each language specified. You need to send a `PUT` request of the subtitle files to those URLs. Once that's done, you need to call the subtitle upload complete API. Only after that, Gumlet will add subtitles to asset. + + Args: + asset_id: An asset id for the previously created asset. + language_codes: List of language Code to upload subtitle file (use ISO 639-1 Language Codes) + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + SubtitleUploadUploadResponse: 200 + + Example: + ```python + subtitle_upload = await client.subtitle_upload.upload( + asset_id="assetId", + language_codes=["en"], + ) + ``` + """ + if asset_id is None or (isinstance(asset_id, str) and not asset_id): + raise ValueError(f"Expected a non-empty value for `asset_id` but received {asset_id!r}") + return await self._post( + path_template("/video/assets/{asset_ID}/subtitle/upload", **{"asset_ID": asset_id}), + body=await async_maybe_transform( + {"language_codes": language_codes}, + subtitle_upload_upload_params.SubtitleUploadUploadParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=SubtitleUploadUploadResponse, + ) + + async def complete( + self, + asset_id: str, + *, + upload_responses: Iterable[subtitle_upload_complete_params.UploadResponse] | 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, + ) -> SubtitleUploadCompleteResponse: + """ + This API must be called after adding subtitles; the add subtitle call gives you URLs to upload, and you complete a `PUT` request to those URLs. + Once that is done, calling this initiates the process to actually add the subtitle to the video. + + Args: + asset_id: An asset id for the previously created asset. + upload_responses: Body parameter. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + SubtitleUploadCompleteResponse: 200 + + Example: + ```python + subtitle_upload = await client.subtitle_upload.complete( + asset_id="assetId", + upload_responses=[{"language_code": "en", "uploaded": True}], + ) + ``` + """ + if asset_id is None or (isinstance(asset_id, str) and not asset_id): + raise ValueError(f"Expected a non-empty value for `asset_id` but received {asset_id!r}") + return await self._post( + path_template("/video/assets/{asset_ID}/subtitle/upload/event", **{"asset_ID": asset_id}), + body=await async_maybe_transform( + {"upload_responses": upload_responses}, + subtitle_upload_complete_params.SubtitleUploadCompleteParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=SubtitleUploadCompleteResponse, + ) + + +class SubtitleUploadResourceWithRawResponse: + def __init__(self, subtitle_upload: SubtitleUploadResource) -> None: + self._subtitle_upload = subtitle_upload + + self.upload = to_raw_response_wrapper( + subtitle_upload.upload, + ) + self.complete = to_raw_response_wrapper( + subtitle_upload.complete, + ) + + +class AsyncSubtitleUploadResourceWithRawResponse: + def __init__(self, subtitle_upload: AsyncSubtitleUploadResource) -> None: + self._subtitle_upload = subtitle_upload + + self.upload = async_to_raw_response_wrapper( + subtitle_upload.upload, + ) + self.complete = async_to_raw_response_wrapper( + subtitle_upload.complete, + ) + + +class SubtitleUploadResourceWithStreamingResponse: + def __init__(self, subtitle_upload: SubtitleUploadResource) -> None: + self._subtitle_upload = subtitle_upload + + self.upload = to_streamed_response_wrapper( + subtitle_upload.upload, + ) + self.complete = to_streamed_response_wrapper( + subtitle_upload.complete, + ) + + +class AsyncSubtitleUploadResourceWithStreamingResponse: + def __init__(self, subtitle_upload: AsyncSubtitleUploadResource) -> None: + self._subtitle_upload = subtitle_upload + + self.upload = async_to_streamed_response_wrapper( + subtitle_upload.upload, + ) + self.complete = async_to_streamed_response_wrapper( + subtitle_upload.complete, + ) diff --git a/src/gumlet/resources/user_data.py b/src/gumlet/resources/user_data.py new file mode 100644 index 0000000..00ec4fe --- /dev/null +++ b/src/gumlet/resources/user_data.py @@ -0,0 +1,145 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +import httpx + +from .._types import Body, Query, Headers, NotGiven, not_given +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.user_data_fetch_response import UserDataFetchResponse + +__all__ = ["UserDataResource", "AsyncUserDataResource"] + + +class UserDataResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> UserDataResourceWithRawResponse: + return UserDataResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> UserDataResourceWithStreamingResponse: + return UserDataResourceWithStreamingResponse(self) + + def fetch( + self, + *, + # 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, + ) -> UserDataFetchResponse: + """ + This endpoint gives information about the user account. + + Args: + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + UserDataFetchResponse: Successful response + + Example: + ```python + user_data = client.user_data.fetch() + ``` + """ + return self._get( + "/user/data", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=UserDataFetchResponse, + ) + + +class AsyncUserDataResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncUserDataResourceWithRawResponse: + return AsyncUserDataResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncUserDataResourceWithStreamingResponse: + return AsyncUserDataResourceWithStreamingResponse(self) + + async def fetch( + self, + *, + # 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, + ) -> UserDataFetchResponse: + """ + This endpoint gives information about the user account. + + Args: + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + UserDataFetchResponse: Successful response + + Example: + ```python + user_data = await client.user_data.fetch() + ``` + """ + return await self._get( + "/user/data", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=UserDataFetchResponse, + ) + + +class UserDataResourceWithRawResponse: + def __init__(self, user_data: UserDataResource) -> None: + self._user_data = user_data + + self.fetch = to_raw_response_wrapper( + user_data.fetch, + ) + + +class AsyncUserDataResourceWithRawResponse: + def __init__(self, user_data: AsyncUserDataResource) -> None: + self._user_data = user_data + + self.fetch = async_to_raw_response_wrapper( + user_data.fetch, + ) + + +class UserDataResourceWithStreamingResponse: + def __init__(self, user_data: UserDataResource) -> None: + self._user_data = user_data + + self.fetch = to_streamed_response_wrapper( + user_data.fetch, + ) + + +class AsyncUserDataResourceWithStreamingResponse: + def __init__(self, user_data: AsyncUserDataResource) -> None: + self._user_data = user_data + + self.fetch = async_to_streamed_response_wrapper( + user_data.fetch, + ) diff --git a/src/gumlet/resources/video_analytics.py b/src/gumlet/resources/video_analytics.py new file mode 100644 index 0000000..34a2ebb --- /dev/null +++ b/src/gumlet/resources/video_analytics.py @@ -0,0 +1,469 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +import httpx + +from typing import Iterable +from typing_extensions import Literal +from .._types import SequenceNotStr + +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.video_analytic_chart_data_response import VideoAnalyticChartDataResponse +from ..types import ( + video_analytic_chart_data_params, + video_analytic_breakdown_data_params, + video_analytic_aggregated_data_params, +) +from ..types.video_analytic_breakdown_data_response import VideoAnalyticBreakdownDataResponse +from ..types.video_analytic_aggregated_data_response import VideoAnalyticAggregatedDataResponse + +__all__ = ["VideoAnalyticsResource", "AsyncVideoAnalyticsResource"] + + +class VideoAnalyticsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> VideoAnalyticsResourceWithRawResponse: + return VideoAnalyticsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> VideoAnalyticsResourceWithStreamingResponse: + return VideoAnalyticsResourceWithStreamingResponse(self) + + def chart_data( + self, + *, + metrics: SequenceNotStr[str], + workspace_id: str, + date_range: video_analytic_chart_data_params.DateRange, + filters: Iterable[video_analytic_chart_data_params.Filter] | Omit = omit, + group_by: Literal["daily", "weekly", "monthly"] | Omit = omit, + chart_dimension: video_analytic_chart_data_params.ChartDimension | 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, + ) -> VideoAnalyticChartDataResponse: + """ + This endpoint retrieves viewer analytics data. This endpoint is use for deep insights on the analytics data. + + Args: + metrics: Get data for one or more `metrics` in the same request. Please add any of these metrics. `views`, `unique_views`, `impressions`. `completion_percent_by_views`, `playing_time`, `concurrent_users`, `widget_form_submitted`, `cta_clicks` + workspace_id: The five to ten character unique identifier of the Gumlet workspace ID available on the Video Workspaces. + date_range: The timeframe to get the data for. + filters: Build *segments* of users using multiple filters on the data, `value` should be an *exact match* + group_by: Data can be grouped by `daily`, `weekly` or `monthly`. + chart_dimension: Metrics result Group by selected dimension, You can select upto 3 dimensions to get nested category result. result will follow selection orders. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoAnalyticChartDataResponse: 200 + + Example: + ```python + video_analytic = client.video_analytics.chart_data( + metrics=[""], + workspace_id="", + date_range={"start_at": "2024-01-01", "end_at": "2024-01-01"}, + group_by="daily", + ) + ``` + """ + return self._post( + "/insights/viewer-analytics", + body=maybe_transform( + { + "metrics": metrics, + "workspace_id": workspace_id, + "date_range": date_range, + "filters": filters, + "group_by": group_by, + "chart_dimension": chart_dimension, + }, + video_analytic_chart_data_params.VideoAnalyticChartDataParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoAnalyticChartDataResponse, + ) + + def breakdown_data( + self, + *, + date_range: video_analytic_breakdown_data_params.DateRange, + filters: Iterable[video_analytic_breakdown_data_params.Filter] | Omit = omit, + breakdowns: Iterable[video_analytic_breakdown_data_params.Breakdown], + workspace_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, + ) -> VideoAnalyticBreakdownDataResponse: + """ + This endpoint retrieves breakdown data of the given metrics by given breakdown field + + Args: + date_range: The timeframe to get the data for. + filters: Build *segments* of users using multiple filters on the data, `value` should be an *exact match* + breakdowns: Breakdown fields and metrics to retrieve data for. Supports 1 to 3 breakdowns per request. + workspace_id: The five to ten character unique identifier of the Gumlet workspace ID available on the Video Workspaces. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoAnalyticBreakdownDataResponse: 200 + + Example: + ```python + video_analytic = client.video_analytics.breakdown_data( + date_range={"start_at": "2026-07-20", "end_at": "2026-08-20"}, + filters=[], + breakdowns=[ + {"name": "custom_video_id", "metric": "views", "page": 1, "page_size": 10}, + {"name": "custom_video_title", "metric": "completion_percent_by_views", "page": 1, "page_size": 10}, + ], + workspace_id="6694c405e63913eecf3cf5fb", + ) + ``` + """ + return self._post( + "/insights/breakdown-data", + body=maybe_transform( + { + "date_range": date_range, + "filters": filters, + "breakdowns": breakdowns, + "workspace_id": workspace_id, + }, + video_analytic_breakdown_data_params.VideoAnalyticBreakdownDataParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoAnalyticBreakdownDataResponse, + ) + + def aggregated_data( + self, + *, + aggregate: Iterable[video_analytic_aggregated_data_params.Aggregate], + workspace_id: str, + timeframe: video_analytic_aggregated_data_params.Timeframe, + filters: Iterable[video_analytic_aggregated_data_params.Filter] | 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, + ) -> VideoAnalyticAggregatedDataResponse: + """ + This endpoint retrieves aggregated data of the given metrics. + + Args: + aggregate: Aggregate multiple metrics at the same time + workspace_id: The unique identifier of the Gumlet workspace ID available on the Video Workspaces. + timeframe: The timeframe to get the data for. Currently we only support maximum difference between `start_at` and `end_at` to be *60 days* + filters: Get aggregations for metrics with multiple filters, `value` should be an exact match + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoAnalyticAggregatedDataResponse: 200 + + Example: + ```python + video_analytic = client.video_analytics.aggregated_data( + aggregate=[{"metric": "views", "function": "sum"}], + workspace_id="", + timeframe={}, + ) + ``` + """ + return self._post( + "/insights/aggregated-data", + body=maybe_transform( + { + "aggregate": aggregate, + "workspace_id": workspace_id, + "timeframe": timeframe, + "filters": filters, + }, + video_analytic_aggregated_data_params.VideoAnalyticAggregatedDataParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoAnalyticAggregatedDataResponse, + ) + + +class AsyncVideoAnalyticsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncVideoAnalyticsResourceWithRawResponse: + return AsyncVideoAnalyticsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncVideoAnalyticsResourceWithStreamingResponse: + return AsyncVideoAnalyticsResourceWithStreamingResponse(self) + + async def chart_data( + self, + *, + metrics: SequenceNotStr[str], + workspace_id: str, + date_range: video_analytic_chart_data_params.DateRange, + filters: Iterable[video_analytic_chart_data_params.Filter] | Omit = omit, + group_by: Literal["daily", "weekly", "monthly"] | Omit = omit, + chart_dimension: video_analytic_chart_data_params.ChartDimension | 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, + ) -> VideoAnalyticChartDataResponse: + """ + This endpoint retrieves viewer analytics data. This endpoint is use for deep insights on the analytics data. + + Args: + metrics: Get data for one or more `metrics` in the same request. Please add any of these metrics. `views`, `unique_views`, `impressions`. `completion_percent_by_views`, `playing_time`, `concurrent_users`, `widget_form_submitted`, `cta_clicks` + workspace_id: The five to ten character unique identifier of the Gumlet workspace ID available on the Video Workspaces. + date_range: The timeframe to get the data for. + filters: Build *segments* of users using multiple filters on the data, `value` should be an *exact match* + group_by: Data can be grouped by `daily`, `weekly` or `monthly`. + chart_dimension: Metrics result Group by selected dimension, You can select upto 3 dimensions to get nested category result. result will follow selection orders. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoAnalyticChartDataResponse: 200 + + Example: + ```python + video_analytic = await client.video_analytics.chart_data( + metrics=[""], + workspace_id="", + date_range={"start_at": "2024-01-01", "end_at": "2024-01-01"}, + group_by="daily", + ) + ``` + """ + return await self._post( + "/insights/viewer-analytics", + body=await async_maybe_transform( + { + "metrics": metrics, + "workspace_id": workspace_id, + "date_range": date_range, + "filters": filters, + "group_by": group_by, + "chart_dimension": chart_dimension, + }, + video_analytic_chart_data_params.VideoAnalyticChartDataParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoAnalyticChartDataResponse, + ) + + async def breakdown_data( + self, + *, + date_range: video_analytic_breakdown_data_params.DateRange, + filters: Iterable[video_analytic_breakdown_data_params.Filter] | Omit = omit, + breakdowns: Iterable[video_analytic_breakdown_data_params.Breakdown], + workspace_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, + ) -> VideoAnalyticBreakdownDataResponse: + """ + This endpoint retrieves breakdown data of the given metrics by given breakdown field + + Args: + date_range: The timeframe to get the data for. + filters: Build *segments* of users using multiple filters on the data, `value` should be an *exact match* + breakdowns: Breakdown fields and metrics to retrieve data for. Supports 1 to 3 breakdowns per request. + workspace_id: The five to ten character unique identifier of the Gumlet workspace ID available on the Video Workspaces. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoAnalyticBreakdownDataResponse: 200 + + Example: + ```python + video_analytic = await client.video_analytics.breakdown_data( + date_range={"start_at": "2026-07-20", "end_at": "2026-08-20"}, + filters=[], + breakdowns=[ + {"name": "custom_video_id", "metric": "views", "page": 1, "page_size": 10}, + {"name": "custom_video_title", "metric": "completion_percent_by_views", "page": 1, "page_size": 10}, + ], + workspace_id="6694c405e63913eecf3cf5fb", + ) + ``` + """ + return await self._post( + "/insights/breakdown-data", + body=await async_maybe_transform( + { + "date_range": date_range, + "filters": filters, + "breakdowns": breakdowns, + "workspace_id": workspace_id, + }, + video_analytic_breakdown_data_params.VideoAnalyticBreakdownDataParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoAnalyticBreakdownDataResponse, + ) + + async def aggregated_data( + self, + *, + aggregate: Iterable[video_analytic_aggregated_data_params.Aggregate], + workspace_id: str, + timeframe: video_analytic_aggregated_data_params.Timeframe, + filters: Iterable[video_analytic_aggregated_data_params.Filter] | 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, + ) -> VideoAnalyticAggregatedDataResponse: + """ + This endpoint retrieves aggregated data of the given metrics. + + Args: + aggregate: Aggregate multiple metrics at the same time + workspace_id: The unique identifier of the Gumlet workspace ID available on the Video Workspaces. + timeframe: The timeframe to get the data for. Currently we only support maximum difference between `start_at` and `end_at` to be *60 days* + filters: Get aggregations for metrics with multiple filters, `value` should be an exact match + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoAnalyticAggregatedDataResponse: 200 + + Example: + ```python + video_analytic = await client.video_analytics.aggregated_data( + aggregate=[{"metric": "views", "function": "sum"}], + workspace_id="", + timeframe={}, + ) + ``` + """ + return await self._post( + "/insights/aggregated-data", + body=await async_maybe_transform( + { + "aggregate": aggregate, + "workspace_id": workspace_id, + "timeframe": timeframe, + "filters": filters, + }, + video_analytic_aggregated_data_params.VideoAnalyticAggregatedDataParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoAnalyticAggregatedDataResponse, + ) + + +class VideoAnalyticsResourceWithRawResponse: + def __init__(self, video_analytics: VideoAnalyticsResource) -> None: + self._video_analytics = video_analytics + + self.chart_data = to_raw_response_wrapper( + video_analytics.chart_data, + ) + self.breakdown_data = to_raw_response_wrapper( + video_analytics.breakdown_data, + ) + self.aggregated_data = to_raw_response_wrapper( + video_analytics.aggregated_data, + ) + + +class AsyncVideoAnalyticsResourceWithRawResponse: + def __init__(self, video_analytics: AsyncVideoAnalyticsResource) -> None: + self._video_analytics = video_analytics + + self.chart_data = async_to_raw_response_wrapper( + video_analytics.chart_data, + ) + self.breakdown_data = async_to_raw_response_wrapper( + video_analytics.breakdown_data, + ) + self.aggregated_data = async_to_raw_response_wrapper( + video_analytics.aggregated_data, + ) + + +class VideoAnalyticsResourceWithStreamingResponse: + def __init__(self, video_analytics: VideoAnalyticsResource) -> None: + self._video_analytics = video_analytics + + self.chart_data = to_streamed_response_wrapper( + video_analytics.chart_data, + ) + self.breakdown_data = to_streamed_response_wrapper( + video_analytics.breakdown_data, + ) + self.aggregated_data = to_streamed_response_wrapper( + video_analytics.aggregated_data, + ) + + +class AsyncVideoAnalyticsResourceWithStreamingResponse: + def __init__(self, video_analytics: AsyncVideoAnalyticsResource) -> None: + self._video_analytics = video_analytics + + self.chart_data = async_to_streamed_response_wrapper( + video_analytics.chart_data, + ) + self.breakdown_data = async_to_streamed_response_wrapper( + video_analytics.breakdown_data, + ) + self.aggregated_data = async_to_streamed_response_wrapper( + video_analytics.aggregated_data, + ) diff --git a/src/gumlet/resources/video_assets.py b/src/gumlet/resources/video_assets.py new file mode 100644 index 0000000..628daeb --- /dev/null +++ b/src/gumlet/resources/video_assets.py @@ -0,0 +1,2145 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +import httpx + +from typing import Dict, Iterable, List +from typing_extensions import Literal +from .._types import SequenceNotStr + +from .._types import Body, Omit, Query, Headers, NotGiven, NoneType, 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.video_asset_create_response import VideoAssetCreateResponse +from ..types import ( + video_asset_create_params, + video_asset_upload_params, + video_asset_update_params, + video_asset_thumbnail_select_params, + video_asset_create_update_chapter_params, + video_asset_list_params, + video_asset_list_deprecated_params, + video_asset_delete_many_params, + video_asset_tag_many_params, + video_asset_analytics_params, +) +from ..types.video_asset_upload_response import VideoAssetUploadResponse +from ..types.video_asset_retrieve_details_response import VideoAssetRetrieveDetailsResponse +from ..types.video_asset_update_response import VideoAssetUpdateResponse +from ..types.video_asset_thumbnail_select_response import VideoAssetThumbnailSelectResponse +from ..types.video_asset_thumbnail_upload_response import VideoAssetThumbnailUploadResponse +from ..types.video_asset_create_update_chapter_response import VideoAssetCreateUpdateChapterResponse +from ..types.video_asset_list_response import VideoAssetListResponse +from ..types.video_asset_list_deprecated_response import VideoAssetListDeprecatedResponse +from ..types.video_asset_delete_many_response import VideoAssetDeleteManyResponse +from ..types.video_asset_tag_many_response import VideoAssetTagManyResponse +from ..types.video_asset_analytics_response import VideoAssetAnalyticsResponse + +__all__ = ["VideoAssetsResource", "AsyncVideoAssetsResource"] + + +class VideoAssetsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> VideoAssetsResourceWithRawResponse: + return VideoAssetsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> VideoAssetsResourceWithStreamingResponse: + return VideoAssetsResourceWithStreamingResponse(self) + + def create( + self, + *, + input: str, + collection_id: str, + profile_id: str | Omit = omit, + format: Literal["ABR", "MP4"], + tag: SequenceNotStr[str] | Omit = omit, + title: str | Omit = omit, + description: str | Omit = omit, + metadata: Dict[str, object] | Omit = omit, + width: str | Omit = omit, + height: str | Omit = omit, + resolution: str | Omit = omit, + crop: video_asset_create_params.Crop | Omit = omit, + pad: video_asset_create_params.Pad | Omit = omit, + trim: video_asset_create_params.Trim | Omit = omit, + image_overlay: video_asset_create_params.ImageOverlay | Omit = omit, + text_overlay: video_asset_create_params.TextOverlay | Omit = omit, + animated_gif: video_asset_create_params.AnimatedGif | Omit = omit, + additional_tracks: Iterable[video_asset_create_params.AdditionalTrack] | Omit = omit, + generate_subtitles: video_asset_create_params.GenerateSubtitles | Omit = omit, + mp4_access: bool | Omit = omit, + per_title_encoding: bool | Omit = omit, + process_low_resolution_input: bool | Omit = omit, + audio_only: bool | Omit = omit, + enable_drm: bool | Omit = omit, + call_to_actions: Iterable[video_asset_create_params.CallToAction] | Omit = omit, + playlist_id: str | Omit = omit, + folder: 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, + ) -> VideoAssetCreateResponse: + """ + An asset refers to a media content/video that is processed, stored, and delivered through Gumlet. This endpoint creates an asset allowing users to ingest media content into the Gumlet system for processing and delivery. + + Args: + input: URL or web address of a file that Gumlet should download to create a new asset. + collection_id: Gumlet video workspace id. + profile_id: Provide `profile_id` of the previously created video profile. This parameter will override all the parameters (except `input` and `collection_id`) from the video profile. + format: Transcode and deliver the asset in the requested format. The options can be one of `ABR` (HLS + DASH) and`MP4`. + tag: Specify a text string or identifier which can identify an asset or bunch of assets later. + title: Specify a text string or identifier which can be used for filtering or searching the asset. + description: Attach some textual data with the asset. This field is neither searchable nor filterable. + metadata: Add your metadata you want to associate with this asset.
Example:
{ "internal_video_id" : "123Abc" } + width: Resize video with the given width. Can be an absolute value in pixels or a percentage value with the `%` suffix. Specified values greater than the original asset width will be ignored. Applicable only when specified format is `MP4`. + height: Resize video with the given height. Can be an absolute value in pixels or a percentage value with the `%` suffix. Specified values greater than the original asset height will be ignored. Applicable only when specified format is `MP4`. + resolution: Required resolutions of the transformed asset in case of HLS or MPEG-DASH delivery format. Can be a comma separated string out of the following values: `240p`, `360p`, `480p`, `540p`, `720p`, and `1080p`. Re-sized rendition will retain the input aspect ratio. + crop: This transformation can be used to crop the video by defining a rectangular area within the dimensions of the output video. + pad: This transformation can be used to add padding to the video. + trim: Trim transformation can be used to trim videos based on time duration. + image_overlay: Image overlay can be used to brand a video or add a visual label in the form of an image. + text_overlay: Text overlay can be used to brand a video or add a label in the form of text. + animated_gif: Create an animated GIF from the video. + additional_tracks: Add additional Audio / Subtitle tracks to Gumlet for transcoding and delivery along with video asset track. + generate_subtitles: Gumlet allows to generate subtitles from the audio stream (use ISO 639-1 Language Codes) + mp4_access: Creates `MP4` version for download purpose in case of `MPEG-DASH` or `HLS` delivery format. **Default: `false`** + per_title_encoding: Gumlet analyzes each input video on a wide range of visual aspects. Based on the analysis, it chooses a unique set of transcoding options for processing the video. This ensures that the output video is of optimal size and best quality. **Default: `true`** + process_low_resolution_input: Currently, the minimum supported frame size is `57600` (`240x240`) pixels for `HLS/DASH` and `21025` (`145x145`) pixels for `MP4` format. However, enabling this flag will allow Gumlet to simply put your video asset into the specified delivery format without transcoding and optimization. Enabling this flag will cause any kind of specified video transformation to be ignored if you input video asset frame size is lower than the minimum supported frame size for the specified format. **Default: `false`** + audio_only: This flag allows Gumlet to transcode and deliver audio-only in the specified format. In this case, video transformation and thumbnails/animated GIFs would not be created. **Default: `false`** + enable_drm: Enable DRM encryption for transcoded videos. Gumlet supports Widevine and Fairplay DRMs. + call_to_actions: CTA, is an explicit prompt within the video content encouraging viewers to take a particular action. + playlist_id: Add this asset to a playlist. + folder: Add this asset to an existing folder by `folder_id`. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoAssetCreateResponse: 200 + + Example: + ```python + video_asset = client.video_assets.create( + input="http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8", + collection_id="646df1c9173a4a2fcac180b4", + profile_id="646df1c9173a4a2fcac180b7", + format="ABR", + tag=["ball"], + description="some description", + metadata={"headermeta": "metavalue"}, + call_to_actions=[ + { + "start_time": 1, + "end_time": 90, + "text": "some test", + "url": "https://some-url.com", + "position_from_top": 11, + "position_from_right": 23, + "border_radius": "11", + "font_color": "#000001", + "background_color": "#ffffff", + } + ], + playlist_id="6597acd5ed6f26a9c5ca9633", + folder="697375fbfa2d1037283140e4", + ) + ``` + """ + return self._post( + "/video/assets", + body=maybe_transform( + { + "input": input, + "collection_id": collection_id, + "profile_id": profile_id, + "format": format, + "tag": tag, + "title": title, + "description": description, + "metadata": metadata, + "width": width, + "height": height, + "resolution": resolution, + "crop": crop, + "pad": pad, + "trim": trim, + "image_overlay": image_overlay, + "text_overlay": text_overlay, + "animated_gif": animated_gif, + "additional_tracks": additional_tracks, + "generate_subtitles": generate_subtitles, + "mp4_access": mp4_access, + "per_title_encoding": per_title_encoding, + "process_low_resolution_input": process_low_resolution_input, + "audio_only": audio_only, + "enable_drm": enable_drm, + "call_to_actions": call_to_actions, + "playlist_id": playlist_id, + "folder": folder, + }, + video_asset_create_params.VideoAssetCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoAssetCreateResponse, + ) + + def upload( + self, + *, + collection_id: str, + profile_id: str | Omit = omit, + format: Literal["ABR", "MP4"] | Omit = omit, + tag: SequenceNotStr[str] | Omit = omit, + title: str | Omit = omit, + description: str | Omit = omit, + metadata: Dict[str, object] | Omit = omit, + width: str | Omit = omit, + height: str | Omit = omit, + resolution: str | Omit = omit, + crop: video_asset_upload_params.Crop | Omit = omit, + pad: video_asset_upload_params.Pad | Omit = omit, + trim: video_asset_upload_params.Trim | Omit = omit, + image_overlay: video_asset_upload_params.ImageOverlay | Omit = omit, + text_overlay: video_asset_upload_params.TextOverlay | Omit = omit, + animated_gif: video_asset_upload_params.AnimatedGif | Omit = omit, + additional_tracks: Iterable[video_asset_upload_params.AdditionalTrack] | Omit = omit, + generate_subtitles: video_asset_upload_params.GenerateSubtitles | Omit = omit, + mp4_access: bool | Omit = omit, + per_title_encoding: bool | Omit = omit, + process_low_resolution_input: bool | Omit = omit, + audio_only: bool | Omit = omit, + enable_drm: bool | Omit = omit, + call_to_actions: Iterable[video_asset_upload_params.CallToAction] | Omit = omit, + playlist_id: str | Omit = omit, + folder: 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, + ) -> VideoAssetUploadResponse: + """ + This endpoint creates a video asset allowing to upload of the video from the local file system and ingest media content into the Gumlet system for processing and delivery.Body Parameters are the same as the Create Asset Body Parameters except for the `input` parameter which this endpoint does not take.A successful response will be returned with `upload_url` field. You can make `PUT` request to that URL to upload video. To upload video using `upload_url` refer to [this](https://docs.gumlet.com/docs/direct-upload#2-use-the-url-to-upload-a-file). + + Args: + collection_id: Gumlet video workspace id. + profile_id: Provide `profile_id` of the previously created video profile. This parameter will override all the parameters (except `input` and `collection_id`) from the video profile. + format: Transcode and deliver the asset in the requested format. The options can be one of `ABR` (HLS + DASH) and`MP4`. + tag: Specify a text string or identifier which can identify an asset or bunch of assets later. + title: Specify a text string or identifier which can be used for filtering or searching the asset. + description: Attach some textual data with the asset. This field is neither searchable nor filterable. + metadata: Add your metadata you want to associate with this asset.
Example:
{ "internal_video_id" : "123Abc" } + width: Resize video with the given width. Can be an absolute value in pixels or a percentage value with the `%` suffix. Specified values greater than the original asset width will be ignored. Applicable only when specified format is `MP4`. + height: Resize video with the given height. Can be an absolute value in pixels or a percentage value with the `%` suffix. Specified values greater than the original asset height will be ignored. Applicable only when specified format is `MP4`. + resolution: Required resolutions of the transformed asset in case of HLS or MPEG-DASH delivery format. Can be a comma separated string out of the following values: `240p`, `360p`, `480p`, `540p`, `720p`, and `1080p`. Re-sized rendition will retain the input aspect ratio. + crop: This transformation can be used to crop the video by defining a rectangular area within the dimensions of the output video. + pad: This transformation can be used to add padding to the video. + trim: Trim transformation can be used to trim videos based on time duration. + image_overlay: Image overlay can be used to brand a video or add a visual label in the form of an image. + text_overlay: Text overlay can be used to brand a video or add a label in the form of text. + animated_gif: Create an animated GIF from the video. + additional_tracks: Add additional Audio / Subtitle tracks to Gumlet for transcoding and delivery along with video asset track. + generate_subtitles: Gumlet allows to generate subtitles from the audio stream (use ISO 639-1 Language Codes) + mp4_access: Creates `MP4` version for download purpose in case of `MPEG-DASH` or `HLS` delivery format. **Default: `false`** + per_title_encoding: Gumlet analyzes each input video on a wide range of visual aspects. Based on the analysis, it chooses a unique set of transcoding options for processing the video. This ensures that the output video is of optimal size and best quality. **Default: `true`** + process_low_resolution_input: Currently, the minimum supported frame size is `57600` (`240x240`) pixels for `HLS/DASH` and `21025` (`145x145`) pixels for `MP4` format. However, enabling this flag will allow Gumlet to simply put your video asset into the specified delivery format without transcoding and optimization. Enabling this flag will cause any kind of specified video transformation to be ignored if you input video asset frame size is lower than the minimum supported frame size for the specified format. **Default: `false`** + audio_only: This flag allows Gumlet to transcode and deliver audio-only in the specified format. In this case, video transformation and thumbnails/animated GIFs would not be created. **Default: `false`** + enable_drm: Enable DRM encryption for transcoded videos. Gumlet supports Widevine and Fairplay DRMs. + call_to_actions: CTA, is an explicit prompt within the video content encouraging viewers to take a particular action. + playlist_id: Add this asset to a playlist. + folder: Add this asset to an existing folder by `folder_id`. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoAssetUploadResponse: 200 + + Example: + ```python + video_asset = client.video_assets.upload( + collection_id="646df1c9173a4a2fcac180b4", + profile_id="646df1c9173a4a2fcac180b7", + format="ABR", + tag=["ball"], + description="some description", + metadata={"headermeta": "metavalue"}, + call_to_actions=[ + { + "start_time": 1, + "end_time": 90, + "text": "some test", + "url": "https://some-url.com", + "position_from_top": 11, + "position_from_right": 23, + "border_radius": "11", + "font_color": "#000001", + "background_color": "#ffffff", + } + ], + playlist_id="6597acd5ed6f26a9c5ca9633", + folder="697375fbfa2d1037283140e4", + ) + ``` + """ + return self._post( + "/video/assets/upload", + body=maybe_transform( + { + "collection_id": collection_id, + "profile_id": profile_id, + "format": format, + "tag": tag, + "title": title, + "description": description, + "metadata": metadata, + "width": width, + "height": height, + "resolution": resolution, + "crop": crop, + "pad": pad, + "trim": trim, + "image_overlay": image_overlay, + "text_overlay": text_overlay, + "animated_gif": animated_gif, + "additional_tracks": additional_tracks, + "generate_subtitles": generate_subtitles, + "mp4_access": mp4_access, + "per_title_encoding": per_title_encoding, + "process_low_resolution_input": process_low_resolution_input, + "audio_only": audio_only, + "enable_drm": enable_drm, + "call_to_actions": call_to_actions, + "playlist_id": playlist_id, + "folder": folder, + }, + video_asset_upload_params.VideoAssetUploadParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoAssetUploadResponse, + ) + + def retrieve_details( + self, + asset_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, + ) -> VideoAssetRetrieveDetailsResponse: + """ + This endpoint retrieves the details of an asset that has previously been created. + + Args: + asset_id: An asset id for the previously created asset. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoAssetRetrieveDetailsResponse: 200 + + Example: + ```python + video_asset = client.video_assets.retrieve_details( + asset_id="assetId", + ) + ``` + """ + if asset_id is None or (isinstance(asset_id, str) and not asset_id): + raise ValueError(f"Expected a non-empty value for `asset_id` but received {asset_id!r}") + return self._get( + path_template("/video/assets/{asset_id}", **{"asset_id": asset_id}), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoAssetRetrieveDetailsResponse, + ) + + def delete( + self, + asset_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, + ) -> None: + """ + This endpoint removes an asset given its unique asset id. The asset will be removed from storage as well, associated URLs will be inaccessible. + + Args: + asset_id: Asset id of the video asset which needs to be deleted. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + 204 + + Example: + ```python + client.video_assets.delete( + asset_id="assetId", + ) + ``` + """ + if asset_id is None or (isinstance(asset_id, str) and not asset_id): + raise ValueError(f"Expected a non-empty value for `asset_id` but received {asset_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return self._delete( + path_template("/video/assets/{asset_id}", **{"asset_id": asset_id}), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + def update( + self, + *, + asset_id: str, + title: str | Omit = omit, + description: str | Omit = omit, + tag: str | Omit = omit, + call_to_actions: Iterable[video_asset_update_params.CallToAction] | Omit = omit, + metadata: str | Omit = omit, + remove_subtitles: SequenceNotStr[str] | Omit = omit, + input: str | Omit = omit, + reprocess: bool | 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, + ) -> VideoAssetUpdateResponse: + """ + This endpoint allows users to update video asset that has previously been created. + + Args: + asset_id: Asset Id + title: Specify a text string or identifier which can be used for filtering or searching the asset. + description: Attach some textual data with the asset. This field is neither searchable nor filterable. + tag: Specify a text string or identifier which can identify an asset or bunch of assets later. You can pass multiple comma separated values. + call_to_actions: CTA, is an explicit prompt within the video content encouraging viewers to take a particular action. + metadata: Set of key-value pairs that you can attach to this Asset. This can be useful for storing additional information.
Example:
{ "internal_video_id" : "123Abc" } + remove_subtitles: Comma separated string of language codes. + input: For replacing videos, pass this along with `asset_id` + reprocess: To reprocess same video, pass this as true. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoAssetUpdateResponse: 200 + + Example: + ```python + video_asset = client.video_assets.update( + asset_id="", + title="Updated Title", + ) + ``` + """ + return self._post( + "/video/assets/update", + body=maybe_transform( + { + "asset_id": asset_id, + "title": title, + "description": description, + "tag": tag, + "call_to_actions": call_to_actions, + "metadata": metadata, + "remove_subtitles": remove_subtitles, + "input": input, + "reprocess": reprocess, + }, + video_asset_update_params.VideoAssetUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoAssetUpdateResponse, + ) + + def thumbnail_select( + self, + asset_id: str, + *, + frame_at_second: int, + # 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, + ) -> VideoAssetThumbnailSelectResponse: + """ + Select frame from video to use as thumbnail. + + Args: + asset_id: Asset id of the video asset which needs to be deleted. + frame_at_second: Frame secound + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoAssetThumbnailSelectResponse: 200 + + Example: + ```python + video_asset = client.video_assets.thumbnail_select( + asset_id="assetId", + frame_at_second=2, + ) + ``` + """ + if asset_id is None or (isinstance(asset_id, str) and not asset_id): + raise ValueError(f"Expected a non-empty value for `asset_id` but received {asset_id!r}") + extra_headers = {"Accept": "text/plain", **(extra_headers or {})} + return self._post( + path_template("/video/assets/{asset_id}/thumbnail-select", **{"asset_id": asset_id}), + body=maybe_transform( + {"frame_at_second": frame_at_second}, + video_asset_thumbnail_select_params.VideoAssetThumbnailSelectParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoAssetThumbnailSelectResponse, + ) + + def thumbnail_upload( + self, + asset_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, + ) -> VideoAssetThumbnailUploadResponse: + """ + Use any image file to use as thumbnail. Once you use the API, you will get `upload_url` in the response, and that can be used to upload the image file. + + Here is the sample curl request. + + ```bash + curl --location --request PUT '' \ + --data '' + ``` + + Args: + asset_id: An asset id for the previously created asset. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoAssetThumbnailUploadResponse + + Example: + ```python + video_asset = client.video_assets.thumbnail_upload( + asset_id="assetId", + ) + ``` + """ + if asset_id is None or (isinstance(asset_id, str) and not asset_id): + raise ValueError(f"Expected a non-empty value for `asset_id` but received {asset_id!r}") + return self._post( + path_template("/video/assets/{asset_ID}/thumbnail", **{"asset_ID": asset_id}), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoAssetThumbnailUploadResponse, + ) + + def create_update_chapter( + self, + asset_id: str, + *, + chapters: Iterable[video_asset_create_update_chapter_params.Chapter], + # 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, + ) -> VideoAssetCreateUpdateChapterResponse: + """ + This endpoint will create/update video asset chapters. + + Args: + asset_id: Gumlet asset ID + chapters: Body parameter. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoAssetCreateUpdateChapterResponse: 200 + + Example: + ```python + video_asset = client.video_assets.create_update_chapter( + asset_id="assetId", + chapters=[{"label": "Chapter 1", "startTime": 0}, {"label": "Chapter 2", "startTime": 10}], + ) + ``` + """ + if asset_id is None or (isinstance(asset_id, str) and not asset_id): + raise ValueError(f"Expected a non-empty value for `asset_id` but received {asset_id!r}") + return self._post( + path_template("/video/assets/{asset_id}/chapters", **{"asset_id": asset_id}), + body=maybe_transform( + {"chapters": chapters}, + video_asset_create_update_chapter_params.VideoAssetCreateUpdateChapterParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoAssetCreateUpdateChapterResponse, + ) + + def list( + self, + workspace_id: str, + *, + type: Literal["folders", "videos", "all"] | Omit = omit, + parent_id: str | Omit = omit, + title: str | Omit = omit, + status: str | Omit = omit, + tag: str | Omit = omit, + playlist_id: str | Omit = omit, + start_date: str | Omit = omit, + end_date: str | Omit = omit, + min_duration: float | Omit = omit, + max_duration: float | Omit = omit, + sort_by: Literal["title", "duration", "uploaded_at", "created_at"] | Omit = omit, + order_by: Literal["asc", "desc"] | Omit = omit, + search_index: Literal["search_index_for_asset_list", "cms-search", "cms-search-v2"] | Omit = omit, + offset: int | Omit = omit, + size: int | Omit = omit, + signed_token: Literal["true", "false"] | 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, + ) -> VideoAssetListResponse: + """ + List folders and assets for a workspace in a single response. Use `parent_id` to browse a specific folder, or filters like `title`, `status`, and `playlist_id` to search assets. + + Args: + workspace_id: Video workspace id. + type: Return `folders`, `videos`, or `all`. Default is `all`. + parent_id: Parent folder id. Send `null` to browse the root level. + title: Search folders or assets by title or description. + status: Comma-separated asset status values. + tag: Comma-separated asset tags. + playlist_id: Filter assets to a playlist. + start_date: Asset created_at lower bound. + end_date: Asset created_at upper bound. + min_duration: Minimum asset duration in seconds. + max_duration: Maximum asset duration in seconds. + sort_by: Sort assets by a supported field. + order_by: Asset sort order. + search_index: Search index used for asset title search. + offset: Offset for paginated results. + size: Page size. Maximum 100. + signed_token: Whether URLs should be pre-signed in the API response. Possible values: `true` and `false`. Default is `false`. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoAssetListResponse: 200 + + Example: + ```python + video_asset = client.video_assets.list( + workspace_id="workspaceId", + type="all", + offset=0, + size=20, + signed_token="false", + ) + ``` + """ + if workspace_id is None or (isinstance(workspace_id, str) and not workspace_id): + raise ValueError(f"Expected a non-empty value for `workspace_id` but received {workspace_id!r}") + return self._get( + path_template("/video/workspaces/{workspace_id}/list", **{"workspace_id": workspace_id}), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "type": type, + "parent_id": parent_id, + "title": title, + "status": status, + "tag": tag, + "playlist_id": playlist_id, + "start_date": start_date, + "end_date": end_date, + "min_duration": min_duration, + "max_duration": max_duration, + "sort_by": sort_by, + "order_by": order_by, + "search_index": search_index, + "offset": offset, + "size": size, + "signed_token": signed_token, + }, + video_asset_list_params.VideoAssetListParams, + ), + ), + cast_to=VideoAssetListResponse, + ) + + def list_deprecated( + self, + workspace_id: str, + *, + status: Literal["queued", "processing", "ready", "errored", "deleted"] | Omit = omit, + tag: str | Omit = omit, + title: str | Omit = omit, + folder: str | Omit = omit, + offset: str | Omit = omit, + size: str | Omit = omit, + playlist_id: str | Omit = omit, + sort_by: Literal["title", "duration", "uploaded_at", "created_at"] | Omit = omit, + order_by: Literal["asc", "desc"] | Omit = omit, + type: 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, + ) -> VideoAssetListDeprecatedResponse: + """ + [Deprecated] This endpoint list assets in video workspace. You can also pass `status` and `tag` to filter assets. + + Args: + workspace_id: Gumlet workspace ID. You can get it on Gumlet dashboard or retrieve it using list workspace API. + status: To filter assets on the basis of their current status. Can be specified as a single status value string or comma-separated status values. The status value can be one of `queued`, `processing`, `ready`, `errored`, and `deleted`. + tag: Input tag on the basis of which assets need to be filtered. To filter on multiple tags use comma-separated string. + title: Title on the basis of which assets need to be filtered. + folder: Folder name on the basis of which assets need to be filtered. + offset: Offset value for a paginated list of assets. + size: Page size for the paginated list. **Default: `10`** **Max Size: `100`** + playlist_id: filter assets from a playlist. + sort_by: assets will be sorted based on the provided field. + order_by: assets will be sorted in the specified order based on provided sortBy field or by default createAt field. + type: Search for folders, videos, or both. For videos, use `videos`. For folders, use `folders`. If you do not send this parameter, it will search for both. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoAssetListDeprecatedResponse: 200 + + Example: + ```python + video_asset = client.video_assets.list_deprecated( + workspace_id="workspaceId", + sort_by="created_at", + order_by="desc", + ) + ``` + + Deprecated: this method is deprecated. + """ + if workspace_id is None or (isinstance(workspace_id, str) and not workspace_id): + raise ValueError(f"Expected a non-empty value for `workspace_id` but received {workspace_id!r}") + return self._get( + path_template("/video/assets/list/{workspace_id}", **{"workspace_id": workspace_id}), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "status": status, + "tag": tag, + "title": title, + "folder": folder, + "offset": offset, + "size": size, + "playlist_id": playlist_id, + "sort_by": sort_by, + "order_by": order_by, + "type": type, + }, + video_asset_list_deprecated_params.VideoAssetListDeprecatedParams, + ), + ), + cast_to=VideoAssetListDeprecatedResponse, + ) + + def delete_many( + self, + *, + asset_list: SequenceNotStr[str], + source_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, + ) -> VideoAssetDeleteManyResponse: + """ + Delete multiple VOD assets at once. + + Args: + asset_list: LIst of asset ids to delete + source_id: Workspace ID from which assets needs to be deleted. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoAssetDeleteManyResponse: Successful response + + Example: + ```python + video_asset = client.video_assets.delete_many( + asset_list=["64249a8858fd3a208b987702", "64784bae843b155b829bbf84"], + source_id="60bd2ba353ff754d28179ee6", + ) + ``` + """ + return self._delete( + "/video/assets/bulk/delete", + body=maybe_transform( + { + "asset_list": asset_list, + "source_id": source_id, + }, + video_asset_delete_many_params.VideoAssetDeleteManyParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoAssetDeleteManyResponse, + ) + + def tag_many( + self, + *, + asset_list: SequenceNotStr[str], + source_id: str, + add_tags: SequenceNotStr[str], + remove_tags: SequenceNotStr[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, + ) -> VideoAssetTagManyResponse: + """ + Add / remove tags from multiple assets at once. + + Args: + asset_list: List of asset ids to update the tags for. + source_id: Workspace ID in which the videos needs the operation + add_tags: List of tags to add to given assets. + remove_tags: List of tags to remove from given assets. Pass empty array if nothing is to be removed. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoAssetTagManyResponse: Successful response + + Example: + ```python + video_asset = client.video_assets.tag_many( + asset_list=["6221db301c8b821b0519fba0", "61e8f2726ec832ab2ac4fa6e"], + source_id="60bd2ba353ff754d28179ee6", + add_tags=["tag-1"], + remove_tags=["playlist-1"], + ) + ``` + """ + return self._post( + "/video/assets/bulk/tag", + body=maybe_transform( + { + "asset_list": asset_list, + "source_id": source_id, + "add_tags": add_tags, + "remove_tags": remove_tags, + }, + video_asset_tag_many_params.VideoAssetTagManyParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoAssetTagManyResponse, + ) + + def analytics( + self, + asset_id: str, + *, + group_by: Literal["daily", "monthly", "weekly"], + date_range: video_asset_analytics_params.DateRange, + metrics: List[ + Literal[ + "impressions", + "views", + "playing_time", + "top_countries", + "top_pages", + "top_cities", + "top_device_types", + "top_browsers", + "heatmap", + "widget_data", + ] + ], + page_number: int | Omit = omit, + page_size: int | 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, + ) -> VideoAssetAnalyticsResponse: + """ + Get video analytics for a single asset. + + Args: + asset_id: Gumlet asset ID + group_by: Group the data by this period. + date_range: Body parameter. + metrics: List of metrics to return in response. + page_number: Page number to fetch. Starting at 1 + page_size: Number of items to return per page + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoAssetAnalyticsResponse: Successful response + + Example: + ```python + video_asset = client.video_assets.analytics( + asset_id="assetId", + group_by="daily", + date_range={"start_at": "", "end_at": ""}, + metrics=["impressions"], + ) + ``` + """ + if asset_id is None or (isinstance(asset_id, str) and not asset_id): + raise ValueError(f"Expected a non-empty value for `asset_id` but received {asset_id!r}") + return self._post( + path_template("/video/assets/{asset_id}/analytics", **{"asset_id": asset_id}), + body=maybe_transform( + { + "group_by": group_by, + "date_range": date_range, + "metrics": metrics, + "page_number": page_number, + "page_size": page_size, + }, + video_asset_analytics_params.VideoAssetAnalyticsParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoAssetAnalyticsResponse, + ) + + +class AsyncVideoAssetsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncVideoAssetsResourceWithRawResponse: + return AsyncVideoAssetsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncVideoAssetsResourceWithStreamingResponse: + return AsyncVideoAssetsResourceWithStreamingResponse(self) + + async def create( + self, + *, + input: str, + collection_id: str, + profile_id: str | Omit = omit, + format: Literal["ABR", "MP4"], + tag: SequenceNotStr[str] | Omit = omit, + title: str | Omit = omit, + description: str | Omit = omit, + metadata: Dict[str, object] | Omit = omit, + width: str | Omit = omit, + height: str | Omit = omit, + resolution: str | Omit = omit, + crop: video_asset_create_params.Crop | Omit = omit, + pad: video_asset_create_params.Pad | Omit = omit, + trim: video_asset_create_params.Trim | Omit = omit, + image_overlay: video_asset_create_params.ImageOverlay | Omit = omit, + text_overlay: video_asset_create_params.TextOverlay | Omit = omit, + animated_gif: video_asset_create_params.AnimatedGif | Omit = omit, + additional_tracks: Iterable[video_asset_create_params.AdditionalTrack] | Omit = omit, + generate_subtitles: video_asset_create_params.GenerateSubtitles | Omit = omit, + mp4_access: bool | Omit = omit, + per_title_encoding: bool | Omit = omit, + process_low_resolution_input: bool | Omit = omit, + audio_only: bool | Omit = omit, + enable_drm: bool | Omit = omit, + call_to_actions: Iterable[video_asset_create_params.CallToAction] | Omit = omit, + playlist_id: str | Omit = omit, + folder: 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, + ) -> VideoAssetCreateResponse: + """ + An asset refers to a media content/video that is processed, stored, and delivered through Gumlet. This endpoint creates an asset allowing users to ingest media content into the Gumlet system for processing and delivery. + + Args: + input: URL or web address of a file that Gumlet should download to create a new asset. + collection_id: Gumlet video workspace id. + profile_id: Provide `profile_id` of the previously created video profile. This parameter will override all the parameters (except `input` and `collection_id`) from the video profile. + format: Transcode and deliver the asset in the requested format. The options can be one of `ABR` (HLS + DASH) and`MP4`. + tag: Specify a text string or identifier which can identify an asset or bunch of assets later. + title: Specify a text string or identifier which can be used for filtering or searching the asset. + description: Attach some textual data with the asset. This field is neither searchable nor filterable. + metadata: Add your metadata you want to associate with this asset.
Example:
{ "internal_video_id" : "123Abc" } + width: Resize video with the given width. Can be an absolute value in pixels or a percentage value with the `%` suffix. Specified values greater than the original asset width will be ignored. Applicable only when specified format is `MP4`. + height: Resize video with the given height. Can be an absolute value in pixels or a percentage value with the `%` suffix. Specified values greater than the original asset height will be ignored. Applicable only when specified format is `MP4`. + resolution: Required resolutions of the transformed asset in case of HLS or MPEG-DASH delivery format. Can be a comma separated string out of the following values: `240p`, `360p`, `480p`, `540p`, `720p`, and `1080p`. Re-sized rendition will retain the input aspect ratio. + crop: This transformation can be used to crop the video by defining a rectangular area within the dimensions of the output video. + pad: This transformation can be used to add padding to the video. + trim: Trim transformation can be used to trim videos based on time duration. + image_overlay: Image overlay can be used to brand a video or add a visual label in the form of an image. + text_overlay: Text overlay can be used to brand a video or add a label in the form of text. + animated_gif: Create an animated GIF from the video. + additional_tracks: Add additional Audio / Subtitle tracks to Gumlet for transcoding and delivery along with video asset track. + generate_subtitles: Gumlet allows to generate subtitles from the audio stream (use ISO 639-1 Language Codes) + mp4_access: Creates `MP4` version for download purpose in case of `MPEG-DASH` or `HLS` delivery format. **Default: `false`** + per_title_encoding: Gumlet analyzes each input video on a wide range of visual aspects. Based on the analysis, it chooses a unique set of transcoding options for processing the video. This ensures that the output video is of optimal size and best quality. **Default: `true`** + process_low_resolution_input: Currently, the minimum supported frame size is `57600` (`240x240`) pixels for `HLS/DASH` and `21025` (`145x145`) pixels for `MP4` format. However, enabling this flag will allow Gumlet to simply put your video asset into the specified delivery format without transcoding and optimization. Enabling this flag will cause any kind of specified video transformation to be ignored if you input video asset frame size is lower than the minimum supported frame size for the specified format. **Default: `false`** + audio_only: This flag allows Gumlet to transcode and deliver audio-only in the specified format. In this case, video transformation and thumbnails/animated GIFs would not be created. **Default: `false`** + enable_drm: Enable DRM encryption for transcoded videos. Gumlet supports Widevine and Fairplay DRMs. + call_to_actions: CTA, is an explicit prompt within the video content encouraging viewers to take a particular action. + playlist_id: Add this asset to a playlist. + folder: Add this asset to an existing folder by `folder_id`. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoAssetCreateResponse: 200 + + Example: + ```python + video_asset = await client.video_assets.create( + input="http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8", + collection_id="646df1c9173a4a2fcac180b4", + profile_id="646df1c9173a4a2fcac180b7", + format="ABR", + tag=["ball"], + description="some description", + metadata={"headermeta": "metavalue"}, + call_to_actions=[ + { + "start_time": 1, + "end_time": 90, + "text": "some test", + "url": "https://some-url.com", + "position_from_top": 11, + "position_from_right": 23, + "border_radius": "11", + "font_color": "#000001", + "background_color": "#ffffff", + } + ], + playlist_id="6597acd5ed6f26a9c5ca9633", + folder="697375fbfa2d1037283140e4", + ) + ``` + """ + return await self._post( + "/video/assets", + body=await async_maybe_transform( + { + "input": input, + "collection_id": collection_id, + "profile_id": profile_id, + "format": format, + "tag": tag, + "title": title, + "description": description, + "metadata": metadata, + "width": width, + "height": height, + "resolution": resolution, + "crop": crop, + "pad": pad, + "trim": trim, + "image_overlay": image_overlay, + "text_overlay": text_overlay, + "animated_gif": animated_gif, + "additional_tracks": additional_tracks, + "generate_subtitles": generate_subtitles, + "mp4_access": mp4_access, + "per_title_encoding": per_title_encoding, + "process_low_resolution_input": process_low_resolution_input, + "audio_only": audio_only, + "enable_drm": enable_drm, + "call_to_actions": call_to_actions, + "playlist_id": playlist_id, + "folder": folder, + }, + video_asset_create_params.VideoAssetCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoAssetCreateResponse, + ) + + async def upload( + self, + *, + collection_id: str, + profile_id: str | Omit = omit, + format: Literal["ABR", "MP4"] | Omit = omit, + tag: SequenceNotStr[str] | Omit = omit, + title: str | Omit = omit, + description: str | Omit = omit, + metadata: Dict[str, object] | Omit = omit, + width: str | Omit = omit, + height: str | Omit = omit, + resolution: str | Omit = omit, + crop: video_asset_upload_params.Crop | Omit = omit, + pad: video_asset_upload_params.Pad | Omit = omit, + trim: video_asset_upload_params.Trim | Omit = omit, + image_overlay: video_asset_upload_params.ImageOverlay | Omit = omit, + text_overlay: video_asset_upload_params.TextOverlay | Omit = omit, + animated_gif: video_asset_upload_params.AnimatedGif | Omit = omit, + additional_tracks: Iterable[video_asset_upload_params.AdditionalTrack] | Omit = omit, + generate_subtitles: video_asset_upload_params.GenerateSubtitles | Omit = omit, + mp4_access: bool | Omit = omit, + per_title_encoding: bool | Omit = omit, + process_low_resolution_input: bool | Omit = omit, + audio_only: bool | Omit = omit, + enable_drm: bool | Omit = omit, + call_to_actions: Iterable[video_asset_upload_params.CallToAction] | Omit = omit, + playlist_id: str | Omit = omit, + folder: 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, + ) -> VideoAssetUploadResponse: + """ + This endpoint creates a video asset allowing to upload of the video from the local file system and ingest media content into the Gumlet system for processing and delivery.Body Parameters are the same as the Create Asset Body Parameters except for the `input` parameter which this endpoint does not take.A successful response will be returned with `upload_url` field. You can make `PUT` request to that URL to upload video. To upload video using `upload_url` refer to [this](https://docs.gumlet.com/docs/direct-upload#2-use-the-url-to-upload-a-file). + + Args: + collection_id: Gumlet video workspace id. + profile_id: Provide `profile_id` of the previously created video profile. This parameter will override all the parameters (except `input` and `collection_id`) from the video profile. + format: Transcode and deliver the asset in the requested format. The options can be one of `ABR` (HLS + DASH) and`MP4`. + tag: Specify a text string or identifier which can identify an asset or bunch of assets later. + title: Specify a text string or identifier which can be used for filtering or searching the asset. + description: Attach some textual data with the asset. This field is neither searchable nor filterable. + metadata: Add your metadata you want to associate with this asset.
Example:
{ "internal_video_id" : "123Abc" } + width: Resize video with the given width. Can be an absolute value in pixels or a percentage value with the `%` suffix. Specified values greater than the original asset width will be ignored. Applicable only when specified format is `MP4`. + height: Resize video with the given height. Can be an absolute value in pixels or a percentage value with the `%` suffix. Specified values greater than the original asset height will be ignored. Applicable only when specified format is `MP4`. + resolution: Required resolutions of the transformed asset in case of HLS or MPEG-DASH delivery format. Can be a comma separated string out of the following values: `240p`, `360p`, `480p`, `540p`, `720p`, and `1080p`. Re-sized rendition will retain the input aspect ratio. + crop: This transformation can be used to crop the video by defining a rectangular area within the dimensions of the output video. + pad: This transformation can be used to add padding to the video. + trim: Trim transformation can be used to trim videos based on time duration. + image_overlay: Image overlay can be used to brand a video or add a visual label in the form of an image. + text_overlay: Text overlay can be used to brand a video or add a label in the form of text. + animated_gif: Create an animated GIF from the video. + additional_tracks: Add additional Audio / Subtitle tracks to Gumlet for transcoding and delivery along with video asset track. + generate_subtitles: Gumlet allows to generate subtitles from the audio stream (use ISO 639-1 Language Codes) + mp4_access: Creates `MP4` version for download purpose in case of `MPEG-DASH` or `HLS` delivery format. **Default: `false`** + per_title_encoding: Gumlet analyzes each input video on a wide range of visual aspects. Based on the analysis, it chooses a unique set of transcoding options for processing the video. This ensures that the output video is of optimal size and best quality. **Default: `true`** + process_low_resolution_input: Currently, the minimum supported frame size is `57600` (`240x240`) pixels for `HLS/DASH` and `21025` (`145x145`) pixels for `MP4` format. However, enabling this flag will allow Gumlet to simply put your video asset into the specified delivery format without transcoding and optimization. Enabling this flag will cause any kind of specified video transformation to be ignored if you input video asset frame size is lower than the minimum supported frame size for the specified format. **Default: `false`** + audio_only: This flag allows Gumlet to transcode and deliver audio-only in the specified format. In this case, video transformation and thumbnails/animated GIFs would not be created. **Default: `false`** + enable_drm: Enable DRM encryption for transcoded videos. Gumlet supports Widevine and Fairplay DRMs. + call_to_actions: CTA, is an explicit prompt within the video content encouraging viewers to take a particular action. + playlist_id: Add this asset to a playlist. + folder: Add this asset to an existing folder by `folder_id`. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoAssetUploadResponse: 200 + + Example: + ```python + video_asset = await client.video_assets.upload( + collection_id="646df1c9173a4a2fcac180b4", + profile_id="646df1c9173a4a2fcac180b7", + format="ABR", + tag=["ball"], + description="some description", + metadata={"headermeta": "metavalue"}, + call_to_actions=[ + { + "start_time": 1, + "end_time": 90, + "text": "some test", + "url": "https://some-url.com", + "position_from_top": 11, + "position_from_right": 23, + "border_radius": "11", + "font_color": "#000001", + "background_color": "#ffffff", + } + ], + playlist_id="6597acd5ed6f26a9c5ca9633", + folder="697375fbfa2d1037283140e4", + ) + ``` + """ + return await self._post( + "/video/assets/upload", + body=await async_maybe_transform( + { + "collection_id": collection_id, + "profile_id": profile_id, + "format": format, + "tag": tag, + "title": title, + "description": description, + "metadata": metadata, + "width": width, + "height": height, + "resolution": resolution, + "crop": crop, + "pad": pad, + "trim": trim, + "image_overlay": image_overlay, + "text_overlay": text_overlay, + "animated_gif": animated_gif, + "additional_tracks": additional_tracks, + "generate_subtitles": generate_subtitles, + "mp4_access": mp4_access, + "per_title_encoding": per_title_encoding, + "process_low_resolution_input": process_low_resolution_input, + "audio_only": audio_only, + "enable_drm": enable_drm, + "call_to_actions": call_to_actions, + "playlist_id": playlist_id, + "folder": folder, + }, + video_asset_upload_params.VideoAssetUploadParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoAssetUploadResponse, + ) + + async def retrieve_details( + self, + asset_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, + ) -> VideoAssetRetrieveDetailsResponse: + """ + This endpoint retrieves the details of an asset that has previously been created. + + Args: + asset_id: An asset id for the previously created asset. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoAssetRetrieveDetailsResponse: 200 + + Example: + ```python + video_asset = await client.video_assets.retrieve_details( + asset_id="assetId", + ) + ``` + """ + if asset_id is None or (isinstance(asset_id, str) and not asset_id): + raise ValueError(f"Expected a non-empty value for `asset_id` but received {asset_id!r}") + return await self._get( + path_template("/video/assets/{asset_id}", **{"asset_id": asset_id}), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoAssetRetrieveDetailsResponse, + ) + + async def delete( + self, + asset_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, + ) -> None: + """ + This endpoint removes an asset given its unique asset id. The asset will be removed from storage as well, associated URLs will be inaccessible. + + Args: + asset_id: Asset id of the video asset which needs to be deleted. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + 204 + + Example: + ```python + await client.video_assets.delete( + asset_id="assetId", + ) + ``` + """ + if asset_id is None or (isinstance(asset_id, str) and not asset_id): + raise ValueError(f"Expected a non-empty value for `asset_id` but received {asset_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return await self._delete( + path_template("/video/assets/{asset_id}", **{"asset_id": asset_id}), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + async def update( + self, + *, + asset_id: str, + title: str | Omit = omit, + description: str | Omit = omit, + tag: str | Omit = omit, + call_to_actions: Iterable[video_asset_update_params.CallToAction] | Omit = omit, + metadata: str | Omit = omit, + remove_subtitles: SequenceNotStr[str] | Omit = omit, + input: str | Omit = omit, + reprocess: bool | 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, + ) -> VideoAssetUpdateResponse: + """ + This endpoint allows users to update video asset that has previously been created. + + Args: + asset_id: Asset Id + title: Specify a text string or identifier which can be used for filtering or searching the asset. + description: Attach some textual data with the asset. This field is neither searchable nor filterable. + tag: Specify a text string or identifier which can identify an asset or bunch of assets later. You can pass multiple comma separated values. + call_to_actions: CTA, is an explicit prompt within the video content encouraging viewers to take a particular action. + metadata: Set of key-value pairs that you can attach to this Asset. This can be useful for storing additional information.
Example:
{ "internal_video_id" : "123Abc" } + remove_subtitles: Comma separated string of language codes. + input: For replacing videos, pass this along with `asset_id` + reprocess: To reprocess same video, pass this as true. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoAssetUpdateResponse: 200 + + Example: + ```python + video_asset = await client.video_assets.update( + asset_id="", + title="Updated Title", + ) + ``` + """ + return await self._post( + "/video/assets/update", + body=await async_maybe_transform( + { + "asset_id": asset_id, + "title": title, + "description": description, + "tag": tag, + "call_to_actions": call_to_actions, + "metadata": metadata, + "remove_subtitles": remove_subtitles, + "input": input, + "reprocess": reprocess, + }, + video_asset_update_params.VideoAssetUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoAssetUpdateResponse, + ) + + async def thumbnail_select( + self, + asset_id: str, + *, + frame_at_second: int, + # 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, + ) -> VideoAssetThumbnailSelectResponse: + """ + Select frame from video to use as thumbnail. + + Args: + asset_id: Asset id of the video asset which needs to be deleted. + frame_at_second: Frame secound + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoAssetThumbnailSelectResponse: 200 + + Example: + ```python + video_asset = await client.video_assets.thumbnail_select( + asset_id="assetId", + frame_at_second=2, + ) + ``` + """ + if asset_id is None or (isinstance(asset_id, str) and not asset_id): + raise ValueError(f"Expected a non-empty value for `asset_id` but received {asset_id!r}") + extra_headers = {"Accept": "text/plain", **(extra_headers or {})} + return await self._post( + path_template("/video/assets/{asset_id}/thumbnail-select", **{"asset_id": asset_id}), + body=await async_maybe_transform( + {"frame_at_second": frame_at_second}, + video_asset_thumbnail_select_params.VideoAssetThumbnailSelectParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoAssetThumbnailSelectResponse, + ) + + async def thumbnail_upload( + self, + asset_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, + ) -> VideoAssetThumbnailUploadResponse: + """ + Use any image file to use as thumbnail. Once you use the API, you will get `upload_url` in the response, and that can be used to upload the image file. + + Here is the sample curl request. + + ```bash + curl --location --request PUT '' \ + --data '' + ``` + + Args: + asset_id: An asset id for the previously created asset. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoAssetThumbnailUploadResponse + + Example: + ```python + video_asset = await client.video_assets.thumbnail_upload( + asset_id="assetId", + ) + ``` + """ + if asset_id is None or (isinstance(asset_id, str) and not asset_id): + raise ValueError(f"Expected a non-empty value for `asset_id` but received {asset_id!r}") + return await self._post( + path_template("/video/assets/{asset_ID}/thumbnail", **{"asset_ID": asset_id}), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoAssetThumbnailUploadResponse, + ) + + async def create_update_chapter( + self, + asset_id: str, + *, + chapters: Iterable[video_asset_create_update_chapter_params.Chapter], + # 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, + ) -> VideoAssetCreateUpdateChapterResponse: + """ + This endpoint will create/update video asset chapters. + + Args: + asset_id: Gumlet asset ID + chapters: Body parameter. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoAssetCreateUpdateChapterResponse: 200 + + Example: + ```python + video_asset = await client.video_assets.create_update_chapter( + asset_id="assetId", + chapters=[{"label": "Chapter 1", "startTime": 0}, {"label": "Chapter 2", "startTime": 10}], + ) + ``` + """ + if asset_id is None or (isinstance(asset_id, str) and not asset_id): + raise ValueError(f"Expected a non-empty value for `asset_id` but received {asset_id!r}") + return await self._post( + path_template("/video/assets/{asset_id}/chapters", **{"asset_id": asset_id}), + body=await async_maybe_transform( + {"chapters": chapters}, + video_asset_create_update_chapter_params.VideoAssetCreateUpdateChapterParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoAssetCreateUpdateChapterResponse, + ) + + async def list( + self, + workspace_id: str, + *, + type: Literal["folders", "videos", "all"] | Omit = omit, + parent_id: str | Omit = omit, + title: str | Omit = omit, + status: str | Omit = omit, + tag: str | Omit = omit, + playlist_id: str | Omit = omit, + start_date: str | Omit = omit, + end_date: str | Omit = omit, + min_duration: float | Omit = omit, + max_duration: float | Omit = omit, + sort_by: Literal["title", "duration", "uploaded_at", "created_at"] | Omit = omit, + order_by: Literal["asc", "desc"] | Omit = omit, + search_index: Literal["search_index_for_asset_list", "cms-search", "cms-search-v2"] | Omit = omit, + offset: int | Omit = omit, + size: int | Omit = omit, + signed_token: Literal["true", "false"] | 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, + ) -> VideoAssetListResponse: + """ + List folders and assets for a workspace in a single response. Use `parent_id` to browse a specific folder, or filters like `title`, `status`, and `playlist_id` to search assets. + + Args: + workspace_id: Video workspace id. + type: Return `folders`, `videos`, or `all`. Default is `all`. + parent_id: Parent folder id. Send `null` to browse the root level. + title: Search folders or assets by title or description. + status: Comma-separated asset status values. + tag: Comma-separated asset tags. + playlist_id: Filter assets to a playlist. + start_date: Asset created_at lower bound. + end_date: Asset created_at upper bound. + min_duration: Minimum asset duration in seconds. + max_duration: Maximum asset duration in seconds. + sort_by: Sort assets by a supported field. + order_by: Asset sort order. + search_index: Search index used for asset title search. + offset: Offset for paginated results. + size: Page size. Maximum 100. + signed_token: Whether URLs should be pre-signed in the API response. Possible values: `true` and `false`. Default is `false`. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoAssetListResponse: 200 + + Example: + ```python + video_asset = await client.video_assets.list( + workspace_id="workspaceId", + type="all", + offset=0, + size=20, + signed_token="false", + ) + ``` + """ + if workspace_id is None or (isinstance(workspace_id, str) and not workspace_id): + raise ValueError(f"Expected a non-empty value for `workspace_id` but received {workspace_id!r}") + return await self._get( + path_template("/video/workspaces/{workspace_id}/list", **{"workspace_id": workspace_id}), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "type": type, + "parent_id": parent_id, + "title": title, + "status": status, + "tag": tag, + "playlist_id": playlist_id, + "start_date": start_date, + "end_date": end_date, + "min_duration": min_duration, + "max_duration": max_duration, + "sort_by": sort_by, + "order_by": order_by, + "search_index": search_index, + "offset": offset, + "size": size, + "signed_token": signed_token, + }, + video_asset_list_params.VideoAssetListParams, + ), + ), + cast_to=VideoAssetListResponse, + ) + + async def list_deprecated( + self, + workspace_id: str, + *, + status: Literal["queued", "processing", "ready", "errored", "deleted"] | Omit = omit, + tag: str | Omit = omit, + title: str | Omit = omit, + folder: str | Omit = omit, + offset: str | Omit = omit, + size: str | Omit = omit, + playlist_id: str | Omit = omit, + sort_by: Literal["title", "duration", "uploaded_at", "created_at"] | Omit = omit, + order_by: Literal["asc", "desc"] | Omit = omit, + type: 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, + ) -> VideoAssetListDeprecatedResponse: + """ + [Deprecated] This endpoint list assets in video workspace. You can also pass `status` and `tag` to filter assets. + + Args: + workspace_id: Gumlet workspace ID. You can get it on Gumlet dashboard or retrieve it using list workspace API. + status: To filter assets on the basis of their current status. Can be specified as a single status value string or comma-separated status values. The status value can be one of `queued`, `processing`, `ready`, `errored`, and `deleted`. + tag: Input tag on the basis of which assets need to be filtered. To filter on multiple tags use comma-separated string. + title: Title on the basis of which assets need to be filtered. + folder: Folder name on the basis of which assets need to be filtered. + offset: Offset value for a paginated list of assets. + size: Page size for the paginated list. **Default: `10`** **Max Size: `100`** + playlist_id: filter assets from a playlist. + sort_by: assets will be sorted based on the provided field. + order_by: assets will be sorted in the specified order based on provided sortBy field or by default createAt field. + type: Search for folders, videos, or both. For videos, use `videos`. For folders, use `folders`. If you do not send this parameter, it will search for both. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoAssetListDeprecatedResponse: 200 + + Example: + ```python + video_asset = await client.video_assets.list_deprecated( + workspace_id="workspaceId", + sort_by="created_at", + order_by="desc", + ) + ``` + + Deprecated: this method is deprecated. + """ + if workspace_id is None or (isinstance(workspace_id, str) and not workspace_id): + raise ValueError(f"Expected a non-empty value for `workspace_id` but received {workspace_id!r}") + return await self._get( + path_template("/video/assets/list/{workspace_id}", **{"workspace_id": workspace_id}), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "status": status, + "tag": tag, + "title": title, + "folder": folder, + "offset": offset, + "size": size, + "playlist_id": playlist_id, + "sort_by": sort_by, + "order_by": order_by, + "type": type, + }, + video_asset_list_deprecated_params.VideoAssetListDeprecatedParams, + ), + ), + cast_to=VideoAssetListDeprecatedResponse, + ) + + async def delete_many( + self, + *, + asset_list: SequenceNotStr[str], + source_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, + ) -> VideoAssetDeleteManyResponse: + """ + Delete multiple VOD assets at once. + + Args: + asset_list: LIst of asset ids to delete + source_id: Workspace ID from which assets needs to be deleted. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoAssetDeleteManyResponse: Successful response + + Example: + ```python + video_asset = await client.video_assets.delete_many( + asset_list=["64249a8858fd3a208b987702", "64784bae843b155b829bbf84"], + source_id="60bd2ba353ff754d28179ee6", + ) + ``` + """ + return await self._delete( + "/video/assets/bulk/delete", + body=await async_maybe_transform( + { + "asset_list": asset_list, + "source_id": source_id, + }, + video_asset_delete_many_params.VideoAssetDeleteManyParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoAssetDeleteManyResponse, + ) + + async def tag_many( + self, + *, + asset_list: SequenceNotStr[str], + source_id: str, + add_tags: SequenceNotStr[str], + remove_tags: SequenceNotStr[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, + ) -> VideoAssetTagManyResponse: + """ + Add / remove tags from multiple assets at once. + + Args: + asset_list: List of asset ids to update the tags for. + source_id: Workspace ID in which the videos needs the operation + add_tags: List of tags to add to given assets. + remove_tags: List of tags to remove from given assets. Pass empty array if nothing is to be removed. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoAssetTagManyResponse: Successful response + + Example: + ```python + video_asset = await client.video_assets.tag_many( + asset_list=["6221db301c8b821b0519fba0", "61e8f2726ec832ab2ac4fa6e"], + source_id="60bd2ba353ff754d28179ee6", + add_tags=["tag-1"], + remove_tags=["playlist-1"], + ) + ``` + """ + return await self._post( + "/video/assets/bulk/tag", + body=await async_maybe_transform( + { + "asset_list": asset_list, + "source_id": source_id, + "add_tags": add_tags, + "remove_tags": remove_tags, + }, + video_asset_tag_many_params.VideoAssetTagManyParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoAssetTagManyResponse, + ) + + async def analytics( + self, + asset_id: str, + *, + group_by: Literal["daily", "monthly", "weekly"], + date_range: video_asset_analytics_params.DateRange, + metrics: List[ + Literal[ + "impressions", + "views", + "playing_time", + "top_countries", + "top_pages", + "top_cities", + "top_device_types", + "top_browsers", + "heatmap", + "widget_data", + ] + ], + page_number: int | Omit = omit, + page_size: int | 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, + ) -> VideoAssetAnalyticsResponse: + """ + Get video analytics for a single asset. + + Args: + asset_id: Gumlet asset ID + group_by: Group the data by this period. + date_range: Body parameter. + metrics: List of metrics to return in response. + page_number: Page number to fetch. Starting at 1 + page_size: Number of items to return per page + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoAssetAnalyticsResponse: Successful response + + Example: + ```python + video_asset = await client.video_assets.analytics( + asset_id="assetId", + group_by="daily", + date_range={"start_at": "", "end_at": ""}, + metrics=["impressions"], + ) + ``` + """ + if asset_id is None or (isinstance(asset_id, str) and not asset_id): + raise ValueError(f"Expected a non-empty value for `asset_id` but received {asset_id!r}") + return await self._post( + path_template("/video/assets/{asset_id}/analytics", **{"asset_id": asset_id}), + body=await async_maybe_transform( + { + "group_by": group_by, + "date_range": date_range, + "metrics": metrics, + "page_number": page_number, + "page_size": page_size, + }, + video_asset_analytics_params.VideoAssetAnalyticsParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoAssetAnalyticsResponse, + ) + + +class VideoAssetsResourceWithRawResponse: + def __init__(self, video_assets: VideoAssetsResource) -> None: + self._video_assets = video_assets + + self.create = to_raw_response_wrapper( + video_assets.create, + ) + self.upload = to_raw_response_wrapper( + video_assets.upload, + ) + self.retrieve_details = to_raw_response_wrapper( + video_assets.retrieve_details, + ) + self.delete = to_raw_response_wrapper( + video_assets.delete, + ) + self.update = to_raw_response_wrapper( + video_assets.update, + ) + self.thumbnail_select = to_raw_response_wrapper( + video_assets.thumbnail_select, + ) + self.thumbnail_upload = to_raw_response_wrapper( + video_assets.thumbnail_upload, + ) + self.create_update_chapter = to_raw_response_wrapper( + video_assets.create_update_chapter, + ) + self.list = to_raw_response_wrapper( + video_assets.list, + ) + self.list_deprecated = to_raw_response_wrapper( + video_assets.list_deprecated, + ) + self.delete_many = to_raw_response_wrapper( + video_assets.delete_many, + ) + self.tag_many = to_raw_response_wrapper( + video_assets.tag_many, + ) + self.analytics = to_raw_response_wrapper( + video_assets.analytics, + ) + + +class AsyncVideoAssetsResourceWithRawResponse: + def __init__(self, video_assets: AsyncVideoAssetsResource) -> None: + self._video_assets = video_assets + + self.create = async_to_raw_response_wrapper( + video_assets.create, + ) + self.upload = async_to_raw_response_wrapper( + video_assets.upload, + ) + self.retrieve_details = async_to_raw_response_wrapper( + video_assets.retrieve_details, + ) + self.delete = async_to_raw_response_wrapper( + video_assets.delete, + ) + self.update = async_to_raw_response_wrapper( + video_assets.update, + ) + self.thumbnail_select = async_to_raw_response_wrapper( + video_assets.thumbnail_select, + ) + self.thumbnail_upload = async_to_raw_response_wrapper( + video_assets.thumbnail_upload, + ) + self.create_update_chapter = async_to_raw_response_wrapper( + video_assets.create_update_chapter, + ) + self.list = async_to_raw_response_wrapper( + video_assets.list, + ) + self.list_deprecated = async_to_raw_response_wrapper( + video_assets.list_deprecated, + ) + self.delete_many = async_to_raw_response_wrapper( + video_assets.delete_many, + ) + self.tag_many = async_to_raw_response_wrapper( + video_assets.tag_many, + ) + self.analytics = async_to_raw_response_wrapper( + video_assets.analytics, + ) + + +class VideoAssetsResourceWithStreamingResponse: + def __init__(self, video_assets: VideoAssetsResource) -> None: + self._video_assets = video_assets + + self.create = to_streamed_response_wrapper( + video_assets.create, + ) + self.upload = to_streamed_response_wrapper( + video_assets.upload, + ) + self.retrieve_details = to_streamed_response_wrapper( + video_assets.retrieve_details, + ) + self.delete = to_streamed_response_wrapper( + video_assets.delete, + ) + self.update = to_streamed_response_wrapper( + video_assets.update, + ) + self.thumbnail_select = to_streamed_response_wrapper( + video_assets.thumbnail_select, + ) + self.thumbnail_upload = to_streamed_response_wrapper( + video_assets.thumbnail_upload, + ) + self.create_update_chapter = to_streamed_response_wrapper( + video_assets.create_update_chapter, + ) + self.list = to_streamed_response_wrapper( + video_assets.list, + ) + self.list_deprecated = to_streamed_response_wrapper( + video_assets.list_deprecated, + ) + self.delete_many = to_streamed_response_wrapper( + video_assets.delete_many, + ) + self.tag_many = to_streamed_response_wrapper( + video_assets.tag_many, + ) + self.analytics = to_streamed_response_wrapper( + video_assets.analytics, + ) + + +class AsyncVideoAssetsResourceWithStreamingResponse: + def __init__(self, video_assets: AsyncVideoAssetsResource) -> None: + self._video_assets = video_assets + + self.create = async_to_streamed_response_wrapper( + video_assets.create, + ) + self.upload = async_to_streamed_response_wrapper( + video_assets.upload, + ) + self.retrieve_details = async_to_streamed_response_wrapper( + video_assets.retrieve_details, + ) + self.delete = async_to_streamed_response_wrapper( + video_assets.delete, + ) + self.update = async_to_streamed_response_wrapper( + video_assets.update, + ) + self.thumbnail_select = async_to_streamed_response_wrapper( + video_assets.thumbnail_select, + ) + self.thumbnail_upload = async_to_streamed_response_wrapper( + video_assets.thumbnail_upload, + ) + self.create_update_chapter = async_to_streamed_response_wrapper( + video_assets.create_update_chapter, + ) + self.list = async_to_streamed_response_wrapper( + video_assets.list, + ) + self.list_deprecated = async_to_streamed_response_wrapper( + video_assets.list_deprecated, + ) + self.delete_many = async_to_streamed_response_wrapper( + video_assets.delete_many, + ) + self.tag_many = async_to_streamed_response_wrapper( + video_assets.tag_many, + ) + self.analytics = async_to_streamed_response_wrapper( + video_assets.analytics, + ) diff --git a/src/gumlet/resources/video_playlists.py b/src/gumlet/resources/video_playlists.py new file mode 100644 index 0000000..c23f272 --- /dev/null +++ b/src/gumlet/resources/video_playlists.py @@ -0,0 +1,1143 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +import httpx + +from typing import Iterable +from typing_extensions import Literal, overload +from .._types import SequenceNotStr + +from .._types import Body, Omit, Query, Headers, NotGiven, NoneType, omit, not_given +from .._utils import path_template, maybe_transform, async_maybe_transform, required_args +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.video_playlist_create_response import VideoPlaylistCreateResponse +from ..types import ( + video_playlist_create_params, + video_playlist_list_all_params, + video_playlist_create_asset_params, + video_playlist_delete_asset_params, + video_playlist_update_params, + video_playlist_list_assets_params, + video_playlist_reorder_asset_params, +) +from ..types.video_playlist_list_all_response import VideoPlaylistListAllResponse +from ..types.video_playlist_create_asset_response import VideoPlaylistCreateAssetResponse +from ..types.video_playlist_delete_asset_response import VideoPlaylistDeleteAssetResponse +from ..types.video_playlist_update_response import VideoPlaylistUpdateResponse +from ..types.video_playlist_list_assets_response import VideoPlaylistListAssetsResponse +from ..types.video_playlist_reorder_asset_response import VideoPlaylistReorderAssetResponse + +__all__ = ["VideoPlaylistsResource", "AsyncVideoPlaylistsResource"] + + +class VideoPlaylistsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> VideoPlaylistsResourceWithRawResponse: + return VideoPlaylistsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> VideoPlaylistsResourceWithStreamingResponse: + return VideoPlaylistsResourceWithStreamingResponse(self) + + def create( + self, + *, + collection_id: str, + title: str, + description: 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, + ) -> VideoPlaylistCreateResponse: + """ + Create new playlist inside video wprkspace + + Args: + collection_id: Body parameter. + title: Body parameter. + description: Body parameter. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoPlaylistCreateResponse: 200 + + Example: + ```python + video_playlist = client.video_playlists.create( + collection_id="{{video-source-id}}", + title="Playlist-Title", + description="This is description for playlist.", + ) + ``` + """ + return self._post( + "/video/playlist", + body=maybe_transform( + { + "collection_id": collection_id, + "title": title, + "description": description, + }, + video_playlist_create_params.VideoPlaylistCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoPlaylistCreateResponse, + ) + + def list_all( + self, + *, + collection_id: 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, + ) -> VideoPlaylistListAllResponse: + """ + Get all playlists for given workspace + + Args: + collection_id: Video Collection ID + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoPlaylistListAllResponse: 200 + + Example: + ```python + video_playlist = client.video_playlists.list_all() + ``` + """ + return self._get( + "/video/playlist", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + {"collection_id": collection_id}, video_playlist_list_all_params.VideoPlaylistListAllParams + ), + ), + cast_to=VideoPlaylistListAllResponse, + ) + + def create_asset( + self, + playlist_id: str, + *, + asset_list: Iterable[video_playlist_create_asset_params.AssetList], + # 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, + ) -> VideoPlaylistCreateAssetResponse: + """ + This operation adds a single asset or a list of assets to a playlist. + + Args: + playlist_id: Playlist ID in which the asset needs to be added. + asset_list: Body parameter. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoPlaylistCreateAssetResponse: 200 + + Example: + ```python + video_playlist = client.video_playlists.create_asset( + playlist_id="playlistId", + asset_list=[ + {"asset_id": "6508790283e4d60611846790"}, + {"position": 1, "asset_id": "650878f883e4d6061184677d"}, + {"asset_id": "650878de83e4d6061184676a"}, + {"position": 2, "asset_id": "650878d883e4d60611846757"}, + {"position": 3, "asset_id": "65578dd87eebc22dcdd549a2"}, + ], + ) + ``` + """ + if playlist_id is None or (isinstance(playlist_id, str) and not playlist_id): + raise ValueError(f"Expected a non-empty value for `playlist_id` but received {playlist_id!r}") + return self._post( + path_template("/video/playlist/{playlist_id}/asset", **{"playlist_id": playlist_id}), + body=maybe_transform( + {"asset_list": asset_list}, + video_playlist_create_asset_params.VideoPlaylistCreateAssetParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoPlaylistCreateAssetResponse, + ) + + def delete_asset( + self, + playlist_id: str, + *, + delete_list: SequenceNotStr[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, + ) -> VideoPlaylistDeleteAssetResponse: + """ + Removed an asset or list of assets from a given playlist. + + Args: + playlist_id: Playlist ID that is to be deleted. + delete_list: Array of video asset ids. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoPlaylistDeleteAssetResponse: 200 + + Example: + ```python + video_playlist = client.video_playlists.delete_asset( + playlist_id="playlistId", + delete_list=["6508790783e4d606118467a3"], + ) + ``` + """ + if playlist_id is None or (isinstance(playlist_id, str) and not playlist_id): + raise ValueError(f"Expected a non-empty value for `playlist_id` but received {playlist_id!r}") + return self._delete( + path_template("/video/playlist/{playlist_id}/asset", **{"playlist_id": playlist_id}), + body=maybe_transform( + {"delete_list": delete_list}, + video_playlist_delete_asset_params.VideoPlaylistDeleteAssetParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoPlaylistDeleteAssetResponse, + ) + + def update( + self, + playlist_id: str, + *, + title: str | Omit = omit, + description: str | Omit = omit, + position: int | Omit = omit, + player_config: video_playlist_update_params.PlayerConfig | Omit = omit, + channel_visibility: bool | 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, + ) -> VideoPlaylistUpdateResponse: + """ + This endpoint allows you to update playlist name, channel visibility, or playlist order on a channel page. + + Args: + playlist_id: ID for the playlist to update. + title: Body parameter. + description: Body parameter. + position: Playlists have order in which they will be shown on the channel page. + player_config: Configure player settings for this playlist, it overrides the setting set on collection. + channel_visibility: If true then playlist will be visible on channel page. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoPlaylistUpdateResponse: 200 + + Example: + ```python + video_playlist = client.video_playlists.update( + playlist_id="playlistId", + title="Playlist-Title-Updated", + description="This is updated description", + position=6, + player_config={ + "preload": True, + "autoplay": False, + "disable_seek": True, + "disable_player_controls": False, + "powered_by_gumlet_overlay": False, + "allow_drm_protected_videos": False, + "loop": False, + "player_color": "#6658ea", + "include_seo": True, + "subtitle_enabled": True, + "pixel_tags": {}, + "logo_width": 51, + "logo_height": 100, + "dynamic_watermark": False, + "watermark_font_size": 1, + "watermark_font_color": "#ff0000", + "watermark_bg_color": "transparent", + "watermark_interval": 1000, + }, + channel_visibility=False, + ) + ``` + """ + if playlist_id is None or (isinstance(playlist_id, str) and not playlist_id): + raise ValueError(f"Expected a non-empty value for `playlist_id` but received {playlist_id!r}") + return self._post( + path_template("/video/playlist/{playlist_id}", **{"playlist_id": playlist_id}), + body=maybe_transform( + { + "title": title, + "description": description, + "position": position, + "player_config": player_config, + "channel_visibility": channel_visibility, + }, + video_playlist_update_params.VideoPlaylistUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoPlaylistUpdateResponse, + ) + + def delete( + self, + playlist_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, + ) -> None: + """ + Deletes this playlist. + + Args: + playlist_id: Playlist ID that is to be deleted. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Example: + ```python + client.video_playlists.delete( + playlist_id="playlistId", + ) + ``` + """ + if playlist_id is None or (isinstance(playlist_id, str) and not playlist_id): + raise ValueError(f"Expected a non-empty value for `playlist_id` but received {playlist_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return self._delete( + path_template("/video/playlist/{playlist_id}", **{"playlist_id": playlist_id}), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + def list_assets( + self, + playlist_id: str, + *, + sort_by: str | Omit = omit, + sort_order: int | Omit = omit, + page_number: int | Omit = omit, + page_size: 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, + ) -> VideoPlaylistListAssetsResponse: + """ + Get a list of all assets inside playlist. You can choose in which order are assets returned. + + Args: + playlist_id: ID of playlist in which you need to list assets. + sort_by: Optional, if sort_by is set to asset_title it will sorted by title name. Otherwise order in which user added the assets in playlist. + sort_order: -1 or 1 + page_number: Optional, Minimun 1 + page_size: Optional, Minimun 10 + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoPlaylistListAssetsResponse: 200 + + Example: + ```python + video_playlist = client.video_playlists.list_assets( + playlist_id="playlistId", + sort_order=1, + page_number=1, + page_size="10", + ) + ``` + """ + if playlist_id is None or (isinstance(playlist_id, str) and not playlist_id): + raise ValueError(f"Expected a non-empty value for `playlist_id` but received {playlist_id!r}") + return self._get( + path_template("/video/playlist/{playlist_id}/assets", **{"playlist_id": playlist_id}), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + {"sort_by": sort_by, "sort_order": sort_order, "page_number": page_number, "page_size": page_size}, + video_playlist_list_assets_params.VideoPlaylistListAssetsParams, + ), + ), + cast_to=VideoPlaylistListAssetsResponse, + ) + + @overload + def reorder_asset( + self, + playlist_id: str, + *, + asset_id: str, + page_number: int, + page_size: int, + asset_position: int, + # 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, + ) -> VideoPlaylistReorderAssetResponse: ... + + @overload + def reorder_asset( + self, + playlist_id: str, + *, + sort_by: Literal["title", "created_at"], + sort_order: Literal["asc", "desc"], + # 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, + ) -> VideoPlaylistReorderAssetResponse: ... + + @required_args(["asset_id", "asset_position", "page_number", "page_size"], ["sort_by", "sort_order"]) + def reorder_asset( + self, + playlist_id: str, + *, + asset_id: str | Omit = omit, + page_number: int | Omit = omit, + page_size: int | Omit = omit, + asset_position: int | Omit = omit, + sort_by: Literal["title", "created_at"] | Omit = omit, + sort_order: Literal["asc", "desc"] | 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, + ) -> VideoPlaylistReorderAssetResponse: + """ + Reorder videos inside a playlist either by moving a single asset to a position or by sorting the playlist by title or created date. + + Args: + playlist_id: Playlist id. + asset_id: Asset id to move. + page_number: Current playlist page number. + page_size: Playlist page size used by the caller. + asset_position: Zero-based position inside the provided page. + sort_by: Body parameter. + sort_order: Body parameter. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoPlaylistReorderAssetResponse: 200 + + Example: + ```python + video_playlist = client.video_playlists.reorder_asset( + playlist_id="playlistId", + asset_id="6e82bf783e88be000ab45ed2", + page_number=1, + page_size=10, + asset_position=0, + ) + ``` + """ + if playlist_id is None or (isinstance(playlist_id, str) and not playlist_id): + raise ValueError(f"Expected a non-empty value for `playlist_id` but received {playlist_id!r}") + return self._post( + path_template("/video/playlists/{playlist_id}/reorder", **{"playlist_id": playlist_id}), + body=maybe_transform( + { + "asset_id": asset_id, + "page_number": page_number, + "page_size": page_size, + "asset_position": asset_position, + "sort_by": sort_by, + "sort_order": sort_order, + }, + video_playlist_reorder_asset_params.VideoPlaylistReorderAssetParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoPlaylistReorderAssetResponse, + ) + + +class AsyncVideoPlaylistsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncVideoPlaylistsResourceWithRawResponse: + return AsyncVideoPlaylistsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncVideoPlaylistsResourceWithStreamingResponse: + return AsyncVideoPlaylistsResourceWithStreamingResponse(self) + + async def create( + self, + *, + collection_id: str, + title: str, + description: 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, + ) -> VideoPlaylistCreateResponse: + """ + Create new playlist inside video wprkspace + + Args: + collection_id: Body parameter. + title: Body parameter. + description: Body parameter. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoPlaylistCreateResponse: 200 + + Example: + ```python + video_playlist = await client.video_playlists.create( + collection_id="{{video-source-id}}", + title="Playlist-Title", + description="This is description for playlist.", + ) + ``` + """ + return await self._post( + "/video/playlist", + body=await async_maybe_transform( + { + "collection_id": collection_id, + "title": title, + "description": description, + }, + video_playlist_create_params.VideoPlaylistCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoPlaylistCreateResponse, + ) + + async def list_all( + self, + *, + collection_id: 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, + ) -> VideoPlaylistListAllResponse: + """ + Get all playlists for given workspace + + Args: + collection_id: Video Collection ID + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoPlaylistListAllResponse: 200 + + Example: + ```python + video_playlist = await client.video_playlists.list_all() + ``` + """ + return await self._get( + "/video/playlist", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + {"collection_id": collection_id}, video_playlist_list_all_params.VideoPlaylistListAllParams + ), + ), + cast_to=VideoPlaylistListAllResponse, + ) + + async def create_asset( + self, + playlist_id: str, + *, + asset_list: Iterable[video_playlist_create_asset_params.AssetList], + # 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, + ) -> VideoPlaylistCreateAssetResponse: + """ + This operation adds a single asset or a list of assets to a playlist. + + Args: + playlist_id: Playlist ID in which the asset needs to be added. + asset_list: Body parameter. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoPlaylistCreateAssetResponse: 200 + + Example: + ```python + video_playlist = await client.video_playlists.create_asset( + playlist_id="playlistId", + asset_list=[ + {"asset_id": "6508790283e4d60611846790"}, + {"position": 1, "asset_id": "650878f883e4d6061184677d"}, + {"asset_id": "650878de83e4d6061184676a"}, + {"position": 2, "asset_id": "650878d883e4d60611846757"}, + {"position": 3, "asset_id": "65578dd87eebc22dcdd549a2"}, + ], + ) + ``` + """ + if playlist_id is None or (isinstance(playlist_id, str) and not playlist_id): + raise ValueError(f"Expected a non-empty value for `playlist_id` but received {playlist_id!r}") + return await self._post( + path_template("/video/playlist/{playlist_id}/asset", **{"playlist_id": playlist_id}), + body=await async_maybe_transform( + {"asset_list": asset_list}, + video_playlist_create_asset_params.VideoPlaylistCreateAssetParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoPlaylistCreateAssetResponse, + ) + + async def delete_asset( + self, + playlist_id: str, + *, + delete_list: SequenceNotStr[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, + ) -> VideoPlaylistDeleteAssetResponse: + """ + Removed an asset or list of assets from a given playlist. + + Args: + playlist_id: Playlist ID that is to be deleted. + delete_list: Array of video asset ids. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoPlaylistDeleteAssetResponse: 200 + + Example: + ```python + video_playlist = await client.video_playlists.delete_asset( + playlist_id="playlistId", + delete_list=["6508790783e4d606118467a3"], + ) + ``` + """ + if playlist_id is None or (isinstance(playlist_id, str) and not playlist_id): + raise ValueError(f"Expected a non-empty value for `playlist_id` but received {playlist_id!r}") + return await self._delete( + path_template("/video/playlist/{playlist_id}/asset", **{"playlist_id": playlist_id}), + body=await async_maybe_transform( + {"delete_list": delete_list}, + video_playlist_delete_asset_params.VideoPlaylistDeleteAssetParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoPlaylistDeleteAssetResponse, + ) + + async def update( + self, + playlist_id: str, + *, + title: str | Omit = omit, + description: str | Omit = omit, + position: int | Omit = omit, + player_config: video_playlist_update_params.PlayerConfig | Omit = omit, + channel_visibility: bool | 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, + ) -> VideoPlaylistUpdateResponse: + """ + This endpoint allows you to update playlist name, channel visibility, or playlist order on a channel page. + + Args: + playlist_id: ID for the playlist to update. + title: Body parameter. + description: Body parameter. + position: Playlists have order in which they will be shown on the channel page. + player_config: Configure player settings for this playlist, it overrides the setting set on collection. + channel_visibility: If true then playlist will be visible on channel page. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoPlaylistUpdateResponse: 200 + + Example: + ```python + video_playlist = await client.video_playlists.update( + playlist_id="playlistId", + title="Playlist-Title-Updated", + description="This is updated description", + position=6, + player_config={ + "preload": True, + "autoplay": False, + "disable_seek": True, + "disable_player_controls": False, + "powered_by_gumlet_overlay": False, + "allow_drm_protected_videos": False, + "loop": False, + "player_color": "#6658ea", + "include_seo": True, + "subtitle_enabled": True, + "pixel_tags": {}, + "logo_width": 51, + "logo_height": 100, + "dynamic_watermark": False, + "watermark_font_size": 1, + "watermark_font_color": "#ff0000", + "watermark_bg_color": "transparent", + "watermark_interval": 1000, + }, + channel_visibility=False, + ) + ``` + """ + if playlist_id is None or (isinstance(playlist_id, str) and not playlist_id): + raise ValueError(f"Expected a non-empty value for `playlist_id` but received {playlist_id!r}") + return await self._post( + path_template("/video/playlist/{playlist_id}", **{"playlist_id": playlist_id}), + body=await async_maybe_transform( + { + "title": title, + "description": description, + "position": position, + "player_config": player_config, + "channel_visibility": channel_visibility, + }, + video_playlist_update_params.VideoPlaylistUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoPlaylistUpdateResponse, + ) + + async def delete( + self, + playlist_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, + ) -> None: + """ + Deletes this playlist. + + Args: + playlist_id: Playlist ID that is to be deleted. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Example: + ```python + await client.video_playlists.delete( + playlist_id="playlistId", + ) + ``` + """ + if playlist_id is None or (isinstance(playlist_id, str) and not playlist_id): + raise ValueError(f"Expected a non-empty value for `playlist_id` but received {playlist_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return await self._delete( + path_template("/video/playlist/{playlist_id}", **{"playlist_id": playlist_id}), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + async def list_assets( + self, + playlist_id: str, + *, + sort_by: str | Omit = omit, + sort_order: int | Omit = omit, + page_number: int | Omit = omit, + page_size: 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, + ) -> VideoPlaylistListAssetsResponse: + """ + Get a list of all assets inside playlist. You can choose in which order are assets returned. + + Args: + playlist_id: ID of playlist in which you need to list assets. + sort_by: Optional, if sort_by is set to asset_title it will sorted by title name. Otherwise order in which user added the assets in playlist. + sort_order: -1 or 1 + page_number: Optional, Minimun 1 + page_size: Optional, Minimun 10 + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoPlaylistListAssetsResponse: 200 + + Example: + ```python + video_playlist = await client.video_playlists.list_assets( + playlist_id="playlistId", + sort_order=1, + page_number=1, + page_size="10", + ) + ``` + """ + if playlist_id is None or (isinstance(playlist_id, str) and not playlist_id): + raise ValueError(f"Expected a non-empty value for `playlist_id` but received {playlist_id!r}") + return await self._get( + path_template("/video/playlist/{playlist_id}/assets", **{"playlist_id": playlist_id}), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + {"sort_by": sort_by, "sort_order": sort_order, "page_number": page_number, "page_size": page_size}, + video_playlist_list_assets_params.VideoPlaylistListAssetsParams, + ), + ), + cast_to=VideoPlaylistListAssetsResponse, + ) + + @overload + async def reorder_asset( + self, + playlist_id: str, + *, + asset_id: str, + page_number: int, + page_size: int, + asset_position: int, + # 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, + ) -> VideoPlaylistReorderAssetResponse: ... + + @overload + async def reorder_asset( + self, + playlist_id: str, + *, + sort_by: Literal["title", "created_at"], + sort_order: Literal["asc", "desc"], + # 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, + ) -> VideoPlaylistReorderAssetResponse: ... + + @required_args(["asset_id", "asset_position", "page_number", "page_size"], ["sort_by", "sort_order"]) + async def reorder_asset( + self, + playlist_id: str, + *, + asset_id: str | Omit = omit, + page_number: int | Omit = omit, + page_size: int | Omit = omit, + asset_position: int | Omit = omit, + sort_by: Literal["title", "created_at"] | Omit = omit, + sort_order: Literal["asc", "desc"] | 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, + ) -> VideoPlaylistReorderAssetResponse: + """ + Reorder videos inside a playlist either by moving a single asset to a position or by sorting the playlist by title or created date. + + Args: + playlist_id: Playlist id. + asset_id: Asset id to move. + page_number: Current playlist page number. + page_size: Playlist page size used by the caller. + asset_position: Zero-based position inside the provided page. + sort_by: Body parameter. + sort_order: Body parameter. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoPlaylistReorderAssetResponse: 200 + + Example: + ```python + video_playlist = await client.video_playlists.reorder_asset( + playlist_id="playlistId", + asset_id="6e82bf783e88be000ab45ed2", + page_number=1, + page_size=10, + asset_position=0, + ) + ``` + """ + if playlist_id is None or (isinstance(playlist_id, str) and not playlist_id): + raise ValueError(f"Expected a non-empty value for `playlist_id` but received {playlist_id!r}") + return await self._post( + path_template("/video/playlists/{playlist_id}/reorder", **{"playlist_id": playlist_id}), + body=await async_maybe_transform( + { + "asset_id": asset_id, + "page_number": page_number, + "page_size": page_size, + "asset_position": asset_position, + "sort_by": sort_by, + "sort_order": sort_order, + }, + video_playlist_reorder_asset_params.VideoPlaylistReorderAssetParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoPlaylistReorderAssetResponse, + ) + + +class VideoPlaylistsResourceWithRawResponse: + def __init__(self, video_playlists: VideoPlaylistsResource) -> None: + self._video_playlists = video_playlists + + self.create = to_raw_response_wrapper( + video_playlists.create, + ) + self.list_all = to_raw_response_wrapper( + video_playlists.list_all, + ) + self.create_asset = to_raw_response_wrapper( + video_playlists.create_asset, + ) + self.delete_asset = to_raw_response_wrapper( + video_playlists.delete_asset, + ) + self.update = to_raw_response_wrapper( + video_playlists.update, + ) + self.delete = to_raw_response_wrapper( + video_playlists.delete, + ) + self.list_assets = to_raw_response_wrapper( + video_playlists.list_assets, + ) + self.reorder_asset = to_raw_response_wrapper( + video_playlists.reorder_asset, + ) + + +class AsyncVideoPlaylistsResourceWithRawResponse: + def __init__(self, video_playlists: AsyncVideoPlaylistsResource) -> None: + self._video_playlists = video_playlists + + self.create = async_to_raw_response_wrapper( + video_playlists.create, + ) + self.list_all = async_to_raw_response_wrapper( + video_playlists.list_all, + ) + self.create_asset = async_to_raw_response_wrapper( + video_playlists.create_asset, + ) + self.delete_asset = async_to_raw_response_wrapper( + video_playlists.delete_asset, + ) + self.update = async_to_raw_response_wrapper( + video_playlists.update, + ) + self.delete = async_to_raw_response_wrapper( + video_playlists.delete, + ) + self.list_assets = async_to_raw_response_wrapper( + video_playlists.list_assets, + ) + self.reorder_asset = async_to_raw_response_wrapper( + video_playlists.reorder_asset, + ) + + +class VideoPlaylistsResourceWithStreamingResponse: + def __init__(self, video_playlists: VideoPlaylistsResource) -> None: + self._video_playlists = video_playlists + + self.create = to_streamed_response_wrapper( + video_playlists.create, + ) + self.list_all = to_streamed_response_wrapper( + video_playlists.list_all, + ) + self.create_asset = to_streamed_response_wrapper( + video_playlists.create_asset, + ) + self.delete_asset = to_streamed_response_wrapper( + video_playlists.delete_asset, + ) + self.update = to_streamed_response_wrapper( + video_playlists.update, + ) + self.delete = to_streamed_response_wrapper( + video_playlists.delete, + ) + self.list_assets = to_streamed_response_wrapper( + video_playlists.list_assets, + ) + self.reorder_asset = to_streamed_response_wrapper( + video_playlists.reorder_asset, + ) + + +class AsyncVideoPlaylistsResourceWithStreamingResponse: + def __init__(self, video_playlists: AsyncVideoPlaylistsResource) -> None: + self._video_playlists = video_playlists + + self.create = async_to_streamed_response_wrapper( + video_playlists.create, + ) + self.list_all = async_to_streamed_response_wrapper( + video_playlists.list_all, + ) + self.create_asset = async_to_streamed_response_wrapper( + video_playlists.create_asset, + ) + self.delete_asset = async_to_streamed_response_wrapper( + video_playlists.delete_asset, + ) + self.update = async_to_streamed_response_wrapper( + video_playlists.update, + ) + self.delete = async_to_streamed_response_wrapper( + video_playlists.delete, + ) + self.list_assets = async_to_streamed_response_wrapper( + video_playlists.list_assets, + ) + self.reorder_asset = async_to_streamed_response_wrapper( + video_playlists.reorder_asset, + ) diff --git a/src/gumlet/resources/video_profiles.py b/src/gumlet/resources/video_profiles.py new file mode 100644 index 0000000..3743c80 --- /dev/null +++ b/src/gumlet/resources/video_profiles.py @@ -0,0 +1,796 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +import httpx + +from typing_extensions import Literal +from .._types import SequenceNotStr + +from .._types import Body, Omit, Query, Headers, NotGiven, 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.video_profile_create_response import VideoProfileCreateResponse +from ..types import video_profile_create_params, video_profile_list_params, video_profile_update_params +from ..types.video_profile_list_response import VideoProfileListResponse +from ..types.video_profile_update_response import VideoProfileUpdateResponse +from ..types.video_profile_retrieve_response import VideoProfileRetrieveResponse +from ..types.video_profile_delete_response import VideoProfileDeleteResponse + +__all__ = ["VideoProfilesResource", "AsyncVideoProfilesResource"] + + +class VideoProfilesResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> VideoProfilesResourceWithRawResponse: + return VideoProfilesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> VideoProfilesResourceWithStreamingResponse: + return VideoProfilesResourceWithStreamingResponse(self) + + def create( + self, + *, + name: str, + format: Literal["ABR", "MP4"], + width: str | Omit = omit, + height: str | Omit = omit, + resolution: str | Omit = omit, + crop: video_profile_create_params.Crop | Omit = omit, + pad: video_profile_create_params.Pad | Omit = omit, + trim: video_profile_create_params.Trim | Omit = omit, + image_overlay: video_profile_create_params.ImageOverlay | Omit = omit, + text_overlay: video_profile_create_params.TextOverlay | Omit = omit, + animated_gif: video_profile_create_params.AnimatedGif | Omit = omit, + generate_subtitles: video_profile_create_params.GenerateSubtitles | Omit = omit, + mp4_access: bool | Omit = omit, + per_title_encoding: bool | Omit = omit, + process_low_resolution_input: bool | Omit = omit, + audio_only: bool | Omit = omit, + enable_drm: bool | 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, + ) -> VideoProfileCreateResponse: + """ + Gumlet provides the functionality of creating multiple video assets using the same set of parameters. A Video profile is a set of parameters that can be referenced/used while creating a video as a single parameter. + + Args: + name: Profile name or identifier. + format: Transcode and deliver the asset in the requested format. The options can be one of `ABR` (HLS + DASH) and `MP4`. + width: Resize video with the given width. Can be an absolute value in pixels or a percentage value with the `%` suffix. Specified values greater than the original asset width will be ignored. Only applicable when specified `format` is `MP4`. + height: Resize video with the given height. Can be an absolute value in pixels or a percentage value with the `%` suffix. Specified values greater than the original asset height will be ignored. Only applicable when specified `format` is `MP4`. + resolution: Required resolutions of the transformed asset in case of HLS or MPEG-DASH delivery format. Can be a comma separated string out of the following values: `240p`, `360p`, `480p`, `540p`, `720p`, and `1080p `. Re-sized rendition will retain the input aspect ratio. + crop: This transformation can be used to crop the video by defining a rectangular area within the dimensions of the output video. + pad: This transformation can be used to add padding to the video. + trim: Trim transformation can be used to trim videos based on time duration. + image_overlay: Image overlay can be used to brand a video or add a visual label in the form of an image. + text_overlay: Text overlay can be used to brand a video or add a label in the form of text. + animated_gif: Create an animated GIF from a video. + generate_subtitles: Gumlet allows to generate subtitles from the audio stream (use ISO 639-1 Language Codes). Remove this object if you do not want to generate AI subtitles. + mp4_access: Creates `mp4` version for download purpose in case of `MPEG-DASH` or `HLS` delivery format. **Default: `false`** + per_title_encoding: Gumlet analyzes each input video on a wide range of visual aspects. Based on the analysis, it chooses a unique set of transcoding options for processing the video. This ensures that the output video is of optimal size and best quality. **Default: `true`** + process_low_resolution_input: Currently, the minimum supported frame size is `57600` (`240x240`) pixels for `HLS/DASH` and `21025` (`145x145`) pixels for `MP4` format. However, enabling this flag will allow Gumlet to simply put your video asset into the specified delivery format without transcoding and optimization. Enabling this flag will cause any kind of specified video transformation to be ignored if you input video asset frame size is lower than the minimum supported frame size for the specified format. **Default: `false`** + audio_only: This flag allows Gumlet to transcode and deliver audio-only in the specified format. In this case,This flag allows Gumlet to transcode and deliver audio-only in the specified format. In this case, video transformation and thumbnails/animated GIFs would not be created. **Default: `false`** + enable_drm: Enable DRM encryption for transcoded videos. Gumlet supports Widevine and Fairplay DRMs. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoProfileCreateResponse: 200 + + Example: + ```python + video_profile = client.video_profiles.create( + name="Gumlet-Profile-1", + format="ABR", + ) + ``` + """ + return self._post( + "/video/profiles", + body=maybe_transform( + { + "name": name, + "format": format, + "width": width, + "height": height, + "resolution": resolution, + "crop": crop, + "pad": pad, + "trim": trim, + "image_overlay": image_overlay, + "text_overlay": text_overlay, + "animated_gif": animated_gif, + "generate_subtitles": generate_subtitles, + "mp4_access": mp4_access, + "per_title_encoding": per_title_encoding, + "process_low_resolution_input": process_low_resolution_input, + "audio_only": audio_only, + "enable_drm": enable_drm, + }, + video_profile_create_params.VideoProfileCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoProfileCreateResponse, + ) + + def list( + self, + *, + offset: int | Omit = omit, + size: int | 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, + ) -> VideoProfileListResponse: + """ + This endpoint retrieves the details of all profiles that have previously been created. + + Args: + offset: Offset value for a paginated list of profiles. Can be zero for the first time and `current_offset` value received from the last request afterwards. + size: Page size for the paginated list. **Default: `10`** + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoProfileListResponse: 200 + + Example: + ```python + video_profile = client.video_profiles.list() + ``` + """ + return self._get( + "/video/profiles", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + {"offset": offset, "size": size}, video_profile_list_params.VideoProfileListParams + ), + ), + cast_to=VideoProfileListResponse, + ) + + def update( + self, + path_profile_id: str, + *, + body_profile_id: str, + name: str | Omit = omit, + format: Literal["ABR", "MP4"] | Omit = omit, + width: str | Omit = omit, + height: str | Omit = omit, + resolution: str | Omit = omit, + crop: video_profile_update_params.Crop | Omit = omit, + pad: video_profile_update_params.Pad | Omit = omit, + trim: video_profile_update_params.Trim | Omit = omit, + image_overlay: video_profile_update_params.ImageOverlay | Omit = omit, + text_overlay: video_profile_update_params.TextOverlay | Omit = omit, + animated_gif: video_profile_update_params.AnimatedGif | Omit = omit, + generate_subtitles: video_profile_update_params.GenerateSubtitles | Omit = omit, + mp4_access: bool | Omit = omit, + per_title_encoding: bool | Omit = omit, + process_low_resolution_input: bool | Omit = omit, + audio_only: bool | Omit = omit, + enable_drm: bool | Omit = omit, + vc: SequenceNotStr[str] | Omit = omit, + generate_chapters: bool | Omit = omit, + generate_description: bool | 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, + ) -> VideoProfileUpdateResponse: + """ + Update an existing profile. Settings provided in body parameters will only be updated in the existing profile. + + Args: + path_profile_id: Profile id of the profile which need to be updated. + body_profile_id: Profile id of the profile which needs to be deleted. + name: Profile name or identifier. + format: Transcode and deliver the asset in the requested format. The options can be one of `ABR` (HLS + DASH) and `MP4`. + width: Resize video with the given width. Can be an absolute value in pixels or a percentage value with the `%` suffix. Specified values greater than the original asset width will be ignored. Only applicable when specified `format` is `MP4`. + height: Resize video with the given height. Can be an absolute value in pixels or a percentage value with the `%` suffix. Specified values greater than the original asset height will be ignored. Only applicable when specified `format` is `MP4`. + resolution: Resize video with the given height. Can be an absolute value in pixels or a percentage value with the `%` suffix. Specified values greater than the original asset height will be ignored. Only applicable when specified `format` is `MP4`. + crop: This transformation can be used to crop the video by defining a rectangular area within the dimensions of the output video. + pad: This transformation can be used to add padding to the video. + trim: Trim transformation can be used to trim videos based on time duration. + image_overlay: Image overlay can be used to brand a video or add a visual label in the form of an image. + text_overlay: Text overlay can be used to brand a video or add a label in the form of text. + animated_gif: Create an animated GIF from a video. + generate_subtitles: Gumlet allows to generate subtitles from the audio stream (use ISO 639-1 Language Codes). You can remove this object if don't want to generate AI subtitles. + mp4_access: Creates `mp4` version for download purpose in case of `MPEG-DASH` or `HLS` delivery format. **Default: `false`** + per_title_encoding: Gumlet analyzes each input video on a wide range of visual aspects. Based on the analysis, it chooses a unique set of transcoding options for processing the video. This ensures that the output video is of optimal size and best quality. **Default: `true`** + process_low_resolution_input: Currently, the minimum supported frame size is `57600` (`240x240`) pixels for `HLS/DASH` and `21025` (`145x145`) pixels for `MP4` format. However, enabling this flag will allow Gumlet to simply put your video asset into the specified delivery format without transcoding and optimization. Enabling this flag will cause any kind of specified video transformation to be ignored if you input video asset frame size is lower than the minimum supported frame size for the specified format. **Default: `false`** + audio_only: This flag allows Gumlet to transcode and deliver audio-only in the specified format. In this case,This flag allows Gumlet to transcode and deliver audio-only in the specified format. In this case, video transformation and thumbnails/animated GIFs would not be created. **Default: `false`** + enable_drm: Enable DRM encryption for transcoded videos. Gumlet supports Widevine and Fairplay DRMs. + vc: Video Codecs + generate_chapters: Whether Gumlet should generate chapters. + generate_description: Whether Gumlet should generate descriptions. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoProfileUpdateResponse: 200 + + Example: + ```python + video_profile = client.video_profiles.update( + path_profile_id="profileId", + body_profile_id="", + format="ABR", + ) + ``` + """ + if path_profile_id is None or (isinstance(path_profile_id, str) and not path_profile_id): + raise ValueError(f"Expected a non-empty value for `path_profile_id` but received {path_profile_id!r}") + return self._post( + path_template("/video/profiles/{profile_id}", **{"profile_id": path_profile_id}), + body=maybe_transform( + { + "body_profile_id": body_profile_id, + "name": name, + "format": format, + "width": width, + "height": height, + "resolution": resolution, + "crop": crop, + "pad": pad, + "trim": trim, + "image_overlay": image_overlay, + "text_overlay": text_overlay, + "animated_gif": animated_gif, + "generate_subtitles": generate_subtitles, + "mp4_access": mp4_access, + "per_title_encoding": per_title_encoding, + "process_low_resolution_input": process_low_resolution_input, + "audio_only": audio_only, + "enable_drm": enable_drm, + "vc": vc, + "generate_chapters": generate_chapters, + "generate_description": generate_description, + }, + video_profile_update_params.VideoProfileUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoProfileUpdateResponse, + ) + + def retrieve( + self, + profile_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, + ) -> VideoProfileRetrieveResponse: + """ + This endpoint retrieves the details of a video profile that has previously been created. + + Args: + profile_id: Profile id of the profile which needs to be retrieved. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoProfileRetrieveResponse: 200 + + Example: + ```python + video_profile = client.video_profiles.retrieve( + profile_id="profileId", + ) + ``` + """ + if profile_id is None or (isinstance(profile_id, str) and not profile_id): + raise ValueError(f"Expected a non-empty value for `profile_id` but received {profile_id!r}") + return self._get( + path_template("/video/profiles/{profile_id}", **{"profile_id": profile_id}), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoProfileRetrieveResponse, + ) + + def delete( + self, + profile_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, + ) -> VideoProfileDeleteResponse: + """ + This endpoint removes a profile given its unique `profile_id`. The profile will be removed but video assets created using the profile will remain as it is. + + Args: + profile_id: Profile id of the profile which needs to be deleted. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoProfileDeleteResponse: 204 + + Example: + ```python + video_profile = client.video_profiles.delete( + profile_id="profileId", + ) + ``` + """ + if profile_id is None or (isinstance(profile_id, str) and not profile_id): + raise ValueError(f"Expected a non-empty value for `profile_id` but received {profile_id!r}") + return self._delete( + path_template("/video/profiles/{profile_id}", **{"profile_id": profile_id}), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoProfileDeleteResponse, + ) + + +class AsyncVideoProfilesResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncVideoProfilesResourceWithRawResponse: + return AsyncVideoProfilesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncVideoProfilesResourceWithStreamingResponse: + return AsyncVideoProfilesResourceWithStreamingResponse(self) + + async def create( + self, + *, + name: str, + format: Literal["ABR", "MP4"], + width: str | Omit = omit, + height: str | Omit = omit, + resolution: str | Omit = omit, + crop: video_profile_create_params.Crop | Omit = omit, + pad: video_profile_create_params.Pad | Omit = omit, + trim: video_profile_create_params.Trim | Omit = omit, + image_overlay: video_profile_create_params.ImageOverlay | Omit = omit, + text_overlay: video_profile_create_params.TextOverlay | Omit = omit, + animated_gif: video_profile_create_params.AnimatedGif | Omit = omit, + generate_subtitles: video_profile_create_params.GenerateSubtitles | Omit = omit, + mp4_access: bool | Omit = omit, + per_title_encoding: bool | Omit = omit, + process_low_resolution_input: bool | Omit = omit, + audio_only: bool | Omit = omit, + enable_drm: bool | 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, + ) -> VideoProfileCreateResponse: + """ + Gumlet provides the functionality of creating multiple video assets using the same set of parameters. A Video profile is a set of parameters that can be referenced/used while creating a video as a single parameter. + + Args: + name: Profile name or identifier. + format: Transcode and deliver the asset in the requested format. The options can be one of `ABR` (HLS + DASH) and `MP4`. + width: Resize video with the given width. Can be an absolute value in pixels or a percentage value with the `%` suffix. Specified values greater than the original asset width will be ignored. Only applicable when specified `format` is `MP4`. + height: Resize video with the given height. Can be an absolute value in pixels or a percentage value with the `%` suffix. Specified values greater than the original asset height will be ignored. Only applicable when specified `format` is `MP4`. + resolution: Required resolutions of the transformed asset in case of HLS or MPEG-DASH delivery format. Can be a comma separated string out of the following values: `240p`, `360p`, `480p`, `540p`, `720p`, and `1080p `. Re-sized rendition will retain the input aspect ratio. + crop: This transformation can be used to crop the video by defining a rectangular area within the dimensions of the output video. + pad: This transformation can be used to add padding to the video. + trim: Trim transformation can be used to trim videos based on time duration. + image_overlay: Image overlay can be used to brand a video or add a visual label in the form of an image. + text_overlay: Text overlay can be used to brand a video or add a label in the form of text. + animated_gif: Create an animated GIF from a video. + generate_subtitles: Gumlet allows to generate subtitles from the audio stream (use ISO 639-1 Language Codes). Remove this object if you do not want to generate AI subtitles. + mp4_access: Creates `mp4` version for download purpose in case of `MPEG-DASH` or `HLS` delivery format. **Default: `false`** + per_title_encoding: Gumlet analyzes each input video on a wide range of visual aspects. Based on the analysis, it chooses a unique set of transcoding options for processing the video. This ensures that the output video is of optimal size and best quality. **Default: `true`** + process_low_resolution_input: Currently, the minimum supported frame size is `57600` (`240x240`) pixels for `HLS/DASH` and `21025` (`145x145`) pixels for `MP4` format. However, enabling this flag will allow Gumlet to simply put your video asset into the specified delivery format without transcoding and optimization. Enabling this flag will cause any kind of specified video transformation to be ignored if you input video asset frame size is lower than the minimum supported frame size for the specified format. **Default: `false`** + audio_only: This flag allows Gumlet to transcode and deliver audio-only in the specified format. In this case,This flag allows Gumlet to transcode and deliver audio-only in the specified format. In this case, video transformation and thumbnails/animated GIFs would not be created. **Default: `false`** + enable_drm: Enable DRM encryption for transcoded videos. Gumlet supports Widevine and Fairplay DRMs. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoProfileCreateResponse: 200 + + Example: + ```python + video_profile = await client.video_profiles.create( + name="Gumlet-Profile-1", + format="ABR", + ) + ``` + """ + return await self._post( + "/video/profiles", + body=await async_maybe_transform( + { + "name": name, + "format": format, + "width": width, + "height": height, + "resolution": resolution, + "crop": crop, + "pad": pad, + "trim": trim, + "image_overlay": image_overlay, + "text_overlay": text_overlay, + "animated_gif": animated_gif, + "generate_subtitles": generate_subtitles, + "mp4_access": mp4_access, + "per_title_encoding": per_title_encoding, + "process_low_resolution_input": process_low_resolution_input, + "audio_only": audio_only, + "enable_drm": enable_drm, + }, + video_profile_create_params.VideoProfileCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoProfileCreateResponse, + ) + + async def list( + self, + *, + offset: int | Omit = omit, + size: int | 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, + ) -> VideoProfileListResponse: + """ + This endpoint retrieves the details of all profiles that have previously been created. + + Args: + offset: Offset value for a paginated list of profiles. Can be zero for the first time and `current_offset` value received from the last request afterwards. + size: Page size for the paginated list. **Default: `10`** + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoProfileListResponse: 200 + + Example: + ```python + video_profile = await client.video_profiles.list() + ``` + """ + return await self._get( + "/video/profiles", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + {"offset": offset, "size": size}, video_profile_list_params.VideoProfileListParams + ), + ), + cast_to=VideoProfileListResponse, + ) + + async def update( + self, + path_profile_id: str, + *, + body_profile_id: str, + name: str | Omit = omit, + format: Literal["ABR", "MP4"] | Omit = omit, + width: str | Omit = omit, + height: str | Omit = omit, + resolution: str | Omit = omit, + crop: video_profile_update_params.Crop | Omit = omit, + pad: video_profile_update_params.Pad | Omit = omit, + trim: video_profile_update_params.Trim | Omit = omit, + image_overlay: video_profile_update_params.ImageOverlay | Omit = omit, + text_overlay: video_profile_update_params.TextOverlay | Omit = omit, + animated_gif: video_profile_update_params.AnimatedGif | Omit = omit, + generate_subtitles: video_profile_update_params.GenerateSubtitles | Omit = omit, + mp4_access: bool | Omit = omit, + per_title_encoding: bool | Omit = omit, + process_low_resolution_input: bool | Omit = omit, + audio_only: bool | Omit = omit, + enable_drm: bool | Omit = omit, + vc: SequenceNotStr[str] | Omit = omit, + generate_chapters: bool | Omit = omit, + generate_description: bool | 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, + ) -> VideoProfileUpdateResponse: + """ + Update an existing profile. Settings provided in body parameters will only be updated in the existing profile. + + Args: + path_profile_id: Profile id of the profile which need to be updated. + body_profile_id: Profile id of the profile which needs to be deleted. + name: Profile name or identifier. + format: Transcode and deliver the asset in the requested format. The options can be one of `ABR` (HLS + DASH) and `MP4`. + width: Resize video with the given width. Can be an absolute value in pixels or a percentage value with the `%` suffix. Specified values greater than the original asset width will be ignored. Only applicable when specified `format` is `MP4`. + height: Resize video with the given height. Can be an absolute value in pixels or a percentage value with the `%` suffix. Specified values greater than the original asset height will be ignored. Only applicable when specified `format` is `MP4`. + resolution: Resize video with the given height. Can be an absolute value in pixels or a percentage value with the `%` suffix. Specified values greater than the original asset height will be ignored. Only applicable when specified `format` is `MP4`. + crop: This transformation can be used to crop the video by defining a rectangular area within the dimensions of the output video. + pad: This transformation can be used to add padding to the video. + trim: Trim transformation can be used to trim videos based on time duration. + image_overlay: Image overlay can be used to brand a video or add a visual label in the form of an image. + text_overlay: Text overlay can be used to brand a video or add a label in the form of text. + animated_gif: Create an animated GIF from a video. + generate_subtitles: Gumlet allows to generate subtitles from the audio stream (use ISO 639-1 Language Codes). You can remove this object if don't want to generate AI subtitles. + mp4_access: Creates `mp4` version for download purpose in case of `MPEG-DASH` or `HLS` delivery format. **Default: `false`** + per_title_encoding: Gumlet analyzes each input video on a wide range of visual aspects. Based on the analysis, it chooses a unique set of transcoding options for processing the video. This ensures that the output video is of optimal size and best quality. **Default: `true`** + process_low_resolution_input: Currently, the minimum supported frame size is `57600` (`240x240`) pixels for `HLS/DASH` and `21025` (`145x145`) pixels for `MP4` format. However, enabling this flag will allow Gumlet to simply put your video asset into the specified delivery format without transcoding and optimization. Enabling this flag will cause any kind of specified video transformation to be ignored if you input video asset frame size is lower than the minimum supported frame size for the specified format. **Default: `false`** + audio_only: This flag allows Gumlet to transcode and deliver audio-only in the specified format. In this case,This flag allows Gumlet to transcode and deliver audio-only in the specified format. In this case, video transformation and thumbnails/animated GIFs would not be created. **Default: `false`** + enable_drm: Enable DRM encryption for transcoded videos. Gumlet supports Widevine and Fairplay DRMs. + vc: Video Codecs + generate_chapters: Whether Gumlet should generate chapters. + generate_description: Whether Gumlet should generate descriptions. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoProfileUpdateResponse: 200 + + Example: + ```python + video_profile = await client.video_profiles.update( + path_profile_id="profileId", + body_profile_id="", + format="ABR", + ) + ``` + """ + if path_profile_id is None or (isinstance(path_profile_id, str) and not path_profile_id): + raise ValueError(f"Expected a non-empty value for `path_profile_id` but received {path_profile_id!r}") + return await self._post( + path_template("/video/profiles/{profile_id}", **{"profile_id": path_profile_id}), + body=await async_maybe_transform( + { + "body_profile_id": body_profile_id, + "name": name, + "format": format, + "width": width, + "height": height, + "resolution": resolution, + "crop": crop, + "pad": pad, + "trim": trim, + "image_overlay": image_overlay, + "text_overlay": text_overlay, + "animated_gif": animated_gif, + "generate_subtitles": generate_subtitles, + "mp4_access": mp4_access, + "per_title_encoding": per_title_encoding, + "process_low_resolution_input": process_low_resolution_input, + "audio_only": audio_only, + "enable_drm": enable_drm, + "vc": vc, + "generate_chapters": generate_chapters, + "generate_description": generate_description, + }, + video_profile_update_params.VideoProfileUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoProfileUpdateResponse, + ) + + async def retrieve( + self, + profile_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, + ) -> VideoProfileRetrieveResponse: + """ + This endpoint retrieves the details of a video profile that has previously been created. + + Args: + profile_id: Profile id of the profile which needs to be retrieved. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoProfileRetrieveResponse: 200 + + Example: + ```python + video_profile = await client.video_profiles.retrieve( + profile_id="profileId", + ) + ``` + """ + if profile_id is None or (isinstance(profile_id, str) and not profile_id): + raise ValueError(f"Expected a non-empty value for `profile_id` but received {profile_id!r}") + return await self._get( + path_template("/video/profiles/{profile_id}", **{"profile_id": profile_id}), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoProfileRetrieveResponse, + ) + + async def delete( + self, + profile_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, + ) -> VideoProfileDeleteResponse: + """ + This endpoint removes a profile given its unique `profile_id`. The profile will be removed but video assets created using the profile will remain as it is. + + Args: + profile_id: Profile id of the profile which needs to be deleted. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoProfileDeleteResponse: 204 + + Example: + ```python + video_profile = await client.video_profiles.delete( + profile_id="profileId", + ) + ``` + """ + if profile_id is None or (isinstance(profile_id, str) and not profile_id): + raise ValueError(f"Expected a non-empty value for `profile_id` but received {profile_id!r}") + return await self._delete( + path_template("/video/profiles/{profile_id}", **{"profile_id": profile_id}), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoProfileDeleteResponse, + ) + + +class VideoProfilesResourceWithRawResponse: + def __init__(self, video_profiles: VideoProfilesResource) -> None: + self._video_profiles = video_profiles + + self.create = to_raw_response_wrapper( + video_profiles.create, + ) + self.list = to_raw_response_wrapper( + video_profiles.list, + ) + self.update = to_raw_response_wrapper( + video_profiles.update, + ) + self.retrieve = to_raw_response_wrapper( + video_profiles.retrieve, + ) + self.delete = to_raw_response_wrapper( + video_profiles.delete, + ) + + +class AsyncVideoProfilesResourceWithRawResponse: + def __init__(self, video_profiles: AsyncVideoProfilesResource) -> None: + self._video_profiles = video_profiles + + self.create = async_to_raw_response_wrapper( + video_profiles.create, + ) + self.list = async_to_raw_response_wrapper( + video_profiles.list, + ) + self.update = async_to_raw_response_wrapper( + video_profiles.update, + ) + self.retrieve = async_to_raw_response_wrapper( + video_profiles.retrieve, + ) + self.delete = async_to_raw_response_wrapper( + video_profiles.delete, + ) + + +class VideoProfilesResourceWithStreamingResponse: + def __init__(self, video_profiles: VideoProfilesResource) -> None: + self._video_profiles = video_profiles + + self.create = to_streamed_response_wrapper( + video_profiles.create, + ) + self.list = to_streamed_response_wrapper( + video_profiles.list, + ) + self.update = to_streamed_response_wrapper( + video_profiles.update, + ) + self.retrieve = to_streamed_response_wrapper( + video_profiles.retrieve, + ) + self.delete = to_streamed_response_wrapper( + video_profiles.delete, + ) + + +class AsyncVideoProfilesResourceWithStreamingResponse: + def __init__(self, video_profiles: AsyncVideoProfilesResource) -> None: + self._video_profiles = video_profiles + + self.create = async_to_streamed_response_wrapper( + video_profiles.create, + ) + self.list = async_to_streamed_response_wrapper( + video_profiles.list, + ) + self.update = async_to_streamed_response_wrapper( + video_profiles.update, + ) + self.retrieve = async_to_streamed_response_wrapper( + video_profiles.retrieve, + ) + self.delete = async_to_streamed_response_wrapper( + video_profiles.delete, + ) diff --git a/src/gumlet/resources/video_usage_analytics.py b/src/gumlet/resources/video_usage_analytics.py new file mode 100644 index 0000000..ef54cbb --- /dev/null +++ b/src/gumlet/resources/video_usage_analytics.py @@ -0,0 +1,367 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +import httpx + +from typing import List +from typing_extensions import Literal + +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.video_usage_analytic_retrieve_response import VideoUsageAnalyticRetrieveResponse +from ..types import video_usage_analytic_retrieve_params, video_usage_analytic_top_assets_params +from ..types.video_usage_analytic_top_assets_response import VideoUsageAnalyticTopAssetsResponse + +__all__ = ["VideoUsageAnalyticsResource", "AsyncVideoUsageAnalyticsResource"] + + +class VideoUsageAnalyticsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> VideoUsageAnalyticsResourceWithRawResponse: + return VideoUsageAnalyticsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> VideoUsageAnalyticsResourceWithStreamingResponse: + return VideoUsageAnalyticsResourceWithStreamingResponse(self) + + def retrieve( + self, + *, + metrics: List[ + Literal[ + "bandwidth_consumption", + "asset_duration", + "storage_unit", + "top_assets", + "drm_requests", + "ai_credit_usage", + "errored_videos", + ] + ], + date_range: video_usage_analytic_retrieve_params.DateRange, + filters: video_usage_analytic_retrieve_params.Filters | Omit = omit, + top_assets_count: str | Omit = omit, + top_assets_page: str | Omit = omit, + group_by: Literal["hourly", "daily", "monthly"] | 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, + ) -> VideoUsageAnalyticRetrieveResponse: + """ + This endpoint gives usage analytics data of your videos. Ex - top assets, bandwidth consumption + + Args: + metrics: Define the metric you need the data for, currently we only support `bandwidth_consumption`, `asset_duration`, `storage_unit`, `top_assets`, `bandwidth_consumption_by_collection`, `errored_videos` and `widget_data` + date_range: The timeframe to get the data for. Currently we only support a maximum of 60 days between `start_at` and `end_at`. + filters: Body parameter. + top_assets_count: Count of video assets that should be returned. Max assets count is 1000 per page. + top_assets_page: top_assets metric may get paginated response. Iterate this parameter to get more data. + group_by: Group by hourly, daily or monthly. If you don't specify anything it's `hourly` by default. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoUsageAnalyticRetrieveResponse: 200 + + Example: + ```python + video_usage_analytic = client.video_usage_analytics.retrieve( + metrics=["bandwidth_consumption", "asset_duration", "storage_unit", "top_assets", "drm_requests"], + date_range={"start_at": "2026-08-01", "end_at": "2026-08-20"}, + top_assets_count="5", + top_assets_page="0", + group_by="hourly", + ) + ``` + """ + return self._post( + "/video/analytics", + body=maybe_transform( + { + "metrics": metrics, + "date_range": date_range, + "filters": filters, + "top_assets_count": top_assets_count, + "top_assets_page": top_assets_page, + "group_by": group_by, + }, + video_usage_analytic_retrieve_params.VideoUsageAnalyticRetrieveParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoUsageAnalyticRetrieveResponse, + ) + + def top_assets( + self, + *, + start_at: str, + end_at: str, + collection_id: str | Omit = omit, + page: str | Omit = omit, + page_size: 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, + ) -> VideoUsageAnalyticTopAssetsResponse: + """ + This endpoint lists top streamed assets in a video collection + + Args: + start_at: Date string in "yyyy-mm-dd" format + end_at: Date string in "yyyy-mm-dd" format + collection_id: Gumlet workspace ID + page: Page number of the response. + page_size: Assets to list per page. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoUsageAnalyticTopAssetsResponse: 200 + + Example: + ```python + video_usage_analytic = client.video_usage_analytics.top_assets( + start_at="2026-06-21", + end_at="2026-06-30", + page="1", + page_size="1000", + ) + ``` + """ + return self._get( + "/video/streaming-duration", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "start_at": start_at, + "end_at": end_at, + "collection_id": collection_id, + "page": page, + "page_size": page_size, + }, + video_usage_analytic_top_assets_params.VideoUsageAnalyticTopAssetsParams, + ), + ), + cast_to=VideoUsageAnalyticTopAssetsResponse, + ) + + +class AsyncVideoUsageAnalyticsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncVideoUsageAnalyticsResourceWithRawResponse: + return AsyncVideoUsageAnalyticsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncVideoUsageAnalyticsResourceWithStreamingResponse: + return AsyncVideoUsageAnalyticsResourceWithStreamingResponse(self) + + async def retrieve( + self, + *, + metrics: List[ + Literal[ + "bandwidth_consumption", + "asset_duration", + "storage_unit", + "top_assets", + "drm_requests", + "ai_credit_usage", + "errored_videos", + ] + ], + date_range: video_usage_analytic_retrieve_params.DateRange, + filters: video_usage_analytic_retrieve_params.Filters | Omit = omit, + top_assets_count: str | Omit = omit, + top_assets_page: str | Omit = omit, + group_by: Literal["hourly", "daily", "monthly"] | 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, + ) -> VideoUsageAnalyticRetrieveResponse: + """ + This endpoint gives usage analytics data of your videos. Ex - top assets, bandwidth consumption + + Args: + metrics: Define the metric you need the data for, currently we only support `bandwidth_consumption`, `asset_duration`, `storage_unit`, `top_assets`, `bandwidth_consumption_by_collection`, `errored_videos` and `widget_data` + date_range: The timeframe to get the data for. Currently we only support a maximum of 60 days between `start_at` and `end_at`. + filters: Body parameter. + top_assets_count: Count of video assets that should be returned. Max assets count is 1000 per page. + top_assets_page: top_assets metric may get paginated response. Iterate this parameter to get more data. + group_by: Group by hourly, daily or monthly. If you don't specify anything it's `hourly` by default. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoUsageAnalyticRetrieveResponse: 200 + + Example: + ```python + video_usage_analytic = await client.video_usage_analytics.retrieve( + metrics=["bandwidth_consumption", "asset_duration", "storage_unit", "top_assets", "drm_requests"], + date_range={"start_at": "2026-08-01", "end_at": "2026-08-20"}, + top_assets_count="5", + top_assets_page="0", + group_by="hourly", + ) + ``` + """ + return await self._post( + "/video/analytics", + body=await async_maybe_transform( + { + "metrics": metrics, + "date_range": date_range, + "filters": filters, + "top_assets_count": top_assets_count, + "top_assets_page": top_assets_page, + "group_by": group_by, + }, + video_usage_analytic_retrieve_params.VideoUsageAnalyticRetrieveParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoUsageAnalyticRetrieveResponse, + ) + + async def top_assets( + self, + *, + start_at: str, + end_at: str, + collection_id: str | Omit = omit, + page: str | Omit = omit, + page_size: 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, + ) -> VideoUsageAnalyticTopAssetsResponse: + """ + This endpoint lists top streamed assets in a video collection + + Args: + start_at: Date string in "yyyy-mm-dd" format + end_at: Date string in "yyyy-mm-dd" format + collection_id: Gumlet workspace ID + page: Page number of the response. + page_size: Assets to list per page. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoUsageAnalyticTopAssetsResponse: 200 + + Example: + ```python + video_usage_analytic = await client.video_usage_analytics.top_assets( + start_at="2026-06-21", + end_at="2026-06-30", + page="1", + page_size="1000", + ) + ``` + """ + return await self._get( + "/video/streaming-duration", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "start_at": start_at, + "end_at": end_at, + "collection_id": collection_id, + "page": page, + "page_size": page_size, + }, + video_usage_analytic_top_assets_params.VideoUsageAnalyticTopAssetsParams, + ), + ), + cast_to=VideoUsageAnalyticTopAssetsResponse, + ) + + +class VideoUsageAnalyticsResourceWithRawResponse: + def __init__(self, video_usage_analytics: VideoUsageAnalyticsResource) -> None: + self._video_usage_analytics = video_usage_analytics + + self.retrieve = to_raw_response_wrapper( + video_usage_analytics.retrieve, + ) + self.top_assets = to_raw_response_wrapper( + video_usage_analytics.top_assets, + ) + + +class AsyncVideoUsageAnalyticsResourceWithRawResponse: + def __init__(self, video_usage_analytics: AsyncVideoUsageAnalyticsResource) -> None: + self._video_usage_analytics = video_usage_analytics + + self.retrieve = async_to_raw_response_wrapper( + video_usage_analytics.retrieve, + ) + self.top_assets = async_to_raw_response_wrapper( + video_usage_analytics.top_assets, + ) + + +class VideoUsageAnalyticsResourceWithStreamingResponse: + def __init__(self, video_usage_analytics: VideoUsageAnalyticsResource) -> None: + self._video_usage_analytics = video_usage_analytics + + self.retrieve = to_streamed_response_wrapper( + video_usage_analytics.retrieve, + ) + self.top_assets = to_streamed_response_wrapper( + video_usage_analytics.top_assets, + ) + + +class AsyncVideoUsageAnalyticsResourceWithStreamingResponse: + def __init__(self, video_usage_analytics: AsyncVideoUsageAnalyticsResource) -> None: + self._video_usage_analytics = video_usage_analytics + + self.retrieve = async_to_streamed_response_wrapper( + video_usage_analytics.retrieve, + ) + self.top_assets = async_to_streamed_response_wrapper( + video_usage_analytics.top_assets, + ) diff --git a/src/gumlet/resources/video_workspaces.py b/src/gumlet/resources/video_workspaces.py new file mode 100644 index 0000000..4d2f28c --- /dev/null +++ b/src/gumlet/resources/video_workspaces.py @@ -0,0 +1,864 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +import httpx + +from typing_extensions import Literal +from .._types import SequenceNotStr + +from .._types import Body, Omit, Query, Headers, NotGiven, 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.video_workspace_list_response import VideoWorkspaceListResponse +from ..types import video_workspace_list_params, video_workspace_create_params, video_workspace_update_params +from ..types.video_workspace_create_response import VideoWorkspaceCreateResponse +from ..types.video_workspace_update_response import VideoWorkspaceUpdateResponse +from ..types.video_workspace_retrieve_response import VideoWorkspaceRetrieveResponse +from ..types.video_workspace_delete_response import VideoWorkspaceDeleteResponse + +__all__ = ["VideoWorkspacesResource", "AsyncVideoWorkspacesResource"] + + +class VideoWorkspacesResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> VideoWorkspacesResourceWithRawResponse: + return VideoWorkspacesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> VideoWorkspacesResourceWithStreamingResponse: + return VideoWorkspacesResourceWithStreamingResponse(self) + + def list( + self, + *, + offset: str | Omit = omit, + size: 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, + ) -> VideoWorkspaceListResponse: + """ + This endpoint list video workspace which are assigned to the user or token. + + Args: + offset: Number of workspaces to skip. For example if you need to list 11 to 20th workspaces, pass offset as 10 and size as 10. + size: Number of workspace to return in single response. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoWorkspaceListResponse: 200 + + Example: + ```python + video_workspace = client.video_workspaces.list( + offset="0", + size="10", + ) + ``` + """ + return self._get( + "/video/workspaces", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + {"offset": offset, "size": size}, video_workspace_list_params.VideoWorkspaceListParams + ), + ), + cast_to=VideoWorkspaceListResponse, + ) + + def create( + self, + *, + name: str, + type: Literal[ + "proxy", + "direct-upload", + "webfolder", + "aws", + "gcs", + "dostorage", + "wasabi", + "cloudinary", + "azure", + "linode", + "backblaze", + "cloudflare", + "zoom", + ], + default_profile_id: str | Omit = omit, + insight_property_id: str | Omit = omit, + video_protection: video_workspace_create_params.VideoProtection | Omit = omit, + aws: video_workspace_create_params.Aws | Omit = omit, + proxy: video_workspace_create_params.Proxy | Omit = omit, + gcs: video_workspace_create_params.Gcs | Omit = omit, + dostorage: video_workspace_create_params.Dostorage | Omit = omit, + wasabi: video_workspace_create_params.Wasabi | Omit = omit, + cloudinary: video_workspace_create_params.Cloudinary | Omit = omit, + azure: video_workspace_create_params.Azure | Omit = omit, + linode: video_workspace_create_params.Linode | Omit = omit, + backblaze: video_workspace_create_params.Backblaze | Omit = omit, + cloudflare: video_workspace_create_params.Cloudflare | Omit = omit, + zoom: video_workspace_create_params.Zoom | 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, + ) -> VideoWorkspaceCreateResponse: + """ + Video workspaces are top-level entities in Gumlet. You can use them to organize videos for different teams/departments or use cases. + + Args: + name: Specify a text string or identifier for the workspace. + type: Video workspaces are top-level entities in Gumlet. You can use them to organize videos for different teams/departments or use cases. + default_profile_id: Gumlet provides the functionality of creating multiple video assets using the same set of parameters. + insight_property_id: The five to ten character unique identifier of the Gumlet Insight Property available on the dashboard. + video_protection: Gumlet provides multiple options for securing your video playback. + aws: This is a required field if workspace type is aws. + proxy: This is a required field if workspace type is proxy. + gcs: This is a required field if workspace type is gcs. + dostorage: This is a required field if workspace type is dostorage. + wasabi: This is a required field if workspace type is wasabi. + cloudinary: This is a required field if workspace type is cloudinary. + azure: This is a required field if workspace type is azure. + linode: This is a required field if workspace type is linode. + backblaze: This is a required field if workspace type is backblaze. + cloudflare: This is a required field if workspace type is cloudflare. + zoom: This is a required field if workspace type is zoom. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoWorkspaceCreateResponse: 200 + + Example: + ```python + video_workspace = client.video_workspaces.create( + name="zoom-workspace", + type="direct-upload", + zoom={"secret": "yourSecret"}, + ) + ``` + """ + return self._post( + "/video/workspaces", + body=maybe_transform( + { + "name": name, + "type": type, + "default_profile_id": default_profile_id, + "insight_property_id": insight_property_id, + "video_protection": video_protection, + "aws": aws, + "proxy": proxy, + "gcs": gcs, + "dostorage": dostorage, + "wasabi": wasabi, + "cloudinary": cloudinary, + "azure": azure, + "linode": linode, + "backblaze": backblaze, + "cloudflare": cloudflare, + "zoom": zoom, + }, + video_workspace_create_params.VideoWorkspaceCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoWorkspaceCreateResponse, + ) + + def update( + self, + workspace_id: str, + *, + name: str | Omit = omit, + default_profile_id: str | Omit = omit, + temp_cname: SequenceNotStr[str] | Omit = omit, + insight_property_id: str | Omit = omit, + player_config: video_workspace_update_params.PlayerConfig | Omit = omit, + video_protection: video_workspace_update_params.VideoProtection | Omit = omit, + channel_settings: video_workspace_update_params.ChannelSettings | Omit = omit, + type: Literal[ + "proxy", + "direct-upload", + "webfolder", + "aws", + "gcs", + "dostorage", + "wasabi", + "cloudinary", + "azure", + "linode", + "backblaze", + "cloudflare", + "zoom", + ] + | Omit = omit, + webfolder: video_workspace_update_params.Webfolder | Omit = omit, + aws: video_workspace_update_params.Aws | Omit = omit, + proxy: video_workspace_update_params.Proxy | Omit = omit, + gcs: video_workspace_update_params.Gcs | Omit = omit, + dostorage: video_workspace_update_params.Dostorage | Omit = omit, + wasabi: video_workspace_update_params.Wasabi | Omit = omit, + linode: video_workspace_update_params.Linode | Omit = omit, + backblaze: video_workspace_update_params.Backblaze | Omit = omit, + cloudflare: video_workspace_update_params.Cloudflare | Omit = omit, + cloudinary: video_workspace_update_params.Cloudinary | Omit = omit, + azure: video_workspace_update_params.Azure | Omit = omit, + zoom: video_workspace_update_params.Zoom | 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, + ) -> VideoWorkspaceUpdateResponse: + """ + This endpoint allows users to update video workspace that has previously been created. + + Args: + workspace_id: Gumlet workspace ID. You can get it on Gumlet dashboard or retrieve it using list workspace API. + name: video workspace name + default_profile_id: Gumlet provides the functionality of creating multiple video assets using the same set of parameters. + temp_cname: cname for channel + insight_property_id: The five to ten character unique identifier of the Gumlet Insight Property available on the dashboard. + player_config: Configure player settings for this playlist, it overrides the setting set on workspace. + video_protection: Gumlet provides multiple options for securing your video playback. + channel_settings: Configurations to set various channel settings. + type: Video workspaces are top-level entities in Gumlet. You can use them to organize videos for different teams/departments or use cases. + webfolder: This is a required field if workspace type is webfolder. + aws: This is a required field if workspace type is aws. + proxy: This is a required field if workspace type is proxy. + gcs: This is a required field if workspace type is gcs. + dostorage: This is a required field if workspace type is dostorage. + wasabi: This is a required field if workspace type is wasabi. + linode: This is a required field if workspace type is linode. + backblaze: This is a required field if workspace type is backblaze. + cloudflare: This is a required field if workspace type is cloudflare. + cloudinary: This is a required field if workspace type is cloudinary. + azure: This is a required field if workspace type is azure. + zoom: This is a required field if workspace type is zoom. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoWorkspaceUpdateResponse: 200 + + Example: + ```python + video_workspace = client.video_workspaces.update( + workspace_id="workspaceId", + name="awsrename", + default_profile_id="646df1c9173a4a2fcac180b7", + type="aws", + aws={ + "bucket_name": "my-bucket-test", + "bucket_region": "ap-southeast-1", + "access_key": "BQUA6QFXVWHAAB6IO2X1", + "secret": "aws_secret", + }, + ) + ``` + """ + if workspace_id is None or (isinstance(workspace_id, str) and not workspace_id): + raise ValueError(f"Expected a non-empty value for `workspace_id` but received {workspace_id!r}") + return self._post( + path_template("/video/workspaces/{workspace_id}", **{"workspace_id": workspace_id}), + body=maybe_transform( + { + "name": name, + "default_profile_id": default_profile_id, + "temp_cname": temp_cname, + "insight_property_id": insight_property_id, + "player_config": player_config, + "video_protection": video_protection, + "channel_settings": channel_settings, + "type": type, + "webfolder": webfolder, + "aws": aws, + "proxy": proxy, + "gcs": gcs, + "dostorage": dostorage, + "wasabi": wasabi, + "linode": linode, + "backblaze": backblaze, + "cloudflare": cloudflare, + "cloudinary": cloudinary, + "azure": azure, + "zoom": zoom, + }, + video_workspace_update_params.VideoWorkspaceUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoWorkspaceUpdateResponse, + ) + + def retrieve( + self, + workspace_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, + ) -> VideoWorkspaceRetrieveResponse: + """ + This endpoint get all the data of video workspace that has previously been created. + + Args: + workspace_id: Gumlet workspace ID. You can get it on Gumlet dashboard or retrieve it using list workspace API. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoWorkspaceRetrieveResponse: 200 + + Example: + ```python + video_workspace = client.video_workspaces.retrieve( + workspace_id="workspaceId", + ) + ``` + """ + if workspace_id is None or (isinstance(workspace_id, str) and not workspace_id): + raise ValueError(f"Expected a non-empty value for `workspace_id` but received {workspace_id!r}") + return self._get( + path_template("/video/workspaces/{workspace_id}", **{"workspace_id": workspace_id}), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoWorkspaceRetrieveResponse, + ) + + def delete( + self, + workspace_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, + ) -> VideoWorkspaceDeleteResponse: + """ + This endpoint removes a video workspace given its unique asset id. All the asset in workspace will be removed from storage as well, associated URLs will be inaccessible. + + Args: + workspace_id: Gumlet workspace ID. You can get it on Gumlet dashboard or retrieve it using list workspace API. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoWorkspaceDeleteResponse: 200 + + Example: + ```python + video_workspace = client.video_workspaces.delete( + workspace_id="workspaceId", + ) + ``` + """ + if workspace_id is None or (isinstance(workspace_id, str) and not workspace_id): + raise ValueError(f"Expected a non-empty value for `workspace_id` but received {workspace_id!r}") + return self._delete( + path_template("/video/workspaces/{workspace_id}", **{"workspace_id": workspace_id}), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoWorkspaceDeleteResponse, + ) + + +class AsyncVideoWorkspacesResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncVideoWorkspacesResourceWithRawResponse: + return AsyncVideoWorkspacesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncVideoWorkspacesResourceWithStreamingResponse: + return AsyncVideoWorkspacesResourceWithStreamingResponse(self) + + async def list( + self, + *, + offset: str | Omit = omit, + size: 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, + ) -> VideoWorkspaceListResponse: + """ + This endpoint list video workspace which are assigned to the user or token. + + Args: + offset: Number of workspaces to skip. For example if you need to list 11 to 20th workspaces, pass offset as 10 and size as 10. + size: Number of workspace to return in single response. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoWorkspaceListResponse: 200 + + Example: + ```python + video_workspace = await client.video_workspaces.list( + offset="0", + size="10", + ) + ``` + """ + return await self._get( + "/video/workspaces", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + {"offset": offset, "size": size}, video_workspace_list_params.VideoWorkspaceListParams + ), + ), + cast_to=VideoWorkspaceListResponse, + ) + + async def create( + self, + *, + name: str, + type: Literal[ + "proxy", + "direct-upload", + "webfolder", + "aws", + "gcs", + "dostorage", + "wasabi", + "cloudinary", + "azure", + "linode", + "backblaze", + "cloudflare", + "zoom", + ], + default_profile_id: str | Omit = omit, + insight_property_id: str | Omit = omit, + video_protection: video_workspace_create_params.VideoProtection | Omit = omit, + aws: video_workspace_create_params.Aws | Omit = omit, + proxy: video_workspace_create_params.Proxy | Omit = omit, + gcs: video_workspace_create_params.Gcs | Omit = omit, + dostorage: video_workspace_create_params.Dostorage | Omit = omit, + wasabi: video_workspace_create_params.Wasabi | Omit = omit, + cloudinary: video_workspace_create_params.Cloudinary | Omit = omit, + azure: video_workspace_create_params.Azure | Omit = omit, + linode: video_workspace_create_params.Linode | Omit = omit, + backblaze: video_workspace_create_params.Backblaze | Omit = omit, + cloudflare: video_workspace_create_params.Cloudflare | Omit = omit, + zoom: video_workspace_create_params.Zoom | 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, + ) -> VideoWorkspaceCreateResponse: + """ + Video workspaces are top-level entities in Gumlet. You can use them to organize videos for different teams/departments or use cases. + + Args: + name: Specify a text string or identifier for the workspace. + type: Video workspaces are top-level entities in Gumlet. You can use them to organize videos for different teams/departments or use cases. + default_profile_id: Gumlet provides the functionality of creating multiple video assets using the same set of parameters. + insight_property_id: The five to ten character unique identifier of the Gumlet Insight Property available on the dashboard. + video_protection: Gumlet provides multiple options for securing your video playback. + aws: This is a required field if workspace type is aws. + proxy: This is a required field if workspace type is proxy. + gcs: This is a required field if workspace type is gcs. + dostorage: This is a required field if workspace type is dostorage. + wasabi: This is a required field if workspace type is wasabi. + cloudinary: This is a required field if workspace type is cloudinary. + azure: This is a required field if workspace type is azure. + linode: This is a required field if workspace type is linode. + backblaze: This is a required field if workspace type is backblaze. + cloudflare: This is a required field if workspace type is cloudflare. + zoom: This is a required field if workspace type is zoom. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoWorkspaceCreateResponse: 200 + + Example: + ```python + video_workspace = await client.video_workspaces.create( + name="zoom-workspace", + type="direct-upload", + zoom={"secret": "yourSecret"}, + ) + ``` + """ + return await self._post( + "/video/workspaces", + body=await async_maybe_transform( + { + "name": name, + "type": type, + "default_profile_id": default_profile_id, + "insight_property_id": insight_property_id, + "video_protection": video_protection, + "aws": aws, + "proxy": proxy, + "gcs": gcs, + "dostorage": dostorage, + "wasabi": wasabi, + "cloudinary": cloudinary, + "azure": azure, + "linode": linode, + "backblaze": backblaze, + "cloudflare": cloudflare, + "zoom": zoom, + }, + video_workspace_create_params.VideoWorkspaceCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoWorkspaceCreateResponse, + ) + + async def update( + self, + workspace_id: str, + *, + name: str | Omit = omit, + default_profile_id: str | Omit = omit, + temp_cname: SequenceNotStr[str] | Omit = omit, + insight_property_id: str | Omit = omit, + player_config: video_workspace_update_params.PlayerConfig | Omit = omit, + video_protection: video_workspace_update_params.VideoProtection | Omit = omit, + channel_settings: video_workspace_update_params.ChannelSettings | Omit = omit, + type: Literal[ + "proxy", + "direct-upload", + "webfolder", + "aws", + "gcs", + "dostorage", + "wasabi", + "cloudinary", + "azure", + "linode", + "backblaze", + "cloudflare", + "zoom", + ] + | Omit = omit, + webfolder: video_workspace_update_params.Webfolder | Omit = omit, + aws: video_workspace_update_params.Aws | Omit = omit, + proxy: video_workspace_update_params.Proxy | Omit = omit, + gcs: video_workspace_update_params.Gcs | Omit = omit, + dostorage: video_workspace_update_params.Dostorage | Omit = omit, + wasabi: video_workspace_update_params.Wasabi | Omit = omit, + linode: video_workspace_update_params.Linode | Omit = omit, + backblaze: video_workspace_update_params.Backblaze | Omit = omit, + cloudflare: video_workspace_update_params.Cloudflare | Omit = omit, + cloudinary: video_workspace_update_params.Cloudinary | Omit = omit, + azure: video_workspace_update_params.Azure | Omit = omit, + zoom: video_workspace_update_params.Zoom | 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, + ) -> VideoWorkspaceUpdateResponse: + """ + This endpoint allows users to update video workspace that has previously been created. + + Args: + workspace_id: Gumlet workspace ID. You can get it on Gumlet dashboard or retrieve it using list workspace API. + name: video workspace name + default_profile_id: Gumlet provides the functionality of creating multiple video assets using the same set of parameters. + temp_cname: cname for channel + insight_property_id: The five to ten character unique identifier of the Gumlet Insight Property available on the dashboard. + player_config: Configure player settings for this playlist, it overrides the setting set on workspace. + video_protection: Gumlet provides multiple options for securing your video playback. + channel_settings: Configurations to set various channel settings. + type: Video workspaces are top-level entities in Gumlet. You can use them to organize videos for different teams/departments or use cases. + webfolder: This is a required field if workspace type is webfolder. + aws: This is a required field if workspace type is aws. + proxy: This is a required field if workspace type is proxy. + gcs: This is a required field if workspace type is gcs. + dostorage: This is a required field if workspace type is dostorage. + wasabi: This is a required field if workspace type is wasabi. + linode: This is a required field if workspace type is linode. + backblaze: This is a required field if workspace type is backblaze. + cloudflare: This is a required field if workspace type is cloudflare. + cloudinary: This is a required field if workspace type is cloudinary. + azure: This is a required field if workspace type is azure. + zoom: This is a required field if workspace type is zoom. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoWorkspaceUpdateResponse: 200 + + Example: + ```python + video_workspace = await client.video_workspaces.update( + workspace_id="workspaceId", + name="awsrename", + default_profile_id="646df1c9173a4a2fcac180b7", + type="aws", + aws={ + "bucket_name": "my-bucket-test", + "bucket_region": "ap-southeast-1", + "access_key": "BQUA6QFXVWHAAB6IO2X1", + "secret": "aws_secret", + }, + ) + ``` + """ + if workspace_id is None or (isinstance(workspace_id, str) and not workspace_id): + raise ValueError(f"Expected a non-empty value for `workspace_id` but received {workspace_id!r}") + return await self._post( + path_template("/video/workspaces/{workspace_id}", **{"workspace_id": workspace_id}), + body=await async_maybe_transform( + { + "name": name, + "default_profile_id": default_profile_id, + "temp_cname": temp_cname, + "insight_property_id": insight_property_id, + "player_config": player_config, + "video_protection": video_protection, + "channel_settings": channel_settings, + "type": type, + "webfolder": webfolder, + "aws": aws, + "proxy": proxy, + "gcs": gcs, + "dostorage": dostorage, + "wasabi": wasabi, + "linode": linode, + "backblaze": backblaze, + "cloudflare": cloudflare, + "cloudinary": cloudinary, + "azure": azure, + "zoom": zoom, + }, + video_workspace_update_params.VideoWorkspaceUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoWorkspaceUpdateResponse, + ) + + async def retrieve( + self, + workspace_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, + ) -> VideoWorkspaceRetrieveResponse: + """ + This endpoint get all the data of video workspace that has previously been created. + + Args: + workspace_id: Gumlet workspace ID. You can get it on Gumlet dashboard or retrieve it using list workspace API. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoWorkspaceRetrieveResponse: 200 + + Example: + ```python + video_workspace = await client.video_workspaces.retrieve( + workspace_id="workspaceId", + ) + ``` + """ + if workspace_id is None or (isinstance(workspace_id, str) and not workspace_id): + raise ValueError(f"Expected a non-empty value for `workspace_id` but received {workspace_id!r}") + return await self._get( + path_template("/video/workspaces/{workspace_id}", **{"workspace_id": workspace_id}), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoWorkspaceRetrieveResponse, + ) + + async def delete( + self, + workspace_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, + ) -> VideoWorkspaceDeleteResponse: + """ + This endpoint removes a video workspace given its unique asset id. All the asset in workspace will be removed from storage as well, associated URLs will be inaccessible. + + Args: + workspace_id: Gumlet workspace ID. You can get it on Gumlet dashboard or retrieve it using list workspace API. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + VideoWorkspaceDeleteResponse: 200 + + Example: + ```python + video_workspace = await client.video_workspaces.delete( + workspace_id="workspaceId", + ) + ``` + """ + if workspace_id is None or (isinstance(workspace_id, str) and not workspace_id): + raise ValueError(f"Expected a non-empty value for `workspace_id` but received {workspace_id!r}") + return await self._delete( + path_template("/video/workspaces/{workspace_id}", **{"workspace_id": workspace_id}), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=VideoWorkspaceDeleteResponse, + ) + + +class VideoWorkspacesResourceWithRawResponse: + def __init__(self, video_workspaces: VideoWorkspacesResource) -> None: + self._video_workspaces = video_workspaces + + self.list = to_raw_response_wrapper( + video_workspaces.list, + ) + self.create = to_raw_response_wrapper( + video_workspaces.create, + ) + self.update = to_raw_response_wrapper( + video_workspaces.update, + ) + self.retrieve = to_raw_response_wrapper( + video_workspaces.retrieve, + ) + self.delete = to_raw_response_wrapper( + video_workspaces.delete, + ) + + +class AsyncVideoWorkspacesResourceWithRawResponse: + def __init__(self, video_workspaces: AsyncVideoWorkspacesResource) -> None: + self._video_workspaces = video_workspaces + + self.list = async_to_raw_response_wrapper( + video_workspaces.list, + ) + self.create = async_to_raw_response_wrapper( + video_workspaces.create, + ) + self.update = async_to_raw_response_wrapper( + video_workspaces.update, + ) + self.retrieve = async_to_raw_response_wrapper( + video_workspaces.retrieve, + ) + self.delete = async_to_raw_response_wrapper( + video_workspaces.delete, + ) + + +class VideoWorkspacesResourceWithStreamingResponse: + def __init__(self, video_workspaces: VideoWorkspacesResource) -> None: + self._video_workspaces = video_workspaces + + self.list = to_streamed_response_wrapper( + video_workspaces.list, + ) + self.create = to_streamed_response_wrapper( + video_workspaces.create, + ) + self.update = to_streamed_response_wrapper( + video_workspaces.update, + ) + self.retrieve = to_streamed_response_wrapper( + video_workspaces.retrieve, + ) + self.delete = to_streamed_response_wrapper( + video_workspaces.delete, + ) + + +class AsyncVideoWorkspacesResourceWithStreamingResponse: + def __init__(self, video_workspaces: AsyncVideoWorkspacesResource) -> None: + self._video_workspaces = video_workspaces + + self.list = async_to_streamed_response_wrapper( + video_workspaces.list, + ) + self.create = async_to_streamed_response_wrapper( + video_workspaces.create, + ) + self.update = async_to_streamed_response_wrapper( + video_workspaces.update, + ) + self.retrieve = async_to_streamed_response_wrapper( + video_workspaces.retrieve, + ) + self.delete = async_to_streamed_response_wrapper( + video_workspaces.delete, + ) diff --git a/src/gumlet/resources/webhooks.py b/src/gumlet/resources/webhooks.py new file mode 100644 index 0000000..a04d8bf --- /dev/null +++ b/src/gumlet/resources/webhooks.py @@ -0,0 +1,595 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +import httpx + +from .._types import SequenceNotStr + +from .._types import Body, Omit, Query, Headers, NotGiven, 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_create_response import WebhookCreateResponse +from ..types import webhook_create_params, webhook_update_params +from ..types.webhook_list_response import WebhookListResponse +from ..types.webhook_update_response import WebhookUpdateResponse +from ..types.webhook_delete_response import WebhookDeleteResponse +from ..types.webhook_history_response import WebhookHistoryResponse + +__all__ = ["WebhooksResource", "AsyncWebhooksResource"] + + +class WebhooksResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> WebhooksResourceWithRawResponse: + return WebhooksResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> WebhooksResourceWithStreamingResponse: + return WebhooksResourceWithStreamingResponse(self) + + def create( + self, + *, + url: str, + secret_token: str, + triggers: SequenceNotStr[str], + sources: SequenceNotStr[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, + ) -> WebhookCreateResponse: + """ + Creates a new webhook listener. + + Args: + url: URL from the application you want to send data to. + secret_token: Authentication token to ensure legitimacy of Gumlet Webhook request on your application. + triggers: Triggers for the invocation of webhookos, supported option is `status`. + sources: List of video collection identifiers for which webhooks are needed to be invoked. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + WebhookCreateResponse: 200 + + Example: + ```python + webhook = client.webhooks.create( + url="", + secret_token="", + triggers=[""], + sources=[""], + ) + ``` + """ + return self._post( + "/org/webhooks", + body=maybe_transform( + { + "url": url, + "secret_token": secret_token, + "triggers": triggers, + "sources": sources, + }, + webhook_create_params.WebhookCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=WebhookCreateResponse, + ) + + def list( + self, + *, + # 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: + """ + List all webhooks. + + Args: + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + WebhookListResponse: Successful response + + Example: + ```python + webhook = client.webhooks.list() + ``` + """ + return self._get( + "/org/webhooks", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=WebhookListResponse, + ) + + def update( + self, + webhook_id: str, + *, + url: str | Omit = omit, + secret_token: str | Omit = omit, + triggers: str | Omit = omit, + sources: 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, + ) -> WebhookUpdateResponse: + """ + Update a webhook listener. + + Args: + webhook_id: Unique identifier for the Gumlet Webhook which needs to be updated. + url: URL from the application you want to send data to. + secret_token: Authentication token to ensure legitimacy of Gumlet Webhook request on your application. + triggers: Triggers for the invocation of webhookos, supported option is `status`. + sources: List of video collection identifiers for which webhooks are needed to be invoked. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + WebhookUpdateResponse: 200 + + Example: + ```python + webhook = client.webhooks.update( + webhook_id="webhookId", + ) + ``` + """ + if webhook_id is None or (isinstance(webhook_id, str) and not webhook_id): + raise ValueError(f"Expected a non-empty value for `webhook_id` but received {webhook_id!r}") + return self._post( + path_template("/org/webhooks/{webhook_id}", **{"webhook_id": webhook_id}), + body=maybe_transform( + { + "url": url, + "secret_token": secret_token, + "triggers": triggers, + "sources": sources, + }, + webhook_update_params.WebhookUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=WebhookUpdateResponse, + ) + + def delete( + self, + webhook_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, + ) -> WebhookDeleteResponse: + """ + Delete webhook listener endpoint. + + Args: + webhook_id: Unique identifier for the Gumlet Webhook which needs to be deleted. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + WebhookDeleteResponse: 204 + + Example: + ```python + webhook = client.webhooks.delete( + webhook_id="webhookId", + ) + ``` + """ + if webhook_id is None or (isinstance(webhook_id, str) and not webhook_id): + raise ValueError(f"Expected a non-empty value for `webhook_id` but received {webhook_id!r}") + return self._delete( + path_template("/org/webhooks/{webhook_id}", **{"webhook_id": webhook_id}), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=WebhookDeleteResponse, + ) + + def history( + self, + webhook_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, + ) -> WebhookHistoryResponse: + """ + Get logs history for a given webhook. + + Args: + webhook_id: Webhook ID. You can get it using list webhook endpoint. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + WebhookHistoryResponse: Successful response + + Example: + ```python + webhook = client.webhooks.history( + webhook_id="webhookId", + ) + ``` + """ + if webhook_id is None or (isinstance(webhook_id, str) and not webhook_id): + raise ValueError(f"Expected a non-empty value for `webhook_id` but received {webhook_id!r}") + return self._get( + path_template("/org/webhook/{webhook_id}/history", **{"webhook_id": webhook_id}), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=WebhookHistoryResponse, + ) + + +class AsyncWebhooksResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncWebhooksResourceWithRawResponse: + return AsyncWebhooksResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncWebhooksResourceWithStreamingResponse: + return AsyncWebhooksResourceWithStreamingResponse(self) + + async def create( + self, + *, + url: str, + secret_token: str, + triggers: SequenceNotStr[str], + sources: SequenceNotStr[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, + ) -> WebhookCreateResponse: + """ + Creates a new webhook listener. + + Args: + url: URL from the application you want to send data to. + secret_token: Authentication token to ensure legitimacy of Gumlet Webhook request on your application. + triggers: Triggers for the invocation of webhookos, supported option is `status`. + sources: List of video collection identifiers for which webhooks are needed to be invoked. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + WebhookCreateResponse: 200 + + Example: + ```python + webhook = await client.webhooks.create( + url="", + secret_token="", + triggers=[""], + sources=[""], + ) + ``` + """ + return await self._post( + "/org/webhooks", + body=await async_maybe_transform( + { + "url": url, + "secret_token": secret_token, + "triggers": triggers, + "sources": sources, + }, + webhook_create_params.WebhookCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=WebhookCreateResponse, + ) + + async def list( + self, + *, + # 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: + """ + List all webhooks. + + Args: + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + WebhookListResponse: Successful response + + Example: + ```python + webhook = await client.webhooks.list() + ``` + """ + return await self._get( + "/org/webhooks", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=WebhookListResponse, + ) + + async def update( + self, + webhook_id: str, + *, + url: str | Omit = omit, + secret_token: str | Omit = omit, + triggers: str | Omit = omit, + sources: 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, + ) -> WebhookUpdateResponse: + """ + Update a webhook listener. + + Args: + webhook_id: Unique identifier for the Gumlet Webhook which needs to be updated. + url: URL from the application you want to send data to. + secret_token: Authentication token to ensure legitimacy of Gumlet Webhook request on your application. + triggers: Triggers for the invocation of webhookos, supported option is `status`. + sources: List of video collection identifiers for which webhooks are needed to be invoked. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + WebhookUpdateResponse: 200 + + Example: + ```python + webhook = await client.webhooks.update( + webhook_id="webhookId", + ) + ``` + """ + if webhook_id is None or (isinstance(webhook_id, str) and not webhook_id): + raise ValueError(f"Expected a non-empty value for `webhook_id` but received {webhook_id!r}") + return await self._post( + path_template("/org/webhooks/{webhook_id}", **{"webhook_id": webhook_id}), + body=await async_maybe_transform( + { + "url": url, + "secret_token": secret_token, + "triggers": triggers, + "sources": sources, + }, + webhook_update_params.WebhookUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=WebhookUpdateResponse, + ) + + async def delete( + self, + webhook_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, + ) -> WebhookDeleteResponse: + """ + Delete webhook listener endpoint. + + Args: + webhook_id: Unique identifier for the Gumlet Webhook which needs to be deleted. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + WebhookDeleteResponse: 204 + + Example: + ```python + webhook = await client.webhooks.delete( + webhook_id="webhookId", + ) + ``` + """ + if webhook_id is None or (isinstance(webhook_id, str) and not webhook_id): + raise ValueError(f"Expected a non-empty value for `webhook_id` but received {webhook_id!r}") + return await self._delete( + path_template("/org/webhooks/{webhook_id}", **{"webhook_id": webhook_id}), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=WebhookDeleteResponse, + ) + + async def history( + self, + webhook_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, + ) -> WebhookHistoryResponse: + """ + Get logs history for a given webhook. + + Args: + webhook_id: Webhook ID. You can get it using list webhook endpoint. + extra_headers: Send extra headers with the request. + extra_query: Send extra query parameters with the request. + extra_body: Send extra JSON properties with the request. + timeout: Override the client-level default timeout for this request, in seconds. + + Returns: + WebhookHistoryResponse: Successful response + + Example: + ```python + webhook = await client.webhooks.history( + webhook_id="webhookId", + ) + ``` + """ + if webhook_id is None or (isinstance(webhook_id, str) and not webhook_id): + raise ValueError(f"Expected a non-empty value for `webhook_id` but received {webhook_id!r}") + return await self._get( + path_template("/org/webhook/{webhook_id}/history", **{"webhook_id": webhook_id}), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=WebhookHistoryResponse, + ) + + +class WebhooksResourceWithRawResponse: + def __init__(self, webhooks: WebhooksResource) -> None: + self._webhooks = webhooks + + self.create = to_raw_response_wrapper( + webhooks.create, + ) + self.list = to_raw_response_wrapper( + webhooks.list, + ) + self.update = to_raw_response_wrapper( + webhooks.update, + ) + self.delete = to_raw_response_wrapper( + webhooks.delete, + ) + self.history = to_raw_response_wrapper( + webhooks.history, + ) + + +class AsyncWebhooksResourceWithRawResponse: + def __init__(self, webhooks: AsyncWebhooksResource) -> None: + self._webhooks = webhooks + + self.create = async_to_raw_response_wrapper( + webhooks.create, + ) + self.list = async_to_raw_response_wrapper( + webhooks.list, + ) + self.update = async_to_raw_response_wrapper( + webhooks.update, + ) + self.delete = async_to_raw_response_wrapper( + webhooks.delete, + ) + self.history = async_to_raw_response_wrapper( + webhooks.history, + ) + + +class WebhooksResourceWithStreamingResponse: + def __init__(self, webhooks: WebhooksResource) -> None: + self._webhooks = webhooks + + self.create = to_streamed_response_wrapper( + webhooks.create, + ) + self.list = to_streamed_response_wrapper( + webhooks.list, + ) + self.update = to_streamed_response_wrapper( + webhooks.update, + ) + self.delete = to_streamed_response_wrapper( + webhooks.delete, + ) + self.history = to_streamed_response_wrapper( + webhooks.history, + ) + + +class AsyncWebhooksResourceWithStreamingResponse: + def __init__(self, webhooks: AsyncWebhooksResource) -> None: + self._webhooks = webhooks + + self.create = async_to_streamed_response_wrapper( + webhooks.create, + ) + self.list = async_to_streamed_response_wrapper( + webhooks.list, + ) + self.update = async_to_streamed_response_wrapper( + webhooks.update, + ) + self.delete = async_to_streamed_response_wrapper( + webhooks.delete, + ) + self.history = async_to_streamed_response_wrapper( + webhooks.history, + ) diff --git a/src/gumlet/types/__init__.py b/src/gumlet/types/__init__.py new file mode 100644 index 0000000..2283791 --- /dev/null +++ b/src/gumlet/types/__init__.py @@ -0,0 +1,190 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from .video_asset_create_response import VideoAssetCreateResponse as VideoAssetCreateResponse +from .video_asset_create_params import VideoAssetCreateParams as VideoAssetCreateParams +from .video_asset_upload_response import VideoAssetUploadResponse as VideoAssetUploadResponse +from .video_asset_upload_params import VideoAssetUploadParams as VideoAssetUploadParams +from .video_asset_retrieve_details_response import ( + VideoAssetRetrieveDetailsResponse as VideoAssetRetrieveDetailsResponse, +) +from .video_asset_update_response import VideoAssetUpdateResponse as VideoAssetUpdateResponse +from .video_asset_update_params import VideoAssetUpdateParams as VideoAssetUpdateParams +from .video_asset_thumbnail_select_response import ( + VideoAssetThumbnailSelectResponse as VideoAssetThumbnailSelectResponse, +) +from .video_asset_thumbnail_select_params import VideoAssetThumbnailSelectParams as VideoAssetThumbnailSelectParams +from .video_asset_thumbnail_upload_response import ( + VideoAssetThumbnailUploadResponse as VideoAssetThumbnailUploadResponse, +) +from .video_asset_create_update_chapter_response import ( + VideoAssetCreateUpdateChapterResponse as VideoAssetCreateUpdateChapterResponse, +) +from .video_asset_create_update_chapter_params import ( + VideoAssetCreateUpdateChapterParams as VideoAssetCreateUpdateChapterParams, +) +from .video_asset_list_response import VideoAssetListResponse as VideoAssetListResponse +from .video_asset_list_params import VideoAssetListParams as VideoAssetListParams +from .video_asset_list_deprecated_response import VideoAssetListDeprecatedResponse as VideoAssetListDeprecatedResponse +from .video_asset_list_deprecated_params import VideoAssetListDeprecatedParams as VideoAssetListDeprecatedParams +from .video_asset_delete_many_response import VideoAssetDeleteManyResponse as VideoAssetDeleteManyResponse +from .video_asset_delete_many_params import VideoAssetDeleteManyParams as VideoAssetDeleteManyParams +from .video_asset_tag_many_response import VideoAssetTagManyResponse as VideoAssetTagManyResponse +from .video_asset_tag_many_params import VideoAssetTagManyParams as VideoAssetTagManyParams +from .video_asset_analytics_response import VideoAssetAnalyticsResponse as VideoAssetAnalyticsResponse +from .video_asset_analytics_params import VideoAssetAnalyticsParams as VideoAssetAnalyticsParams +from .subtitle_upload_upload_response import SubtitleUploadUploadResponse as SubtitleUploadUploadResponse +from .subtitle_upload_upload_params import SubtitleUploadUploadParams as SubtitleUploadUploadParams +from .subtitle_upload_complete_response import SubtitleUploadCompleteResponse as SubtitleUploadCompleteResponse +from .subtitle_upload_complete_params import SubtitleUploadCompleteParams as SubtitleUploadCompleteParams +from .audio_upload_upload_response import AudioUploadUploadResponse as AudioUploadUploadResponse +from .audio_upload_upload_params import AudioUploadUploadParams as AudioUploadUploadParams +from .audio_upload_complete_response import AudioUploadCompleteResponse as AudioUploadCompleteResponse +from .audio_upload_complete_params import AudioUploadCompleteParams as AudioUploadCompleteParams +from .video_usage_analytic_retrieve_response import ( + VideoUsageAnalyticRetrieveResponse as VideoUsageAnalyticRetrieveResponse, +) +from .video_usage_analytic_retrieve_params import VideoUsageAnalyticRetrieveParams as VideoUsageAnalyticRetrieveParams +from .video_usage_analytic_top_assets_response import ( + VideoUsageAnalyticTopAssetsResponse as VideoUsageAnalyticTopAssetsResponse, +) +from .video_usage_analytic_top_assets_params import ( + VideoUsageAnalyticTopAssetsParams as VideoUsageAnalyticTopAssetsParams, +) +from .multipart_upload_retrieve_part_url_response import ( + MultipartUploadRetrievePartURLResponse as MultipartUploadRetrievePartURLResponse, +) +from .multipart_upload_complete_response import MultipartUploadCompleteResponse as MultipartUploadCompleteResponse +from .multipart_upload_complete_params import MultipartUploadCompleteParams as MultipartUploadCompleteParams +from .video_profile_create_response import VideoProfileCreateResponse as VideoProfileCreateResponse +from .video_profile_create_params import VideoProfileCreateParams as VideoProfileCreateParams +from .video_profile_list_response import VideoProfileListResponse as VideoProfileListResponse +from .video_profile_list_params import VideoProfileListParams as VideoProfileListParams +from .video_profile_update_response import VideoProfileUpdateResponse as VideoProfileUpdateResponse +from .video_profile_update_params import VideoProfileUpdateParams as VideoProfileUpdateParams +from .video_profile_retrieve_response import VideoProfileRetrieveResponse as VideoProfileRetrieveResponse +from .video_profile_delete_response import VideoProfileDeleteResponse as VideoProfileDeleteResponse +from .video_playlist_create_response import VideoPlaylistCreateResponse as VideoPlaylistCreateResponse +from .video_playlist_create_params import VideoPlaylistCreateParams as VideoPlaylistCreateParams +from .video_playlist_list_all_response import VideoPlaylistListAllResponse as VideoPlaylistListAllResponse +from .video_playlist_list_all_params import VideoPlaylistListAllParams as VideoPlaylistListAllParams +from .video_playlist_create_asset_response import VideoPlaylistCreateAssetResponse as VideoPlaylistCreateAssetResponse +from .video_playlist_create_asset_params import VideoPlaylistCreateAssetParams as VideoPlaylistCreateAssetParams +from .video_playlist_delete_asset_response import VideoPlaylistDeleteAssetResponse as VideoPlaylistDeleteAssetResponse +from .video_playlist_delete_asset_params import VideoPlaylistDeleteAssetParams as VideoPlaylistDeleteAssetParams +from .video_playlist_update_response import VideoPlaylistUpdateResponse as VideoPlaylistUpdateResponse +from .video_playlist_update_params import VideoPlaylistUpdateParams as VideoPlaylistUpdateParams +from .video_playlist_list_assets_response import VideoPlaylistListAssetsResponse as VideoPlaylistListAssetsResponse +from .video_playlist_list_assets_params import VideoPlaylistListAssetsParams as VideoPlaylistListAssetsParams +from .video_playlist_reorder_asset_response import ( + VideoPlaylistReorderAssetResponse as VideoPlaylistReorderAssetResponse, +) +from .video_playlist_reorder_asset_params import VideoPlaylistReorderAssetParams as VideoPlaylistReorderAssetParams +from .webhook_create_response import WebhookCreateResponse as WebhookCreateResponse +from .webhook_create_params import WebhookCreateParams as WebhookCreateParams +from .webhook_list_response import WebhookListResponse as WebhookListResponse +from .webhook_update_response import WebhookUpdateResponse as WebhookUpdateResponse +from .webhook_update_params import WebhookUpdateParams as WebhookUpdateParams +from .webhook_delete_response import WebhookDeleteResponse as WebhookDeleteResponse +from .webhook_history_response import WebhookHistoryResponse as WebhookHistoryResponse +from .image_source_create_response import ImageSourceCreateResponse as ImageSourceCreateResponse +from .image_source_create_params import ImageSourceCreateParams as ImageSourceCreateParams +from .image_source_list_response import ImageSourceListResponse as ImageSourceListResponse +from .image_source_list_params import ImageSourceListParams as ImageSourceListParams +from .image_source_retrieve_response import ImageSourceRetrieveResponse as ImageSourceRetrieveResponse +from .image_source_update_response import ImageSourceUpdateResponse as ImageSourceUpdateResponse +from .image_source_update_params import ImageSourceUpdateParams as ImageSourceUpdateParams +from .image_source_delete_response import ImageSourceDeleteResponse as ImageSourceDeleteResponse +from .image_source_purge_cache_response import ImageSourcePurgeCacheResponse as ImageSourcePurgeCacheResponse +from .image_source_purge_cache_params import ImageSourcePurgeCacheParams as ImageSourcePurgeCacheParams +from .image_source_purge_response import ImageSourcePurgeResponse as ImageSourcePurgeResponse +from .image_source_purge_params import ImageSourcePurgeParams as ImageSourcePurgeParams +from .image_usage_analytic_retrieve_response import ( + ImageUsageAnalyticRetrieveResponse as ImageUsageAnalyticRetrieveResponse, +) +from .image_usage_analytic_retrieve_params import ImageUsageAnalyticRetrieveParams as ImageUsageAnalyticRetrieveParams +from .live_stream_asset_create_response import LiveStreamAssetCreateResponse as LiveStreamAssetCreateResponse +from .live_stream_asset_create_params import LiveStreamAssetCreateParams as LiveStreamAssetCreateParams +from .live_stream_asset_update_response import LiveStreamAssetUpdateResponse as LiveStreamAssetUpdateResponse +from .live_stream_asset_update_params import LiveStreamAssetUpdateParams as LiveStreamAssetUpdateParams +from .live_stream_asset_retrieve_status_response import ( + LiveStreamAssetRetrieveStatusResponse as LiveStreamAssetRetrieveStatusResponse, +) +from .live_stream_asset_delete_response import LiveStreamAssetDeleteResponse as LiveStreamAssetDeleteResponse +from .live_stream_asset_complete_response import LiveStreamAssetCompleteResponse as LiveStreamAssetCompleteResponse +from .live_stream_asset_filter_response import LiveStreamAssetFilterResponse as LiveStreamAssetFilterResponse +from .live_stream_asset_filter_params import LiveStreamAssetFilterParams as LiveStreamAssetFilterParams +from .live_stream_asset_upload_response import LiveStreamAssetUploadResponse as LiveStreamAssetUploadResponse +from .live_stream_asset_upload_params import LiveStreamAssetUploadParams as LiveStreamAssetUploadParams +from .live_stream_asset_status_history_response import ( + LiveStreamAssetStatusHistoryResponse as LiveStreamAssetStatusHistoryResponse, +) +from .recycle_bin_recover_params import RecycleBinRecoverParams as RecycleBinRecoverParams +from .recycle_bin_list_response import RecycleBinListResponse as RecycleBinListResponse +from .recycle_bin_list_params import RecycleBinListParams as RecycleBinListParams +from .video_workspace_list_response import VideoWorkspaceListResponse as VideoWorkspaceListResponse +from .video_workspace_list_params import VideoWorkspaceListParams as VideoWorkspaceListParams +from .video_workspace_create_response import VideoWorkspaceCreateResponse as VideoWorkspaceCreateResponse +from .video_workspace_create_params import VideoWorkspaceCreateParams as VideoWorkspaceCreateParams +from .video_workspace_update_response import VideoWorkspaceUpdateResponse as VideoWorkspaceUpdateResponse +from .video_workspace_update_params import VideoWorkspaceUpdateParams as VideoWorkspaceUpdateParams +from .video_workspace_retrieve_response import VideoWorkspaceRetrieveResponse as VideoWorkspaceRetrieveResponse +from .video_workspace_delete_response import VideoWorkspaceDeleteResponse as VideoWorkspaceDeleteResponse +from .folder_create_response import FolderCreateResponse as FolderCreateResponse +from .folder_create_params import FolderCreateParams as FolderCreateParams +from .folder_list_response import FolderListResponse as FolderListResponse +from .folder_list_params import FolderListParams as FolderListParams +from .folder_retrieve_response import FolderRetrieveResponse as FolderRetrieveResponse +from .folder_update_response import FolderUpdateResponse as FolderUpdateResponse +from .folder_update_params import FolderUpdateParams as FolderUpdateParams +from .folder_delete_response import FolderDeleteResponse as FolderDeleteResponse +from .folder_delete_assets_response import FolderDeleteAssetsResponse as FolderDeleteAssetsResponse +from .folder_delete_assets_params import FolderDeleteAssetsParams as FolderDeleteAssetsParams +from .channel_viewer_invite_response import ChannelViewerInviteResponse as ChannelViewerInviteResponse +from .channel_viewer_invite_params import ChannelViewerInviteParams as ChannelViewerInviteParams +from .channel_viewer_delete_response import ChannelViewerDeleteResponse as ChannelViewerDeleteResponse +from .channel_viewer_delete_params import ChannelViewerDeleteParams as ChannelViewerDeleteParams +from .channel_viewer_invite_csv_response import ChannelViewerInviteCsvResponse as ChannelViewerInviteCsvResponse +from .channel_viewer_invite_csv_params import ChannelViewerInviteCsvParams as ChannelViewerInviteCsvParams +from .channel_viewer_list_subscribers_response import ( + ChannelViewerListSubscribersResponse as ChannelViewerListSubscribersResponse, +) +from .channel_viewer_list_subscribers_params import ( + ChannelViewerListSubscribersParams as ChannelViewerListSubscribersParams, +) +from .video_analytic_chart_data_response import VideoAnalyticChartDataResponse as VideoAnalyticChartDataResponse +from .video_analytic_chart_data_params import VideoAnalyticChartDataParams as VideoAnalyticChartDataParams +from .video_analytic_breakdown_data_response import ( + VideoAnalyticBreakdownDataResponse as VideoAnalyticBreakdownDataResponse, +) +from .video_analytic_breakdown_data_params import VideoAnalyticBreakdownDataParams as VideoAnalyticBreakdownDataParams +from .video_analytic_aggregated_data_response import ( + VideoAnalyticAggregatedDataResponse as VideoAnalyticAggregatedDataResponse, +) +from .video_analytic_aggregated_data_params import ( + VideoAnalyticAggregatedDataParams as VideoAnalyticAggregatedDataParams, +) +from .organization_data_fetch_org_response import OrganizationDataFetchOrgResponse as OrganizationDataFetchOrgResponse +from .user_data_fetch_response import UserDataFetchResponse as UserDataFetchResponse +from .audit_log_fetch_response import AuditLogFetchResponse as AuditLogFetchResponse +from .audit_log_fetch_params import AuditLogFetchParams as AuditLogFetchParams +from .billing_list_invoices_response import BillingListInvoicesResponse as BillingListInvoicesResponse +from .billing_fetch_details_response import BillingFetchDetailsResponse as BillingFetchDetailsResponse +from .billing_update_details_response import BillingUpdateDetailsResponse as BillingUpdateDetailsResponse +from .billing_update_details_params import BillingUpdateDetailsParams as BillingUpdateDetailsParams +from .billing_fetch_upcoming_invoice_response import ( + BillingFetchUpcomingInvoiceResponse as BillingFetchUpcomingInvoiceResponse, +) +from .live_stream_workspace_list_response import LiveStreamWorkspaceListResponse as LiveStreamWorkspaceListResponse +from .live_stream_workspace_create_response import ( + LiveStreamWorkspaceCreateResponse as LiveStreamWorkspaceCreateResponse, +) +from .live_stream_workspace_create_params import LiveStreamWorkspaceCreateParams as LiveStreamWorkspaceCreateParams +from .live_stream_workspace_update_response import ( + LiveStreamWorkspaceUpdateResponse as LiveStreamWorkspaceUpdateResponse, +) +from .live_stream_workspace_update_params import LiveStreamWorkspaceUpdateParams as LiveStreamWorkspaceUpdateParams +from .live_stream_workspace_delete_response import ( + LiveStreamWorkspaceDeleteResponse as LiveStreamWorkspaceDeleteResponse, +) diff --git a/src/gumlet/types/audio_upload_complete_params.py b/src/gumlet/types/audio_upload_complete_params.py new file mode 100644 index 0000000..3b8cc1b --- /dev/null +++ b/src/gumlet/types/audio_upload_complete_params.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import Iterable +from typing_extensions import TypedDict + +__all__ = ["AudioUploadCompleteParams", "UploadResponse"] + + +class AudioUploadCompleteParams(TypedDict, total=False): + upload_responses: Iterable[UploadResponse] + + +class UploadResponse(TypedDict, total=False): + language_code: str + """Language Code for uploaded audio file.""" + + uploaded: bool + """Status of language uploaded audio file. (If status code was 200, You can mark true else false)""" diff --git a/src/gumlet/types/audio_upload_complete_response.py b/src/gumlet/types/audio_upload_complete_response.py new file mode 100644 index 0000000..8c1ca40 --- /dev/null +++ b/src/gumlet/types/audio_upload_complete_response.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from typing_extensions import TypeAlias + +__all__ = ["AudioUploadCompleteResponse"] + +AudioUploadCompleteResponse: TypeAlias = object diff --git a/src/gumlet/types/audio_upload_upload_params.py b/src/gumlet/types/audio_upload_upload_params.py new file mode 100644 index 0000000..a283e29 --- /dev/null +++ b/src/gumlet/types/audio_upload_upload_params.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict +from .._types import SequenceNotStr + +__all__ = ["AudioUploadUploadParams"] + + +class AudioUploadUploadParams(TypedDict, total=False): + language_codes: SequenceNotStr[str] + """List of language Code to upload audio file (use ISO 639-1 Language Codes)""" diff --git a/src/gumlet/types/audio_upload_upload_response.py b/src/gumlet/types/audio_upload_upload_response.py new file mode 100644 index 0000000..b6dcc3b --- /dev/null +++ b/src/gumlet/types/audio_upload_upload_response.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List + +from .._models import BaseModel + +__all__ = ["AudioUploadUploadResponse", "SignedURL"] + + +class SignedURL(BaseModel): + language_code: str + + upload_url: str + + +class AudioUploadUploadResponse(BaseModel): + asset_id: str + """Gumlet Asset ID""" + + signed_urls: List[SignedURL] diff --git a/src/gumlet/types/audit_log_fetch_params.py b/src/gumlet/types/audit_log_fetch_params.py new file mode 100644 index 0000000..6d469a0 --- /dev/null +++ b/src/gumlet/types/audit_log_fetch_params.py @@ -0,0 +1,77 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import Literal, Required, TypedDict +from .._types import SequenceNotStr + +__all__ = ["AuditLogFetchParams", "DateRange"] + + +class AuditLogFetchParams(TypedDict, total=False): + date_range: Required[DateRange] + + page_number: int + """Page number of results.""" + + page_size: int + + user_email: SequenceNotStr[str] + """Array of user emails to filter the activity logs.""" + + activity_type: List[ + Literal[ + "workspace_created", + "video_uploaded", + "video_details_updated", + "video_deleted", + "thumbnail_updated", + "subtitles_updated", + "audio_updated", + "processing_settings_updated", + "player_settings_updated", + "workspace_updated", + "team_member_invited", + "team_member_removed", + "import_started", + "playlist_created", + "playlist_updated", + "playlist_asset_changed", + "security_settings_updated", + "channel_updated", + "video_alert_changed", + "video_report_changed", + "live_stream_created", + "live_stream_started", + "live_stream_thumbnail_updated", + "live_stream_completed", + "live_stream_collection_created", + "live_stream_collection_updated", + "live_stream_deleted", + "image_source_created", + "image_source_updated", + "image_report_changed", + "image_alert_changed", + "user_profile_updated", + "api_key_changed", + "webhook_created", + "webhook_updated", + "webhook_deleted", + "drm_credentials_changed", + "plan_changed", + "billing_details_updated", + "playlist_deleted", + "payment_method_added", + "billing_alert_changed", + ] + ] + """Array of activity types to filter the audit logs""" + + +class DateRange(TypedDict, total=False): + start_at: Required[str] + """Starting date for audit logs. It's a string in YYYY-MM-DD format.""" + + end_at: Required[str] + """Ending date for audit logs. It's a string in YYYY-MM-DD format.""" diff --git a/src/gumlet/types/audit_log_fetch_response.py b/src/gumlet/types/audit_log_fetch_response.py new file mode 100644 index 0000000..4e436a1 --- /dev/null +++ b/src/gumlet/types/audit_log_fetch_response.py @@ -0,0 +1,61 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List + +from .._models import BaseModel + +__all__ = ["AuditLogFetchResponse", "Pagination", "Data", "DataSource"] + + +class DataSource(BaseModel): + id: str + """ID of the resource.""" + + type: str + """What kind of ID is there? For example, if the activity happened on a video workspace, the type would be `workspace`""" + + +class Data(BaseModel): + activity: str + """User friendly name of the activity""" + + ip: str + """IP address from where the activity happened.""" + + action_user: str + """Email id of the user who performed the activity.""" + + status_code: int + """API status code for the event""" + + response_time: float + """Total response time in milliseconds for the event.""" + + timestamp: float + """Milliseconds since epoch marking the exact timestamp for the activity.""" + + source: DataSource + """Details about affected resource""" + + +class Pagination(BaseModel): + page: int + """Current page.""" + + page_size: int + """Items returned in current page""" + + total_pages: int + """Total available pages.""" + + total: int + """Total available audit log items.""" + + +class AuditLogFetchResponse(BaseModel): + pagination: Pagination + """Details about pagination of the audit logs.""" + + data: List[Data] diff --git a/src/gumlet/types/billing_fetch_details_response.py b/src/gumlet/types/billing_fetch_details_response.py new file mode 100644 index 0000000..b9b5c48 --- /dev/null +++ b/src/gumlet/types/billing_fetch_details_response.py @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from .._models import BaseModel + +__all__ = ["BillingFetchDetailsResponse"] + + +class BillingFetchDetailsResponse(BaseModel): + company_name: str + """Company name""" + + gst_number: str + """GST / VAT number""" + + address_line: str + """Address line 1""" + + city: str + """City""" + + state_code: str + """ISO code of the state""" + + postal: str + """Postal code""" + + country_code: str + """ISO code of country. For example, it's "US" for USA.""" diff --git a/src/gumlet/types/billing_fetch_upcoming_invoice_response.py b/src/gumlet/types/billing_fetch_upcoming_invoice_response.py new file mode 100644 index 0000000..02ab17b --- /dev/null +++ b/src/gumlet/types/billing_fetch_upcoming_invoice_response.py @@ -0,0 +1,55 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = ["BillingFetchUpcomingInvoiceResponse", "UpcomingInvoice", "UpcomingInvoiceLines", "UpcomingInvoiceLinesData"] + + +class UpcomingInvoiceLinesData(BaseModel): + description: str + """Description about invoice line item""" + + quantity: str + """User friendly quantity of the line item""" + + unit_price: float + """Price per unit for the given item in given currency.""" + + amount: str + """Total amount of the line item""" + + +class UpcomingInvoiceLines(BaseModel): + data: List[UpcomingInvoiceLinesData] + + +class UpcomingInvoice(BaseModel): + subtotal: str + """Upcoming invoice amount without tax""" + + lines: UpcomingInvoiceLines + """Invoice line items""" + + +class BillingFetchUpcomingInvoiceResponse(BaseModel): + invoice_start_date: str + """Invoice start date in DD MMM YYYY format""" + + invoice_last_date: str + """Invoice end date in DD MMM YYYY format""" + + next_invoice_date: str + """Next invoice date in DD MMM format""" + + trial_end_date: Optional[str] = None + """Trial end date""" + + current_month_usage: str + """Current month total amount in given currency.""" + + upcoming_invoice: UpcomingInvoice + """Upcoming invoice details""" diff --git a/src/gumlet/types/billing_list_invoices_response.py b/src/gumlet/types/billing_list_invoices_response.py new file mode 100644 index 0000000..4b5f6ba --- /dev/null +++ b/src/gumlet/types/billing_list_invoices_response.py @@ -0,0 +1,38 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["BillingListInvoicesResponse", "StripeInvoice"] + + +class StripeInvoice(BaseModel): + id: str + """Invoice ID as per stripe. This is not invoice number.""" + + status: Literal["draft", "open", "paid", "uncollectible", "void"] + """Current status of the invoice""" + + created: int + """Created timestamp of invoice in seconds since epoch""" + + amount_due: int + """Amount due for invoice in the lowest denomination of the given currency. For example if the invoice is in USD, the amount shown here is in cents. Therefore, a value like 10000 means the invoice is $100.""" + + hosted_invoice_url: str + """URL of the invoice page where you can pay the invoice""" + + invoice_pdf: str + """URL of the page from where you can download the invoice PDF""" + + +class BillingListInvoicesResponse(BaseModel): + unpaid_invoices: bool + """Flag which shows if this account has any unpaid invoices.""" + + stripe_invoices: List[StripeInvoice] + """List of invoices""" diff --git a/src/gumlet/types/billing_update_details_params.py b/src/gumlet/types/billing_update_details_params.py new file mode 100644 index 0000000..7a9b911 --- /dev/null +++ b/src/gumlet/types/billing_update_details_params.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["BillingUpdateDetailsParams"] + + +class BillingUpdateDetailsParams(TypedDict, total=False): + address_line: Required[str] + """Address line 1""" + + city: Required[str] + """Name of the city""" + + company_name: Required[str] + """Company name""" + + country_code: Required[str] + """ISO country code""" + + gst_number: Required[str] + """GST / VAT details of the company""" + + postal: Required[str] + """Postal code of the company""" + + state_code: Required[str] + """ISO code of the state / region""" diff --git a/src/gumlet/types/billing_update_details_response.py b/src/gumlet/types/billing_update_details_response.py new file mode 100644 index 0000000..bf8c45c --- /dev/null +++ b/src/gumlet/types/billing_update_details_response.py @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from .._models import BaseModel + +__all__ = ["BillingUpdateDetailsResponse"] + + +class BillingUpdateDetailsResponse(BaseModel): + address_line: str + """Address line 1""" + + city: str + """Name of the city""" + + company_name: str + """Company name""" + + country_code: str + """ISO country code""" + + gst_number: str + """GST / VAT details of the company""" + + postal: str + """Postal code of the company""" + + state_code: str + """ISO code of the state / region""" diff --git a/src/gumlet/types/channel_viewer_delete_params.py b/src/gumlet/types/channel_viewer_delete_params.py new file mode 100644 index 0000000..bf72d49 --- /dev/null +++ b/src/gumlet/types/channel_viewer_delete_params.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict +from .._types import SequenceNotStr + +__all__ = ["ChannelViewerDeleteParams"] + + +class ChannelViewerDeleteParams(TypedDict, total=False): + emails: Required[SequenceNotStr[str]] + """Email addresses of viewers to remove. A maximum of 200 viewers can be removed in one request.""" diff --git a/src/gumlet/types/channel_viewer_delete_response.py b/src/gumlet/types/channel_viewer_delete_response.py new file mode 100644 index 0000000..a0447ef --- /dev/null +++ b/src/gumlet/types/channel_viewer_delete_response.py @@ -0,0 +1,11 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from typing import Optional + +from .._models import BaseModel + +__all__ = ["ChannelViewerDeleteResponse"] + + +class ChannelViewerDeleteResponse(BaseModel): + success: Optional[bool] = None diff --git a/src/gumlet/types/channel_viewer_invite_csv_params.py b/src/gumlet/types/channel_viewer_invite_csv_params.py new file mode 100644 index 0000000..f7f5dce --- /dev/null +++ b/src/gumlet/types/channel_viewer_invite_csv_params.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict +from .._types import FileTypes + +__all__ = ["ChannelViewerInviteCsvParams"] + + +class ChannelViewerInviteCsvParams(TypedDict, total=False): + viewers_csv: Required[FileTypes] + """CSV file containing viewer rows. Required columns: email, name. Maximum 500 viewers.""" diff --git a/src/gumlet/types/channel_viewer_invite_csv_response.py b/src/gumlet/types/channel_viewer_invite_csv_response.py new file mode 100644 index 0000000..dd0c0cb --- /dev/null +++ b/src/gumlet/types/channel_viewer_invite_csv_response.py @@ -0,0 +1,11 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from typing import Optional + +from .._models import BaseModel + +__all__ = ["ChannelViewerInviteCsvResponse"] + + +class ChannelViewerInviteCsvResponse(BaseModel): + success: Optional[bool] = None diff --git a/src/gumlet/types/channel_viewer_invite_params.py b/src/gumlet/types/channel_viewer_invite_params.py new file mode 100644 index 0000000..9114db4 --- /dev/null +++ b/src/gumlet/types/channel_viewer_invite_params.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import Iterable +from typing_extensions import Required, TypedDict + +__all__ = ["ChannelViewerInviteParams", "User"] + + +class ChannelViewerInviteParams(TypedDict, total=False): + users: Required[Iterable[User]] + """List of viewers to invite. A maximum of 200 viewers can be invited in one request.""" + + +class User(TypedDict, total=False): + email: Required[str] + """Viewer email address.""" + + name: Required[str] + """Viewer display name.""" diff --git a/src/gumlet/types/channel_viewer_invite_response.py b/src/gumlet/types/channel_viewer_invite_response.py new file mode 100644 index 0000000..457ac19 --- /dev/null +++ b/src/gumlet/types/channel_viewer_invite_response.py @@ -0,0 +1,11 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from typing import Optional + +from .._models import BaseModel + +__all__ = ["ChannelViewerInviteResponse"] + + +class ChannelViewerInviteResponse(BaseModel): + success: Optional[bool] = None diff --git a/src/gumlet/types/channel_viewer_list_subscribers_params.py b/src/gumlet/types/channel_viewer_list_subscribers_params.py new file mode 100644 index 0000000..d6a86e1 --- /dev/null +++ b/src/gumlet/types/channel_viewer_list_subscribers_params.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["ChannelViewerListSubscribersParams"] + + +class ChannelViewerListSubscribersParams(TypedDict, total=False): + page_number: int + """Page number to retrieve. Starts at 1.""" + + page_size: int + """Number of items to return per page""" diff --git a/src/gumlet/types/channel_viewer_list_subscribers_response.py b/src/gumlet/types/channel_viewer_list_subscribers_response.py new file mode 100644 index 0000000..4cd202b --- /dev/null +++ b/src/gumlet/types/channel_viewer_list_subscribers_response.py @@ -0,0 +1,43 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = ["ChannelViewerListSubscribersResponse", "Subscription"] + + +class Subscription(BaseModel): + id: str + """Subscriber ID""" + + email: str + """Email ID of the subscriber""" + + name: str + """Name of the subscriber""" + + status: str + """Current status of the subscriber""" + + channel_title: str + """Title of channel""" + + invited_at: Optional[str] = None + """ISO 8601 timestamp of the invitation time""" + + invited_by: Optional[str] = None + """User ID of the user who invited this subscriber""" + + invitation_link: Optional[str] = None + """URL of the invitation from where the person can accept the invite.""" + + +class ChannelViewerListSubscribersResponse(BaseModel): + subscriptions: List[Subscription] + """Details about subscribers""" + + total_count: int + """Number of total subscribers""" diff --git a/src/gumlet/types/folder_create_params.py b/src/gumlet/types/folder_create_params.py new file mode 100644 index 0000000..9c91dcf --- /dev/null +++ b/src/gumlet/types/folder_create_params.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Required, TypedDict + +__all__ = ["FolderCreateParams"] + + +class FolderCreateParams(TypedDict, total=False): + name: Required[str] + """Folder name.""" + + parent_id: Optional[str] + """Parent folder id. Send `null` or omit it to create a root-level folder.""" diff --git a/src/gumlet/types/folder_create_response.py b/src/gumlet/types/folder_create_response.py new file mode 100644 index 0000000..b23fc6d --- /dev/null +++ b/src/gumlet/types/folder_create_response.py @@ -0,0 +1,31 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = ["FolderCreateResponse"] + + +class FolderCreateResponse(BaseModel): + id: Optional[str] = None + + name: Optional[str] = None + + video_source_id: Optional[str] = None + + parent_id: Optional[str] = None + + path: Optional[List[str]] = None + + path_names: Optional[List[str]] = None + + depth: Optional[int] = None + + subdirectory_count: Optional[int] = None + + asset_count: Optional[int] = None + + created_at: Optional[str] = None + + updated_at: Optional[str] = None diff --git a/src/gumlet/types/folder_delete_assets_params.py b/src/gumlet/types/folder_delete_assets_params.py new file mode 100644 index 0000000..d3aa9b4 --- /dev/null +++ b/src/gumlet/types/folder_delete_assets_params.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict +from .._types import SequenceNotStr + +__all__ = ["FolderDeleteAssetsParams"] + + +class FolderDeleteAssetsParams(TypedDict, total=False): + asset_ids: Required[SequenceNotStr[str]] diff --git a/src/gumlet/types/folder_delete_assets_response.py b/src/gumlet/types/folder_delete_assets_response.py new file mode 100644 index 0000000..e59e2f2 --- /dev/null +++ b/src/gumlet/types/folder_delete_assets_response.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from typing import Optional + +from pydantic import Field as FieldInfo + +from .._models import BaseModel + +__all__ = ["FolderDeleteAssetsResponse"] + + +class FolderDeleteAssetsResponse(BaseModel): + message: Optional[str] = None + + removed_count: Optional[int] = FieldInfo(alias="removedCount", default=None) diff --git a/src/gumlet/types/folder_delete_response.py b/src/gumlet/types/folder_delete_response.py new file mode 100644 index 0000000..bb9096a --- /dev/null +++ b/src/gumlet/types/folder_delete_response.py @@ -0,0 +1,11 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from typing import Optional + +from .._models import BaseModel + +__all__ = ["FolderDeleteResponse"] + + +class FolderDeleteResponse(BaseModel): + message: Optional[str] = None diff --git a/src/gumlet/types/folder_list_params.py b/src/gumlet/types/folder_list_params.py new file mode 100644 index 0000000..80d5fdc --- /dev/null +++ b/src/gumlet/types/folder_list_params.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["FolderListParams"] + + +class FolderListParams(TypedDict, total=False): + parent_id: str + """Parent folder id. Send `null` to list root folders.""" diff --git a/src/gumlet/types/folder_list_response.py b/src/gumlet/types/folder_list_response.py new file mode 100644 index 0000000..49b158d --- /dev/null +++ b/src/gumlet/types/folder_list_response.py @@ -0,0 +1,35 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from typing import List, Optional +from typing_extensions import TypeAlias + +from .._models import BaseModel + +__all__ = ["FolderListResponse", "FolderListResponseItem"] + + +class FolderListResponseItem(BaseModel): + id: Optional[str] = None + + name: Optional[str] = None + + video_source_id: Optional[str] = None + + parent_id: Optional[str] = None + + path: Optional[List[str]] = None + + path_names: Optional[List[str]] = None + + depth: Optional[int] = None + + subdirectory_count: Optional[int] = None + + asset_count: Optional[int] = None + + created_at: Optional[str] = None + + updated_at: Optional[str] = None + + +FolderListResponse: TypeAlias = List[FolderListResponseItem] diff --git a/src/gumlet/types/folder_retrieve_response.py b/src/gumlet/types/folder_retrieve_response.py new file mode 100644 index 0000000..6c99de3 --- /dev/null +++ b/src/gumlet/types/folder_retrieve_response.py @@ -0,0 +1,31 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = ["FolderRetrieveResponse"] + + +class FolderRetrieveResponse(BaseModel): + id: Optional[str] = None + + name: Optional[str] = None + + video_source_id: Optional[str] = None + + parent_id: Optional[str] = None + + path: Optional[List[str]] = None + + path_names: Optional[List[str]] = None + + depth: Optional[int] = None + + subdirectory_count: Optional[int] = None + + asset_count: Optional[int] = None + + created_at: Optional[str] = None + + updated_at: Optional[str] = None diff --git a/src/gumlet/types/folder_update_params.py b/src/gumlet/types/folder_update_params.py new file mode 100644 index 0000000..40ced3f --- /dev/null +++ b/src/gumlet/types/folder_update_params.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import Optional, Union +from typing_extensions import Required, TypeAlias, TypedDict +from .._types import SequenceNotStr + +__all__ = ["FolderUpdateParams", "Variant", "Variant2"] + + +class Variant2(TypedDict, total=False): + name: str + """New folder name.""" + + parent_id: str + """Parent folder id in which we need to move assets.""" + + asset_ids: Required[SequenceNotStr[str]] + + +class Variant(TypedDict, total=False): + name: Required[str] + """New folder name.""" + + parent_id: Optional[str] + """New parent folder id. Send `null` to move the folder to the root level.""" + + asset_ids: SequenceNotStr[str] + """Asset ids to move into this folder.""" + + +FolderUpdateParams: TypeAlias = Union[Variant, Variant2] diff --git a/src/gumlet/types/folder_update_response.py b/src/gumlet/types/folder_update_response.py new file mode 100644 index 0000000..fd311ce --- /dev/null +++ b/src/gumlet/types/folder_update_response.py @@ -0,0 +1,45 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from typing import List, Optional, Union +from typing_extensions import TypeAlias + +from pydantic import Field as FieldInfo + +from .._models import BaseModel + +__all__ = ["FolderUpdateResponse", "FolderUpdateResponse2"] + + +class FolderUpdateResponse2(BaseModel): + message: Optional[str] = None + + moved_count: Optional[int] = FieldInfo(alias="movedCount", default=None) + + +class FolderUpdateResponse(BaseModel): + id: Optional[str] = None + + name: Optional[str] = None + + video_source_id: Optional[str] = None + + parent_id: Optional[str] = None + + path: Optional[List[str]] = None + + path_names: Optional[List[str]] = None + + depth: Optional[int] = None + + subdirectory_count: Optional[int] = None + + asset_count: Optional[int] = None + + created_at: Optional[str] = None + """ISO Timestamp""" + + updated_at: Optional[str] = None + """ISO Timestamp""" + + +FolderUpdateResponse: TypeAlias = Union[FolderUpdateResponse, FolderUpdateResponse2] diff --git a/src/gumlet/types/image_source_create_params.py b/src/gumlet/types/image_source_create_params.py new file mode 100644 index 0000000..ecd2894 --- /dev/null +++ b/src/gumlet/types/image_source_create_params.py @@ -0,0 +1,180 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = [ + "ImageSourceCreateParams", + "Aws", + "Proxy", + "Gcs", + "Dostorage", + "Wasabi", + "Cloudinary", + "Azure", + "Linode", + "Backblaze", + "Cloudflare", + "Webfolder", +] + + +class ImageSourceCreateParams(TypedDict, total=False): + namespace: Required[str] + """unique subdomain associated with the image source""" + + type: Required[ + Literal[ + "proxy", + "webfolder", + "aws", + "gcs", + "dostorage", + "wasabi", + "cloudinary", + "azure", + "linode", + "backblaze", + "cloudflare", + "zoom", + ] + ] + + aws: Aws + """This is a required field if source type is aws.""" + + proxy: Proxy + """This is a required field if source type is proxy.""" + + gcs: Gcs + """This is a required field if source type is gcs.""" + + dostorage: Dostorage + """This is a required field if source type is dostorage.""" + + wasabi: Wasabi + """This is a required field if source type is wasabi.""" + + cloudinary: Cloudinary + """This is a required field if source type is cloudinary.""" + + azure: Azure + """This is a required field if source type is azure.""" + + linode: Linode + """This is a required field if source type is linode.""" + + backblaze: Backblaze + """This is a required field if source type is backblaze.""" + + cloudflare: Cloudflare + """This is a required field if source type is cloudflare.""" + + webfolder: Webfolder + + +class Webfolder(TypedDict, total=False): + base_url: str + + +class Cloudflare(TypedDict, total=False): + bucket_name: Required[str] + + access_key: Required[str] + + account_id: Required[str] + + secret: Required[str] + + base_path: str + + +class Backblaze(TypedDict, total=False): + bucket_name: Required[str] + + bucket_region: str + """bucket_region or endpoint""" + + endpoint: str + """bucket_region or endpoint""" + + access_key: Required[str] + + secret: Required[str] + + base_path: str + + +class Linode(TypedDict, total=False): + bucket_name: Required[str] + + bucket_region: Required[str] + + access_key: Required[str] + + secret: Required[str] + + +class Azure(TypedDict, total=False): + azure_account_name: Required[str] + + azure_container_name: Required[str] + + azure_shared_token: Required[str] + + azure_path: Required[str] + + +class Cloudinary(TypedDict, total=False): + host_name: Required[str] + + cloud_name: Required[str] + + +class Wasabi(TypedDict, total=False): + bucket_name: Required[str] + + bucket_region: Required[str] + + access_key: Required[str] + + secret: Required[str] + + base_path: str + + +class Dostorage(TypedDict, total=False): + bucket_name: Required[str] + + bucket_region: Required[str] + + access_key: Required[str] + + secret: Required[str] + + base_path: str + + +class Gcs(TypedDict, total=False): + bucket_name: Required[str] + + service_account_key: Required[str] + + +class Proxy(TypedDict, total=False): + whitelisted_domains: Required[str] + + +class Aws(TypedDict, total=False): + bucket_name: Required[str] + + bucket_region: Required[str] + + access_key: Required[str] + + secret: Required[str] + + base_path: str + + endpoint: str diff --git a/src/gumlet/types/image_source_create_response.py b/src/gumlet/types/image_source_create_response.py new file mode 100644 index 0000000..c7d9253 --- /dev/null +++ b/src/gumlet/types/image_source_create_response.py @@ -0,0 +1,49 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import Optional + +from .._models import BaseModel + +__all__ = ["ImageSourceCreateResponse", "Webfolder", "DefaultParams"] + + +class DefaultParams(BaseModel): + format: Optional[str] = None + + compress: Optional[str] = None + + +class Webfolder(BaseModel): + base_url: Optional[str] = None + + +class ImageSourceCreateResponse(BaseModel): + id: Optional[str] = None + + namespace: Optional[str] = None + + type: Optional[str] = None + + cdn_type: Optional[str] = None + + cdn_cache_time: Optional[int] = None + + canonical_url: Optional[bool] = None + + browser_cache_time: Optional[int] = None + + is_cloudfront: Optional[bool] = None + + created_at: Optional[str] = None + + updated_at: Optional[str] = None + + webfolder: Optional[Webfolder] = None + + default_params: Optional[DefaultParams] = None + + subdomain: Optional[str] = None + + is_active: Optional[bool] = None diff --git a/src/gumlet/types/image_source_delete_response.py b/src/gumlet/types/image_source_delete_response.py new file mode 100644 index 0000000..d1eb253 --- /dev/null +++ b/src/gumlet/types/image_source_delete_response.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from typing_extensions import TypeAlias + +__all__ = ["ImageSourceDeleteResponse"] + +ImageSourceDeleteResponse: TypeAlias = object diff --git a/src/gumlet/types/image_source_list_params.py b/src/gumlet/types/image_source_list_params.py new file mode 100644 index 0000000..5b051da --- /dev/null +++ b/src/gumlet/types/image_source_list_params.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["ImageSourceListParams"] + + +class ImageSourceListParams(TypedDict, total=False): + offset: int + """Skip number of items. Helpful for pagination.""" + + size: int + """Results per page.""" diff --git a/src/gumlet/types/image_source_list_response.py b/src/gumlet/types/image_source_list_response.py new file mode 100644 index 0000000..0e4ac63 --- /dev/null +++ b/src/gumlet/types/image_source_list_response.py @@ -0,0 +1,252 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = [ + "ImageSourceListResponse", + "AllSource", + "AllSourceVideoProtection", + "AllSourcePlayerConfig", + "AllSourceAws", + "AllSourceEmbedDetails", + "AllSourceChannelSettings", + "AllSourceWebfolder", + "AllSourceWasabi", + "AllSourceGcs", + "AllSourceWordpress", + "AllSourceBackblaze", + "AllSourceHetzner", + "AllSourceDostorage", + "AllSourceAzure", + "AllSourceLinode", + "AllSourceCloudinary", +] + + +class AllSourceCloudinary(BaseModel): + cloud_name: str + + +class AllSourceLinode(BaseModel): + bucket_name: str + + bucket_region: str + + +class AllSourceAzure(BaseModel): + azure_account_name: str + + azure_container_name: Optional[str] = None + + azure_path: str + + +class AllSourceDostorage(BaseModel): + bucket_name: str + + base_path: Optional[str] = None + + +class AllSourceHetzner(BaseModel): + bucket_name: Optional[str] = None + + base_path: Optional[str] = None + + +class AllSourceBackblaze(BaseModel): + bucket_name: str + + base_path: Optional[str] = None + + +class AllSourceWordpress(BaseModel): + website_url: str + + +class AllSourceGcs(BaseModel): + bucket_name: str + + base_path: Optional[str] = None + + +class AllSourceWasabi(BaseModel): + bucket_name: str + + base_path: Optional[str] = None + + +class AllSourceWebfolder(BaseModel): + base_url: str + + +class AllSourceChannelSettings(BaseModel): + title: Optional[str] = None + + active: Optional[bool] = None + + description: Optional[str] = None + + privacy_type: Optional[str] = None + + custom_logo: Optional[bool] = None + + logo_url: Optional[str] = None + + cname: Optional[List[str]] = None + + temp_cname: Optional[List[str]] = None + + +class AllSourceEmbedDetails(BaseModel): + pixel_tags: Optional[object] = None + + preload: Optional[bool] = None + + autoplay: Optional[bool] = None + + logo_width: Optional[int] = None + + logo_height: Optional[int] = None + + player_color: Optional[str] = None + + is_seo: Optional[bool] = None + + dynamic_watermark: Optional[bool] = None + + disable_seek: Optional[bool] = None + + disable_player_controls: Optional[bool] = None + + allow_drm_protected_videos: Optional[bool] = None + + powered_by_gumlet_overlay: Optional[bool] = None + + loop: Optional[bool] = None + + subtitle_enabled: Optional[bool] = None + + watermark_bg_color: Optional[str] = None + + watermark_font_color: Optional[str] = None + + watermark_font_size: Optional[int] = None + + watermark_interval: Optional[int] = None + + +class AllSourceAws(BaseModel): + bucket_name: Optional[str] = None + + bucket_region: Optional[str] = None + + access_key: Optional[str] = None + + secret: Optional[str] = None + + base_path: Optional[str] = None + + +class AllSourcePlayerConfig(BaseModel): + preload: Optional[bool] = None + + autoplay: Optional[bool] = None + + disable_seek: Optional[bool] = None + + disable_player_controls: Optional[bool] = None + + powered_by_gumlet_overlay: Optional[bool] = None + + allow_drm_protected_videos: Optional[bool] = None + + loop: Optional[bool] = None + + player_color: Optional[str] = None + + include_seo: Optional[bool] = None + + subtitle_enabled: Optional[bool] = None + + pixel_tags: Optional[object] = None + + logo_width: Optional[int] = None + + logo_height: Optional[int] = None + + dynamic_watermark: Optional[bool] = None + + watermark_font_size: Optional[int] = None + + watermark_font_color: Optional[str] = None + + watermark_bg_color: Optional[str] = None + + watermark_interval: Optional[int] = None + + +class AllSourceVideoProtection(BaseModel): + signed_url: Optional[bool] = None + + signed_url_secret: Optional[str] = None + + +class AllSource(BaseModel): + id: str + + type: Optional[str] = None + + created_at: Optional[str] = None + + updated_at: Optional[str] = None + + video_protection: Optional[AllSourceVideoProtection] = None + + player_config: Optional[AllSourcePlayerConfig] = None + + default_profile_id: Optional[str] = None + + insight_property_id: Optional[str] = None + + aws: Optional[AllSourceAws] = None + + embed_details: Optional[AllSourceEmbedDetails] = None + + folders: Optional[List[str]] = None + + channel_settings: Optional[AllSourceChannelSettings] = None + + namespace: str + + subdomain: str + + webfolder: Optional[AllSourceWebfolder] = None + + cname: Optional[List[str]] = None + """Custom domains assigned to this source.""" + + wasabi: Optional[AllSourceWasabi] = None + + gcs: Optional[AllSourceGcs] = None + + wordpress: Optional[AllSourceWordpress] = None + + backblaze: Optional[AllSourceBackblaze] = None + + hetzner: Optional[AllSourceHetzner] = None + + dostorage: Optional[AllSourceDostorage] = None + + azure: Optional[AllSourceAzure] = None + + linode: Optional[AllSourceLinode] = None + + cloudinary: Optional[AllSourceCloudinary] = None + + +class ImageSourceListResponse(BaseModel): + all_sources: Optional[List[AllSource]] = None diff --git a/src/gumlet/types/image_source_purge_cache_params.py b/src/gumlet/types/image_source_purge_cache_params.py new file mode 100644 index 0000000..bbdbade --- /dev/null +++ b/src/gumlet/types/image_source_purge_cache_params.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict +from .._types import SequenceNotStr + +__all__ = ["ImageSourcePurgeCacheParams"] + + +class ImageSourcePurgeCacheParams(TypedDict, total=False): + paths: SequenceNotStr[str] + """An array of path of images to purge. It should be provided without any query parameters.""" diff --git a/src/gumlet/types/image_source_purge_cache_response.py b/src/gumlet/types/image_source_purge_cache_response.py new file mode 100644 index 0000000..6061d98 --- /dev/null +++ b/src/gumlet/types/image_source_purge_cache_response.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from .._models import BaseModel + +__all__ = ["ImageSourcePurgeCacheResponse"] + + +class ImageSourcePurgeCacheResponse(BaseModel): + status: str diff --git a/src/gumlet/types/image_source_purge_params.py b/src/gumlet/types/image_source_purge_params.py new file mode 100644 index 0000000..e2ea86c --- /dev/null +++ b/src/gumlet/types/image_source_purge_params.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict +from .._types import SequenceNotStr + +__all__ = ["ImageSourcePurgeParams"] + + +class ImageSourcePurgeParams(TypedDict, total=False): + paths: Required[SequenceNotStr[str]] + """An array of path of images to purge. It should be provided without any query parameters.""" diff --git a/src/gumlet/types/image_source_purge_response.py b/src/gumlet/types/image_source_purge_response.py new file mode 100644 index 0000000..6dc2b1d --- /dev/null +++ b/src/gumlet/types/image_source_purge_response.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from .._models import BaseModel + +__all__ = ["ImageSourcePurgeResponse"] + + +class ImageSourcePurgeResponse(BaseModel): + status: str diff --git a/src/gumlet/types/image_source_retrieve_response.py b/src/gumlet/types/image_source_retrieve_response.py new file mode 100644 index 0000000..45036a7 --- /dev/null +++ b/src/gumlet/types/image_source_retrieve_response.py @@ -0,0 +1,419 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List, Optional +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from .._models import BaseModel + +__all__ = [ + "ImageSourceRetrieveResponse", + "Webfolder", + "Aws", + "Proxy", + "Gcs", + "Dostorage", + "Wasabi", + "Linode", + "Backblaze", + "Cloudflare", + "Cloudinary", + "Azure", + "FallbackOrigin", + "FallbackOriginReplaceOperation", + "FallbackOriginWebfolder", + "FallbackOriginAws", + "FallbackOriginProxy", + "FallbackOriginGcs", + "FallbackOriginDostorage", + "FallbackOriginWasabi", + "FallbackOriginLinode", + "FallbackOriginBackblaze", + "FallbackOriginCloudflare", + "FallbackOriginCloudinary", + "FallbackOriginAzure", + "Wordpress", +] + + +class Wordpress(BaseModel): + website_url: str + + +class FallbackOriginAzure(BaseModel): + azure_account_name: str + + azure_container_name: str + + azure_shared_token: str + + azure_path: str + + +class FallbackOriginCloudinary(BaseModel): + host_name: str + + cloud_name: str + + +class FallbackOriginCloudflare(BaseModel): + bucket_name: str + + access_key: str + + account_id: str + + secret: str + + base_path: Optional[str] = None + + +class FallbackOriginBackblaze(BaseModel): + bucket_name: str + + bucket_region: Optional[str] = None + """bucket_region or endpoint""" + + endpoint: Optional[str] = None + """bucket_region or endpoint""" + + access_key: str + + secret: str + + base_path: Optional[str] = None + + +class FallbackOriginLinode(BaseModel): + bucket_name: str + + bucket_region: str + + access_key: str + + secret: str + + +class FallbackOriginWasabi(BaseModel): + bucket_name: str + + bucket_region: str + + access_key: str + + secret: str + + base_path: Optional[str] = None + + +class FallbackOriginDostorage(BaseModel): + bucket_name: str + + bucket_region: str + + access_key: str + + secret: str + + base_path: Optional[str] = None + + +class FallbackOriginGcs(BaseModel): + bucket_name: str + + service_account_key: str + + +class FallbackOriginProxy(BaseModel): + whitelisted_domains: str + + +class FallbackOriginAws(BaseModel): + bucket_name: str + + bucket_region: str + + access_key: str + + secret: str + + base_path: Optional[str] = None + + endpoint: Optional[str] = None + + +class FallbackOriginWebfolder(BaseModel): + base_url: Optional[str] = None + + +class FallbackOriginReplaceOperation(BaseModel): + from_: Optional[str] = FieldInfo(alias="from", default=None) + + to: Optional[str] = None + + +class FallbackOrigin(BaseModel): + name: str + + type: Literal[ + "dostorage", + "aws", + "wasabi", + "hetzner", + "backblaze", + "webfolder", + "gcs", + "azure", + "cloudflare", + "imgix", + "cloudinary", + "proxy", + "wordpress", + "linode", + ] + + conditions: Optional[List[str]] = None + + replace_operation: Optional[FallbackOriginReplaceOperation] = None + + webfolder: Optional[FallbackOriginWebfolder] = None + """This is a required field if source type is webfolder.""" + + aws: Optional[FallbackOriginAws] = None + """This is a required field if source type is aws.""" + + proxy: Optional[FallbackOriginProxy] = None + """This is a required field if source type is proxy.""" + + gcs: Optional[FallbackOriginGcs] = None + """This is a required field if source type is gcs.""" + + dostorage: Optional[FallbackOriginDostorage] = None + """This is a required field if source type is dostorage.""" + + wasabi: Optional[FallbackOriginWasabi] = None + """This is a required field if source type is wasabi.""" + + linode: Optional[FallbackOriginLinode] = None + """This is a required field if source type is linode.""" + + backblaze: Optional[FallbackOriginBackblaze] = None + """This is a required field if source type is backblaze.""" + + cloudflare: Optional[FallbackOriginCloudflare] = None + """This is a required field if source type is cloudflare.""" + + cloudinary: Optional[FallbackOriginCloudinary] = None + """This is a required field if source type is cloudinary.""" + + azure: Optional[FallbackOriginAzure] = None + """This is a required field if source type is azure.""" + + field_id: Optional[str] = FieldInfo(alias="_id", default=None) + + hetzner: Optional[object] = None + + imgix: Optional[object] = None + + +class Azure(BaseModel): + azure_account_name: str + + azure_container_name: str + + azure_shared_token: str + + azure_path: str + + +class Cloudinary(BaseModel): + host_name: str + + cloud_name: str + + +class Cloudflare(BaseModel): + bucket_name: str + + access_key: str + + account_id: str + + secret: str + + base_path: Optional[str] = None + + +class Backblaze(BaseModel): + bucket_name: str + + bucket_region: Optional[str] = None + """bucket_region or endpoint""" + + endpoint: Optional[str] = None + """bucket_region or endpoint""" + + access_key: str + + secret: str + + base_path: Optional[str] = None + + +class Linode(BaseModel): + bucket_name: str + + bucket_region: str + + access_key: str + + secret: str + + +class Wasabi(BaseModel): + bucket_name: str + + bucket_region: str + + access_key: str + + secret: str + + base_path: Optional[str] = None + + +class Dostorage(BaseModel): + bucket_name: str + + bucket_region: str + + access_key: str + + secret: str + + base_path: Optional[str] = None + + +class Gcs(BaseModel): + bucket_name: str + + service_account_key: str + + +class Proxy(BaseModel): + whitelisted_domains: str + + +class Aws(BaseModel): + bucket_name: str + + bucket_region: str + + access_key: str + + secret: str + + base_path: Optional[str] = None + + endpoint: Optional[str] = None + + +class Webfolder(BaseModel): + base_url: Optional[str] = None + + +class ImageSourceRetrieveResponse(BaseModel): + id: Optional[str] = None + + namespace: str + + type: Literal[ + "dostorage", + "aws", + "wasabi", + "hetzner", + "backblaze", + "webfolder", + "gcs", + "azure", + "cloudflare", + "imgix", + "cloudinary", + "proxy", + "wordpress", + "linode", + ] + + cdn_type: Optional[str] = None + + cdn_cache_time: Optional[int] = None + + canonical_url: Optional[bool] = None + + browser_cache_time: Optional[int] = None + + is_cloudfront: Optional[bool] = None + + created_at: Optional[str] = None + + updated_at: Optional[str] = None + + webfolder: Optional[Webfolder] = None + """This is a required field if source type is webfolder.""" + + aws: Optional[Aws] = None + """This is a required field if source type is aws.""" + + proxy: Optional[Proxy] = None + """This is a required field if source type is proxy.""" + + gcs: Optional[Gcs] = None + """This is a required field if source type is gcs.""" + + dostorage: Optional[Dostorage] = None + """This is a required field if source type is dostorage.""" + + wasabi: Optional[Wasabi] = None + """This is a required field if source type is wasabi.""" + + linode: Optional[Linode] = None + """This is a required field if source type is linode.""" + + backblaze: Optional[Backblaze] = None + """This is a required field if source type is backblaze.""" + + cloudflare: Optional[Cloudflare] = None + """This is a required field if source type is cloudflare.""" + + cloudinary: Optional[Cloudinary] = None + """This is a required field if source type is cloudinary.""" + + azure: Optional[Azure] = None + """This is a required field if source type is azure.""" + + default_params: Optional[object] = None + + request_headers: Optional[List[object]] = None + + subdomain: Optional[str] = None + + is_active: Optional[bool] = None + + secure_urls: Optional[bool] = None + """Gives information if signed URLs are enabled.""" + + secure_token: Optional[str] = None + """This is the token that you can use to sign URLs.""" + + imgix: Optional[object] = None + + hetzner: Optional[object] = None + + fallback_origins: Optional[List[FallbackOrigin]] = None + + wordpress: Optional[Wordpress] = None + + response_headers: Optional[List[object]] = None diff --git a/src/gumlet/types/image_source_update_params.py b/src/gumlet/types/image_source_update_params.py new file mode 100644 index 0000000..4e95405 --- /dev/null +++ b/src/gumlet/types/image_source_update_params.py @@ -0,0 +1,398 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import Iterable +from typing_extensions import Literal, Required, TypedDict +from .._types import SequenceNotStr + +__all__ = [ + "ImageSourceUpdateParams", + "Webfolder", + "Aws", + "Proxy", + "Gcs", + "Dostorage", + "Wasabi", + "Linode", + "Backblaze", + "Cloudflare", + "Cloudinary", + "Azure", + "FallbackOrigin", + "FallbackOriginReplaceOperation", + "FallbackOriginWebfolder", + "FallbackOriginAws", + "FallbackOriginProxy", + "FallbackOriginGcs", + "FallbackOriginDostorage", + "FallbackOriginWasabi", + "FallbackOriginLinode", + "FallbackOriginBackblaze", + "FallbackOriginCloudflare", + "FallbackOriginCloudinary", + "FallbackOriginAzure", +] + + +class ImageSourceUpdateParams(TypedDict, total=False): + type: Literal[ + "proxy", + "direct-upload", + "webfolder", + "aws", + "gcs", + "dostorage", + "wasabi", + "cloudinary", + "azure", + "linode", + "backblaze", + "cloudflare", + ] + + webfolder: Webfolder + """This is a required field if source type is webfolder.""" + + aws: Aws + """This is a required field if source type is aws.""" + + proxy: Proxy + """This is a required field if source type is proxy.""" + + gcs: Gcs + """This is a required field if source type is gcs.""" + + dostorage: Dostorage + """This is a required field if source type is dostorage.""" + + wasabi: Wasabi + """This is a required field if source type is wasabi.""" + + linode: Linode + """This is a required field if source type is linode.""" + + backblaze: Backblaze + """This is a required field if source type is backblaze.""" + + cloudflare: Cloudflare + """This is a required field if source type is cloudflare.""" + + cloudinary: Cloudinary + """This is a required field if source type is cloudinary.""" + + azure: Azure + """This is a required field if source type is azure.""" + + default_params: object + + error_image: str + """URL for error image to display when we get broken image from your origin.""" + + request_headers: Iterable[object] + + response_headers: Iterable[object] + + temp_cname: SequenceNotStr[str] + + browser_cache_time: int + """Browser cache time in seconds. For example setting this to 3600 caches the image in browser for 3600 seconds (1 hour)""" + + cdn_cache_time: int + """CDN cache time in seconds.""" + + is_active: bool + """Enable / disable source.""" + + cname: SequenceNotStr[str] + """List of verified CNAMEs""" + + fallback_origins: Iterable[FallbackOrigin] + """List of fallback origins""" + + +class FallbackOriginAzure(TypedDict, total=False): + azure_account_name: Required[str] + + azure_container_name: Required[str] + + azure_shared_token: Required[str] + + azure_path: Required[str] + + +class FallbackOriginCloudinary(TypedDict, total=False): + host_name: Required[str] + + cloud_name: Required[str] + + +class FallbackOriginCloudflare(TypedDict, total=False): + bucket_name: Required[str] + + access_key: Required[str] + + account_id: Required[str] + + secret: Required[str] + + base_path: str + + +class FallbackOriginBackblaze(TypedDict, total=False): + bucket_name: Required[str] + + bucket_region: str + """bucket_region or endpoint""" + + endpoint: str + """bucket_region or endpoint""" + + access_key: Required[str] + + secret: Required[str] + + base_path: str + + +class FallbackOriginLinode(TypedDict, total=False): + bucket_name: Required[str] + + bucket_region: Required[str] + + access_key: Required[str] + + secret: Required[str] + + +class FallbackOriginWasabi(TypedDict, total=False): + bucket_name: Required[str] + + bucket_region: Required[str] + + access_key: Required[str] + + secret: Required[str] + + base_path: str + + +class FallbackOriginDostorage(TypedDict, total=False): + bucket_name: Required[str] + + bucket_region: Required[str] + + access_key: Required[str] + + secret: Required[str] + + base_path: str + + +class FallbackOriginGcs(TypedDict, total=False): + bucket_name: Required[str] + + service_account_key: Required[str] + + +class FallbackOriginProxy(TypedDict, total=False): + whitelisted_domains: Required[str] + + +class FallbackOriginAws(TypedDict, total=False): + bucket_name: Required[str] + + bucket_region: Required[str] + + access_key: Required[str] + + secret: Required[str] + + base_path: str + + endpoint: str + + +class FallbackOriginWebfolder(TypedDict, total=False): + base_url: str + + +_FallbackOriginReplaceOperationReservedKeywords = TypedDict( + "_FallbackOriginReplaceOperationReservedKeywords", + { + "from": str, + }, + total=False, +) + + +class FallbackOriginReplaceOperation(_FallbackOriginReplaceOperationReservedKeywords, total=False): + to: str + + +class FallbackOrigin(TypedDict, total=False): + conditions: Required[SequenceNotStr[str]] + + name: Required[str] + """Name of fallback""" + + type: Required[ + Literal[ + "dostorage", + "aws", + "wasabi", + "hetzner", + "backblaze", + "webfolder", + "gcs", + "azure", + "cloudflare", + "imgix", + "cloudinary", + "proxy", + "wordpress", + "linode", + ] + ] + """Type of fallback origin""" + + replace_operation: Required[FallbackOriginReplaceOperation] + + webfolder: FallbackOriginWebfolder + """This is a required field if source type is webfolder.""" + + aws: FallbackOriginAws + """This is a required field if source type is aws.""" + + proxy: FallbackOriginProxy + """This is a required field if source type is proxy.""" + + gcs: FallbackOriginGcs + """This is a required field if source type is gcs.""" + + dostorage: FallbackOriginDostorage + """This is a required field if source type is dostorage.""" + + wasabi: FallbackOriginWasabi + """This is a required field if source type is wasabi.""" + + linode: FallbackOriginLinode + """This is a required field if source type is linode.""" + + backblaze: FallbackOriginBackblaze + """This is a required field if source type is backblaze.""" + + cloudflare: FallbackOriginCloudflare + """This is a required field if source type is cloudflare.""" + + cloudinary: FallbackOriginCloudinary + """This is a required field if source type is cloudinary.""" + + azure: FallbackOriginAzure + """This is a required field if source type is azure.""" + + +class Azure(TypedDict, total=False): + azure_account_name: Required[str] + + azure_container_name: Required[str] + + azure_shared_token: Required[str] + + azure_path: Required[str] + + +class Cloudinary(TypedDict, total=False): + host_name: Required[str] + + cloud_name: Required[str] + + +class Cloudflare(TypedDict, total=False): + bucket_name: Required[str] + + access_key: Required[str] + + account_id: Required[str] + + secret: Required[str] + + base_path: str + + +class Backblaze(TypedDict, total=False): + bucket_name: Required[str] + + bucket_region: str + """bucket_region or endpoint""" + + endpoint: str + """bucket_region or endpoint""" + + access_key: Required[str] + + secret: Required[str] + + base_path: str + + +class Linode(TypedDict, total=False): + bucket_name: Required[str] + + bucket_region: Required[str] + + access_key: Required[str] + + secret: Required[str] + + +class Wasabi(TypedDict, total=False): + bucket_name: Required[str] + + bucket_region: Required[str] + + access_key: Required[str] + + secret: Required[str] + + base_path: str + + +class Dostorage(TypedDict, total=False): + bucket_name: Required[str] + + bucket_region: Required[str] + + access_key: Required[str] + + secret: Required[str] + + base_path: str + + +class Gcs(TypedDict, total=False): + bucket_name: Required[str] + + service_account_key: Required[str] + + +class Proxy(TypedDict, total=False): + whitelisted_domains: Required[str] + + +class Aws(TypedDict, total=False): + bucket_name: Required[str] + + bucket_region: Required[str] + + access_key: Required[str] + + secret: Required[str] + + base_path: str + + endpoint: str + + +class Webfolder(TypedDict, total=False): + base_url: str diff --git a/src/gumlet/types/image_source_update_response.py b/src/gumlet/types/image_source_update_response.py new file mode 100644 index 0000000..b396e28 --- /dev/null +++ b/src/gumlet/types/image_source_update_response.py @@ -0,0 +1,408 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List, Optional +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from .._models import BaseModel + +__all__ = [ + "ImageSourceUpdateResponse", + "Webfolder", + "Aws", + "Proxy", + "FallbackOrigin", + "FallbackOriginReplaceOperation", + "FallbackOriginWebfolder", + "FallbackOriginAws", + "FallbackOriginProxy", + "FallbackOriginGcs", + "FallbackOriginDostorage", + "FallbackOriginWasabi", + "FallbackOriginLinode", + "FallbackOriginBackblaze", + "FallbackOriginCloudflare", + "FallbackOriginCloudinary", + "FallbackOriginAzure", + "Gcs", + "Dostorage", + "Wasabi", + "Linode", + "Backblaze", + "Cloudflare", + "Cloudinary", + "Azure", +] + + +class Azure(BaseModel): + azure_account_name: str + + azure_container_name: str + + azure_shared_token: str + + azure_path: str + + +class Cloudinary(BaseModel): + host_name: str + + cloud_name: str + + +class Cloudflare(BaseModel): + bucket_name: str + + access_key: str + + account_id: str + + secret: str + + base_path: Optional[str] = None + + +class Backblaze(BaseModel): + bucket_name: str + + bucket_region: Optional[str] = None + """bucket_region or endpoint""" + + endpoint: Optional[str] = None + """bucket_region or endpoint""" + + access_key: str + + secret: str + + base_path: Optional[str] = None + + +class Linode(BaseModel): + bucket_name: str + + bucket_region: str + + access_key: str + + secret: str + + +class Wasabi(BaseModel): + bucket_name: str + + bucket_region: str + + access_key: str + + secret: str + + base_path: Optional[str] = None + + +class Dostorage(BaseModel): + bucket_name: str + + bucket_region: str + + access_key: str + + secret: str + + base_path: Optional[str] = None + + +class Gcs(BaseModel): + bucket_name: str + + service_account_key: str + + +class FallbackOriginAzure(BaseModel): + azure_account_name: str + + azure_container_name: str + + azure_shared_token: str + + azure_path: str + + +class FallbackOriginCloudinary(BaseModel): + host_name: str + + cloud_name: str + + +class FallbackOriginCloudflare(BaseModel): + bucket_name: str + + access_key: str + + account_id: str + + secret: str + + base_path: Optional[str] = None + + +class FallbackOriginBackblaze(BaseModel): + bucket_name: str + + bucket_region: Optional[str] = None + """bucket_region or endpoint""" + + endpoint: Optional[str] = None + """bucket_region or endpoint""" + + access_key: str + + secret: str + + base_path: Optional[str] = None + + +class FallbackOriginLinode(BaseModel): + bucket_name: str + + bucket_region: str + + access_key: str + + secret: str + + +class FallbackOriginWasabi(BaseModel): + bucket_name: str + + bucket_region: str + + access_key: str + + secret: str + + base_path: Optional[str] = None + + +class FallbackOriginDostorage(BaseModel): + bucket_name: str + + bucket_region: str + + access_key: str + + secret: str + + base_path: Optional[str] = None + + +class FallbackOriginGcs(BaseModel): + bucket_name: str + + service_account_key: str + + +class FallbackOriginProxy(BaseModel): + whitelisted_domains: str + + +class FallbackOriginAws(BaseModel): + bucket_name: str + + bucket_region: str + + access_key: str + + secret: str + + base_path: Optional[str] = None + + endpoint: Optional[str] = None + + +class FallbackOriginWebfolder(BaseModel): + base_url: Optional[str] = None + + +class FallbackOriginReplaceOperation(BaseModel): + from_: Optional[str] = FieldInfo(alias="from", default=None) + + to: Optional[str] = None + + +class FallbackOrigin(BaseModel): + name: str + + type: Literal[ + "dostorage", + "aws", + "wasabi", + "hetzner", + "backblaze", + "webfolder", + "gcs", + "azure", + "cloudflare", + "imgix", + "cloudinary", + "proxy", + "wordpress", + "linode", + ] + + conditions: Optional[List[str]] = None + + replace_operation: Optional[FallbackOriginReplaceOperation] = None + + webfolder: Optional[FallbackOriginWebfolder] = None + """This is a required field if source type is webfolder.""" + + aws: Optional[FallbackOriginAws] = None + """This is a required field if source type is aws.""" + + proxy: Optional[FallbackOriginProxy] = None + """This is a required field if source type is proxy.""" + + gcs: Optional[FallbackOriginGcs] = None + """This is a required field if source type is gcs.""" + + dostorage: Optional[FallbackOriginDostorage] = None + """This is a required field if source type is dostorage.""" + + wasabi: Optional[FallbackOriginWasabi] = None + """This is a required field if source type is wasabi.""" + + linode: Optional[FallbackOriginLinode] = None + """This is a required field if source type is linode.""" + + backblaze: Optional[FallbackOriginBackblaze] = None + """This is a required field if source type is backblaze.""" + + cloudflare: Optional[FallbackOriginCloudflare] = None + """This is a required field if source type is cloudflare.""" + + cloudinary: Optional[FallbackOriginCloudinary] = None + """This is a required field if source type is cloudinary.""" + + azure: Optional[FallbackOriginAzure] = None + """This is a required field if source type is azure.""" + + field_id: Optional[str] = FieldInfo(alias="_id", default=None) + + hetzner: Optional[object] = None + + imgix: Optional[object] = None + + +class Proxy(BaseModel): + whitelisted_domains: str + + +class Aws(BaseModel): + bucket_name: str + + bucket_region: str + + access_key: str + + secret: str + + base_path: Optional[str] = None + + endpoint: Optional[str] = None + + +class Webfolder(BaseModel): + base_url: Optional[str] = None + + +class ImageSourceUpdateResponse(BaseModel): + id: Optional[str] = None + + namespace: Optional[str] = None + + type: Literal[ + "dostorage", + "aws", + "wasabi", + "hetzner", + "backblaze", + "webfolder", + "gcs", + "azure", + "cloudflare", + "imgix", + "cloudinary", + "proxy", + "wordpress", + "linode", + ] + + cdn_type: Optional[str] = None + + cdn_cache_time: Optional[int] = None + + canonical_url: Optional[bool] = None + + browser_cache_time: Optional[int] = None + + is_cloudfront: Optional[bool] = None + + created_at: Optional[str] = None + + updated_at: Optional[str] = None + + webfolder: Optional[Webfolder] = None + """This is a required field if source type is webfolder.""" + + aws: Optional[Aws] = None + """This is a required field if source type is aws.""" + + proxy: Optional[Proxy] = None + """This is a required field if source type is proxy.""" + + fallback_origins: Optional[List[FallbackOrigin]] = None + + gcs: Optional[Gcs] = None + """This is a required field if source type is gcs.""" + + dostorage: Optional[Dostorage] = None + """This is a required field if source type is dostorage.""" + + wasabi: Optional[Wasabi] = None + """This is a required field if source type is wasabi.""" + + linode: Optional[Linode] = None + """This is a required field if source type is linode.""" + + backblaze: Optional[Backblaze] = None + """This is a required field if source type is backblaze.""" + + cloudflare: Optional[Cloudflare] = None + """This is a required field if source type is cloudflare.""" + + cloudinary: Optional[Cloudinary] = None + """This is a required field if source type is cloudinary.""" + + azure: Optional[Azure] = None + """This is a required field if source type is azure.""" + + default_params: Optional[object] = None + + request_headers: Optional[List[object]] = None + + subdomain: Optional[str] = None + + is_active: Optional[bool] = None + + secure_urls: Optional[bool] = None + + secure_token: Optional[str] = None + + response_headers: Optional[List[object]] = None + + imgix: Optional[object] = None diff --git a/src/gumlet/types/image_usage_analytic_retrieve_params.py b/src/gumlet/types/image_usage_analytic_retrieve_params.py new file mode 100644 index 0000000..353509c --- /dev/null +++ b/src/gumlet/types/image_usage_analytic_retrieve_params.py @@ -0,0 +1,56 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List, Union +from datetime import date +from typing_extensions import Annotated, Literal, Required, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["ImageUsageAnalyticRetrieveParams", "DateRange", "Filters"] + + +class ImageUsageAnalyticRetrieveParams(TypedDict, total=False): + metrics: Required[ + List[ + Literal[ + "bandwidth_consumption", + "requests_count", + "transformations_count", + "bandwidth_savings", + "origin_hit_rate", + "avg_transformation_response_time", + "status_4xx", + "status_5xx", + "cdn_hit_rate", + "content_type", + "status_2xx", + "avg_response_time", + "top_assets", + "bandwidth_consumption_by_source", + "ai_credit_usage", + ] + ] + ] + """Define the metric you need the data for, currently we support "bandwidth_consumption", "requests_count","status_4xx","status_5xx","avg_response_time"\"""" + + date_range: Required[DateRange] + """The timeframe to get the data for. Currently we only support a maximum of 30 days between `start_at` and `end_at`.""" + + group_by: Literal["daily", "weekly", "monthly", "hourly"] + + filters: Filters + + +class Filters(TypedDict, total=False): + source_id: str + """Source ID""" + + +class DateRange(TypedDict, total=False): + start_at: Annotated[Union[str, date], PropertyInfo(format="iso8601")] + """The starting date to consider""" + + end_at: Annotated[Union[str, date], PropertyInfo(format="iso8601")] + """The ending date to consider""" diff --git a/src/gumlet/types/image_usage_analytic_retrieve_response.py b/src/gumlet/types/image_usage_analytic_retrieve_response.py new file mode 100644 index 0000000..0d4aec4 --- /dev/null +++ b/src/gumlet/types/image_usage_analytic_retrieve_response.py @@ -0,0 +1,191 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = [ + "ImageUsageAnalyticRetrieveResponse", + "BandwidthConsumption", + "RequestsCount", + "Status2xx", + "Status4xx", + "Status5xx", + "AvgResponseTime", + "CdnHitRate", + "TransformationsCount", + "OriginHitRate", + "AvgTransformationResponseTime", + "ContentType", + "BandwidthSaving", + "BandwidthConsumptionBySource", + "AICreditUsage", +] + + +class AICreditUsage(BaseModel): + timestamp: int + """Timestamp of data point in seconds since epoch.""" + + bg_removal: Optional[int] = None + """Background removal credits used.""" + + shadow_gen: Optional[int] = None + """Shadow generation credits used.""" + + +class BandwidthConsumptionBySource(BaseModel): + bytes: float + """Bandwidth usage by the source.""" + + domain: str + """Domain name of the source.""" + + +class BandwidthSaving(BaseModel): + units: float + """Number between 0 and 1 depicting bandwidth savings percentage.""" + + timestamp: int + """Timestamp of data point in seconds since epoch.""" + + +class ContentType(BaseModel): + avif: int + """Total count of AVIF images delivered""" + + png: int + """Total count of PNG images delivered""" + + jpeg: int + """Total count of JPEG images delivered""" + + gif: int + """Total count of GIF images delivered""" + + webp: int + """Total count of WEBP images delivered""" + + jxl: int + """Total count of JPEG-XL images delivered""" + + mp4: int + """Total count of MP4 videos delivered""" + + timestamp: int + """Seconds since epoch.""" + + +class AvgTransformationResponseTime(BaseModel): + timestamp: float + """Seconds since epoch for the unit given.""" + + fetch_time: float + """Seconds it took to fetch image from origin""" + + response_time: float + """Seconds it took to prepare final image and send response.""" + + +class OriginHitRate(BaseModel): + units: float + """Number between 0 and 1 depicting Origin Cache hit ratio.""" + + timestamp: float + """Seconds since epoch for the unit given.""" + + +class TransformationsCount(BaseModel): + units: float + """Number of transformations""" + + timestamp: float + """Seconds since epoch for the unit given.""" + + +class CdnHitRate(BaseModel): + units: float + """Number between 0 and 1 depicting CDN hit ratio percentage.""" + + timestamp: float + """Seconds since epoch for the unit given.""" + + +class AvgResponseTime(BaseModel): + units: float + """Response time in seconds.""" + + timestamp: float + """Seconds since epoch for the unit given.""" + + +class Status5xx(BaseModel): + units: float + """Value between 0 and 1 depicting percentage of 5xx requests.""" + + timestamp: float + """Seconds since epoch for the unit given.""" + + +class Status4xx(BaseModel): + units: float + """Value between 0 and 1 depicting percentage of 4xx requests.""" + + timestamp: float + """Seconds since epoch for the unit given.""" + + +class Status2xx(BaseModel): + units: float + """Value between 0 and 1 depicting percentage of 2xx requests.""" + + timestamp: float + """Seconds since epoch for the unit given.""" + + +class RequestsCount(BaseModel): + units: float + """Count of requests.""" + + timestamp: float + """Seconds since epoch for the unit given.""" + + +class BandwidthConsumption(BaseModel): + units: float + """Value of bandwidth consumption in bytes.""" + + timestamp: float + """Seconds since epoch for the unit given.""" + + +class ImageUsageAnalyticRetrieveResponse(BaseModel): + bandwidth_consumption: Optional[List[BandwidthConsumption]] = None + + requests_count: Optional[List[RequestsCount]] = None + + status_2xx: Optional[List[Status2xx]] = None + + status_4xx: Optional[List[Status4xx]] = None + + status_5xx: Optional[List[Status5xx]] = None + + avg_response_time: Optional[List[AvgResponseTime]] = None + + cdn_hit_rate: Optional[List[CdnHitRate]] = None + + transformations_count: Optional[List[TransformationsCount]] = None + + origin_hit_rate: Optional[List[OriginHitRate]] = None + + avg_transformation_response_time: Optional[List[AvgTransformationResponseTime]] = None + + content_type: Optional[List[ContentType]] = None + + bandwidth_savings: Optional[List[BandwidthSaving]] = None + + bandwidth_consumption_by_source: Optional[List[BandwidthConsumptionBySource]] = None + + ai_credit_usage: Optional[List[AICreditUsage]] = None diff --git a/src/gumlet/types/live_stream_asset_complete_response.py b/src/gumlet/types/live_stream_asset_complete_response.py new file mode 100644 index 0000000..aad54de --- /dev/null +++ b/src/gumlet/types/live_stream_asset_complete_response.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from typing_extensions import TypeAlias + +__all__ = ["LiveStreamAssetCompleteResponse"] + +LiveStreamAssetCompleteResponse: TypeAlias = object diff --git a/src/gumlet/types/live_stream_asset_create_params.py b/src/gumlet/types/live_stream_asset_create_params.py new file mode 100644 index 0000000..7ae3403 --- /dev/null +++ b/src/gumlet/types/live_stream_asset_create_params.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import datetime +from typing_extensions import Annotated, Literal, Required, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["LiveStreamAssetCreateParams"] + + +class LiveStreamAssetCreateParams(TypedDict, total=False): + live_source_id: Required[str] + """Gumlet live video source/collection id.""" + + resolution: Required[str] + """Required resolutions in HLS delivery format for live stream. Can be an array of string out of the following values: `240p`, `360p`, `480p`, `540p`, `720p`, and `1080p`. Re-sized rendition will retain the input aspect ratio.""" + + title: str + """Your live stream asset title""" + + mp4_access: bool + """Creates MP4 version for download purpose.""" + + orientation: Literal["landscape", "potrait"] + + start_at: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] diff --git a/src/gumlet/types/live_stream_asset_create_response.py b/src/gumlet/types/live_stream_asset_create_response.py new file mode 100644 index 0000000..4fb0a67 --- /dev/null +++ b/src/gumlet/types/live_stream_asset_create_response.py @@ -0,0 +1,55 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = ["LiveStreamAssetCreateResponse", "Input", "Output", "Thumbnail"] + + +class Thumbnail(BaseModel): + preparing: Optional[str] = None + + disconnected: Optional[str] = None + + end: Optional[str] = None + + +class Output(BaseModel): + playback_url: Optional[str] = None + + recording_playback_url: Optional[str] = None + + recording_dash_playback_url: Optional[str] = None + + +class Input(BaseModel): + resolution: Optional[List[str]] = None + + title: Optional[str] = None + + +class LiveStreamAssetCreateResponse(BaseModel): + status: Optional[str] = None + + stream_key: Optional[str] = None + + live_asset_id: Optional[str] = None + + live_video_source_id: Optional[str] = None + + input: Optional[Input] = None + + stream_url: Optional[str] = None + + output: Optional[Output] = None + + thumbnail: Optional[Thumbnail] = None + + created_at: Optional[int] = None + + updated_at: Optional[int] = None + + vod_asset_id: Optional[str] = None diff --git a/src/gumlet/types/live_stream_asset_delete_response.py b/src/gumlet/types/live_stream_asset_delete_response.py new file mode 100644 index 0000000..e52c45a --- /dev/null +++ b/src/gumlet/types/live_stream_asset_delete_response.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from typing_extensions import TypeAlias + +__all__ = ["LiveStreamAssetDeleteResponse"] + +LiveStreamAssetDeleteResponse: TypeAlias = object diff --git a/src/gumlet/types/live_stream_asset_filter_params.py b/src/gumlet/types/live_stream_asset_filter_params.py new file mode 100644 index 0000000..690356d --- /dev/null +++ b/src/gumlet/types/live_stream_asset_filter_params.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["LiveStreamAssetFilterParams"] + + +class LiveStreamAssetFilterParams(TypedDict, total=False): + status: str + """To filter live assets on the basis of their current status. Can be specified as a single status value string or comma-separated status values. The status value can be one of `created`, `active`, `complete`, `disconnected`, `errored`, and `deleted`.""" + + offset: int + """Offset value for a paginated list of assets.""" + + size: int + """Page size for the paginated list.""" diff --git a/src/gumlet/types/live_stream_asset_filter_response.py b/src/gumlet/types/live_stream_asset_filter_response.py new file mode 100644 index 0000000..a74dc7a --- /dev/null +++ b/src/gumlet/types/live_stream_asset_filter_response.py @@ -0,0 +1,69 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = [ + "LiveStreamAssetFilterResponse", + "AllLiveAsset", + "AllLiveAssetInput", + "AllLiveAssetOutput", + "AllLiveAssetThumbnail", +] + + +class AllLiveAssetThumbnail(BaseModel): + preparing: Optional[str] = None + + disconnected: Optional[str] = None + + end: Optional[str] = None + + +class AllLiveAssetOutput(BaseModel): + playback_url: Optional[str] = None + + recording_playback_url: Optional[str] = None + + recording_dash_playback_url: Optional[str] = None + + +class AllLiveAssetInput(BaseModel): + resolution: Optional[List[str]] = None + + title: Optional[str] = None + + +class AllLiveAsset(BaseModel): + status: Optional[str] = None + + stream_key: Optional[str] = None + + live_asset_id: Optional[str] = None + + live_video_source_id: Optional[str] = None + + input: Optional[AllLiveAssetInput] = None + + stream_url: Optional[str] = None + + output: Optional[AllLiveAssetOutput] = None + + thumbnail: Optional[AllLiveAssetThumbnail] = None + + created_at: Optional[int] = None + + updated_at: Optional[int] = None + + deleted_at: Optional[int] = None + + +class LiveStreamAssetFilterResponse(BaseModel): + all_live_assets: Optional[List[AllLiveAsset]] = None + + total_live_asset_count: Optional[int] = None + + current_offset: Optional[int] = None diff --git a/src/gumlet/types/live_stream_asset_retrieve_status_response.py b/src/gumlet/types/live_stream_asset_retrieve_status_response.py new file mode 100644 index 0000000..c760cff --- /dev/null +++ b/src/gumlet/types/live_stream_asset_retrieve_status_response.py @@ -0,0 +1,55 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = ["LiveStreamAssetRetrieveStatusResponse", "Input", "Output", "Thumbnail"] + + +class Thumbnail(BaseModel): + preparing: Optional[str] = None + + disconnected: Optional[str] = None + + end: Optional[str] = None + + +class Output(BaseModel): + playback_url: Optional[str] = None + + recording_playback_url: Optional[str] = None + + recording_dash_playback_url: Optional[str] = None + + +class Input(BaseModel): + resolution: Optional[List[str]] = None + + title: Optional[str] = None + + +class LiveStreamAssetRetrieveStatusResponse(BaseModel): + status: Optional[str] = None + + stream_key: Optional[str] = None + + live_asset_id: Optional[str] = None + + live_video_source_id: Optional[str] = None + + input: Optional[Input] = None + + stream_url: Optional[str] = None + + output: Optional[Output] = None + + thumbnail: Optional[Thumbnail] = None + + created_at: Optional[int] = None + + updated_at: Optional[int] = None + + vod_asset_id: Optional[str] = None diff --git a/src/gumlet/types/live_stream_asset_status_history_response.py b/src/gumlet/types/live_stream_asset_status_history_response.py new file mode 100644 index 0000000..61f7272 --- /dev/null +++ b/src/gumlet/types/live_stream_asset_status_history_response.py @@ -0,0 +1,37 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = ["LiveStreamAssetStatusHistoryResponse", "Input", "Output"] + + +class Output(BaseModel): + playback_url: Optional[str] = None + + +class Input(BaseModel): + resolution: Optional[List[str]] = None + + +class LiveStreamAssetStatusHistoryResponse(BaseModel): + status: Optional[str] = None + + stream_key: Optional[str] = None + + live_asset_id: Optional[str] = None + + live_video_source_id: Optional[str] = None + + input: Optional[Input] = None + + stream_url: Optional[str] = None + + output: Optional[Output] = None + + created_at: Optional[int] = None + + updated_at: Optional[int] = None diff --git a/src/gumlet/types/live_stream_asset_update_params.py b/src/gumlet/types/live_stream_asset_update_params.py new file mode 100644 index 0000000..b8413af --- /dev/null +++ b/src/gumlet/types/live_stream_asset_update_params.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import datetime +from typing_extensions import Annotated, Required, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["LiveStreamAssetUpdateParams"] + + +class LiveStreamAssetUpdateParams(TypedDict, total=False): + live_asset_id: Required[str] + """Gumlet live video asset id.""" + + title: str + """Your live stream asset title""" + + start_at: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] diff --git a/src/gumlet/types/live_stream_asset_update_response.py b/src/gumlet/types/live_stream_asset_update_response.py new file mode 100644 index 0000000..90bda34 --- /dev/null +++ b/src/gumlet/types/live_stream_asset_update_response.py @@ -0,0 +1,55 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = ["LiveStreamAssetUpdateResponse", "Input", "Output", "Thumbnail"] + + +class Thumbnail(BaseModel): + preparing: Optional[str] = None + + disconnected: Optional[str] = None + + end: Optional[str] = None + + +class Output(BaseModel): + playback_url: Optional[str] = None + + recording_playback_url: Optional[str] = None + + recording_dash_playback_url: Optional[str] = None + + +class Input(BaseModel): + resolution: Optional[List[str]] = None + + title: Optional[str] = None + + +class LiveStreamAssetUpdateResponse(BaseModel): + status: Optional[str] = None + + stream_key: Optional[str] = None + + live_asset_id: Optional[str] = None + + live_video_source_id: Optional[str] = None + + input: Optional[Input] = None + + stream_url: Optional[str] = None + + output: Optional[Output] = None + + thumbnail: Optional[Thumbnail] = None + + created_at: Optional[int] = None + + updated_at: Optional[int] = None + + vod_asset_id: Optional[str] = None diff --git a/src/gumlet/types/live_stream_asset_upload_params.py b/src/gumlet/types/live_stream_asset_upload_params.py new file mode 100644 index 0000000..d55955e --- /dev/null +++ b/src/gumlet/types/live_stream_asset_upload_params.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List, Union +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["LiveStreamAssetUploadParams"] + + +class LiveStreamAssetUploadParams(TypedDict, total=False): + live_asset_id: Required[str] + """Gumlet live video asset id.""" + + statuses: Required[Union[List[Literal["preparing", "disconnected", "end"]], str]] + """Thumbnail states to upload. You can send an array or a comma-separated string.""" diff --git a/src/gumlet/types/live_stream_asset_upload_response.py b/src/gumlet/types/live_stream_asset_upload_response.py new file mode 100644 index 0000000..228bd51 --- /dev/null +++ b/src/gumlet/types/live_stream_asset_upload_response.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import Optional + +from .._models import BaseModel + +__all__ = ["LiveStreamAssetUploadResponse", "PresignedUploadURLs"] + + +class PresignedUploadURLs(BaseModel): + preparing: Optional[str] = None + + disconnected: Optional[str] = None + + end: Optional[str] = None + + +class LiveStreamAssetUploadResponse(BaseModel): + message: Optional[str] = None + + presigned_upload_urls: Optional[PresignedUploadURLs] = None diff --git a/src/gumlet/types/live_stream_workspace_create_params.py b/src/gumlet/types/live_stream_workspace_create_params.py new file mode 100644 index 0000000..af41846 --- /dev/null +++ b/src/gumlet/types/live_stream_workspace_create_params.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["LiveStreamWorkspaceCreateParams"] + + +class LiveStreamWorkspaceCreateParams(TypedDict, total=False): + name: Required[str] + """Collection name""" diff --git a/src/gumlet/types/live_stream_workspace_create_response.py b/src/gumlet/types/live_stream_workspace_create_response.py new file mode 100644 index 0000000..efd1242 --- /dev/null +++ b/src/gumlet/types/live_stream_workspace_create_response.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from typing import Optional +from datetime import datetime + +from .._models import BaseModel + +__all__ = ["LiveStreamWorkspaceCreateResponse"] + + +class LiveStreamWorkspaceCreateResponse(BaseModel): + id: str + """LIve stream workspace ID""" + + video_workspace_id: Optional[str] = None + """Video workspace ID attached to this collection.""" + + created_at: datetime + """Creation timestamp in ISO 8601 format""" + + name: str + """Name of the live stream workspace""" + + updated_at: datetime + """Update timestamp in ISO 8601 format""" diff --git a/src/gumlet/types/live_stream_workspace_delete_response.py b/src/gumlet/types/live_stream_workspace_delete_response.py new file mode 100644 index 0000000..e91d602 --- /dev/null +++ b/src/gumlet/types/live_stream_workspace_delete_response.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from typing_extensions import TypeAlias + +__all__ = ["LiveStreamWorkspaceDeleteResponse"] + +LiveStreamWorkspaceDeleteResponse: TypeAlias = object diff --git a/src/gumlet/types/live_stream_workspace_list_response.py b/src/gumlet/types/live_stream_workspace_list_response.py new file mode 100644 index 0000000..235d79b --- /dev/null +++ b/src/gumlet/types/live_stream_workspace_list_response.py @@ -0,0 +1,32 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List +from datetime import datetime + +from .._models import BaseModel + +__all__ = ["LiveStreamWorkspaceListResponse", "AllLiveSource"] + + +class AllLiveSource(BaseModel): + name: str + """Live stream workspace name""" + + created_at: datetime + """Creation time in ISO 8601 timestamp""" + + updated_at: datetime + """Update time in ISO 8601 timestamp""" + + video_workspace_id: str + """Video workspace ID that is attached to this collection. Once the live stream completes, the video gets stored in this workspace for long-term storage.""" + + id: str + """Workspace ID""" + + +class LiveStreamWorkspaceListResponse(BaseModel): + all_live_sources: List[AllLiveSource] + """List of all live stream collections""" diff --git a/src/gumlet/types/live_stream_workspace_update_params.py b/src/gumlet/types/live_stream_workspace_update_params.py new file mode 100644 index 0000000..9e207e0 --- /dev/null +++ b/src/gumlet/types/live_stream_workspace_update_params.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["LiveStreamWorkspaceUpdateParams"] + + +class LiveStreamWorkspaceUpdateParams(TypedDict, total=False): + name: str + """Live stream collection name""" + + video_source_id: str + """Video on demand workspace ID""" diff --git a/src/gumlet/types/live_stream_workspace_update_response.py b/src/gumlet/types/live_stream_workspace_update_response.py new file mode 100644 index 0000000..97cb429 --- /dev/null +++ b/src/gumlet/types/live_stream_workspace_update_response.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from typing import Optional +from datetime import datetime + +from .._models import BaseModel + +__all__ = ["LiveStreamWorkspaceUpdateResponse"] + + +class LiveStreamWorkspaceUpdateResponse(BaseModel): + id: str + """LIve stream workspace ID""" + + video_workspace_id: Optional[str] = None + """Video workspace ID attached to this collection.""" + + created_at: datetime + """Creation timestamp in ISO 8601 format""" + + name: str + """Name of the live stream workspace""" + + updated_at: datetime + """Update timestamp in ISO 8601 format""" diff --git a/src/gumlet/types/multipart_upload_complete_params.py b/src/gumlet/types/multipart_upload_complete_params.py new file mode 100644 index 0000000..99a30d5 --- /dev/null +++ b/src/gumlet/types/multipart_upload_complete_params.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import Iterable +from typing_extensions import Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["MultipartUploadCompleteParams", "Part"] + + +class MultipartUploadCompleteParams(TypedDict, total=False): + parts: Iterable[Part] + """List of object containing part number with ETag received as a response header while uploading each part""" + + +class Part(TypedDict, total=False): + part_number: Annotated[int, PropertyInfo(alias="PartNumber")] + + e_tag: Annotated[str, PropertyInfo(alias="ETag")] + """ETag received while uploading the part using PUT""" diff --git a/src/gumlet/types/multipart_upload_complete_response.py b/src/gumlet/types/multipart_upload_complete_response.py new file mode 100644 index 0000000..f6f04ee --- /dev/null +++ b/src/gumlet/types/multipart_upload_complete_response.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from typing_extensions import TypeAlias + +__all__ = ["MultipartUploadCompleteResponse"] + +MultipartUploadCompleteResponse: TypeAlias = object diff --git a/src/gumlet/types/multipart_upload_retrieve_part_url_response.py b/src/gumlet/types/multipart_upload_retrieve_part_url_response.py new file mode 100644 index 0000000..fcb8380 --- /dev/null +++ b/src/gumlet/types/multipart_upload_retrieve_part_url_response.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from typing import Optional + +from .._models import BaseModel + +__all__ = ["MultipartUploadRetrievePartURLResponse"] + + +class MultipartUploadRetrievePartURLResponse(BaseModel): + asset_id: Optional[str] = None + + part_number: Optional[str] = None + + part_upload_url: Optional[str] = None diff --git a/src/gumlet/types/organization_data_fetch_org_response.py b/src/gumlet/types/organization_data_fetch_org_response.py new file mode 100644 index 0000000..f465950 --- /dev/null +++ b/src/gumlet/types/organization_data_fetch_org_response.py @@ -0,0 +1,84 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from .._models import BaseModel + +__all__ = ["OrganizationDataFetchOrgResponse", "PlanData", "PlanDataImage", "PlanDataVideo", "Metadata"] + + +class Metadata(BaseModel): + first_image_source_created: bool = FieldInfo(alias="firstImageSourceCreated") + + first_video_uploaded: bool = FieldInfo(alias="firstVideoUploaded") + + nsfw_blocked: bool + """Flag is org is blocked for NSFW uploads.""" + + +class PlanDataVideo(BaseModel): + plan: str + """Video plan name""" + + cycle: Literal["monthly", "yearly"] + """Billing cycle. `monthly` or `yearly`.""" + + trialing: bool + """If the plan is under trial or not.""" + + display_name: str + + +class PlanDataImage(BaseModel): + plan: str + """Image plan name""" + + cycle: Literal["monthly", "yearly"] + """Billing cycle. `monthly` or `yearly`.""" + + trialing: bool + """If the plan is under trial or not.""" + + display_name: str + + +class PlanData(BaseModel): + image: PlanDataImage + + video: PlanDataVideo + + +class OrganizationDataFetchOrgResponse(BaseModel): + id: str + """Organization ID""" + + org_email: str + """Primary email of the organization""" + + unpaid: bool + """Flag if account is blocked due to unpaid dues. False means account is not blocked.""" + + created_at: float + """Org creation time. Epoch timestamp in milliseconds.""" + + plan: str + """Subscription plan name""" + + plan_cycle: Literal["monthly", "yearly"] + """Billing cycle. `monthly` or `yearly`.""" + + plan_data: PlanData + + stripe_account_loc: Literal["ind", "sgp"] + + sso_enabled: Optional[bool] = None + """Flag if SSO is enabled for org.""" + + got_free_trial: Optional[bool] = None + + metadata: Optional[Metadata] = None diff --git a/src/gumlet/types/recycle_bin_list_params.py b/src/gumlet/types/recycle_bin_list_params.py new file mode 100644 index 0000000..b3ab4f7 --- /dev/null +++ b/src/gumlet/types/recycle_bin_list_params.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["RecycleBinListParams"] + + +class RecycleBinListParams(TypedDict, total=False): + offset: int + """Number of items to skip from start of page response.""" + + size: int + """Number of items to return for a single page.""" + + workspace_id: Required[str] + """ID of workspace for which you want to list the recycle bin items.""" diff --git a/src/gumlet/types/recycle_bin_list_response.py b/src/gumlet/types/recycle_bin_list_response.py new file mode 100644 index 0000000..fbc6989 --- /dev/null +++ b/src/gumlet/types/recycle_bin_list_response.py @@ -0,0 +1,45 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = ["RecycleBinListResponse", "AllAsset"] + + +class AllAsset(BaseModel): + asset_id: str + """Asset ID of the deleted asset.""" + + workspace_id: str + """Workspace ID for the asset.""" + + title: str + """Title of the asset.""" + + description: Optional[str] = None + """Description of the video.""" + + tags: List[str] + """Tags associated with the asset.""" + + duration: float + """Duration of the asset in seconds.""" + + deleted_at: float + """Deleted timestamp of asset in milliseconds since epoch.""" + + deleted_by: str + """User ID of the user who deleted the asset.""" + + +class RecycleBinListResponse(BaseModel): + total_asset_count: int + """Number of total assets in recycle bin.""" + + current_offset: int + """Number of total assets in current offset.""" + + all_assets: List[AllAsset] diff --git a/src/gumlet/types/recycle_bin_recover_params.py b/src/gumlet/types/recycle_bin_recover_params.py new file mode 100644 index 0000000..b27dc01 --- /dev/null +++ b/src/gumlet/types/recycle_bin_recover_params.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["RecycleBinRecoverParams"] + + +class RecycleBinRecoverParams(TypedDict, total=False): + asset_id: Required[str] + """Gumlet Video Asset Id which needs to be recovered.""" diff --git a/src/gumlet/types/subtitle_upload_complete_params.py b/src/gumlet/types/subtitle_upload_complete_params.py new file mode 100644 index 0000000..96bea43 --- /dev/null +++ b/src/gumlet/types/subtitle_upload_complete_params.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import Iterable +from typing_extensions import TypedDict + +__all__ = ["SubtitleUploadCompleteParams", "UploadResponse"] + + +class SubtitleUploadCompleteParams(TypedDict, total=False): + upload_responses: Iterable[UploadResponse] + + +class UploadResponse(TypedDict, total=False): + language_code: str + """Language Code for uploaded .srt or .vtt file.""" + + uploaded: bool + """Status of language uploaded .srt or .vtt file. (If status code was 200, You can mark true else false)""" diff --git a/src/gumlet/types/subtitle_upload_complete_response.py b/src/gumlet/types/subtitle_upload_complete_response.py new file mode 100644 index 0000000..01bef9a --- /dev/null +++ b/src/gumlet/types/subtitle_upload_complete_response.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from typing_extensions import TypeAlias + +__all__ = ["SubtitleUploadCompleteResponse"] + +SubtitleUploadCompleteResponse: TypeAlias = object diff --git a/src/gumlet/types/subtitle_upload_upload_params.py b/src/gumlet/types/subtitle_upload_upload_params.py new file mode 100644 index 0000000..01d5bd3 --- /dev/null +++ b/src/gumlet/types/subtitle_upload_upload_params.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict +from .._types import SequenceNotStr + +__all__ = ["SubtitleUploadUploadParams"] + + +class SubtitleUploadUploadParams(TypedDict, total=False): + language_codes: SequenceNotStr[str] + """List of language Code to upload subtitle file (use ISO 639-1 Language Codes)""" diff --git a/src/gumlet/types/subtitle_upload_upload_response.py b/src/gumlet/types/subtitle_upload_upload_response.py new file mode 100644 index 0000000..27e32f9 --- /dev/null +++ b/src/gumlet/types/subtitle_upload_upload_response.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List + +from .._models import BaseModel + +__all__ = ["SubtitleUploadUploadResponse", "SignedURL"] + + +class SignedURL(BaseModel): + language_code: str + + upload_url: str + + +class SubtitleUploadUploadResponse(BaseModel): + asset_id: str + """Asset ID of Gumlet""" + + signed_urls: List[SignedURL] diff --git a/src/gumlet/types/user_data_fetch_response.py b/src/gumlet/types/user_data_fetch_response.py new file mode 100644 index 0000000..5f0172b --- /dev/null +++ b/src/gumlet/types/user_data_fetch_response.py @@ -0,0 +1,42 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List + +from .._models import BaseModel + +__all__ = ["UserDataFetchResponse", "Metadata"] + + +class Metadata(BaseModel): + theme: str + """Dashboard theme.""" + + +class UserDataFetchResponse(BaseModel): + id: str + """User ID""" + + email: str + """User Email""" + + name: str + """Full name of user""" + + creation_date: int + """User creation date. Milliseconds since epoch.""" + + last_login_date: int + """Last login date. Milliseconds since epoch.""" + + last_login_ip: str + """IP address of last login.""" + + roles: List[str] + """Roles assigned to the user.""" + + timezone: str + """Timezone selected by user.""" + + metadata: Metadata diff --git a/src/gumlet/types/video_analytic_aggregated_data_params.py b/src/gumlet/types/video_analytic_aggregated_data_params.py new file mode 100644 index 0000000..907d524 --- /dev/null +++ b/src/gumlet/types/video_analytic_aggregated_data_params.py @@ -0,0 +1,99 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import Iterable, Union +from datetime import date +from typing_extensions import Annotated, Literal, Required, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["VideoAnalyticAggregatedDataParams", "Aggregate", "Timeframe", "Filter"] + + +class VideoAnalyticAggregatedDataParams(TypedDict, total=False): + aggregate: Required[Iterable[Aggregate]] + """Aggregate multiple metrics at the same time""" + + workspace_id: Required[str] + """The unique identifier of the Gumlet workspace ID available on the Video Workspaces.""" + + timeframe: Required[Timeframe] + """The timeframe to get the data for. Currently we only support maximum difference between `start_at` and `end_at` to be *60 days*""" + + filters: Iterable[Filter] + """Get aggregations for metrics with multiple filters, `value` should be an exact match""" + + +class Filter(TypedDict, total=False): + name: Required[ + Literal[ + "meta_browser", + "meta_operating_system", + "meta_operating_system_version", + "meta_device_category", + "meta_device_manufacturer", + "meta_device_name", + "meta_device_display_width", + "meta_device_display_height", + "meta_country", + "meta_city", + "meta_region", + "player_software", + "player_software_version", + "player_height_pixels", + "player_width_pixels", + "player_language_code", + "meta_page_url", + "meta_asn", + "custom_user_id", + "user_name", + "user_email", + "custom_video_id", + "custom_video_title", + "video_source_url", + "video_source_hostname", + "video_source_format", + "custom_video_language", + "custom_video_variant", + "custom_data_1", + "custom_data_2", + "custom_data_3", + "custom_data_4", + "custom_data_5", + ] + ] + """Name of the breakdown to filter data on.""" + + value: Required[str] + """Value to be matched for the given filter name. Currently we support exact matches.""" + + operator: Literal["equals", "does not equal", "contains", "does not contain", "is set", "is not set"] + """Operator to be used while filtering the data""" + + +class Timeframe(TypedDict, total=False): + start_at: Annotated[Union[str, date], PropertyInfo(format="iso8601")] + """Use yyyy-MM-dd format""" + + end_at: Annotated[Union[str, date], PropertyInfo(format="iso8601")] + """Use yyyy-MM-dd format""" + + +class Aggregate(TypedDict, total=False): + metric: Required[ + Literal[ + "views", + "unique_views", + "completion_percent_by_views", + "playing_time", + "concurrent_users", + "impressions", + "widget_form_submitted", + "cta_clicks", + ] + ] + """The metric to be aggregated for this request.""" + + function: Required[Literal["sum", "average"]] + """Aggregation function which is to be used.""" diff --git a/src/gumlet/types/video_analytic_aggregated_data_response.py b/src/gumlet/types/video_analytic_aggregated_data_response.py new file mode 100644 index 0000000..dc05652 --- /dev/null +++ b/src/gumlet/types/video_analytic_aggregated_data_response.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import Optional + +from .._models import BaseModel + +__all__ = ["VideoAnalyticAggregatedDataResponse", "Views", "ViewsSum"] + + +class ViewsSum(BaseModel): + value: Optional[int] = None + + unit: Optional[str] = None + + +class Views(BaseModel): + sum: Optional[ViewsSum] = None + + +class VideoAnalyticAggregatedDataResponse(BaseModel): + views: Optional[Views] = None diff --git a/src/gumlet/types/video_analytic_breakdown_data_params.py b/src/gumlet/types/video_analytic_breakdown_data_params.py new file mode 100644 index 0000000..1501c49 --- /dev/null +++ b/src/gumlet/types/video_analytic_breakdown_data_params.py @@ -0,0 +1,138 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import Iterable, Union +from datetime import date +from typing_extensions import Annotated, Literal, Required, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["VideoAnalyticBreakdownDataParams", "DateRange", "Filter", "Breakdown"] + + +class VideoAnalyticBreakdownDataParams(TypedDict, total=False): + date_range: Required[DateRange] + """ + The timeframe to get the data for. + Currently, we only support a maximum of *60 days* between `start_at` and `end_at`. + """ + + filters: Iterable[Filter] + """Build *segments* of users using multiple filters on the data, `value` should be an *exact match*""" + + breakdowns: Required[Iterable[Breakdown]] + """Breakdown fields and metrics to retrieve data for. Supports 1 to 3 breakdowns per request.""" + + workspace_id: Required[str] + """The five to ten character unique identifier of the Gumlet workspace ID available on the Video Workspaces.""" + + +class Breakdown(TypedDict, total=False): + name: Required[ + Literal[ + "meta_browser", + "meta_operating_system", + "meta_operating_system_version", + "meta_device_category", + "meta_device_manufacturer", + "meta_device_name", + "meta_device_display_width", + "meta_device_display_height", + "meta_country", + "meta_city", + "meta_region", + "player_software", + "player_software_version", + "player_language_code", + "player_name", + "meta_page_url", + "meta_asn", + "custom_user_id", + "custom_user_email", + "custom_video_id", + "custom_video_title", + "video_source_url", + "custom_video_variant_name", + "custom_video_language", + "custom_video_variant", + "custom_data_1", + "custom_data_2", + "custom_data_3", + "custom_data_4", + "custom_data_5", + ] + ] + """Name of the field to break down the data by.""" + + metric: Required[ + Literal[ + "views", + "unique_views", + "impressions", + "completion_percent_by_views", + "playing_time", + "concurrent_users", + "widget_form_submitted", + "cta_clicks", + ] + ] + """The metric to retrieve breakdown data for.""" + + page: int + """Page number for paginated results.""" + + page_size: int + """Number of results per page.""" + + +class Filter(TypedDict, total=False): + name: Required[ + Literal[ + "meta_browser", + "meta_operating_system", + "meta_operating_system_version", + "meta_device_category", + "meta_device_manufacturer", + "meta_device_name", + "meta_device_display_width", + "meta_device_display_height", + "meta_country", + "meta_city", + "meta_region", + "player_software", + "player_software_version", + "player_language_code", + "player_name", + "meta_page_url", + "meta_asn", + "custom_user_id", + "custom_user_email", + "custom_video_id", + "custom_video_title", + "video_source_url", + "custom_video_variant_name", + "custom_video_language", + "custom_video_variant", + "custom_data_1", + "custom_data_2", + "custom_data_3", + "custom_data_4", + "custom_data_5", + ] + ] + """Name of the breakdown to filter data on.""" + + value: Required[str] + """Value to be matched for the given filter name. Currently we support exact matches.""" + + operator: Literal["equals", "does not equal", "contains", "does not contain", "is set", "is not set"] + """Operator to be used while filtering the data""" + + +class DateRange(TypedDict, total=False): + start_at: Required[Annotated[Union[str, date], PropertyInfo(format="iso8601")]] + """Use yyyy-MM-dd format""" + + end_at: Required[Annotated[Union[str, date], PropertyInfo(format="iso8601")]] + """Use yyyy-MM-dd format""" diff --git a/src/gumlet/types/video_analytic_breakdown_data_response.py b/src/gumlet/types/video_analytic_breakdown_data_response.py new file mode 100644 index 0000000..88562e4 --- /dev/null +++ b/src/gumlet/types/video_analytic_breakdown_data_response.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = ["VideoAnalyticBreakdownDataResponse", "Views", "ViewsData"] + + +class ViewsData(BaseModel): + key: Optional[str] = None + """Breakdown field value""" + + value: Optional[float] = None + """Metric value for the breakdown key""" + + unit: Optional[str] = None + + +class Views(BaseModel): + data: Optional[List[ViewsData]] = None + + has_next_page: Optional[bool] = None + """Whether there is another page of results""" + + current_page: Optional[int] = None + """Current page number""" + + +class VideoAnalyticBreakdownDataResponse(BaseModel): + views: Optional[Views] = None diff --git a/src/gumlet/types/video_analytic_chart_data_params.py b/src/gumlet/types/video_analytic_chart_data_params.py new file mode 100644 index 0000000..1053c9e --- /dev/null +++ b/src/gumlet/types/video_analytic_chart_data_params.py @@ -0,0 +1,119 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import Iterable, Union +from datetime import date +from typing_extensions import Annotated, Literal, Required, TypedDict +from .._types import SequenceNotStr + +from .._utils import PropertyInfo + +__all__ = ["VideoAnalyticChartDataParams", "DateRange", "Filter", "ChartDimension", "ChartDimensionGroupBy"] + + +class VideoAnalyticChartDataParams(TypedDict, total=False): + metrics: Required[SequenceNotStr[str]] + """Get data for one or more `metrics` in the same request. Please add any of these metrics. `views`, `unique_views`, `impressions`. `completion_percent_by_views`, `playing_time`, `concurrent_users`, `widget_form_submitted`, `cta_clicks`""" + + workspace_id: Required[str] + """The five to ten character unique identifier of the Gumlet workspace ID available on the Video Workspaces.""" + + date_range: Required[DateRange] + """ + The timeframe to get the data for. + Currently, we only support a maximum of *60 days* between `start_at` and `end_at`. + """ + + filters: Iterable[Filter] + """Build *segments* of users using multiple filters on the data, `value` should be an *exact match*""" + + group_by: Literal["daily", "weekly", "monthly"] + """Data can be grouped by `daily`, `weekly` or `monthly`.""" + + chart_dimension: ChartDimension + """Metrics result Group by selected dimension, You can select upto 3 dimensions to get nested category result. result will follow selection orders.""" + + +class ChartDimensionGroupBy(TypedDict, total=False): + name: Literal[ + "custom_video_title", + "custom_video_id", + "custom_workspace_id", + "video_source_url", + "video_source_format", + "player_software_version", + "player_software", + "meta_page_url", + "audio_language", + "subtitle_language", + "video_width_pixels", + "video_height_pixels", + "meta_country", + "meta_city", + "meta_device_category", + "meta_device_manufacturer", + "meta_browser", + "meta_browser_version", + "meta_browser_language", + "meta_operating_system", + "meta_operating_system_version", + "meta_device_name", + "meta_asn", + ] + + +class ChartDimension(TypedDict, total=False): + group_by: Iterable[ChartDimensionGroupBy] + + +class Filter(TypedDict, total=False): + name: Required[ + Literal[ + "meta_browser", + "meta_operating_system", + "meta_operating_system_version", + "meta_device_category", + "meta_device_manufacturer", + "meta_device_name", + "meta_device_display_width", + "meta_device_display_height", + "meta_country", + "meta_city", + "meta_region", + "player_software", + "player_software_version", + "player_language_code", + "player_name", + "meta_page_url", + "meta_asn", + "custom_user_id", + "custom_user_email", + "custom_video_id", + "custom_video_title", + "video_source_url", + "custom_video_variant_name", + "custom_video_language", + "custom_video_variant", + "custom_data_1", + "custom_data_2", + "custom_data_3", + "custom_data_4", + "custom_data_5", + ] + ] + """Name of the breakdown to filter data on.""" + + value: Required[str] + """Value to be matched for the given filter name. Currently we support exact matches.""" + + operator: Literal["equals", "does not equal", "contains", "does not contain", "is set", "is not set"] + """Operator to be used while filtering the data""" + + +class DateRange(TypedDict, total=False): + start_at: Required[Annotated[Union[str, date], PropertyInfo(format="iso8601")]] + """Use yyyy-MM-dd format""" + + end_at: Required[Annotated[Union[str, date], PropertyInfo(format="iso8601")]] + """Use yyyy-MM-dd format""" diff --git a/src/gumlet/types/video_analytic_chart_data_response.py b/src/gumlet/types/video_analytic_chart_data_response.py new file mode 100644 index 0000000..19d2070 --- /dev/null +++ b/src/gumlet/types/video_analytic_chart_data_response.py @@ -0,0 +1,47 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = ["VideoAnalyticChartDataResponse", "View", "UniqueView", "AnalyticsData", "AnalyticsDataView"] + + +class AnalyticsDataView(BaseModel): + x: Optional[str] = None + """Date in epoch format""" + + y: Optional[str] = None + """Value""" + + unit: Optional[str] = None + + +class AnalyticsData(BaseModel): + views: Optional[List[AnalyticsDataView]] = None + + +class UniqueView(BaseModel): + x: Optional[int] = None + + y: Optional[object] = None + + unit: Optional[str] = None + + +class View(BaseModel): + x: Optional[int] = None + + y: Optional[object] = None + + unit: Optional[str] = None + + +class VideoAnalyticChartDataResponse(BaseModel): + views: Optional[List[View]] = None + + unique_views: Optional[List[UniqueView]] = None + + analytics_data: Optional[AnalyticsData] = None diff --git a/src/gumlet/types/video_asset_analytics_params.py b/src/gumlet/types/video_asset_analytics_params.py new file mode 100644 index 0000000..87be6e5 --- /dev/null +++ b/src/gumlet/types/video_asset_analytics_params.py @@ -0,0 +1,47 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["VideoAssetAnalyticsParams", "DateRange"] + + +class VideoAssetAnalyticsParams(TypedDict, total=False): + group_by: Required[Literal["daily", "monthly", "weekly"]] + """Group the data by this period.""" + + date_range: Required[DateRange] + + metrics: Required[ + List[ + Literal[ + "impressions", + "views", + "playing_time", + "top_countries", + "top_pages", + "top_cities", + "top_device_types", + "top_browsers", + "heatmap", + "widget_data", + ] + ] + ] + """List of metrics to return in response.""" + + page_number: int + """Page number to fetch. Starting at 1""" + + page_size: int + """Number of items to return per page""" + + +class DateRange(TypedDict, total=False): + start_at: Required[str] + """ISO 8601 start timestamp""" + + end_at: Required[str] + """ISO 8601 end timestamp""" diff --git a/src/gumlet/types/video_asset_analytics_response.py b/src/gumlet/types/video_asset_analytics_response.py new file mode 100644 index 0000000..d101e22 --- /dev/null +++ b/src/gumlet/types/video_asset_analytics_response.py @@ -0,0 +1,163 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List, Optional + +from pydantic import Field as FieldInfo + +from .._models import BaseModel + +__all__ = [ + "VideoAssetAnalyticsResponse", + "Heatmap", + "View", + "PlayingTime", + "Impression", + "TopCountry", + "TopCity", + "TopBrowser", + "TopDeviceType", + "WidgetData", + "WidgetDataWidgetData", +] + + +class WidgetDataWidgetData(BaseModel): + asset_id: str + """Asset ID""" + + page_url: str + """Page URL where lead was captured""" + + playback_time_instant_milli: str + """Video time in milliseconds when the lead was captured""" + + workspace_id: str + """Workspace ID""" + + timestamp: str + """Lead capture time in seconds since epoch""" + + email: str + """Email id submitted""" + + name: str + """Name submitted""" + + +class WidgetData(BaseModel): + has_more: bool = FieldInfo(alias="hasMore") + """If there are more items apart from the response given.""" + + widget_data: List[WidgetDataWidgetData] = FieldInfo(alias="widgetData") + """Data about the lead form submission""" + + +class TopDeviceType(BaseModel): + key: str + """Name of the platform""" + + views: str + """Number of views from a given platform""" + + impressions: str + """Number of impressions from a given platform""" + + +class TopBrowser(BaseModel): + key: str + """Name of the browser""" + + views: str + """Number of views from a given browser""" + + impressions: str + """Number of impressions from a given browser""" + + +class TopCity(BaseModel): + key: str + """Name of the city""" + + views: str + """Number of views from a given city""" + + impressions: str + """Number of impressions from a given city""" + + +class TopCountry(BaseModel): + key: str + """Name of the country""" + + views: str + """Number of views from a given country""" + + impressions: str + """Number of impressions from a given country""" + + +class Impression(BaseModel): + date: int + """Milliseconds since epoch timestamp for the data point""" + + value: int + """Number of impressions""" + + +class PlayingTime(BaseModel): + date: int + """Milliseconds since epoch timestamp for the data point""" + + value: int + """Milliseconds of watch time for the timestamp above. A value of 10000 means 10 seconds of watch time.""" + + +class View(BaseModel): + date: int + """Milliseconds since epoch timestamp for the data point""" + + value: int + """Count of views for the given timestamp""" + + +class Heatmap(BaseModel): + bucket: str + """Buckets of seconds of video. E.g. '0-10', '10-20' etc.""" + + count: int + """Percent of viewers who viewed that portion of video""" + + +class VideoAssetAnalyticsResponse(BaseModel): + heatmap: Optional[List[Heatmap]] = None + """Heatmap data""" + + views: Optional[List[View]] = None + """Views data""" + + playing_time: List[PlayingTime] + + field_01_m2_qp6_ks1_hm1_je912_s8_k9_dj2_n: Optional[object] = FieldInfo( + alias="01M2QP6KS1HM1JE912S8K9DJ2N", default=None + ) + + impressions: Optional[List[Impression]] = None + + top_countries: Optional[List[TopCountry]] = None + """Data for top countries""" + + top_pages: Optional[List[object]] = None + """Data for top pages""" + + top_cities: Optional[List[TopCity]] = None + """Data for top cities""" + + top_browsers: Optional[List[TopBrowser]] = None + """Data for top browsers""" + + top_device_types: Optional[List[TopDeviceType]] = None + """Data for top device types""" + + widget_data: Optional[WidgetData] = None diff --git a/src/gumlet/types/video_asset_create_params.py b/src/gumlet/types/video_asset_create_params.py new file mode 100644 index 0000000..f028968 --- /dev/null +++ b/src/gumlet/types/video_asset_create_params.py @@ -0,0 +1,270 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import Dict, Iterable +from typing_extensions import Literal, Required, TypedDict +from .._types import SequenceNotStr + +__all__ = [ + "VideoAssetCreateParams", + "Crop", + "Pad", + "Trim", + "ImageOverlay", + "TextOverlay", + "AnimatedGif", + "AdditionalTrack", + "GenerateSubtitles", + "CallToAction", +] + + +class VideoAssetCreateParams(TypedDict, total=False): + input: Required[str] + """URL or web address of a file that Gumlet should download to create a new asset.""" + + collection_id: Required[str] + """Gumlet video workspace id.""" + + profile_id: str + """Provide `profile_id` of the previously created video profile. This parameter will override all the parameters (except `input` and `collection_id`) from the video profile.""" + + format: Required[Literal["ABR", "MP4"]] + """Transcode and deliver the asset in the requested format. The options can be one of `ABR` (HLS + DASH) and`MP4`.""" + + tag: SequenceNotStr[str] + """Specify a text string or identifier which can identify an asset or bunch of assets later.""" + + title: str + """Specify a text string or identifier which can be used for filtering or searching the asset.""" + + description: str + """Attach some textual data with the asset. This field is neither searchable nor filterable.""" + + metadata: Dict[str, object] + """Add your metadata you want to associate with this asset.
Example:
{ "internal_video_id" : "123Abc" } """ + + width: str + """Resize video with the given width. Can be an absolute value in pixels or a percentage value with the `%` suffix. Specified values greater than the original asset width will be ignored. Applicable only when specified format is `MP4`.""" + + height: str + """Resize video with the given height. Can be an absolute value in pixels or a percentage value with the `%` suffix. Specified values greater than the original asset height will be ignored. Applicable only when specified format is `MP4`.""" + + resolution: str + """Required resolutions of the transformed asset in case of HLS or MPEG-DASH delivery format. Can be a comma separated string out of the following values: `240p`, `360p`, `480p`, `540p`, `720p`, and `1080p`. Re-sized rendition will retain the input aspect ratio.""" + + crop: Crop + """This transformation can be used to crop the video by defining a rectangular area within the dimensions of the output video.""" + + pad: Pad + """This transformation can be used to add padding to the video.""" + + trim: Trim + """Trim transformation can be used to trim videos based on time duration.""" + + image_overlay: ImageOverlay + """Image overlay can be used to brand a video or add a visual label in the form of an image.""" + + text_overlay: TextOverlay + """Text overlay can be used to brand a video or add a label in the form of text.""" + + animated_gif: AnimatedGif + """Create an animated GIF from the video.""" + + additional_tracks: Iterable[AdditionalTrack] + """Add additional Audio / Subtitle tracks to Gumlet for transcoding and delivery along with video asset track.""" + + generate_subtitles: GenerateSubtitles + """Gumlet allows to generate subtitles from the audio stream (use ISO 639-1 Language Codes)""" + + mp4_access: bool + """Creates `MP4` version for download purpose in case of `MPEG-DASH` or `HLS` delivery format. **Default: `false`**""" + + per_title_encoding: bool + """Gumlet analyzes each input video on a wide range of visual aspects. Based on the analysis, it chooses a unique set of transcoding options for processing the video. This ensures that the output video is of optimal size and best quality. **Default: `true`**""" + + process_low_resolution_input: bool + """Currently, the minimum supported frame size is `57600` (`240x240`) pixels for `HLS/DASH` and `21025` (`145x145`) pixels for `MP4` format. However, enabling this flag will allow Gumlet to simply put your video asset into the specified delivery format without transcoding and optimization. Enabling this flag will cause any kind of specified video transformation to be ignored if you input video asset frame size is lower than the minimum supported frame size for the specified format. **Default: `false`**""" + + audio_only: bool + """This flag allows Gumlet to transcode and deliver audio-only in the specified format. In this case, video transformation and thumbnails/animated GIFs would not be created. **Default: `false`**""" + + enable_drm: bool + """Enable DRM encryption for transcoded videos. Gumlet supports Widevine and Fairplay DRMs.""" + + call_to_actions: Iterable[CallToAction] + """CTA, is an explicit prompt within the video content encouraging viewers to take a particular action.""" + + playlist_id: str + """Add this asset to a playlist.""" + + folder: str + """Add this asset to an existing folder by `folder_id`.""" + + +class CallToAction(TypedDict, total=False): + text: str + + url: str + + start_time: int + + end_time: int + + font_color: str + """hex value of color""" + + background_color: str + """hex code of color""" + + position_from_top: int + """number of pixels from top""" + + position_from_right: int + """number of pixels from right""" + + +class GenerateSubtitles(TypedDict, total=False): + audio_language: str + """Language code for native language of the audio.""" + + subtitle_languages: str + """Comma separated string of language codes for which subtitle needs to be generated. Maximum four language codes are allowed.""" + + +class AdditionalTrack(TypedDict, total=False): + url: Required[str] + """URL or web address of a file that Gumlet should download to add a stream.""" + + type: Required[str] + """Type of additional track. Value can be either audio or subtitle.""" + + language_code: Required[str] + """The language code value represents BCP 47 specification compliant value. For example, en for English.""" + + name: str + """The name of the track containing a human-readable description.""" + + +class AnimatedGif(TypedDict, total=False): + start_offset: str + """The time (in seconds or `HH:MM:SS` format) of the video timeline where the animated gif should begin. **Default: `0`**""" + + end_offset: str + """The time (in seconds or `HH:MM:SS` format) of the video timeline where the GIF ends. Defaults to `10` seconds after the start_offset. Maximum duration of GIF is limited to `10` seconds.""" + + width: str + """The width in pixels (or in percentage value of asset width) of the animated GIF. Max width is `640px`.""" + + height: str + """The height in pixels (or in percentage value of asset height) of the animated GIF. Max height is `640px`.""" + + fps: str + """The frame rate of the generated GIF. Defaults to `15` fps. Max `30` fps.""" + + +class TextOverlay(TypedDict, total=False): + text: Required[str] + """Text to be overlayed on video.""" + + horizontal_align: str + """This parameter specifies the horizontal alignment of the overlayed image and can be either `left` or `right`. **Default: `right`**""" + + vertical_align: str + """This parameter specifies the vertical alignment of the overlayed image and can be either `top` or `bottom`. **Default: `bottom`**""" + + horizontal_margin: str + """This parameter defines the horizontal coordinate value of the corner (determined by `horizontal_align`) of the overlay area. Values can be an absolute number of pixels relative to the video width. **Default: `0`**""" + + vertical_margin: str + """This parameter defines the vertical coordinate value of the corner (determined by vertical_align) of the overlay area. Values can be an absolute number of pixels relative to the video height. **Default: `0`**""" + + color: str + """Font color for text. **Default: `black`**""" + + font: str + """Font family type for text. **Default: `sans`**""" + + font_size: str + """Font size in pixels. **Default: `16`**""" + + opacity: str + """Overlay text opacity can be specified with opacity parameter where value can be between `0` and `100` where `0` is considered completely transparent and `100` is considered completely opaque. **Default: `100`**""" + + box: bool + """This parameter allows rectangular drawing a box over the overlayed text. **Default: `false`**""" + + box_color: str + """Box color can be specified with this parameter. **Default: `white`**""" + + box_opacity: str + """Box opacity can be specified with this parameter. **Default: `100`**""" + + box_border: str + """Padding between the box border and the text can be specified with this parameter in pixels. **Default: `0`**""" + + +class ImageOverlay(TypedDict, total=False): + url: Required[str] + """This is the required parameter for image overlay, it can be a URL to an image that needs to be overlayed.""" + + horizontal_margin: str + """This parameter defines the horizontal coordinate value of the corner (determined by `horizontal_align`) of the overlay area. Values can be an absolute number of pixels or a percentage value relative to the video width. **Default: `0`**""" + + vertical_margin: str + """This parameter defines the vertical coordinate value of the corner (determined by `vertical_align`) of the overlay area. Values can be an absolute number of pixels or a percentage value relative to the video height. **Default: `0`**""" + + horizontal_align: str + """This parameter specifies the horizontal alignment of the overlayed image and can be either `left` or `right`. **Default: `right`**""" + + vertical_align: str + """This parameter specifies the vertical alignment of the overlayed image and can be either `top` or `bottom`. **Default: `bottom`**""" + + width: str + """Width of the overlayed image. **Default: `image width`**""" + + height: str + """Height of the overlayed image. **Default: `image height`**""" + + +class Trim(TypedDict, total=False): + start_offset: Required[float] + """Start offset in number of seconds or in `HH:MM:SS` format.""" + + end_offset: Required[float] + """End offset in number of seconds or in `HH:MM:SS` format.""" + + duration: float + """Duration can be used in conjunction with `start_offset` parameter, can be specified in number of seconds.""" + + +class Pad(TypedDict, total=False): + top: str + """Width of padding on the top side. Values can be an absolute number of pixels or a percentage value relative to the video height. **Default: `%5`**""" + + left: str + """Width of padding on the left side. Values can be an absolute number of pixels or a percentage value relative to the video width. **Default: `0`**""" + + bottom: str + """Width of padding on the bottom side. Values can be an absolute number of pixels or a percentage value relative to the video height. **Default: `%5`**""" + + right: str + """Width of padding on the right side. Values can be an absolute number of pixels or a percentage value relative to the video width. **Default: `0`**""" + + color: str + """Color of padding area. **Default: `black`**""" + + +class Crop(TypedDict, total=False): + horizontal_margin: str + """This parameter defines the horizontal coordinate value of the upper-left corner of the cropping area. Values can be an absolute number of pixels or a percentage value relative to the video width. **Default: `0`**""" + + vertical_margin: str + """This parameter defines the vertical coordinate value of the upper-left corner of the cropping area. Values can be an absolute number of pixels or a percentage value relative to the video height. **Default: `0`**""" + + width: Required[str] + """Width of the cropping area in pixels.""" + + height: Required[str] + """Height of the cropping area in pixels.""" diff --git a/src/gumlet/types/video_asset_create_response.py b/src/gumlet/types/video_asset_create_response.py new file mode 100644 index 0000000..ab0d8ea --- /dev/null +++ b/src/gumlet/types/video_asset_create_response.py @@ -0,0 +1,103 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = ["VideoAssetCreateResponse", "Input", "InputTransformations", "InputMetadata", "InputCallToAction", "Output"] + + +class Output(BaseModel): + format: Optional[str] = None + + status_url: Optional[str] = None + + playback_url: Optional[str] = None + + thumbnail_url: Optional[List[str]] = None + + +class InputCallToAction(BaseModel): + start_time: Optional[int] = None + + end_time: Optional[int] = None + + text: Optional[str] = None + + url: Optional[str] = None + + position_from_top: Optional[int] = None + + position_from_right: Optional[int] = None + + border_radius: Optional[int] = None + + font_color: Optional[str] = None + + background_color: Optional[str] = None + + html_target: Optional[str] = None + + +class InputMetadata(BaseModel): + headermeta: Optional[str] = None + + +class InputTransformations(BaseModel): + format: Optional[str] = None + + resolution: Optional[List[str]] = None + + audio_codec: Optional[List[str]] = None + + video_codec: Optional[List[str]] = None + + thumbnail: Optional[List[str]] = None + + thumbnail_format: Optional[str] = None + + mp4_access: Optional[bool] = None + + per_title_encoding: Optional[bool] = None + + +class Input(BaseModel): + transformations: Optional[InputTransformations] = None + + profile_id: Optional[str] = None + + title: Optional[str] = None + + description: Optional[str] = None + + metadata: Optional[InputMetadata] = None + + source_url: Optional[str] = None + + call_to_actions: Optional[List[InputCallToAction]] = None + + +class VideoAssetCreateResponse(BaseModel): + asset_id: Optional[str] = None + + progress: Optional[int] = None + + created_at: Optional[int] = None + + updated_at: Optional[int] = None + + status: Optional[str] = None + + tag: Optional[List[str]] = None + + source_id: Optional[str] = None + + collection_id: Optional[str] = None + + input: Optional[Input] = None + + output: Optional[Output] = None + + playlists: Optional[List[str]] = None diff --git a/src/gumlet/types/video_asset_create_update_chapter_params.py b/src/gumlet/types/video_asset_create_update_chapter_params.py new file mode 100644 index 0000000..74b42b6 --- /dev/null +++ b/src/gumlet/types/video_asset_create_update_chapter_params.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import Iterable +from typing_extensions import Annotated, Required, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["VideoAssetCreateUpdateChapterParams", "Chapter"] + + +class VideoAssetCreateUpdateChapterParams(TypedDict, total=False): + chapters: Required[Iterable[Chapter]] + + +class Chapter(TypedDict, total=False): + label: Required[str] + """Label for the chapter.""" + + start_time: Required[Annotated[int, PropertyInfo(alias="startTime")]] + """Start time of chapter in seconds. 0 means chapter is put as the video starts.""" diff --git a/src/gumlet/types/video_asset_create_update_chapter_response.py b/src/gumlet/types/video_asset_create_update_chapter_response.py new file mode 100644 index 0000000..461224c --- /dev/null +++ b/src/gumlet/types/video_asset_create_update_chapter_response.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from typing_extensions import TypeAlias + +__all__ = ["VideoAssetCreateUpdateChapterResponse"] + +VideoAssetCreateUpdateChapterResponse: TypeAlias = object diff --git a/src/gumlet/types/video_asset_delete_many_params.py b/src/gumlet/types/video_asset_delete_many_params.py new file mode 100644 index 0000000..061d950 --- /dev/null +++ b/src/gumlet/types/video_asset_delete_many_params.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict +from .._types import SequenceNotStr + +__all__ = ["VideoAssetDeleteManyParams"] + + +class VideoAssetDeleteManyParams(TypedDict, total=False): + asset_list: Required[SequenceNotStr[str]] + """LIst of asset ids to delete""" + + source_id: Required[str] + """Workspace ID from which assets needs to be deleted.""" diff --git a/src/gumlet/types/video_asset_delete_many_response.py b/src/gumlet/types/video_asset_delete_many_response.py new file mode 100644 index 0000000..6a47bc3 --- /dev/null +++ b/src/gumlet/types/video_asset_delete_many_response.py @@ -0,0 +1,10 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from .._models import BaseModel + +__all__ = ["VideoAssetDeleteManyResponse"] + + +class VideoAssetDeleteManyResponse(BaseModel): + success: bool + """Boolean parameter indicating if the delete was successful.""" diff --git a/src/gumlet/types/video_asset_list_deprecated_params.py b/src/gumlet/types/video_asset_list_deprecated_params.py new file mode 100644 index 0000000..ef7a016 --- /dev/null +++ b/src/gumlet/types/video_asset_list_deprecated_params.py @@ -0,0 +1,41 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import Annotated, Literal, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["VideoAssetListDeprecatedParams"] + + +class VideoAssetListDeprecatedParams(TypedDict, total=False): + status: Literal["queued", "processing", "ready", "errored", "deleted"] + """To filter assets on the basis of their current status. Can be specified as a single status value string or comma-separated status values. The status value can be one of `queued`, `processing`, `ready`, `errored`, and `deleted`.""" + + tag: str + """Input tag on the basis of which assets need to be filtered. To filter on multiple tags use comma-separated string.""" + + title: str + """Title on the basis of which assets need to be filtered.""" + + folder: str + """Folder name on the basis of which assets need to be filtered.""" + + offset: str + """Offset value for a paginated list of assets.""" + + size: str + """Page size for the paginated list. **Default: `10`** **Max Size: `100`**""" + + playlist_id: str + """filter assets from a playlist.""" + + sort_by: Annotated[Literal["title", "duration", "uploaded_at", "created_at"], PropertyInfo(alias="sortBy")] + """assets will be sorted based on the provided field.""" + + order_by: Annotated[Literal["asc", "desc"], PropertyInfo(alias="orderBy")] + """assets will be sorted in the specified order based on provided sortBy field or by default createAt field.""" + + type: str + """Search for folders, videos, or both. For videos, use `videos`. For folders, use `folders`. If you do not send this parameter, it will search for both.""" diff --git a/src/gumlet/types/video_asset_list_deprecated_response.py b/src/gumlet/types/video_asset_list_deprecated_response.py new file mode 100644 index 0000000..c8e8ffc --- /dev/null +++ b/src/gumlet/types/video_asset_list_deprecated_response.py @@ -0,0 +1,108 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = [ + "VideoAssetListDeprecatedResponse", + "AllAsset", + "AllAssetInput", + "AllAssetInputTransformations", + "AllAssetInputAdditionalTrack", + "AllAssetOutput", +] + + +class AllAssetOutput(BaseModel): + format: Optional[str] = None + + status_url: Optional[str] = None + + playback_url: Optional[str] = None + + thumbnail_url: Optional[List[str]] = None + + +class AllAssetInputAdditionalTrack(BaseModel): + url: Optional[str] = None + + type: Optional[str] = None + + language_code: Optional[str] = None + + name: Optional[str] = None + + +class AllAssetInputTransformations(BaseModel): + resolution: Optional[str] = None + + format: Optional[str] = None + + audio_codec: Optional[List[str]] = None + + video_codec: Optional[List[str]] = None + + thumbnail: Optional[List[str]] = None + + thumbnail_format: Optional[str] = None + + mp4_access: Optional[bool] = None + + audio_only: Optional[bool] = None + + keep_original: Optional[bool] = None + + per_title_encoding: Optional[bool] = None + + process_low_resolution_input: Optional[bool] = None + + +class AllAssetInput(BaseModel): + transformations: Optional[AllAssetInputTransformations] = None + + source_url: Optional[str] = None + + size: Optional[int] = None + + duration: Optional[float] = None + + aspect_ratio: Optional[str] = None + + fps: Optional[int] = None + + width: Optional[int] = None + + height: Optional[int] = None + + additional_tracks: Optional[List[AllAssetInputAdditionalTrack]] = None + + +class AllAsset(BaseModel): + asset_id: Optional[str] = None + + progress: Optional[int] = None + + created_at: Optional[int] = None + + status: Optional[str] = None + + tag: Optional[str] = None + + source_id: Optional[str] = None + + input: Optional[AllAssetInput] = None + + output: Optional[AllAssetOutput] = None + + +class VideoAssetListDeprecatedResponse(BaseModel): + all_assets: Optional[List[AllAsset]] = None + + total_asset_count: Optional[int] = None + + current_offset: Optional[int] = None + + distinct_tags: Optional[List[str]] = None diff --git a/src/gumlet/types/video_asset_list_params.py b/src/gumlet/types/video_asset_list_params.py new file mode 100644 index 0000000..f83d15a --- /dev/null +++ b/src/gumlet/types/video_asset_list_params.py @@ -0,0 +1,61 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import Annotated, Literal, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["VideoAssetListParams"] + + +class VideoAssetListParams(TypedDict, total=False): + type: Literal["folders", "videos", "all"] + """Return `folders`, `videos`, or `all`. Default is `all`.""" + + parent_id: str + """Parent folder id. Send `null` to browse the root level.""" + + title: str + """Search folders or assets by title or description.""" + + status: str + """Comma-separated asset status values.""" + + tag: str + """Comma-separated asset tags.""" + + playlist_id: str + """Filter assets to a playlist.""" + + start_date: str + """Asset created_at lower bound.""" + + end_date: str + """Asset created_at upper bound.""" + + min_duration: float + """Minimum asset duration in seconds.""" + + max_duration: float + """Maximum asset duration in seconds.""" + + sort_by: Annotated[Literal["title", "duration", "uploaded_at", "created_at"], PropertyInfo(alias="sortBy")] + """Sort assets by a supported field.""" + + order_by: Annotated[Literal["asc", "desc"], PropertyInfo(alias="orderBy")] + """Asset sort order.""" + + search_index: Annotated[ + Literal["search_index_for_asset_list", "cms-search", "cms-search-v2"], PropertyInfo(alias="searchIndex") + ] + """Search index used for asset title search.""" + + offset: int + """Offset for paginated results.""" + + size: int + """Page size. Maximum 100.""" + + signed_token: Literal["true", "false"] + """Whether URLs should be pre-signed in the API response. Possible values: `true` and `false`. Default is `false`.""" diff --git a/src/gumlet/types/video_asset_list_response.py b/src/gumlet/types/video_asset_list_response.py new file mode 100644 index 0000000..172e1c5 --- /dev/null +++ b/src/gumlet/types/video_asset_list_response.py @@ -0,0 +1,140 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = [ + "VideoAssetListResponse", + "Folder", + "AllAsset", + "AllAssetInput", + "AllAssetInputTransformations", + "AllAssetInputAdditionalTrack", + "AllAssetOutput", +] + + +class AllAssetOutput(BaseModel): + format: Optional[str] = None + + status_url: Optional[str] = None + + playback_url: Optional[str] = None + + thumbnail_url: Optional[List[str]] = None + + +class AllAssetInputAdditionalTrack(BaseModel): + url: Optional[str] = None + + type: Optional[str] = None + + language_code: Optional[str] = None + + name: Optional[str] = None + + +class AllAssetInputTransformations(BaseModel): + resolution: Optional[str] = None + + format: Optional[str] = None + + audio_codec: Optional[List[str]] = None + + video_codec: Optional[List[str]] = None + + thumbnail: Optional[List[str]] = None + + thumbnail_format: Optional[str] = None + + mp4_access: Optional[bool] = None + + audio_only: Optional[bool] = None + + keep_original: Optional[bool] = None + + per_title_encoding: Optional[bool] = None + + process_low_resolution_input: Optional[bool] = None + + +class AllAssetInput(BaseModel): + transformations: Optional[AllAssetInputTransformations] = None + + source_url: Optional[str] = None + + size: Optional[int] = None + + duration: Optional[float] = None + + aspect_ratio: Optional[str] = None + + fps: Optional[int] = None + + width: Optional[int] = None + + height: Optional[int] = None + + additional_tracks: Optional[List[AllAssetInputAdditionalTrack]] = None + + title: Optional[str] = None + """Title of the asset.""" + + +class AllAsset(BaseModel): + asset_id: Optional[str] = None + + progress: Optional[int] = None + + created_at: Optional[int] = None + + status: Optional[str] = None + + tag: Optional[str] = None + + source_id: Optional[str] = None + + input: Optional[AllAssetInput] = None + + output: Optional[AllAssetOutput] = None + + +class Folder(BaseModel): + id: Optional[str] = None + + name: Optional[str] = None + + video_source_id: Optional[str] = None + + parent_id: Optional[str] = None + + path: Optional[List[str]] = None + + path_names: Optional[List[str]] = None + + depth: Optional[int] = None + + subdirectory_count: Optional[int] = None + + asset_count: Optional[int] = None + + created_at: Optional[str] = None + + updated_at: Optional[str] = None + + +class VideoAssetListResponse(BaseModel): + folders: Optional[List[Folder]] = None + + all_assets: List[AllAsset] + + folder_count: Optional[int] = None + + asset_count: Optional[int] = None + + total_count: Optional[int] = None + + current_offset: Optional[int] = None diff --git a/src/gumlet/types/video_asset_retrieve_details_response.py b/src/gumlet/types/video_asset_retrieve_details_response.py new file mode 100644 index 0000000..ebc1415 --- /dev/null +++ b/src/gumlet/types/video_asset_retrieve_details_response.py @@ -0,0 +1,224 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List, Optional + +from pydantic import Field as FieldInfo + +from .._models import BaseModel + +__all__ = [ + "VideoAssetRetrieveDetailsResponse", + "Input", + "InputTransformations", + "InputTransformationsImageOverlay", + "InputTransformationsGenerateSubtitles", + "InputTransformationsPreviewThumbnails", + "InputChapter", + "Output", + "OutputStorageDetails", + "OutputStorageDetailsVideo", + "OutputStorageDetailsAudio", + "OutputStorageDetailsPlaylist", + "OutputStorageDetailsThumbnail", + "OutputStorageDetailsSubtitle", + "OutputStorageDetailsPreviewThumbnail", +] + + +class OutputStorageDetailsPreviewThumbnail(BaseModel): + file_name: Optional[str] = FieldInfo(alias="fileName", default=None) + + size: Optional[int] = None + + +class OutputStorageDetailsSubtitle(BaseModel): + file_name: Optional[str] = FieldInfo(alias="fileName", default=None) + + size: Optional[int] = None + + +class OutputStorageDetailsThumbnail(BaseModel): + file_name: Optional[str] = FieldInfo(alias="fileName", default=None) + + size: Optional[int] = None + + resolution: Optional[str] = None + + +class OutputStorageDetailsPlaylist(BaseModel): + file_name: Optional[str] = FieldInfo(alias="fileName", default=None) + + size: Optional[int] = None + + +class OutputStorageDetailsAudio(BaseModel): + file_name: Optional[str] = FieldInfo(alias="fileName", default=None) + + size: Optional[int] = None + + duration: Optional[int] = None + + +class OutputStorageDetailsVideo(BaseModel): + file_name: Optional[str] = FieldInfo(alias="fileName", default=None) + + size: Optional[int] = None + + resolution: Optional[str] = None + + duration: Optional[int] = None + + +class OutputStorageDetails(BaseModel): + video: Optional[List[OutputStorageDetailsVideo]] = None + + audio: Optional[List[OutputStorageDetailsAudio]] = None + + playlist: Optional[List[OutputStorageDetailsPlaylist]] = None + + thumbnail: Optional[List[OutputStorageDetailsThumbnail]] = None + + subtitle: Optional[List[OutputStorageDetailsSubtitle]] = None + + preview_thumbnail: Optional[List[OutputStorageDetailsPreviewThumbnail]] = FieldInfo( + alias="previewThumbnail", default=None + ) + + +class Output(BaseModel): + format: Optional[str] = None + + status_url: Optional[str] = None + + playback_url: Optional[str] = None + + dash_playback_url: Optional[str] = None + + thumbnail_url: Optional[List[str]] = None + + storage_details: Optional[OutputStorageDetails] = None + + transcription_word_level_timestamps: Optional[str] = None + + storage_bytes: Optional[int] = None + + preview_thumbnails_url: Optional[str] = None + + +class InputChapter(BaseModel): + end_time: Optional[int] = FieldInfo(alias="endTime", default=None) + + label: Optional[str] = None + + +class InputTransformationsPreviewThumbnails(BaseModel): + max_tiles: Optional[int] = None + + +class InputTransformationsGenerateSubtitles(BaseModel): + audio_language: Optional[str] = None + + subtitle_languages: Optional[List[str]] = None + + +class InputTransformationsImageOverlay(BaseModel): + url: Optional[str] = None + + vertical_align: Optional[str] = None + + horizontal_align: Optional[str] = None + + vertical_margin: Optional[str] = None + + horizontal_margin: Optional[str] = None + + width: Optional[str] = None + + height: Optional[str] = None + + image_downloaded: Optional[bool] = None + + +class InputTransformations(BaseModel): + format: Optional[str] = None + + resolution: Optional[List[str]] = None + + audio_codec: Optional[List[str]] = None + + video_codec: Optional[List[str]] = None + + image_overlay: Optional[InputTransformationsImageOverlay] = None + + thumbnail: Optional[List[str]] = None + + thumbnail_format: Optional[str] = None + + mp4_access: Optional[bool] = None + + audio_only: Optional[bool] = None + + original_deleted: Optional[bool] = None + + per_title_encoding: Optional[bool] = None + + generate_subtitles: Optional[InputTransformationsGenerateSubtitles] = None + + preview_thumbnails: Optional[InputTransformationsPreviewThumbnails] = None + + +class Input(BaseModel): + transformations: Optional[InputTransformations] = None + + profile_id: Optional[str] = None + + title: Optional[str] = None + + description: Optional[str] = None + + chapters: Optional[List[InputChapter]] = None + + source_url: Optional[str] = None + + size: Optional[int] = None + + duration: Optional[float] = None + + aspect_ratio: Optional[str] = None + + fps: Optional[float] = None + + width: Optional[int] = None + + height: Optional[int] = None + + +class VideoAssetRetrieveDetailsResponse(BaseModel): + asset_id: Optional[str] = None + + progress: Optional[int] = None + + created_at: Optional[int] = None + + updated_at: Optional[int] = None + + status: Optional[str] = None + + tag: Optional[List[str]] = None + + source_id: Optional[str] = None + + collection_id: Optional[str] = None + + input: Optional[Input] = None + + output: Optional[Output] = None + + processed_at: Optional[int] = None + + folder: Optional[str] = None + + playlists: Optional[List[str]] = None + """Array of Playlist IDs""" diff --git a/src/gumlet/types/video_asset_tag_many_params.py b/src/gumlet/types/video_asset_tag_many_params.py new file mode 100644 index 0000000..0c6ee23 --- /dev/null +++ b/src/gumlet/types/video_asset_tag_many_params.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict +from .._types import SequenceNotStr + +__all__ = ["VideoAssetTagManyParams"] + + +class VideoAssetTagManyParams(TypedDict, total=False): + asset_list: Required[SequenceNotStr[str]] + """List of asset ids to update the tags for.""" + + source_id: Required[str] + """Workspace ID in which the videos needs the operation""" + + add_tags: Required[SequenceNotStr[str]] + """List of tags to add to given assets.""" + + remove_tags: Required[SequenceNotStr[str]] + """List of tags to remove from given assets. Pass empty array if nothing is to be removed.""" diff --git a/src/gumlet/types/video_asset_tag_many_response.py b/src/gumlet/types/video_asset_tag_many_response.py new file mode 100644 index 0000000..c8a95e7 --- /dev/null +++ b/src/gumlet/types/video_asset_tag_many_response.py @@ -0,0 +1,10 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from .._models import BaseModel + +__all__ = ["VideoAssetTagManyResponse"] + + +class VideoAssetTagManyResponse(BaseModel): + success: bool + """Boolean flag indicating if operation was successful.""" diff --git a/src/gumlet/types/video_asset_thumbnail_select_params.py b/src/gumlet/types/video_asset_thumbnail_select_params.py new file mode 100644 index 0000000..59f3da5 --- /dev/null +++ b/src/gumlet/types/video_asset_thumbnail_select_params.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["VideoAssetThumbnailSelectParams"] + + +class VideoAssetThumbnailSelectParams(TypedDict, total=False): + frame_at_second: Required[int] + """Frame secound""" diff --git a/src/gumlet/types/video_asset_thumbnail_select_response.py b/src/gumlet/types/video_asset_thumbnail_select_response.py new file mode 100644 index 0000000..84bf5b4 --- /dev/null +++ b/src/gumlet/types/video_asset_thumbnail_select_response.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from typing import Optional + +from .._models import BaseModel + +__all__ = ["VideoAssetThumbnailSelectResponse"] + + +class VideoAssetThumbnailSelectResponse(BaseModel): + success: Optional[bool] = None + + asset_id: Optional[str] = None + + thumbnail_updated_at: Optional[int] = None + """Milliseconds since epoch for the updated time.""" diff --git a/src/gumlet/types/video_asset_thumbnail_upload_response.py b/src/gumlet/types/video_asset_thumbnail_upload_response.py new file mode 100644 index 0000000..6ff94c0 --- /dev/null +++ b/src/gumlet/types/video_asset_thumbnail_upload_response.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from typing import Optional + +from .._models import BaseModel + +__all__ = ["VideoAssetThumbnailUploadResponse"] + + +class VideoAssetThumbnailUploadResponse(BaseModel): + upload_url: Optional[str] = None + + asset_id: Optional[str] = None + + thumbnail_updated_at: Optional[int] = None + """Thumbnail updated at timestamp in milliseconds since epoch""" diff --git a/src/gumlet/types/video_asset_update_params.py b/src/gumlet/types/video_asset_update_params.py new file mode 100644 index 0000000..3d8feaa --- /dev/null +++ b/src/gumlet/types/video_asset_update_params.py @@ -0,0 +1,64 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import Iterable +from typing_extensions import Required, TypedDict +from .._types import SequenceNotStr + +__all__ = ["VideoAssetUpdateParams", "CallToAction"] + + +class VideoAssetUpdateParams(TypedDict, total=False): + asset_id: Required[str] + """Asset Id""" + + title: str + """Specify a text string or identifier which can be used for filtering or searching the asset.""" + + description: str + """Attach some textual data with the asset. This field is neither searchable nor filterable.""" + + tag: str + """Specify a text string or identifier which can identify an asset or bunch of assets later. You can pass multiple comma separated values.""" + + call_to_actions: Iterable[CallToAction] + """CTA, is an explicit prompt within the video content encouraging viewers to take a particular action.""" + + metadata: str + """Set of key-value pairs that you can attach to this Asset. This can be useful for storing additional information.
Example:
{ "internal_video_id" : "123Abc" } """ + + remove_subtitles: SequenceNotStr[str] + """Comma separated string of language codes.""" + + input: str + """ + For replacing videos, pass this along with `asset_id` + + `{workspace_id}/{asset_id}/origin-{asset_id}` + """ + + reprocess: bool + """To reprocess same video, pass this as true.""" + + +class CallToAction(TypedDict, total=False): + text: str + + url: str + + start_time: int + + end_time: int + + font_color: str + """hex value of color""" + + background_color: str + """hex code of color""" + + position_from_top: int + """number of pixels from top""" + + position_from_right: str + """number of pixels from right""" diff --git a/src/gumlet/types/video_asset_update_response.py b/src/gumlet/types/video_asset_update_response.py new file mode 100644 index 0000000..61d7fdd --- /dev/null +++ b/src/gumlet/types/video_asset_update_response.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from typing_extensions import TypeAlias + +__all__ = ["VideoAssetUpdateResponse"] + +VideoAssetUpdateResponse: TypeAlias = object diff --git a/src/gumlet/types/video_asset_upload_params.py b/src/gumlet/types/video_asset_upload_params.py new file mode 100644 index 0000000..e3faedf --- /dev/null +++ b/src/gumlet/types/video_asset_upload_params.py @@ -0,0 +1,267 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import Dict, Iterable +from typing_extensions import Literal, Required, TypedDict +from .._types import SequenceNotStr + +__all__ = [ + "VideoAssetUploadParams", + "Crop", + "Pad", + "Trim", + "ImageOverlay", + "TextOverlay", + "AnimatedGif", + "AdditionalTrack", + "GenerateSubtitles", + "CallToAction", +] + + +class VideoAssetUploadParams(TypedDict, total=False): + collection_id: Required[str] + """Gumlet video workspace id.""" + + profile_id: str + """Provide `profile_id` of the previously created video profile. This parameter will override all the parameters (except `input` and `collection_id`) from the video profile.""" + + format: Literal["ABR", "MP4"] + """Transcode and deliver the asset in the requested format. The options can be one of `ABR` (HLS + DASH) and`MP4`.""" + + tag: SequenceNotStr[str] + """Specify a text string or identifier which can identify an asset or bunch of assets later.""" + + title: str + """Specify a text string or identifier which can be used for filtering or searching the asset.""" + + description: str + """Attach some textual data with the asset. This field is neither searchable nor filterable.""" + + metadata: Dict[str, object] + """Add your metadata you want to associate with this asset.
Example:
{ "internal_video_id" : "123Abc" } """ + + width: str + """Resize video with the given width. Can be an absolute value in pixels or a percentage value with the `%` suffix. Specified values greater than the original asset width will be ignored. Applicable only when specified format is `MP4`.""" + + height: str + """Resize video with the given height. Can be an absolute value in pixels or a percentage value with the `%` suffix. Specified values greater than the original asset height will be ignored. Applicable only when specified format is `MP4`.""" + + resolution: str + """Required resolutions of the transformed asset in case of HLS or MPEG-DASH delivery format. Can be a comma separated string out of the following values: `240p`, `360p`, `480p`, `540p`, `720p`, and `1080p`. Re-sized rendition will retain the input aspect ratio.""" + + crop: Crop + """This transformation can be used to crop the video by defining a rectangular area within the dimensions of the output video.""" + + pad: Pad + """This transformation can be used to add padding to the video.""" + + trim: Trim + """Trim transformation can be used to trim videos based on time duration.""" + + image_overlay: ImageOverlay + """Image overlay can be used to brand a video or add a visual label in the form of an image.""" + + text_overlay: TextOverlay + """Text overlay can be used to brand a video or add a label in the form of text.""" + + animated_gif: AnimatedGif + """Create an animated GIF from the video.""" + + additional_tracks: Iterable[AdditionalTrack] + """Add additional Audio / Subtitle tracks to Gumlet for transcoding and delivery along with video asset track.""" + + generate_subtitles: GenerateSubtitles + """Gumlet allows to generate subtitles from the audio stream (use ISO 639-1 Language Codes)""" + + mp4_access: bool + """Creates `MP4` version for download purpose in case of `MPEG-DASH` or `HLS` delivery format. **Default: `false`**""" + + per_title_encoding: bool + """Gumlet analyzes each input video on a wide range of visual aspects. Based on the analysis, it chooses a unique set of transcoding options for processing the video. This ensures that the output video is of optimal size and best quality. **Default: `true`**""" + + process_low_resolution_input: bool + """Currently, the minimum supported frame size is `57600` (`240x240`) pixels for `HLS/DASH` and `21025` (`145x145`) pixels for `MP4` format. However, enabling this flag will allow Gumlet to simply put your video asset into the specified delivery format without transcoding and optimization. Enabling this flag will cause any kind of specified video transformation to be ignored if you input video asset frame size is lower than the minimum supported frame size for the specified format. **Default: `false`**""" + + audio_only: bool + """This flag allows Gumlet to transcode and deliver audio-only in the specified format. In this case, video transformation and thumbnails/animated GIFs would not be created. **Default: `false`**""" + + enable_drm: bool + """Enable DRM encryption for transcoded videos. Gumlet supports Widevine and Fairplay DRMs.""" + + call_to_actions: Iterable[CallToAction] + """CTA, is an explicit prompt within the video content encouraging viewers to take a particular action.""" + + playlist_id: str + """Add this asset to a playlist.""" + + folder: str + """Add this asset to an existing folder by `folder_id`.""" + + +class CallToAction(TypedDict, total=False): + text: str + + url: str + + start_time: int + + end_time: int + + font_color: str + """hex value of color""" + + background_color: str + """hex code of color""" + + position_from_top: int + """number of pixels from top""" + + position_from_right: int + """number of pixels from right""" + + +class GenerateSubtitles(TypedDict, total=False): + audio_language: str + """Language code for native language of the audio.""" + + subtitle_languages: str + """Comma separated string of language codes for which subtitle needs to be generated. Maximum four language codes are allowed.""" + + +class AdditionalTrack(TypedDict, total=False): + url: Required[str] + """URL or web address of a file that Gumlet should download to add a stream.""" + + type: Required[str] + """Type of additional track. Value can be either audio or subtitle.""" + + language_code: Required[str] + """The language code value represents BCP 47 specification compliant value. For example, en for English.""" + + name: str + """The name of the track containing a human-readable description.""" + + +class AnimatedGif(TypedDict, total=False): + start_offset: str + """The time (in seconds or `HH:MM:SS` format) of the video timeline where the animated gif should begin. **Default: `0`**""" + + end_offset: str + """The time (in seconds or `HH:MM:SS` format) of the video timeline where the GIF ends. Defaults to `10` seconds after the start_offset. Maximum duration of GIF is limited to `10` seconds.""" + + width: str + """The width in pixels (or in percentage value of asset width) of the animated GIF. Max width is `640px`.""" + + height: str + """The height in pixels (or in percentage value of asset height) of the animated GIF. Max height is `640px`.""" + + fps: str + """The frame rate of the generated GIF. Defaults to `15` fps. Max `30` fps.""" + + +class TextOverlay(TypedDict, total=False): + text: Required[str] + """Text to be overlayed on video.""" + + horizontal_align: str + """This parameter specifies the horizontal alignment of the overlayed image and can be either `left` or `right`. **Default: `right`**""" + + vertical_align: str + """This parameter specifies the vertical alignment of the overlayed image and can be either `top` or `bottom`. **Default: `bottom`**""" + + horizontal_margin: str + """This parameter defines the horizontal coordinate value of the corner (determined by `horizontal_align`) of the overlay area. Values can be an absolute number of pixels relative to the video width. **Default: `0`**""" + + vertical_margin: str + """This parameter defines the vertical coordinate value of the corner (determined by vertical_align) of the overlay area. Values can be an absolute number of pixels relative to the video height. **Default: `0`**""" + + color: str + """Font color for text. **Default: `black`**""" + + font: str + """Font family type for text. **Default: `sans`**""" + + font_size: str + """Font size in pixels. **Default: `16`**""" + + opacity: str + """Overlay text opacity can be specified with opacity parameter where value can be between `0` and `100` where `0` is considered completely transparent and `100` is considered completely opaque. **Default: `100`**""" + + box: bool + """This parameter allows rectangular drawing a box over the overlayed text. **Default: `false`**""" + + box_color: str + """Box color can be specified with this parameter. **Default: `white`**""" + + box_opacity: str + """Box opacity can be specified with this parameter. **Default: `100`**""" + + box_border: str + """Padding between the box border and the text can be specified with this parameter in pixels. **Default: `0`**""" + + +class ImageOverlay(TypedDict, total=False): + url: Required[str] + """This is the required parameter for image overlay, it can be a URL to an image that needs to be overlayed.""" + + horizontal_margin: str + """This parameter defines the horizontal coordinate value of the corner (determined by `horizontal_align`) of the overlay area. Values can be an absolute number of pixels or a percentage value relative to the video width. **Default: `0`**""" + + vertical_margin: str + """This parameter defines the vertical coordinate value of the corner (determined by `vertical_align`) of the overlay area. Values can be an absolute number of pixels or a percentage value relative to the video height. **Default: `0`**""" + + horizontal_align: str + """This parameter specifies the horizontal alignment of the overlayed image and can be either `left` or `right`. **Default: `right`**""" + + vertical_align: str + """This parameter specifies the vertical alignment of the overlayed image and can be either `top` or `bottom`. **Default: `bottom`**""" + + width: str + """Width of the overlayed image. **Default: `image width`**""" + + height: str + """Height of the overlayed image. **Default: `image height`**""" + + +class Trim(TypedDict, total=False): + start_offset: Required[float] + """Start offset in number of seconds or in `HH:MM:SS` format.""" + + end_offset: Required[float] + """End offset in number of seconds or in `HH:MM:SS` format.""" + + duration: float + """Duration can be used in conjunction with `start_offset` parameter, can be specified in number of seconds.""" + + +class Pad(TypedDict, total=False): + top: str + """Width of padding on the top side. Values can be an absolute number of pixels or a percentage value relative to the video height. **Default: `%5`**""" + + left: str + """Width of padding on the left side. Values can be an absolute number of pixels or a percentage value relative to the video width. **Default: `0`**""" + + bottom: str + """Width of padding on the bottom side. Values can be an absolute number of pixels or a percentage value relative to the video height. **Default: `%5`**""" + + right: str + """Width of padding on the right side. Values can be an absolute number of pixels or a percentage value relative to the video width. **Default: `0`**""" + + color: str + """Color of padding area. **Default: `black`**""" + + +class Crop(TypedDict, total=False): + horizontal_margin: str + """This parameter defines the horizontal coordinate value of the upper-left corner of the cropping area. Values can be an absolute number of pixels or a percentage value relative to the video width. **Default: `0`**""" + + vertical_margin: str + """This parameter defines the vertical coordinate value of the upper-left corner of the cropping area. Values can be an absolute number of pixels or a percentage value relative to the video height. **Default: `0`**""" + + width: Required[str] + """Width of the cropping area in pixels.""" + + height: Required[str] + """Height of the cropping area in pixels.""" diff --git a/src/gumlet/types/video_asset_upload_response.py b/src/gumlet/types/video_asset_upload_response.py new file mode 100644 index 0000000..9b44f73 --- /dev/null +++ b/src/gumlet/types/video_asset_upload_response.py @@ -0,0 +1,97 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = ["VideoAssetUploadResponse", "Input", "InputTransformations", "InputMetadata", "InputCallToAction", "Output"] + + +class Output(BaseModel): + format: Optional[str] = None + + status_url: Optional[str] = None + + playback_url: Optional[str] = None + + thumbnail_url: Optional[List[str]] = None + + +class InputCallToAction(BaseModel): + start_time: Optional[int] = None + + end_time: Optional[int] = None + + text: Optional[str] = None + + url: Optional[str] = None + + html_target: Optional[str] = None + + +class InputMetadata(BaseModel): + headermeta: Optional[str] = None + + +class InputTransformations(BaseModel): + format: Optional[str] = None + + resolution: Optional[List[str]] = None + + audio_codec: Optional[List[str]] = None + + video_codec: Optional[List[str]] = None + + thumbnail: Optional[List[str]] = None + + thumbnail_format: Optional[str] = None + + mp4_access: Optional[bool] = None + + per_title_encoding: Optional[bool] = None + + original_deleted: Optional[bool] = None + + +class Input(BaseModel): + transformations: Optional[InputTransformations] = None + + profile_id: Optional[str] = None + + title: Optional[str] = None + + description: Optional[str] = None + + metadata: Optional[InputMetadata] = None + + source_url: Optional[str] = None + + call_to_actions: Optional[List[InputCallToAction]] = None + + +class VideoAssetUploadResponse(BaseModel): + asset_id: Optional[str] = None + + progress: Optional[int] = None + + created_at: Optional[int] = None + + updated_at: Optional[int] = None + + status: Optional[str] = None + + tag: Optional[List[str]] = None + + source_id: Optional[str] = None + + collection_id: Optional[str] = None + + input: Optional[Input] = None + + output: Optional[Output] = None + + upload_url: Optional[str] = None + + playlists: Optional[List[str]] = None diff --git a/src/gumlet/types/video_playlist_create_asset_params.py b/src/gumlet/types/video_playlist_create_asset_params.py new file mode 100644 index 0000000..553c2a9 --- /dev/null +++ b/src/gumlet/types/video_playlist_create_asset_params.py @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import Iterable +from typing_extensions import Required, TypedDict + +__all__ = ["VideoPlaylistCreateAssetParams", "AssetList"] + + +class VideoPlaylistCreateAssetParams(TypedDict, total=False): + asset_list: Required[Iterable[AssetList]] + + +class AssetList(TypedDict, total=False): + asset_id: str + + position: int + """Optional, if not provided asset will added at the back/last of playlist""" diff --git a/src/gumlet/types/video_playlist_create_asset_response.py b/src/gumlet/types/video_playlist_create_asset_response.py new file mode 100644 index 0000000..2c55296 --- /dev/null +++ b/src/gumlet/types/video_playlist_create_asset_response.py @@ -0,0 +1,11 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from typing import Optional + +from .._models import BaseModel + +__all__ = ["VideoPlaylistCreateAssetResponse"] + + +class VideoPlaylistCreateAssetResponse(BaseModel): + success: Optional[bool] = None diff --git a/src/gumlet/types/video_playlist_create_params.py b/src/gumlet/types/video_playlist_create_params.py new file mode 100644 index 0000000..8160cdd --- /dev/null +++ b/src/gumlet/types/video_playlist_create_params.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["VideoPlaylistCreateParams"] + + +class VideoPlaylistCreateParams(TypedDict, total=False): + collection_id: Required[str] + + title: Required[str] + + description: str diff --git a/src/gumlet/types/video_playlist_create_response.py b/src/gumlet/types/video_playlist_create_response.py new file mode 100644 index 0000000..33dffac --- /dev/null +++ b/src/gumlet/types/video_playlist_create_response.py @@ -0,0 +1,59 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import Optional + +from .._models import BaseModel + +__all__ = ["VideoPlaylistCreateResponse", "PlayerConfig"] + + +class PlayerConfig(BaseModel): + preload: Optional[bool] = None + + autoplay: Optional[bool] = None + + disable_seek: Optional[bool] = None + + disable_player_controls: Optional[bool] = None + + powered_by_gumlet_overlay: Optional[bool] = None + + allow_drm_protected_videos: Optional[bool] = None + + loop: Optional[bool] = None + + player_color: Optional[str] = None + + include_seo: Optional[bool] = None + + subtitle_enabled: Optional[bool] = None + + pixel_tags: Optional[object] = None + + logo_width: Optional[int] = None + + logo_height: Optional[int] = None + + dynamic_watermark: Optional[bool] = None + + watermark_font_size: Optional[int] = None + + watermark_font_color: Optional[str] = None + + watermark_bg_color: Optional[str] = None + + watermark_interval: Optional[int] = None + + +class VideoPlaylistCreateResponse(BaseModel): + id: Optional[str] = None + + collection_id: Optional[str] = None + + title: Optional[str] = None + + description: Optional[str] = None + + player_config: Optional[PlayerConfig] = None diff --git a/src/gumlet/types/video_playlist_delete_asset_params.py b/src/gumlet/types/video_playlist_delete_asset_params.py new file mode 100644 index 0000000..5dd0407 --- /dev/null +++ b/src/gumlet/types/video_playlist_delete_asset_params.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict +from .._types import SequenceNotStr + +__all__ = ["VideoPlaylistDeleteAssetParams"] + + +class VideoPlaylistDeleteAssetParams(TypedDict, total=False): + delete_list: Required[SequenceNotStr[str]] + """Array of video asset ids.""" diff --git a/src/gumlet/types/video_playlist_delete_asset_response.py b/src/gumlet/types/video_playlist_delete_asset_response.py new file mode 100644 index 0000000..2d9316e --- /dev/null +++ b/src/gumlet/types/video_playlist_delete_asset_response.py @@ -0,0 +1,11 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from typing import Optional + +from .._models import BaseModel + +__all__ = ["VideoPlaylistDeleteAssetResponse"] + + +class VideoPlaylistDeleteAssetResponse(BaseModel): + success: Optional[bool] = None diff --git a/src/gumlet/types/video_playlist_list_all_params.py b/src/gumlet/types/video_playlist_list_all_params.py new file mode 100644 index 0000000..e9f298d --- /dev/null +++ b/src/gumlet/types/video_playlist_list_all_params.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["VideoPlaylistListAllParams"] + + +class VideoPlaylistListAllParams(TypedDict, total=False): + collection_id: str + """Video Collection ID""" diff --git a/src/gumlet/types/video_playlist_list_all_response.py b/src/gumlet/types/video_playlist_list_all_response.py new file mode 100644 index 0000000..b5d8817 --- /dev/null +++ b/src/gumlet/types/video_playlist_list_all_response.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from typing import List, Optional +from typing_extensions import TypeAlias + +from .._models import BaseModel + +__all__ = ["VideoPlaylistListAllResponse", "VideoPlaylistListAllResponseItem"] + + +class VideoPlaylistListAllResponseItem(BaseModel): + id: Optional[str] = None + + collection_id: Optional[str] = None + + title: Optional[str] = None + + description: Optional[str] = None + + player_config: Optional[object] = None + + +VideoPlaylistListAllResponse: TypeAlias = List[VideoPlaylistListAllResponseItem] diff --git a/src/gumlet/types/video_playlist_list_assets_params.py b/src/gumlet/types/video_playlist_list_assets_params.py new file mode 100644 index 0000000..3de983a --- /dev/null +++ b/src/gumlet/types/video_playlist_list_assets_params.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["VideoPlaylistListAssetsParams"] + + +class VideoPlaylistListAssetsParams(TypedDict, total=False): + sort_by: str + """Optional, if sort_by is set to asset_title it will sorted by title name. Otherwise order in which user added the assets in playlist.""" + + sort_order: int + """-1 or 1""" + + page_number: int + """Optional, Minimun 1""" + + page_size: str + """Optional, Minimun 10""" diff --git a/src/gumlet/types/video_playlist_list_assets_response.py b/src/gumlet/types/video_playlist_list_assets_response.py new file mode 100644 index 0000000..6802b94 --- /dev/null +++ b/src/gumlet/types/video_playlist_list_assets_response.py @@ -0,0 +1,31 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = ["VideoPlaylistListAssetsResponse", "AssetList"] + + +class AssetList(BaseModel): + id: Optional[str] = None + + title: Optional[str] = None + + description: Optional[str] = None + + status: Optional[str] = None + + created_at: Optional[str] = None + + duration: Optional[int] = None + + +class VideoPlaylistListAssetsResponse(BaseModel): + asset_list: Optional[List[AssetList]] = None + + has_next_page: Optional[bool] = None + + next_page: Optional[int] = None diff --git a/src/gumlet/types/video_playlist_reorder_asset_params.py b/src/gumlet/types/video_playlist_reorder_asset_params.py new file mode 100644 index 0000000..695345b --- /dev/null +++ b/src/gumlet/types/video_playlist_reorder_asset_params.py @@ -0,0 +1,31 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +__all__ = ["VideoPlaylistReorderAssetParams", "Variant", "Variant2"] + + +class Variant2(TypedDict, total=False): + sort_by: Required[Literal["title", "created_at"]] + + sort_order: Required[Literal["asc", "desc"]] + + +class Variant(TypedDict, total=False): + asset_id: Required[str] + """Asset id to move.""" + + page_number: Required[int] + """Current playlist page number.""" + + page_size: Required[int] + """Playlist page size used by the caller.""" + + asset_position: Required[int] + """Zero-based position inside the provided page.""" + + +VideoPlaylistReorderAssetParams: TypeAlias = Union[Variant, Variant2] diff --git a/src/gumlet/types/video_playlist_reorder_asset_response.py b/src/gumlet/types/video_playlist_reorder_asset_response.py new file mode 100644 index 0000000..f742fc8 --- /dev/null +++ b/src/gumlet/types/video_playlist_reorder_asset_response.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = ["VideoPlaylistReorderAssetResponse"] + + +class VideoPlaylistReorderAssetResponse(BaseModel): + success: Optional[bool] = None + + asset_list: Optional[List[str]] = None diff --git a/src/gumlet/types/video_playlist_update_params.py b/src/gumlet/types/video_playlist_update_params.py new file mode 100644 index 0000000..93d55fb --- /dev/null +++ b/src/gumlet/types/video_playlist_update_params.py @@ -0,0 +1,60 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["VideoPlaylistUpdateParams", "PlayerConfig"] + + +class VideoPlaylistUpdateParams(TypedDict, total=False): + title: str + + description: str + + position: int + """Playlists have order in which they will be shown on the channel page.""" + + player_config: PlayerConfig + """Configure player settings for this playlist, it overrides the setting set on collection.""" + + channel_visibility: bool + """If true then playlist will be visible on channel page.""" + + +class PlayerConfig(TypedDict, total=False): + preload: bool + + autoplay: bool + + disable_seek: bool + + disable_player_controls: bool + + powered_by_gumlet_overlay: bool + + allow_drm_protected_videos: bool + + loop: bool + + player_color: str + + include_seo: bool + + subtitle_enabled: bool + + pixel_tags: object + + logo_width: int + + logo_height: int + + dynamic_watermark: bool + + watermark_font_size: int + + watermark_font_color: str + + watermark_bg_color: str + + watermark_interval: int diff --git a/src/gumlet/types/video_playlist_update_response.py b/src/gumlet/types/video_playlist_update_response.py new file mode 100644 index 0000000..7607ce1 --- /dev/null +++ b/src/gumlet/types/video_playlist_update_response.py @@ -0,0 +1,59 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import Optional + +from .._models import BaseModel + +__all__ = ["VideoPlaylistUpdateResponse", "PlayerConfig"] + + +class PlayerConfig(BaseModel): + preload: Optional[bool] = None + + autoplay: Optional[bool] = None + + disable_seek: Optional[bool] = None + + disable_player_controls: Optional[bool] = None + + powered_by_gumlet_overlay: Optional[bool] = None + + allow_drm_protected_videos: Optional[bool] = None + + loop: Optional[bool] = None + + player_color: Optional[str] = None + + include_seo: Optional[bool] = None + + subtitle_enabled: Optional[bool] = None + + pixel_tags: Optional[object] = None + + logo_width: Optional[int] = None + + logo_height: Optional[int] = None + + dynamic_watermark: Optional[bool] = None + + watermark_font_size: Optional[int] = None + + watermark_font_color: Optional[str] = None + + watermark_bg_color: Optional[str] = None + + watermark_interval: Optional[int] = None + + +class VideoPlaylistUpdateResponse(BaseModel): + id: Optional[str] = None + + collection_id: Optional[str] = None + + title: Optional[str] = None + + description: Optional[str] = None + + player_config: Optional[PlayerConfig] = None diff --git a/src/gumlet/types/video_profile_create_params.py b/src/gumlet/types/video_profile_create_params.py new file mode 100644 index 0000000..a8d876b --- /dev/null +++ b/src/gumlet/types/video_profile_create_params.py @@ -0,0 +1,200 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = [ + "VideoProfileCreateParams", + "Crop", + "Pad", + "Trim", + "ImageOverlay", + "TextOverlay", + "AnimatedGif", + "GenerateSubtitles", +] + + +class VideoProfileCreateParams(TypedDict, total=False): + name: Required[str] + """Profile name or identifier.""" + + format: Required[Literal["ABR", "MP4"]] + """Transcode and deliver the asset in the requested format. The options can be one of `ABR` (HLS + DASH) and `MP4`.""" + + width: str + """Resize video with the given width. Can be an absolute value in pixels or a percentage value with the `%` suffix. Specified values greater than the original asset width will be ignored. Only applicable when specified `format` is `MP4`.""" + + height: str + """Resize video with the given height. Can be an absolute value in pixels or a percentage value with the `%` suffix. Specified values greater than the original asset height will be ignored. Only applicable when specified `format` is `MP4`.""" + + resolution: str + """Required resolutions of the transformed asset in case of HLS or MPEG-DASH delivery format. Can be a comma separated string out of the following values: `240p`, `360p`, `480p`, `540p`, `720p`, and `1080p `. Re-sized rendition will retain the input aspect ratio.""" + + crop: Crop + """This transformation can be used to crop the video by defining a rectangular area within the dimensions of the output video.""" + + pad: Pad + """This transformation can be used to add padding to the video.""" + + trim: Trim + """Trim transformation can be used to trim videos based on time duration.""" + + image_overlay: ImageOverlay + """Image overlay can be used to brand a video or add a visual label in the form of an image.""" + + text_overlay: TextOverlay + """Text overlay can be used to brand a video or add a label in the form of text.""" + + animated_gif: AnimatedGif + """Create an animated GIF from a video.""" + + generate_subtitles: GenerateSubtitles + """Gumlet allows to generate subtitles from the audio stream (use ISO 639-1 Language Codes). Remove this object if you do not want to generate AI subtitles.""" + + mp4_access: bool + """Creates `mp4` version for download purpose in case of `MPEG-DASH` or `HLS` delivery format. **Default: `false`**""" + + per_title_encoding: bool + """Gumlet analyzes each input video on a wide range of visual aspects. Based on the analysis, it chooses a unique set of transcoding options for processing the video. This ensures that the output video is of optimal size and best quality. **Default: `true`**""" + + process_low_resolution_input: bool + """Currently, the minimum supported frame size is `57600` (`240x240`) pixels for `HLS/DASH` and `21025` (`145x145`) pixels for `MP4` format. However, enabling this flag will allow Gumlet to simply put your video asset into the specified delivery format without transcoding and optimization. Enabling this flag will cause any kind of specified video transformation to be ignored if you input video asset frame size is lower than the minimum supported frame size for the specified format. **Default: `false`**""" + + audio_only: bool + """This flag allows Gumlet to transcode and deliver audio-only in the specified format. In this case,This flag allows Gumlet to transcode and deliver audio-only in the specified format. In this case, video transformation and thumbnails/animated GIFs would not be created. **Default: `false`**""" + + enable_drm: bool + """Enable DRM encryption for transcoded videos. Gumlet supports Widevine and Fairplay DRMs.""" + + +class GenerateSubtitles(TypedDict, total=False): + transcribe: bool + """This will generate the subtitles in the native audio.""" + + subtitle_languages: str + """Array of string of language codes for which subtitle needs to be generated. Maximum four language codes are allowed.""" + + +class AnimatedGif(TypedDict, total=False): + start_offset: str + """The time (in seconds or `HH:MM:SS` format) of the video timeline where the animated gif should begin. **Default: `0`**""" + + end_offset: str + """The time (in seconds or `HH:MM:SS` format) of the video timeline where the GIF ends. Defaults to `10` seconds after the start_offset. Maximum duration of GIF is limited to `10` seconds.""" + + width: str + """The width in pixels (or in percentage value of asset width) of the animated GIF. Max width is `640px`.""" + + height: str + """The height in pixels (or in percentage value of asset height) of the animated GIF. Max height is `640px`.""" + + fps: str + """The frame rate of the generated GIF. Defaults to `15` fps. Max `30` fps.""" + + +class TextOverlay(TypedDict, total=False): + text: Required[str] + """Text to be overlayed on video.""" + + horizontal_align: str + """This parameter specifies the horizontal alignment of the overlayed image and can be either `left` or `right`. **Default: `right`**""" + + vertical_align: str + """This parameter specifies the vertical alignment of the overlayed image and can be either `top` or `bottom`. **Default: `bottom`**""" + + horizontal_margin: str + """This parameter defines the horizontal coordinate value of the corner (determined by `horizontal_align`) of the overlay area. Values can be an absolute number of pixels relative to the video width. **Default: `0`**""" + + vertical_margin: str + """This parameter defines the vertical coordinate value of the corner (determined by vertical_align) of the overlay area. Values can be an absolute number of pixels relative to the video height. **Default: `0`**""" + + color: str + """Font color for text. **Default: `black`**""" + + font: str + """Font family type for text. **Default: `sans`**""" + + font_size: str + """Font size in pixels. **Default: `16`**""" + + opacity: str + """Overlay text opacity can be specified with opacity parameter where value can be between `0` and `100` where `0` is considered completely transparent and `100` is considered completely opaque. **Default: `100`**""" + + box: bool + """This parameter allows rectangular drawing a box over the overlayed text. **Default: `false`**""" + + box_color: str + """Box color can be specified with this parameter. **Default: `white`**""" + + box_opacity: str + """Box opacity can be specified with this parameter. **Default: `100`**""" + + box_border: str + """Padding between the box border and the text can be specified with this parameter in pixels. **Default: `0`**""" + + +class ImageOverlay(TypedDict, total=False): + url: Required[str] + """This is the required parameter for image overlay, it can be a URL to an image that needs to be overlayed.""" + + horizontal_margin: str + """This parameter defines the horizontal coordinate value of the corner (determined by `horizontal_align`) of the overlay area. Values can be an absolute number of pixels or a percentage value relative to the video width. **Default: `0`**""" + + vertical_margin: str + """This parameter defines the vertical coordinate value of the corner (determined by `vertical_align`) of the overlay area. Values can be an absolute number of pixels or a percentage value relative to the video height. **Default: `0`**""" + + horizontal_align: str + """This parameter specifies the horizontal alignment of the overlayed image and can be either `left` or `right`. **Default: `right`**""" + + vertical_align: str + """This parameter specifies the vertical alignment of the overlayed image and can be either `top` or `bottom`. **Default: `bottom`**""" + + width: str + """Width of the overlayed image. **Default: `image width`**""" + + height: str + """Height of the overlayed image. **Default: `image height`**""" + + +class Trim(TypedDict, total=False): + start_offset: Required[float] + """Start offset in number of seconds or in `HH:MM:SS` format.""" + + end_offset: Required[float] + """End offset in number of seconds or in `HH:MM:SS` format.""" + + duration: float + """Duration can be used in conjunction with `start_offset` parameter, can be specified in number of seconds.""" + + +class Pad(TypedDict, total=False): + top: str + """Width of padding on the top side. Values can be an absolute number of pixels or a percentage value relative to the video height. **Default: `%5`**""" + + left: str + """Width of padding on the left side. Values can be an absolute number of pixels or a percentage value relative to the video width. **Default: `0`**""" + + bottom: str + """Width of padding on the bottom side. Values can be an absolute number of pixels or a percentage value relative to the video height. **Default: `%5`**""" + + right: str + """Width of padding on the right side. Values can be an absolute number of pixels or a percentage value relative to the video width. **Default: `0`**""" + + color: str + """Color of padding area. **Default: `black`**""" + + +class Crop(TypedDict, total=False): + horizontal_margin: str + """This parameter defines the horizontal coordinate value of the upper-left corner of the cropping area. Values can be an absolute number of pixels or a percentage value relative to the video width. **Default: `0`**""" + + vertical_margin: str + """This parameter defines the vertical coordinate value of the upper-left corner of the cropping area. Values can be an absolute number of pixels or a percentage value relative to the video height. **Default: `0`**""" + + width: Required[str] + """Width of the cropping area in pixels.""" + + height: Required[str] + """Height of the cropping area in pixels.""" diff --git a/src/gumlet/types/video_profile_create_response.py b/src/gumlet/types/video_profile_create_response.py new file mode 100644 index 0000000..13bb674 --- /dev/null +++ b/src/gumlet/types/video_profile_create_response.py @@ -0,0 +1,35 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = ["VideoProfileCreateResponse", "Transformations"] + + +class Transformations(BaseModel): + format: Optional[str] = None + + audio_codec: Optional[List[str]] = None + + video_codec: Optional[List[str]] = None + + thumbnail: Optional[List[str]] = None + + thumbnail_format: Optional[str] = None + + mp4_access: Optional[bool] = None + + per_title_encoding: Optional[bool] = None + + +class VideoProfileCreateResponse(BaseModel): + profile_id: Optional[str] = None + + name: Optional[str] = None + + transformations: Optional[Transformations] = None + + created_at: Optional[int] = None diff --git a/src/gumlet/types/video_profile_delete_response.py b/src/gumlet/types/video_profile_delete_response.py new file mode 100644 index 0000000..53b7b79 --- /dev/null +++ b/src/gumlet/types/video_profile_delete_response.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from typing_extensions import TypeAlias + +__all__ = ["VideoProfileDeleteResponse"] + +VideoProfileDeleteResponse: TypeAlias = object diff --git a/src/gumlet/types/video_profile_list_params.py b/src/gumlet/types/video_profile_list_params.py new file mode 100644 index 0000000..fc3f73d --- /dev/null +++ b/src/gumlet/types/video_profile_list_params.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["VideoProfileListParams"] + + +class VideoProfileListParams(TypedDict, total=False): + offset: int + """Offset value for a paginated list of profiles. Can be zero for the first time and `current_offset` value received from the last request afterwards.""" + + size: int + """Page size for the paginated list. **Default: `10`**""" diff --git a/src/gumlet/types/video_profile_list_response.py b/src/gumlet/types/video_profile_list_response.py new file mode 100644 index 0000000..0269b29 --- /dev/null +++ b/src/gumlet/types/video_profile_list_response.py @@ -0,0 +1,45 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = ["VideoProfileListResponse", "AllProfile", "AllProfileTransformations"] + + +class AllProfileTransformations(BaseModel): + format: Optional[str] = None + + audio_codec: Optional[List[str]] = None + + video_codec: Optional[List[str]] = None + + thumbnail: Optional[List[str]] = None + + thumbnail_format: Optional[str] = None + + mp4_access: Optional[bool] = None + + per_title_encoding: Optional[bool] = None + + +class AllProfile(BaseModel): + profile_id: Optional[str] = None + + name: Optional[str] = None + + transformations: Optional[AllProfileTransformations] = None + + created_at: Optional[int] = None + + updated_at: Optional[int] = None + + +class VideoProfileListResponse(BaseModel): + all_profiles: Optional[List[AllProfile]] = None + + total_profile_count: Optional[int] = None + + current_offset: Optional[int] = None diff --git a/src/gumlet/types/video_profile_retrieve_response.py b/src/gumlet/types/video_profile_retrieve_response.py new file mode 100644 index 0000000..de75ec2 --- /dev/null +++ b/src/gumlet/types/video_profile_retrieve_response.py @@ -0,0 +1,39 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = ["VideoProfileRetrieveResponse", "Transformations"] + + +class Transformations(BaseModel): + format: Optional[str] = None + + audio_codec: Optional[List[str]] = None + + video_codec: Optional[List[str]] = None + + thumbnail: Optional[List[str]] = None + + thumbnail_format: Optional[str] = None + + mp4_access: Optional[bool] = None + + per_title_encoding: Optional[bool] = None + + resolution: Optional[str] = None + + +class VideoProfileRetrieveResponse(BaseModel): + profile_id: Optional[str] = None + + name: Optional[str] = None + + transformations: Optional[Transformations] = None + + created_at: Optional[int] = None + + updated_at: Optional[int] = None diff --git a/src/gumlet/types/video_profile_update_params.py b/src/gumlet/types/video_profile_update_params.py new file mode 100644 index 0000000..2483e57 --- /dev/null +++ b/src/gumlet/types/video_profile_update_params.py @@ -0,0 +1,215 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import Annotated, Literal, Required, TypedDict +from .._types import SequenceNotStr + +from .._utils import PropertyInfo + +__all__ = [ + "VideoProfileUpdateParams", + "Crop", + "Pad", + "Trim", + "ImageOverlay", + "TextOverlay", + "AnimatedGif", + "GenerateSubtitles", +] + + +class VideoProfileUpdateParams(TypedDict, total=False): + body_profile_id: Required[Annotated[str, PropertyInfo(alias="profile_id")]] + """Profile id of the profile which needs to be deleted.""" + + name: str + """Profile name or identifier.""" + + format: Literal["ABR", "MP4"] + """Transcode and deliver the asset in the requested format. The options can be one of `ABR` (HLS + DASH) and `MP4`.""" + + width: str + """Resize video with the given width. Can be an absolute value in pixels or a percentage value with the `%` suffix. Specified values greater than the original asset width will be ignored. Only applicable when specified `format` is `MP4`.""" + + height: str + """Resize video with the given height. Can be an absolute value in pixels or a percentage value with the `%` suffix. Specified values greater than the original asset height will be ignored. Only applicable when specified `format` is `MP4`.""" + + resolution: str + """Resize video with the given height. Can be an absolute value in pixels or a percentage value with the `%` suffix. Specified values greater than the original asset height will be ignored. Only applicable when specified `format` is `MP4`.""" + + crop: Crop + """This transformation can be used to crop the video by defining a rectangular area within the dimensions of the output video.""" + + pad: Pad + """This transformation can be used to add padding to the video.""" + + trim: Trim + """Trim transformation can be used to trim videos based on time duration.""" + + image_overlay: ImageOverlay + """Image overlay can be used to brand a video or add a visual label in the form of an image.""" + + text_overlay: TextOverlay + """Text overlay can be used to brand a video or add a label in the form of text.""" + + animated_gif: AnimatedGif + """Create an animated GIF from a video.""" + + generate_subtitles: GenerateSubtitles + """Gumlet allows to generate subtitles from the audio stream (use ISO 639-1 Language Codes). You can remove this object if don't want to generate AI subtitles.""" + + mp4_access: bool + """Creates `mp4` version for download purpose in case of `MPEG-DASH` or `HLS` delivery format. **Default: `false`**""" + + per_title_encoding: bool + """Gumlet analyzes each input video on a wide range of visual aspects. Based on the analysis, it chooses a unique set of transcoding options for processing the video. This ensures that the output video is of optimal size and best quality. **Default: `true`**""" + + process_low_resolution_input: bool + """Currently, the minimum supported frame size is `57600` (`240x240`) pixels for `HLS/DASH` and `21025` (`145x145`) pixels for `MP4` format. However, enabling this flag will allow Gumlet to simply put your video asset into the specified delivery format without transcoding and optimization. Enabling this flag will cause any kind of specified video transformation to be ignored if you input video asset frame size is lower than the minimum supported frame size for the specified format. **Default: `false`**""" + + audio_only: bool + """This flag allows Gumlet to transcode and deliver audio-only in the specified format. In this case,This flag allows Gumlet to transcode and deliver audio-only in the specified format. In this case, video transformation and thumbnails/animated GIFs would not be created. **Default: `false`**""" + + enable_drm: bool + """Enable DRM encryption for transcoded videos. Gumlet supports Widevine and Fairplay DRMs.""" + + vc: SequenceNotStr[str] + """Video Codecs""" + + generate_chapters: bool + """Whether Gumlet should generate chapters.""" + + generate_description: bool + """Whether Gumlet should generate descriptions.""" + + +class GenerateSubtitles(TypedDict, total=False): + subtitle_languages: SequenceNotStr[str] + """Array of string of language codes for which subtitle needs to be generated. Maximum four language codes are allowed.""" + + transcribe: bool + """This is by default true to generate the subtitles in the native audio.""" + + +class AnimatedGif(TypedDict, total=False): + start_offset: str + """The time (in seconds or `HH:MM:SS` format) of the video timeline where the animated gif should begin. **Default: `0`**""" + + end_offset: str + """The time (in seconds or `HH:MM:SS` format) of the video timeline where the GIF ends. Defaults to `10` seconds after the start_offset. Maximum duration of GIF is limited to `10` seconds.""" + + width: str + """The width in pixels (or in percentage value of asset width) of the animated GIF. Max width is `640px`.""" + + height: str + """The height in pixels (or in percentage value of asset height) of the animated GIF. Max height is `640px`.""" + + fps: str + """The frame rate of the generated GIF. Defaults to `15` fps. Max `30` fps.""" + + +class TextOverlay(TypedDict, total=False): + text: Required[str] + """Text to be overlayed on video.""" + + horizontal_align: str + """This parameter specifies the horizontal alignment of the overlayed image and can be either `left` or `right`. **Default: `right`**""" + + vertical_align: str + """This parameter specifies the vertical alignment of the overlayed image and can be either `top` or `bottom`. **Default: `bottom`**""" + + horizontal_margin: str + """This parameter defines the horizontal coordinate value of the corner (determined by `horizontal_align`) of the overlay area. Values can be an absolute number of pixels relative to the video width. **Default: `0`**""" + + vertical_margin: str + """This parameter defines the vertical coordinate value of the corner (determined by vertical_align) of the overlay area. Values can be an absolute number of pixels relative to the video height. **Default: `0`**""" + + color: str + """Font color for text. **Default: `black`**""" + + font: str + """Font family type for text. **Default: `sans`**""" + + font_size: str + """Font size in pixels. **Default: `16`**""" + + opacity: str + """Overlay text opacity can be specified with opacity parameter where value can be between `0` and `100` where `0` is considered completely transparent and `100` is considered completely opaque. **Default: `100`**""" + + box: bool + """This parameter allows rectangular drawing a box over the overlayed text. **Default: `false`**""" + + box_color: str + """Box color can be specified with this parameter. **Default: `white`**""" + + box_opacity: str + """Box opacity can be specified with this parameter. **Default: `100`**""" + + box_border: str + """Padding between the box border and the text can be specified with this parameter in pixels. **Default: `0`**""" + + +class ImageOverlay(TypedDict, total=False): + url: Required[str] + """This is the required parameter for image overlay, it can be a URL to an image that needs to be overlayed.""" + + horizontal_margin: str + """This parameter defines the horizontal coordinate value of the corner (determined by `horizontal_align`) of the overlay area. Values can be an absolute number of pixels or a percentage value relative to the video width. **Default: `0`**""" + + vertical_margin: str + """This parameter defines the vertical coordinate value of the corner (determined by `vertical_align`) of the overlay area. Values can be an absolute number of pixels or a percentage value relative to the video height. **Default: `0`**""" + + horizontal_align: str + """This parameter specifies the horizontal alignment of the overlayed image and can be either `left` or `right`. **Default: `right`**""" + + vertical_align: str + """This parameter specifies the vertical alignment of the overlayed image and can be either `top` or `bottom`. **Default: `bottom`**""" + + width: str + """Width of the overlayed image. **Default: `image width`**""" + + height: str + """Height of the overlayed image. **Default: `image height`**""" + + +class Trim(TypedDict, total=False): + start_offset: Required[float] + """Start offset in number of seconds or in `HH:MM:SS` format.""" + + end_offset: Required[float] + """End offset in number of seconds or in `HH:MM:SS` format.""" + + duration: float + """Duration can be used in conjunction with `start_offset` parameter, can be specified in number of seconds.""" + + +class Pad(TypedDict, total=False): + top: str + """Width of padding on the top side. Values can be an absolute number of pixels or a percentage value relative to the video height. **Default: `%5`**""" + + left: str + """Width of padding on the left side. Values can be an absolute number of pixels or a percentage value relative to the video width. **Default: `0`**""" + + bottom: str + """Width of padding on the bottom side. Values can be an absolute number of pixels or a percentage value relative to the video height. **Default: `%5`**""" + + right: str + """Width of padding on the right side. Values can be an absolute number of pixels or a percentage value relative to the video width. **Default: `0`**""" + + color: str + """Color of padding area. **Default: `black`**""" + + +class Crop(TypedDict, total=False): + horizontal_margin: str + """This parameter defines the horizontal coordinate value of the upper-left corner of the cropping area. Values can be an absolute number of pixels or a percentage value relative to the video width. **Default: `0`**""" + + vertical_margin: str + """This parameter defines the vertical coordinate value of the upper-left corner of the cropping area. Values can be an absolute number of pixels or a percentage value relative to the video height. **Default: `0`**""" + + width: Required[str] + """Width of the cropping area in pixels.""" + + height: Required[str] + """Height of the cropping area in pixels.""" diff --git a/src/gumlet/types/video_profile_update_response.py b/src/gumlet/types/video_profile_update_response.py new file mode 100644 index 0000000..5ac58c9 --- /dev/null +++ b/src/gumlet/types/video_profile_update_response.py @@ -0,0 +1,39 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = ["VideoProfileUpdateResponse", "Transformations"] + + +class Transformations(BaseModel): + resolution: Optional[str] = None + + format: Optional[str] = None + + audio_codec: Optional[List[str]] = None + + video_codec: Optional[List[str]] = None + + thumbnail: Optional[List[str]] = None + + thumbnail_format: Optional[str] = None + + mp4_access: Optional[bool] = None + + per_title_encoding: Optional[bool] = None + + +class VideoProfileUpdateResponse(BaseModel): + profile_id: Optional[str] = None + + name: Optional[str] = None + + transformations: Optional[Transformations] = None + + created_at: Optional[int] = None + + updated_at: Optional[int] = None diff --git a/src/gumlet/types/video_usage_analytic_retrieve_params.py b/src/gumlet/types/video_usage_analytic_retrieve_params.py new file mode 100644 index 0000000..a8a7be7 --- /dev/null +++ b/src/gumlet/types/video_usage_analytic_retrieve_params.py @@ -0,0 +1,58 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List, Union +from datetime import date +from typing_extensions import Annotated, Literal, Required, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["VideoUsageAnalyticRetrieveParams", "DateRange", "Filters"] + + +class VideoUsageAnalyticRetrieveParams(TypedDict, total=False): + metrics: Required[ + List[ + Literal[ + "bandwidth_consumption", + "asset_duration", + "storage_unit", + "top_assets", + "drm_requests", + "ai_credit_usage", + "errored_videos", + ] + ] + ] + """Define the metric you need the data for, currently we only support `bandwidth_consumption`, `asset_duration`, `storage_unit`, `top_assets`, `bandwidth_consumption_by_collection`, `errored_videos` and `widget_data`""" + + date_range: Required[DateRange] + """The timeframe to get the data for. Currently we only support a maximum of 60 days between `start_at` and `end_at`.""" + + filters: Filters + + top_assets_count: str + """Count of video assets that should be returned. Max assets count is 1000 per page.""" + + top_assets_page: str + """top_assets metric may get paginated response. Iterate this parameter to get more data.""" + + group_by: Literal["hourly", "daily", "monthly"] + """Group by hourly, daily or monthly. If you don't specify anything it's `hourly` by default.""" + + +class Filters(TypedDict, total=False): + collection_id: str + """The ID of the `workspace` you want to filter the data for.""" + + source_id: str + """The ID of the `workspace` you want to filter the data for. Deprecated.""" + + +class DateRange(TypedDict, total=False): + start_at: Annotated[Union[str, date], PropertyInfo(format="iso8601")] + """The starting date to consider""" + + end_at: Annotated[Union[str, date], PropertyInfo(format="iso8601")] + """The ending date to consider""" diff --git a/src/gumlet/types/video_usage_analytic_retrieve_response.py b/src/gumlet/types/video_usage_analytic_retrieve_response.py new file mode 100644 index 0000000..edb2aa6 --- /dev/null +++ b/src/gumlet/types/video_usage_analytic_retrieve_response.py @@ -0,0 +1,109 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List, Optional +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = [ + "VideoUsageAnalyticRetrieveResponse", + "DrmRequest", + "BandwidthConsumption", + "StorageUnit", + "AssetDuration", + "TopAsset", + "AICreditUsage", + "ErroredVideo", +] + + +class ErroredVideo(BaseModel): + timestamp: int + """Seconds since epoch for the unit given.""" + + units: int + """Number of errored videos in the timeframe""" + + +class AICreditUsage(BaseModel): + timestamp: int + """Seconds since epoch for the unit given.""" + + +class TopAsset(BaseModel): + asset_id: str + """Asset ID""" + + units: str + """Bandwidth consumption by this asset in bytes.""" + + workspace_id: str + """Workspace ID""" + + duration: str + """Seconds of streaming minutes consumed by this asset.""" + + title: str + """Asset Title""" + + collection_name: str + """Workspace Name""" + + +class AssetDuration(BaseModel): + units: int + """Total transcoding duration in seconds.""" + + timestamp: int + """Seconds since epoch for the unit given.""" + + +class StorageUnit(BaseModel): + units: int + """The storage data in bytes or seconds.""" + + timestamp: int + """Seconds since epoch for the unit given.""" + + +class BandwidthConsumption(BaseModel): + units: int + """The bandwidth consumption data in bytes.""" + + timestamp: int + """Seconds since epoch for the unit given.""" + + +class DrmRequest(BaseModel): + units: int + """Number of DRM requests.""" + + timestamp: int + """Seconds since epoch for the unit given.""" + + +class VideoUsageAnalyticRetrieveResponse(BaseModel): + drm_requests: Optional[List[DrmRequest]] = None + + bandwidth_saving_unit: Optional[Literal[""]] = None + """The unit for bandwidth saving data.""" + + storage_data_unit: Optional[Literal["gb", "min"]] = None + """The unit for the storage data.""" + + bandwidth_consumption: Optional[List[BandwidthConsumption]] = None + + storage_unit: Optional[List[StorageUnit]] = None + + asset_duration: Optional[List[AssetDuration]] = None + + top_assets: Optional[List[TopAsset]] = None + + has_more_top_asset: Optional[bool] = None + """Indicates whether the list of top assets is exhaustive or if it has more assets.""" + + ai_credit_usage: Optional[List[AICreditUsage]] = None + + errored_videos: Optional[List[ErroredVideo]] = None diff --git a/src/gumlet/types/video_usage_analytic_top_assets_params.py b/src/gumlet/types/video_usage_analytic_top_assets_params.py new file mode 100644 index 0000000..80692f5 --- /dev/null +++ b/src/gumlet/types/video_usage_analytic_top_assets_params.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["VideoUsageAnalyticTopAssetsParams"] + + +class VideoUsageAnalyticTopAssetsParams(TypedDict, total=False): + start_at: Required[str] + """Date string in "yyyy-mm-dd" format""" + + end_at: Required[str] + """Date string in "yyyy-mm-dd" format""" + + collection_id: str + """Gumlet workspace ID""" + + page: str + """Page number of the response.""" + + page_size: str + """Assets to list per page.""" diff --git a/src/gumlet/types/video_usage_analytic_top_assets_response.py b/src/gumlet/types/video_usage_analytic_top_assets_response.py new file mode 100644 index 0000000..6880664 --- /dev/null +++ b/src/gumlet/types/video_usage_analytic_top_assets_response.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = ["VideoUsageAnalyticTopAssetsResponse", "Data"] + + +class Data(BaseModel): + asset_id: Optional[str] = None + + units: Optional[int] = None + + +class VideoUsageAnalyticTopAssetsResponse(BaseModel): + data: Optional[List[Data]] = None + + has_next_page: Optional[bool] = None diff --git a/src/gumlet/types/video_workspace_create_params.py b/src/gumlet/types/video_workspace_create_params.py new file mode 100644 index 0000000..e35117a --- /dev/null +++ b/src/gumlet/types/video_workspace_create_params.py @@ -0,0 +1,205 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict +from .._types import SequenceNotStr + +__all__ = [ + "VideoWorkspaceCreateParams", + "VideoProtection", + "Aws", + "Proxy", + "Gcs", + "Dostorage", + "Wasabi", + "Cloudinary", + "Azure", + "Linode", + "Backblaze", + "Cloudflare", + "Zoom", +] + + +class VideoWorkspaceCreateParams(TypedDict, total=False): + name: Required[str] + """Specify a text string or identifier for the workspace.""" + + type: Required[ + Literal[ + "proxy", + "direct-upload", + "webfolder", + "aws", + "gcs", + "dostorage", + "wasabi", + "cloudinary", + "azure", + "linode", + "backblaze", + "cloudflare", + "zoom", + ] + ] + """Video workspaces are top-level entities in Gumlet. You can use them to organize videos for different teams/departments or use cases.""" + + default_profile_id: str + """Gumlet provides the functionality of creating multiple video assets using the same set of parameters.""" + + insight_property_id: str + """The five to ten character unique identifier of the Gumlet Insight Property available on the dashboard.""" + + video_protection: VideoProtection + """Gumlet provides multiple options for securing your video playback.""" + + aws: Aws + """This is a required field if workspace type is aws.""" + + proxy: Proxy + """This is a required field if workspace type is proxy.""" + + gcs: Gcs + """This is a required field if workspace type is gcs.""" + + dostorage: Dostorage + """This is a required field if workspace type is dostorage.""" + + wasabi: Wasabi + """This is a required field if workspace type is wasabi.""" + + cloudinary: Cloudinary + """This is a required field if workspace type is cloudinary.""" + + azure: Azure + """This is a required field if workspace type is azure.""" + + linode: Linode + """This is a required field if workspace type is linode.""" + + backblaze: Backblaze + """This is a required field if workspace type is backblaze.""" + + cloudflare: Cloudflare + """This is a required field if workspace type is cloudflare.""" + + zoom: Zoom + """This is a required field if workspace type is zoom.""" + + +class Zoom(TypedDict, total=False): + secret: Required[str] + + +class Cloudflare(TypedDict, total=False): + bucket_name: Required[str] + + access_key: Required[str] + + account_id: Required[str] + + secret: Required[str] + + base_path: str + + +class Backblaze(TypedDict, total=False): + bucket_name: Required[str] + + bucket_region: str + """bucket_region or endpoint""" + + endpoint: str + """bucket_region or endpoint""" + + access_key: Required[str] + + secret: Required[str] + + base_path: str + + +class Linode(TypedDict, total=False): + bucket_name: Required[str] + + bucket_region: Required[str] + + access_key: Required[str] + + secret: Required[str] + + +class Azure(TypedDict, total=False): + azure_account_name: Required[str] + + azure_container_name: Required[str] + + azure_shared_token: Required[str] + + azure_path: Required[str] + + +class Cloudinary(TypedDict, total=False): + host_name: Required[str] + + cloud_name: Required[str] + + +class Wasabi(TypedDict, total=False): + bucket_name: Required[str] + + bucket_region: Required[str] + + access_key: Required[str] + + secret: Required[str] + + base_path: str + + +class Dostorage(TypedDict, total=False): + bucket_name: Required[str] + + bucket_region: Required[str] + + access_key: Required[str] + + secret: Required[str] + + base_path: str + + +class Gcs(TypedDict, total=False): + bucket_name: Required[str] + + service_account_key: Required[str] + + +class Proxy(TypedDict, total=False): + whitelisted_domains: Required[str] + + +class Aws(TypedDict, total=False): + bucket_name: Required[str] + + bucket_region: Required[str] + + access_key: Required[str] + + secret: Required[str] + + base_path: str + + endpoint: str + + +class VideoProtection(TypedDict, total=False): + signed_url: bool + + signed_url_secret: str + + blacklisted_countries: SequenceNotStr[str] + """Example: ["IN","USA"]""" + + whitelisted_referrers: str diff --git a/src/gumlet/types/video_workspace_create_response.py b/src/gumlet/types/video_workspace_create_response.py new file mode 100644 index 0000000..8c348f9 --- /dev/null +++ b/src/gumlet/types/video_workspace_create_response.py @@ -0,0 +1,121 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = ["VideoWorkspaceCreateResponse", "PlayerConfig", "Zoom", "EmbedDetails", "ChannelSettings"] + + +class ChannelSettings(BaseModel): + active: Optional[bool] = None + + privacy_type: Optional[str] = None + + +class EmbedDetails(BaseModel): + powered_by_gumlet_overlay: Optional[bool] = None + + allow_drm_protected_videos: Optional[bool] = None + + preload: Optional[bool] = None + + autoplay: Optional[bool] = None + + logo_width: Optional[int] = None + + logo_height: Optional[int] = None + + player_color: Optional[str] = None + + is_seo: Optional[bool] = None + + dynamic_watermark: Optional[bool] = None + + watermark_font_size: Optional[int] = None + + watermark_font_color: Optional[str] = None + + watermark_bg_color: Optional[str] = None + + watermark_interval: Optional[int] = None + + disable_seek: Optional[bool] = None + + disable_player_controls: Optional[bool] = None + + loop: Optional[bool] = None + + subtitle_enabled: Optional[bool] = None + + +class Zoom(BaseModel): + secret: Optional[str] = None + + +class PlayerConfig(BaseModel): + preload: Optional[bool] = None + + autoplay: Optional[bool] = None + + disable_seek: Optional[bool] = None + + disable_player_controls: Optional[bool] = None + + powered_by_gumlet_overlay: Optional[bool] = None + + allow_drm_protected_videos: Optional[bool] = None + + loop: Optional[bool] = None + + player_color: Optional[str] = None + + include_seo: Optional[bool] = None + + subtitle_enabled: Optional[bool] = None + + pixel_tags: Optional[object] = None + + logo_width: Optional[int] = None + + logo_height: Optional[int] = None + + dynamic_watermark: Optional[bool] = None + + watermark_font_size: Optional[int] = None + + watermark_font_color: Optional[str] = None + + watermark_bg_color: Optional[str] = None + + watermark_interval: Optional[int] = None + + +class VideoWorkspaceCreateResponse(BaseModel): + id: Optional[str] = None + + name: Optional[str] = None + + type: Optional[str] = None + + created_at: Optional[str] = None + + updated_at: Optional[str] = None + + video_protection: Optional[object] = None + + player_config: Optional[PlayerConfig] = None + + default_profile_id: Optional[str] = None + + insight_property_id: Optional[str] = None + + zoom: Optional[Zoom] = None + + embed_details: Optional[EmbedDetails] = None + + folders: Optional[List[str]] = None + + channel_settings: Optional[ChannelSettings] = None diff --git a/src/gumlet/types/video_workspace_delete_response.py b/src/gumlet/types/video_workspace_delete_response.py new file mode 100644 index 0000000..d9834c4 --- /dev/null +++ b/src/gumlet/types/video_workspace_delete_response.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from typing_extensions import TypeAlias + +__all__ = ["VideoWorkspaceDeleteResponse"] + +VideoWorkspaceDeleteResponse: TypeAlias = object diff --git a/src/gumlet/types/video_workspace_list_params.py b/src/gumlet/types/video_workspace_list_params.py new file mode 100644 index 0000000..1d6572f --- /dev/null +++ b/src/gumlet/types/video_workspace_list_params.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["VideoWorkspaceListParams"] + + +class VideoWorkspaceListParams(TypedDict, total=False): + offset: str + """Number of workspaces to skip. For example if you need to list 11 to 20th workspaces, pass offset as 10 and size as 10.""" + + size: str + """Number of workspace to return in single response.""" diff --git a/src/gumlet/types/video_workspace_list_response.py b/src/gumlet/types/video_workspace_list_response.py new file mode 100644 index 0000000..2388f84 --- /dev/null +++ b/src/gumlet/types/video_workspace_list_response.py @@ -0,0 +1,162 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = [ + "VideoWorkspaceListResponse", + "AllSource", + "AllSourceVideoProtection", + "AllSourcePlayerConfig", + "AllSourceAws", + "AllSourceEmbedDetails", + "AllSourceChannelSettings", +] + + +class AllSourceChannelSettings(BaseModel): + title: Optional[str] = None + + active: Optional[bool] = None + + description: Optional[str] = None + + privacy_type: Optional[str] = None + + custom_logo: Optional[bool] = None + + logo_url: Optional[str] = None + + cname: Optional[List[str]] = None + + temp_cname: Optional[List[str]] = None + + +class AllSourceEmbedDetails(BaseModel): + pixel_tags: Optional[object] = None + + preload: Optional[bool] = None + + autoplay: Optional[bool] = None + + logo_width: Optional[int] = None + + logo_height: Optional[int] = None + + player_color: Optional[str] = None + + is_seo: Optional[bool] = None + + dynamic_watermark: Optional[bool] = None + + disable_seek: Optional[bool] = None + + disable_player_controls: Optional[bool] = None + + allow_drm_protected_videos: Optional[bool] = None + + powered_by_gumlet_overlay: Optional[bool] = None + + loop: Optional[bool] = None + + subtitle_enabled: Optional[bool] = None + + watermark_bg_color: Optional[str] = None + + watermark_font_color: Optional[str] = None + + watermark_font_size: Optional[int] = None + + watermark_interval: Optional[int] = None + + +class AllSourceAws(BaseModel): + bucket_name: Optional[str] = None + + bucket_region: Optional[str] = None + + access_key: Optional[str] = None + + secret: Optional[str] = None + + +class AllSourcePlayerConfig(BaseModel): + preload: Optional[bool] = None + + autoplay: Optional[bool] = None + + disable_seek: Optional[bool] = None + + disable_player_controls: Optional[bool] = None + + powered_by_gumlet_overlay: Optional[bool] = None + + allow_drm_protected_videos: Optional[bool] = None + + loop: Optional[bool] = None + + player_color: Optional[str] = None + + include_seo: Optional[bool] = None + + subtitle_enabled: Optional[bool] = None + + pixel_tags: Optional[object] = None + + logo_width: Optional[int] = None + + logo_height: Optional[int] = None + + dynamic_watermark: Optional[bool] = None + + watermark_font_size: Optional[int] = None + + watermark_font_color: Optional[str] = None + + watermark_bg_color: Optional[str] = None + + watermark_interval: Optional[int] = None + + +class AllSourceVideoProtection(BaseModel): + signed_url: Optional[bool] = None + + signed_url_secret: Optional[str] = None + + +class AllSource(BaseModel): + id: Optional[str] = None + + name: Optional[str] = None + + type: Optional[str] = None + + created_at: Optional[str] = None + + updated_at: Optional[str] = None + + video_protection: Optional[AllSourceVideoProtection] = None + + player_config: Optional[AllSourcePlayerConfig] = None + + default_profile_id: Optional[str] = None + + insight_property_id: Optional[str] = None + + aws: Optional[AllSourceAws] = None + + embed_details: Optional[AllSourceEmbedDetails] = None + + folders: Optional[List[str]] = None + + channel_settings: Optional[AllSourceChannelSettings] = None + + on_streaming_halt: Optional[bool] = None + """Whether the workspace is disabled for streaming. `true` means it's disabled.""" + + +class VideoWorkspaceListResponse(BaseModel): + all_sources: Optional[List[AllSource]] = None diff --git a/src/gumlet/types/video_workspace_retrieve_response.py b/src/gumlet/types/video_workspace_retrieve_response.py new file mode 100644 index 0000000..feccc72 --- /dev/null +++ b/src/gumlet/types/video_workspace_retrieve_response.py @@ -0,0 +1,234 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List, Optional +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from .._models import BaseModel + +__all__ = [ + "VideoWorkspaceRetrieveResponse", + "VideoProtection", + "PlayerConfig", + "ChannelSettings", + "ChannelSettingsVisiblePlaylist", + "StorageEventListener", +] + + +class StorageEventListener(BaseModel): + event_id: str + """Storage event id""" + + profile_id: str + """Profile ID associated with storage event""" + + profile_name: Optional[str] = None + """Profile Name""" + + is_varified: bool + """If this webhook listener is verified.""" + + callback_url: Optional[str] = None + """Callback URL""" + + status: Optional[str] = None + """Status""" + + +class ChannelSettingsVisiblePlaylist(BaseModel): + field_id: str = FieldInfo(alias="_id") + """Playlist ID""" + + title: str + """Playlist Title""" + + +class ChannelSettings(BaseModel): + title: str + """Channel title.""" + + active: bool + """Whether the channel is active.""" + + privacy_type: Literal["private", "public", "password", "dashboardOnly"] + """Privacy type of videos in this workspace""" + + channel_access_control: Literal["private", "public"] + """Whether channel can be accessed publicly or it's invite only channel.""" + + dynamic_watermark_type: str + """What to show in dynamic watermark.""" + + disable_invite_email: bool + """Enable / disable channel invite email.""" + + password: Optional[str] = None + """Password for the channel.""" + + color_scheme: Optional[str] = None + """Color scheme for the channel UI.""" + + description: Optional[str] = None + """Channel description.""" + + theme_preference: Optional[str] = None + """Dark / light theme preference for the channel.""" + + cname: Optional[List[str]] = None + """Custom domains assigned to this channel.""" + + hero_url: Optional[str] = None + """Hero banner URL""" + + hero_updated_at: Optional[str] = None + """ISO 8601 hero banner update timestamp.""" + + icon_url: Optional[str] = None + """Channel icon URL""" + + icon_updated_at: Optional[str] = None + """ISO 8601 channel icon update timestamp.""" + + temp_cname: Optional[List[str]] = None + """Array of custom domains which are not yet verified.""" + + logo_url: Optional[str] = None + """Logo URL""" + + logo_updated_at: str + """ISO 8601 logo update timestamp.""" + + visible_playlists: Optional[List[ChannelSettingsVisiblePlaylist]] = None + + +class PlayerConfig(BaseModel): + autoplay: bool + """Video autoplay enable flag. True means autoplay is enabled.""" + + disable_seek: bool + """Seek bar enable / disable""" + + disable_player_controls: bool + """Disable / enable all player controls""" + + powered_by_gumlet_overlay: bool + """Whether Gumlet logo shows on player.""" + + loop: bool + """Whether video should loop once it ends. `true` value means video will loop""" + + player_color: str + """Hex color string of video player color""" + + enable_download_button: bool + """Enable / disable download button on player.""" + + caption_enabled: bool + """Enable / disable video captions on player.""" + + logo_width: Optional[float] = None + """Logo display width in pixels.""" + + logo_height: Optional[float] = None + """Logo display height in pixels.""" + + dynamic_watermark: bool + """Flag if dynamic watermark is enabled.""" + + watermark_font_size: Optional[float] = None + """Dynamic watermark font size in pixels.""" + + watermark_font_color: Optional[str] = None + """Hex color code of dynamic watermark text.""" + + watermark_bg_color: Optional[str] = None + """Hex color code of background for dynamic watermark text.""" + + watermark_interval: Optional[int] = None + """Interval in milliseconds between dynamic watermark flashing.""" + + watermark_visiblity_duration: Optional[int] = None + """Duration for which the dynamic watermark will be visible on screen.""" + + show_video_title: bool + """Enable / disable video title on player.""" + + cast: bool + """Enable / disable cast button on player.""" + + resume_where_left: bool + """Enable / disable playback from last viewed position. `true` means the playback will resume from last position.""" + + cc_color: Optional[str] = None + """Closed captions / subtitle color.""" + + cc_bg_color: Optional[str] = None + """Closed captions / subtitle background color.""" + + cc_font_size: Optional[Literal["small", "medium", "large"]] = None + """Font size of closed captions.""" + + +class VideoProtection(BaseModel): + signed_url_secret: Optional[str] = None + """Secret that is to be used to sign URLs""" + + blacklisted_countries: Optional[List[str]] = None + """Blacklist of 2 letter country codes.""" + + whitelisted_referrers: Optional[List[str]] = None + """List of whitelisted domains which allow playback for the videos.""" + + whitelisted_countries: Optional[str] = None + """List of whitelisted of 2 letter country codes.""" + + signed_url: Optional[bool] = None + """Boolean value indicating whether signed URL is enabled.""" + + +class VideoWorkspaceRetrieveResponse(BaseModel): + id: str + """Workspace ID""" + + name: str + """Workspace Name""" + + type: str + """Workspace Type""" + + created_at: str + """ISO 8601 formatted creation timestamp of this workspace.""" + + updated_at: str + """ISO 8601 formatted update timestamp of this workspace.""" + + default_profile_id: Optional[str] = None + """Profile ID for the default profile of this workspace""" + + default_profile_updated_at: Optional[str] = None + """ISO 8601 formatted profile update timestamp""" + + insights_enabled: bool + """Whether Gumlet video analytics is enabled for video embeds in this workspace.""" + + video_protection: Optional[VideoProtection] = None + + player_config: PlayerConfig + """Video player configuration""" + + folders: List[str] + """List of folder names in this workspace.""" + + channel_settings: ChannelSettings + + distinct_tags: Optional[List[str]] = None + """List of distinct tags in entire workspace.""" + + on_streaming_halt: Optional[bool] = None + """`true` means the workspace is disabled for streaming. No videos will stream from this workspace.""" + + storage_event_listener: Optional[List[StorageEventListener]] = None diff --git a/src/gumlet/types/video_workspace_update_params.py b/src/gumlet/types/video_workspace_update_params.py new file mode 100644 index 0000000..6ce466c --- /dev/null +++ b/src/gumlet/types/video_workspace_update_params.py @@ -0,0 +1,280 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict +from .._types import SequenceNotStr + +__all__ = [ + "VideoWorkspaceUpdateParams", + "PlayerConfig", + "VideoProtection", + "ChannelSettings", + "Webfolder", + "Aws", + "Proxy", + "Gcs", + "Dostorage", + "Wasabi", + "Linode", + "Backblaze", + "Cloudflare", + "Cloudinary", + "Azure", + "Zoom", +] + + +class VideoWorkspaceUpdateParams(TypedDict, total=False): + name: str + """video workspace name""" + + default_profile_id: str + """Gumlet provides the functionality of creating multiple video assets using the same set of parameters.""" + + temp_cname: SequenceNotStr[str] + """cname for channel""" + + insight_property_id: str + """The five to ten character unique identifier of the Gumlet Insight Property available on the dashboard.""" + + player_config: PlayerConfig + """Configure player settings for this playlist, it overrides the setting set on workspace.""" + + video_protection: VideoProtection + """Gumlet provides multiple options for securing your video playback.""" + + channel_settings: ChannelSettings + """Configurations to set various channel settings.""" + + type: Literal[ + "proxy", + "direct-upload", + "webfolder", + "aws", + "gcs", + "dostorage", + "wasabi", + "cloudinary", + "azure", + "linode", + "backblaze", + "cloudflare", + "zoom", + ] + """Video workspaces are top-level entities in Gumlet. You can use them to organize videos for different teams/departments or use cases.""" + + webfolder: Webfolder + """This is a required field if workspace type is webfolder.""" + + aws: Aws + """This is a required field if workspace type is aws.""" + + proxy: Proxy + """This is a required field if workspace type is proxy.""" + + gcs: Gcs + """This is a required field if workspace type is gcs.""" + + dostorage: Dostorage + """This is a required field if workspace type is dostorage.""" + + wasabi: Wasabi + """This is a required field if workspace type is wasabi.""" + + linode: Linode + """This is a required field if workspace type is linode.""" + + backblaze: Backblaze + """This is a required field if workspace type is backblaze.""" + + cloudflare: Cloudflare + """This is a required field if workspace type is cloudflare.""" + + cloudinary: Cloudinary + """This is a required field if workspace type is cloudinary.""" + + azure: Azure + """This is a required field if workspace type is azure.""" + + zoom: Zoom + """This is a required field if workspace type is zoom.""" + + +class Zoom(TypedDict, total=False): + secret: Required[str] + + +class Azure(TypedDict, total=False): + azure_account_name: Required[str] + + azure_container_name: Required[str] + + azure_shared_token: Required[str] + + azure_path: Required[str] + + +class Cloudinary(TypedDict, total=False): + host_name: Required[str] + + cloud_name: Required[str] + + +class Cloudflare(TypedDict, total=False): + bucket_name: Required[str] + + access_key: Required[str] + + account_id: Required[str] + + secret: Required[str] + + base_path: str + + +class Backblaze(TypedDict, total=False): + bucket_name: Required[str] + + bucket_region: str + """bucket_region or endpoint""" + + endpoint: str + """bucket_region or endpoint""" + + access_key: Required[str] + + secret: Required[str] + + base_path: str + + +class Linode(TypedDict, total=False): + bucket_name: Required[str] + + bucket_region: Required[str] + + access_key: Required[str] + + secret: Required[str] + + +class Wasabi(TypedDict, total=False): + bucket_name: Required[str] + + bucket_region: Required[str] + + access_key: Required[str] + + secret: Required[str] + + base_path: str + + +class Dostorage(TypedDict, total=False): + bucket_name: Required[str] + + bucket_region: Required[str] + + access_key: Required[str] + + secret: Required[str] + + base_path: str + + +class Gcs(TypedDict, total=False): + bucket_name: Required[str] + + service_account_key: Required[str] + + +class Proxy(TypedDict, total=False): + whitelisted_domains: Required[str] + + +class Aws(TypedDict, total=False): + bucket_name: Required[str] + + bucket_region: Required[str] + + access_key: Required[str] + + secret: Required[str] + + base_path: str + + endpoint: str + + +class Webfolder(TypedDict, total=False): + base_url: str + + +class ChannelSettings(TypedDict, total=False): + active: bool + + description: str + + title: str + + privacy_type: Literal['"public"', '"private"', '"password-protected"'] + + featured_video: str + """Video asset id, the asset should be in the same workspace as channel""" + + password: str + """under channel_settings privacy_type must be "password-protected". Password length should be greater than 5 and lesser than 100 characters.""" + + +class VideoProtection(TypedDict, total=False): + signed_url: bool + + signed_url_secret: str + + blacklisted_countries: SequenceNotStr[str] + """Example: ["IN","USA"]""" + + whitelisted_referrers: SequenceNotStr[str] + + +class PlayerConfig(TypedDict, total=False): + preload: bool + + autoplay: bool + + disable_seek: bool + + disable_player_controls: bool + + powered_by_gumlet_overlay: bool + + allow_drm_protected_videos: bool + + loop: bool + + player_color: str + + include_seo: bool + + subtitle_enabled: bool + + pixel_tags: object + + logo_width: int + + logo_height: int + + dynamic_watermark: bool + + watermark_font_size: int + + watermark_font_color: str + + watermark_bg_color: str + + watermark_interval: int + + cast: bool + + show_video_title: bool diff --git a/src/gumlet/types/video_workspace_update_response.py b/src/gumlet/types/video_workspace_update_response.py new file mode 100644 index 0000000..8c74ed5 --- /dev/null +++ b/src/gumlet/types/video_workspace_update_response.py @@ -0,0 +1,150 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = ["VideoWorkspaceUpdateResponse", "VideoProtection", "PlayerConfig", "Aws", "EmbedDetails", "ChannelSettings"] + + +class ChannelSettings(BaseModel): + title: Optional[str] = None + + active: Optional[bool] = None + + description: Optional[str] = None + + privacy_type: Optional[str] = None + + custom_logo: Optional[bool] = None + + logo_url: Optional[str] = None + + cname: Optional[List[str]] = None + + temp_cname: Optional[List[str]] = None + + +class EmbedDetails(BaseModel): + preload: Optional[bool] = None + + autoplay: Optional[bool] = None + + logo_width: Optional[int] = None + + logo_height: Optional[int] = None + + player_color: Optional[str] = None + + is_seo: Optional[bool] = None + + dynamic_watermark: Optional[bool] = None + + watermark_font_size: Optional[int] = None + + watermark_font_color: Optional[str] = None + + watermark_bg_color: Optional[str] = None + + watermark_interval: Optional[int] = None + + disable_seek: Optional[bool] = None + + disable_player_controls: Optional[bool] = None + + powered_by_gumlet_overlay: Optional[bool] = None + + allow_drm_protected_videos: Optional[bool] = None + + pixel_tags: Optional[object] = None + + loop: Optional[bool] = None + + subtitle_enabled: Optional[bool] = None + + +class Aws(BaseModel): + bucket_name: Optional[str] = None + + bucket_region: Optional[str] = None + + access_key: Optional[str] = None + + secret: Optional[str] = None + + +class PlayerConfig(BaseModel): + preload: Optional[bool] = None + + autoplay: Optional[bool] = None + + disable_seek: Optional[bool] = None + + disable_player_controls: Optional[bool] = None + + powered_by_gumlet_overlay: Optional[bool] = None + + allow_drm_protected_videos: Optional[bool] = None + + loop: Optional[bool] = None + + player_color: Optional[str] = None + + include_seo: Optional[bool] = None + + subtitle_enabled: Optional[bool] = None + + pixel_tags: Optional[object] = None + + logo_width: Optional[int] = None + + logo_height: Optional[int] = None + + dynamic_watermark: Optional[bool] = None + + watermark_font_size: Optional[int] = None + + watermark_font_color: Optional[str] = None + + watermark_bg_color: Optional[str] = None + + watermark_interval: Optional[int] = None + + +class VideoProtection(BaseModel): + signed_url: Optional[bool] = None + + signed_url_secret: Optional[str] = None + + +class VideoWorkspaceUpdateResponse(BaseModel): + id: str + + name: str + + type: str + + created_at: str + + updated_at: str + + video_protection: Optional[VideoProtection] = None + + player_config: PlayerConfig + + default_profile_id: Optional[str] = None + + insight_property_id: Optional[str] = None + + aws: Optional[Aws] = None + + embed_details: Optional[EmbedDetails] = None + + folders: List[str] + + channel_settings: ChannelSettings + + insights_enabled: bool + """Whether Gumlet video analytics is enabled for this workspace.""" diff --git a/src/gumlet/types/webhook_create_params.py b/src/gumlet/types/webhook_create_params.py new file mode 100644 index 0000000..8625a38 --- /dev/null +++ b/src/gumlet/types/webhook_create_params.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict +from .._types import SequenceNotStr + +__all__ = ["WebhookCreateParams"] + + +class WebhookCreateParams(TypedDict, total=False): + url: Required[str] + """URL from the application you want to send data to.""" + + secret_token: Required[str] + """Authentication token to ensure legitimacy of Gumlet Webhook request on your application.""" + + triggers: Required[SequenceNotStr[str]] + """Triggers for the invocation of webhookos, supported option is `status`.""" + + sources: Required[SequenceNotStr[str]] + """List of video collection identifiers for which webhooks are needed to be invoked.""" diff --git a/src/gumlet/types/webhook_create_response.py b/src/gumlet/types/webhook_create_response.py new file mode 100644 index 0000000..27ad3cd --- /dev/null +++ b/src/gumlet/types/webhook_create_response.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = ["WebhookCreateResponse"] + + +class WebhookCreateResponse(BaseModel): + id: Optional[str] = None + + url: Optional[str] = None + + triggers: Optional[List[str]] = None + + created_at: Optional[str] = None + + updated_at: Optional[str] = None + + sources: Optional[List[str]] = None + + secret_token: Optional[str] = None diff --git a/src/gumlet/types/webhook_delete_response.py b/src/gumlet/types/webhook_delete_response.py new file mode 100644 index 0000000..d4430ed --- /dev/null +++ b/src/gumlet/types/webhook_delete_response.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from typing_extensions import TypeAlias + +__all__ = ["WebhookDeleteResponse"] + +WebhookDeleteResponse: TypeAlias = object diff --git a/src/gumlet/types/webhook_history_response.py b/src/gumlet/types/webhook_history_response.py new file mode 100644 index 0000000..103c494 --- /dev/null +++ b/src/gumlet/types/webhook_history_response.py @@ -0,0 +1,31 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from typing import List +from typing_extensions import TypeAlias + +from .._models import BaseModel + +__all__ = ["WebhookHistoryResponse", "WebhookHistoryResponseItem"] + + +class WebhookHistoryResponseItem(BaseModel): + id: str + """Webhook event ID""" + + status: str + """Status of webhook event""" + + retry_count: int + """Number of retries it needed to deliver webhook.""" + + event: str + """Name of the webhook event""" + + asset_id: str + """Asset ID for which the event was fired""" + + created_at: str + """Event timestamp in ISO 8601 format""" + + +WebhookHistoryResponse: TypeAlias = List[WebhookHistoryResponseItem] diff --git a/src/gumlet/types/webhook_list_response.py b/src/gumlet/types/webhook_list_response.py new file mode 100644 index 0000000..4c7dda6 --- /dev/null +++ b/src/gumlet/types/webhook_list_response.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = ["WebhookListResponse"] + + +class WebhookListResponse(BaseModel): + id: str + """Webhook ID""" + + url: str + """Webhook URL""" + + triggers: List[str] + """List of triggers configured for this webhook""" + + created_at: str + """Creation timestamp in ISO 8601 format""" + + updated_at: str + """Update timestamp in ISO 8601 format""" + + sources: List[str] + """List of workspace IDs for which the webhook is enabled.""" + + secret_token: Optional[str] = None + """The token which you must validate when you receive the webhook. It's given by you when you create the webhook.""" diff --git a/src/gumlet/types/webhook_update_params.py b/src/gumlet/types/webhook_update_params.py new file mode 100644 index 0000000..f5f7eb4 --- /dev/null +++ b/src/gumlet/types/webhook_update_params.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["WebhookUpdateParams"] + + +class WebhookUpdateParams(TypedDict, total=False): + url: str + """URL from the application you want to send data to.""" + + secret_token: str + """Authentication token to ensure legitimacy of Gumlet Webhook request on your application.""" + + triggers: str + """Triggers for the invocation of webhookos, supported option is `status`.""" + + sources: str + """List of video collection identifiers for which webhooks are needed to be invoked.""" diff --git a/src/gumlet/types/webhook_update_response.py b/src/gumlet/types/webhook_update_response.py new file mode 100644 index 0000000..58bbf23 --- /dev/null +++ b/src/gumlet/types/webhook_update_response.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = ["WebhookUpdateResponse"] + + +class WebhookUpdateResponse(BaseModel): + id: Optional[str] = None + + url: Optional[str] = None + + triggers: Optional[List[str]] = None + + created_at: Optional[str] = None + + updated_at: Optional[str] = None + + sources: Optional[List[str]] = None + + secret_token: Optional[str] = None diff --git a/tests/smoke-test.py b/tests/smoke-test.py new file mode 100644 index 0000000..9bc6197 --- /dev/null +++ b/tests/smoke-test.py @@ -0,0 +1,2194 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +# Smoke test: calls every generated operation once to confirm the SDK can reach each endpoint. +# Run it from this repo with `python tests/smoke-test.py`. The generator also runs this file +# against a mock server and reads the JSON report produced via SCALAR_SMOKE_REPORT. +from __future__ import annotations + +import json +import os +import sys +import time +import traceback +from concurrent.futures import ThreadPoolExecutor +from pathlib import Path +from typing import Any, Callable, TypedDict + +from gumlet import Gumlet + +# The shared smoke-test runner injects base URL and credentials through the same +# environment variables the generated client reads in normal use. +client = Gumlet(max_retries=0, timeout=30) + + +class SmokeResult(TypedDict, total=False): + operation: str + method: str + path: str + label: str + status: str + durationMs: int + error: str + + +class _SmokeCaseBase(TypedDict): + operation: str + method: str + path: str + run: Callable[[], Any] + + +# `label` says which of an operation's two calls this is — "required params" or "all params". +# It sits in a total=False extension because it is absent when the operation contributed a +# single case, while the fields above are always present. +class SmokeCase(_SmokeCaseBase, total=False): + label: str + + +def _smoke_case_0() -> None: + video_asset = client.video_assets.create( + input="http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8", + collection_id="646df1c9173a4a2fcac180b4", + profile_id="646df1c9173a4a2fcac180b7", + format="ABR", + tag=["ball"], + description="some description", + metadata={"headermeta": "metavalue"}, + call_to_actions=[ + { + "start_time": 1, + "end_time": 90, + "text": "some test", + "url": "https://some-url.com", + "position_from_top": 11, + "position_from_right": 23, + "border_radius": "11", + "font_color": "#000001", + "background_color": "#ffffff", + } + ], + playlist_id="6597acd5ed6f26a9c5ca9633", + folder="697375fbfa2d1037283140e4", + ) + + +def _smoke_case_1() -> None: + video_asset = client.video_assets.create( + input="http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8", + collection_id="646df1c9173a4a2fcac180b4", + profile_id="646df1c9173a4a2fcac180b7", + format="ABR", + tag=["ball"], + title="", + description="some description", + metadata={"headermeta": "metavalue"}, + width="", + height="", + resolution="", + crop={"horizontal_margin": "", "vertical_margin": "", "width": "", "height": ""}, + pad={"top": "", "left": "", "bottom": "", "right": "", "color": ""}, + trim={"start_offset": 0, "end_offset": 0, "duration": 0}, + image_overlay={ + "url": "", + "horizontal_margin": "", + "vertical_margin": "", + "horizontal_align": "", + "vertical_align": "", + "width": "", + "height": "", + }, + text_overlay={ + "text": "", + "horizontal_align": "", + "vertical_align": "", + "horizontal_margin": "", + "vertical_margin": "", + "color": "", + "font": "", + "font_size": "", + "opacity": "", + "box": False, + "box_color": "", + "box_opacity": "", + "box_border": "", + }, + animated_gif={"start_offset": "", "end_offset": "", "width": "", "height": "", "fps": ""}, + additional_tracks=[{"url": "", "type": "", "language_code": ""}], + generate_subtitles={"audio_language": "", "subtitle_languages": ""}, + mp4_access=False, + per_title_encoding=False, + process_low_resolution_input=False, + audio_only=False, + enable_drm=False, + call_to_actions=[ + { + "start_time": 1, + "end_time": 90, + "text": "some test", + "url": "https://some-url.com", + "position_from_top": 11, + "position_from_right": 23, + "border_radius": "11", + "font_color": "#000001", + "background_color": "#ffffff", + } + ], + playlist_id="6597acd5ed6f26a9c5ca9633", + folder="697375fbfa2d1037283140e4", + ) + + +def _smoke_case_2() -> None: + video_asset = client.video_assets.upload( + collection_id="646df1c9173a4a2fcac180b4", + profile_id="646df1c9173a4a2fcac180b7", + format="ABR", + tag=["ball"], + description="some description", + metadata={"headermeta": "metavalue"}, + call_to_actions=[ + { + "start_time": 1, + "end_time": 90, + "text": "some test", + "url": "https://some-url.com", + "position_from_top": 11, + "position_from_right": 23, + "border_radius": "11", + "font_color": "#000001", + "background_color": "#ffffff", + } + ], + playlist_id="6597acd5ed6f26a9c5ca9633", + folder="697375fbfa2d1037283140e4", + ) + + +def _smoke_case_3() -> None: + video_asset = client.video_assets.upload( + collection_id="646df1c9173a4a2fcac180b4", + profile_id="646df1c9173a4a2fcac180b7", + format="ABR", + tag=["ball"], + title="", + description="some description", + metadata={"headermeta": "metavalue"}, + width="", + height="", + resolution="", + crop={"horizontal_margin": "", "vertical_margin": "", "width": "", "height": ""}, + pad={"top": "", "left": "", "bottom": "", "right": "", "color": ""}, + trim={"start_offset": 0, "end_offset": 0, "duration": 0}, + image_overlay={ + "url": "", + "horizontal_margin": "", + "vertical_margin": "", + "horizontal_align": "", + "vertical_align": "", + "width": "", + "height": "", + }, + text_overlay={ + "text": "", + "horizontal_align": "", + "vertical_align": "", + "horizontal_margin": "", + "vertical_margin": "", + "color": "", + "font": "", + "font_size": "", + "opacity": "", + "box": False, + "box_color": "", + "box_opacity": "", + "box_border": "", + }, + animated_gif={"start_offset": "", "end_offset": "", "width": "", "height": "", "fps": ""}, + additional_tracks=[{"url": "", "type": "", "language_code": ""}], + generate_subtitles={"audio_language": "", "subtitle_languages": ""}, + mp4_access=False, + per_title_encoding=False, + process_low_resolution_input=False, + audio_only=False, + enable_drm=False, + call_to_actions=[ + { + "start_time": 1, + "end_time": 90, + "text": "some test", + "url": "https://some-url.com", + "position_from_top": 11, + "position_from_right": 23, + "border_radius": "11", + "font_color": "#000001", + "background_color": "#ffffff", + } + ], + playlist_id="6597acd5ed6f26a9c5ca9633", + folder="697375fbfa2d1037283140e4", + ) + + +def _smoke_case_4() -> None: + video_asset = client.video_assets.retrieve_details( + asset_id="assetId", + ) + + +def _smoke_case_5() -> None: + client.video_assets.delete( + asset_id="assetId", + ) + + +def _smoke_case_6() -> None: + video_asset = client.video_assets.update( + asset_id="", + title="Updated Title", + ) + + +def _smoke_case_7() -> None: + video_asset = client.video_assets.update( + asset_id="", + title="Updated Title", + description="", + tag="", + call_to_actions=[{}], + metadata="", + remove_subtitles=[""], + input="", + reprocess=False, + ) + + +def _smoke_case_8() -> None: + video_asset = client.video_assets.thumbnail_select( + asset_id="assetId", + frame_at_second=2, + ) + + +def _smoke_case_9() -> None: + video_asset = client.video_assets.thumbnail_upload( + asset_id="assetId", + ) + + +def _smoke_case_10() -> None: + video_asset = client.video_assets.create_update_chapter( + asset_id="assetId", + chapters=[{"label": "Chapter 1", "startTime": 0}, {"label": "Chapter 2", "startTime": 10}], + ) + + +def _smoke_case_11() -> None: + video_asset = client.video_assets.list( + workspace_id="workspaceId", + type="all", + offset=0, + size=20, + signed_token="false", + ) + + +def _smoke_case_12() -> None: + video_asset = client.video_assets.list( + workspace_id="workspaceId", + type="all", + parent_id="parent_id", + title="title", + status="status", + tag="tag", + playlist_id="playlist_id", + start_date="start_date", + end_date="end_date", + min_duration=1, + max_duration=1, + sort_by="title", + order_by="asc", + search_index="search_index_for_asset_list", + offset=0, + size=20, + signed_token="false", + ) + + +def _smoke_case_13() -> None: + video_asset = client.video_assets.list_deprecated( + workspace_id="workspaceId", + sort_by="created_at", + order_by="desc", + ) + + +def _smoke_case_14() -> None: + video_asset = client.video_assets.list_deprecated( + workspace_id="workspaceId", + status="queued", + tag="tag", + title="title", + folder="folder", + offset="offset", + size="size", + playlist_id="playlist_id", + sort_by="created_at", + order_by="desc", + type="type", + ) + + +def _smoke_case_15() -> None: + video_asset = client.video_assets.delete_many( + asset_list=["64249a8858fd3a208b987702", "64784bae843b155b829bbf84"], + source_id="60bd2ba353ff754d28179ee6", + ) + + +def _smoke_case_16() -> None: + video_asset = client.video_assets.tag_many( + asset_list=["6221db301c8b821b0519fba0", "61e8f2726ec832ab2ac4fa6e"], + source_id="60bd2ba353ff754d28179ee6", + add_tags=["tag-1"], + remove_tags=["playlist-1"], + ) + + +def _smoke_case_17() -> None: + video_asset = client.video_assets.analytics( + asset_id="assetId", + group_by="daily", + date_range={"start_at": "", "end_at": ""}, + metrics=["impressions"], + ) + + +def _smoke_case_18() -> None: + video_asset = client.video_assets.analytics( + asset_id="assetId", + group_by="daily", + date_range={"start_at": "", "end_at": ""}, + metrics=["impressions"], + page_number=0, + page_size=0, + ) + + +def _smoke_case_19() -> None: + subtitle_upload = client.subtitle_upload.upload( + asset_id="assetId", + language_codes=["en"], + ) + + +def _smoke_case_20() -> None: + subtitle_upload = client.subtitle_upload.complete( + asset_id="assetId", + upload_responses=[{"language_code": "en", "uploaded": True}], + ) + + +def _smoke_case_21() -> None: + audio_upload = client.audio_upload.upload( + asset_id="assetId", + language_codes=["en"], + ) + + +def _smoke_case_22() -> None: + audio_upload = client.audio_upload.complete( + asset_id="assetId", + upload_responses=[{"language_codes": ["en"], "uploaded": True}], + ) + + +def _smoke_case_23() -> None: + video_usage_analytic = client.video_usage_analytics.retrieve( + metrics=["bandwidth_consumption", "asset_duration", "storage_unit", "top_assets", "drm_requests"], + date_range={"start_at": "2026-08-01", "end_at": "2026-08-20"}, + top_assets_count="5", + top_assets_page="0", + group_by="hourly", + ) + + +def _smoke_case_24() -> None: + video_usage_analytic = client.video_usage_analytics.retrieve( + metrics=["bandwidth_consumption", "asset_duration", "storage_unit", "top_assets", "drm_requests"], + date_range={"start_at": "2026-08-01", "end_at": "2026-08-20"}, + filters={"collection_id": "", "source_id": ""}, + top_assets_count="5", + top_assets_page="0", + group_by="hourly", + ) + + +def _smoke_case_25() -> None: + video_usage_analytic = client.video_usage_analytics.top_assets( + start_at="2026-06-21", + end_at="2026-06-30", + page="1", + page_size="1000", + ) + + +def _smoke_case_26() -> None: + video_usage_analytic = client.video_usage_analytics.top_assets( + start_at="2026-06-21", + end_at="2026-06-30", + collection_id="collection_id", + page="1", + page_size="1000", + ) + + +def _smoke_case_27() -> None: + multipart_upload = client.multipart_upload.retrieve_part_url( + asset_id="assetId", + part_number="partNumber", + ) + + +def _smoke_case_28() -> None: + multipart_upload = client.multipart_upload.complete( + asset_id="assetId", + ) + + +def _smoke_case_29() -> None: + multipart_upload = client.multipart_upload.complete( + asset_id="assetId", + parts=[{}], + ) + + +def _smoke_case_30() -> None: + video_profile = client.video_profiles.create( + name="Gumlet-Profile-1", + format="ABR", + ) + + +def _smoke_case_31() -> None: + video_profile = client.video_profiles.create( + name="Gumlet-Profile-1", + format="ABR", + width="", + height="", + resolution="", + crop={"horizontal_margin": "", "vertical_margin": "", "width": "", "height": ""}, + pad={"top": "", "left": "", "bottom": "", "right": "", "color": ""}, + trim={"start_offset": 0, "end_offset": 0, "duration": 0}, + image_overlay={ + "url": "", + "horizontal_margin": "", + "vertical_margin": "", + "horizontal_align": "", + "vertical_align": "", + "width": "", + "height": "", + }, + text_overlay={ + "text": "", + "horizontal_align": "", + "vertical_align": "", + "horizontal_margin": "", + "vertical_margin": "", + "color": "", + "font": "", + "font_size": "", + "opacity": "", + "box": False, + "box_color": "", + "box_opacity": "", + "box_border": "", + }, + animated_gif={"start_offset": "", "end_offset": "", "width": "", "height": "", "fps": ""}, + generate_subtitles={"transcribe": True, "subtitle_languages": ""}, + mp4_access=False, + per_title_encoding=False, + process_low_resolution_input=False, + audio_only=False, + enable_drm=False, + ) + + +def _smoke_case_32() -> None: + video_profile = client.video_profiles.list() + + +def _smoke_case_33() -> None: + video_profile = client.video_profiles.list( + offset=1, + size=1, + ) + + +def _smoke_case_34() -> None: + video_profile = client.video_profiles.update( + path_profile_id="profileId", + body_profile_id="", + format="ABR", + ) + + +def _smoke_case_35() -> None: + video_profile = client.video_profiles.update( + path_profile_id="profileId", + body_profile_id="", + name="", + format="ABR", + width="", + height="", + resolution="", + crop={"horizontal_margin": "", "vertical_margin": "", "width": "", "height": ""}, + pad={"top": "", "left": "", "bottom": "", "right": "", "color": ""}, + trim={"start_offset": 0, "end_offset": 0, "duration": 0}, + image_overlay={ + "url": "", + "horizontal_margin": "", + "vertical_margin": "", + "horizontal_align": "", + "vertical_align": "", + "width": "", + "height": "", + }, + text_overlay={ + "text": "", + "horizontal_align": "", + "vertical_align": "", + "horizontal_margin": "", + "vertical_margin": "", + "color": "", + "font": "", + "font_size": "", + "opacity": "", + "box": False, + "box_color": "", + "box_opacity": "", + "box_border": "", + }, + animated_gif={"start_offset": "", "end_offset": "", "width": "", "height": "", "fps": ""}, + generate_subtitles={"subtitle_languages": [""], "transcribe": True}, + mp4_access=False, + per_title_encoding=False, + process_low_resolution_input=False, + audio_only=False, + enable_drm=False, + vc=[""], + generate_chapters=False, + generate_description=False, + ) + + +def _smoke_case_36() -> None: + video_profile = client.video_profiles.retrieve( + profile_id="profileId", + ) + + +def _smoke_case_37() -> None: + video_profile = client.video_profiles.delete( + profile_id="profileId", + ) + + +def _smoke_case_38() -> None: + video_playlist = client.video_playlists.create( + collection_id="{{video-source-id}}", + title="Playlist-Title", + description="This is description for playlist.", + ) + + +def _smoke_case_39() -> None: + video_playlist = client.video_playlists.list_all() + + +def _smoke_case_40() -> None: + video_playlist = client.video_playlists.list_all( + collection_id="collection_id", + ) + + +def _smoke_case_41() -> None: + video_playlist = client.video_playlists.create_asset( + playlist_id="playlistId", + asset_list=[ + {"asset_id": "6508790283e4d60611846790"}, + {"position": 1, "asset_id": "650878f883e4d6061184677d"}, + {"asset_id": "650878de83e4d6061184676a"}, + {"position": 2, "asset_id": "650878d883e4d60611846757"}, + {"position": 3, "asset_id": "65578dd87eebc22dcdd549a2"}, + ], + ) + + +def _smoke_case_42() -> None: + video_playlist = client.video_playlists.delete_asset( + playlist_id="playlistId", + delete_list=["6508790783e4d606118467a3"], + ) + + +def _smoke_case_43() -> None: + video_playlist = client.video_playlists.update( + playlist_id="playlistId", + title="Playlist-Title-Updated", + description="This is updated description", + position=6, + player_config={ + "preload": True, + "autoplay": False, + "disable_seek": True, + "disable_player_controls": False, + "powered_by_gumlet_overlay": False, + "allow_drm_protected_videos": False, + "loop": False, + "player_color": "#6658ea", + "include_seo": True, + "subtitle_enabled": True, + "pixel_tags": {}, + "logo_width": 51, + "logo_height": 100, + "dynamic_watermark": False, + "watermark_font_size": 1, + "watermark_font_color": "#ff0000", + "watermark_bg_color": "transparent", + "watermark_interval": 1000, + }, + channel_visibility=False, + ) + + +def _smoke_case_44() -> None: + client.video_playlists.delete( + playlist_id="playlistId", + ) + + +def _smoke_case_45() -> None: + video_playlist = client.video_playlists.list_assets( + playlist_id="playlistId", + sort_order=1, + page_number=1, + page_size="10", + ) + + +def _smoke_case_46() -> None: + video_playlist = client.video_playlists.list_assets( + playlist_id="playlistId", + sort_by="sort_by", + sort_order=1, + page_number=1, + page_size="10", + ) + + +def _smoke_case_47() -> None: + video_playlist = client.video_playlists.reorder_asset( + playlist_id="playlistId", + asset_id="6e82bf783e88be000ab45ed2", + page_number=1, + page_size=10, + asset_position=0, + ) + + +def _smoke_case_48() -> None: + webhook = client.webhooks.create( + url="", + secret_token="", + triggers=[""], + sources=[""], + ) + + +def _smoke_case_49() -> None: + webhook = client.webhooks.list() + + +def _smoke_case_50() -> None: + webhook = client.webhooks.update( + webhook_id="webhookId", + ) + + +def _smoke_case_51() -> None: + webhook = client.webhooks.update( + webhook_id="webhookId", + url="", + secret_token="", + triggers="", + sources="", + ) + + +def _smoke_case_52() -> None: + webhook = client.webhooks.delete( + webhook_id="webhookId", + ) + + +def _smoke_case_53() -> None: + webhook = client.webhooks.history( + webhook_id="webhookId", + ) + + +def _smoke_case_54() -> None: + image_source = client.image_sources.create( + namespace="google-demo", + type="webfolder", + webfolder={"base_url": "https://www.google.com"}, + ) + + +def _smoke_case_55() -> None: + image_source = client.image_sources.create( + namespace="google-demo", + type="webfolder", + aws={"bucket_name": "", "bucket_region": "", "access_key": "", "secret": "", "base_path": "", "endpoint": ""}, + proxy={"whitelisted_domains": ""}, + gcs={"bucket_name": "", "service_account_key": ""}, + dostorage={"bucket_name": "", "bucket_region": "", "access_key": "", "secret": "", "base_path": ""}, + wasabi={"bucket_name": "", "bucket_region": "", "access_key": "", "secret": "", "base_path": ""}, + cloudinary={"host_name": "", "cloud_name": ""}, + azure={"azure_account_name": "", "azure_container_name": "", "azure_shared_token": "", "azure_path": ""}, + linode={"bucket_name": "", "bucket_region": "", "access_key": "", "secret": ""}, + backblaze={ + "bucket_name": "", + "bucket_region": "", + "endpoint": "", + "access_key": "", + "secret": "", + "base_path": "", + }, + cloudflare={"bucket_name": "", "access_key": "", "account_id": "", "secret": "", "base_path": ""}, + webfolder={"base_url": "https://www.google.com"}, + ) + + +def _smoke_case_56() -> None: + image_source = client.image_sources.list( + offset=0, + size=20, + ) + + +def _smoke_case_57() -> None: + image_source = client.image_sources.retrieve( + image_source_id="imageSourceId", + ) + + +def _smoke_case_58() -> None: + image_source = client.image_sources.update( + image_source_id="imageSourceId", + type="aws", + aws={ + "bucket_name": "my-bucket-test", + "bucket_region": "ap-southeast-1", + "access_key": "BQUA6QFXVWHAAB6IO2X1", + "secret": "aws_secret", + }, + ) + + +def _smoke_case_59() -> None: + image_source = client.image_sources.update( + image_source_id="imageSourceId", + type="aws", + webfolder={"base_url": ""}, + aws={ + "bucket_name": "my-bucket-test", + "bucket_region": "ap-southeast-1", + "access_key": "BQUA6QFXVWHAAB6IO2X1", + "secret": "aws_secret", + }, + proxy={"whitelisted_domains": ""}, + gcs={"bucket_name": "", "service_account_key": ""}, + dostorage={"bucket_name": "", "bucket_region": "", "access_key": "", "secret": "", "base_path": ""}, + wasabi={"bucket_name": "", "bucket_region": "", "access_key": "", "secret": "", "base_path": ""}, + linode={"bucket_name": "", "bucket_region": "", "access_key": "", "secret": ""}, + backblaze={ + "bucket_name": "", + "bucket_region": "", + "endpoint": "", + "access_key": "", + "secret": "", + "base_path": "", + }, + cloudflare={"bucket_name": "", "access_key": "", "account_id": "", "secret": "", "base_path": ""}, + cloudinary={"host_name": "", "cloud_name": ""}, + azure={"azure_account_name": "", "azure_container_name": "", "azure_shared_token": "", "azure_path": ""}, + default_params={}, + error_image="", + request_headers=[{}], + response_headers=[{}], + temp_cname=[""], + browser_cache_time=0, + cdn_cache_time=0, + is_active=False, + cname=[""], + fallback_origins=[{"conditions": [""], "name": "", "type": "dostorage", "replace_operation": {}}], + ) + + +def _smoke_case_60() -> None: + image_source = client.image_sources.delete( + image_source_id="imageSourceId", + ) + + +def _smoke_case_61() -> None: + image_source = client.image_sources.purge_cache( + subdomain="subdomain", + paths=["image.jpeg", "image2.png"], + ) + + +def _smoke_case_62() -> None: + image_source = client.image_sources.purge( + source_id="sourceId", + paths=["image.jpeg", "image2.png"], + ) + + +def _smoke_case_63() -> None: + image_usage_analytic = client.image_usage_analytics.retrieve( + metrics=["bandwidth_consumption"], + date_range={}, + group_by="daily", + ) + + +def _smoke_case_64() -> None: + image_usage_analytic = client.image_usage_analytics.retrieve( + metrics=["bandwidth_consumption"], + date_range={"start_at": "2024-01-01", "end_at": "2024-01-01"}, + group_by="daily", + filters={"source_id": ""}, + ) + + +def _smoke_case_65() -> None: + live_stream_asset = client.live_stream_assets.create( + live_source_id="", + resolution="", + ) + + +def _smoke_case_66() -> None: + live_stream_asset = client.live_stream_assets.create( + live_source_id="", + resolution="", + title="", + mp4_access=False, + orientation="landscape", + start_at="2024-01-01T00:00:00.000Z", + ) + + +def _smoke_case_67() -> None: + live_stream_asset = client.live_stream_assets.update( + live_asset_id="", + ) + + +def _smoke_case_68() -> None: + live_stream_asset = client.live_stream_assets.update( + live_asset_id="", + title="", + start_at="2024-01-01T00:00:00.000Z", + ) + + +def _smoke_case_69() -> None: + live_stream_asset = client.live_stream_assets.retrieve_status( + live_asset_id="liveAssetId", + ) + + +def _smoke_case_70() -> None: + live_stream_asset = client.live_stream_assets.delete( + live_asset_id="liveAssetId", + ) + + +def _smoke_case_71() -> None: + live_stream_asset = client.live_stream_assets.complete( + live_asset_id="liveAssetId", + ) + + +def _smoke_case_72() -> None: + live_stream_asset = client.live_stream_assets.filter( + live_source_id="liveSourceId", + ) + + +def _smoke_case_73() -> None: + live_stream_asset = client.live_stream_assets.filter( + live_source_id="liveSourceId", + status="status", + offset=1, + size=1, + ) + + +def _smoke_case_74() -> None: + client.live_stream_assets.start( + live_asset_id="liveAssetId", + ) + + +def _smoke_case_75() -> None: + live_stream_asset = client.live_stream_assets.upload( + live_asset_id="68c406b147f9ad0c0d584ce2", + statuses="preparing", + ) + + +def _smoke_case_76() -> None: + live_stream_asset = client.live_stream_assets.status_history( + live_asset_id="liveAssetId", + ) + + +def _smoke_case_77() -> None: + client.recycle_bin.recover( + asset_id="", + ) + + +def _smoke_case_78() -> None: + recycle_bin = client.recycle_bin.list( + size=20, + workspace_id="workspace_id", + ) + + +def _smoke_case_79() -> None: + recycle_bin = client.recycle_bin.list( + offset=1, + size=20, + workspace_id="workspace_id", + ) + + +def _smoke_case_80() -> None: + video_workspace = client.video_workspaces.list( + offset="0", + size="10", + ) + + +def _smoke_case_81() -> None: + video_workspace = client.video_workspaces.create( + name="zoom-workspace", + type="direct-upload", + zoom={"secret": "yourSecret"}, + ) + + +def _smoke_case_82() -> None: + video_workspace = client.video_workspaces.create( + name="zoom-workspace", + type="direct-upload", + default_profile_id="", + insight_property_id="", + video_protection={ + "signed_url": False, + "signed_url_secret": "", + "blacklisted_countries": [""], + "whitelisted_referrers": "", + }, + aws={"bucket_name": "", "bucket_region": "", "access_key": "", "secret": "", "base_path": "", "endpoint": ""}, + proxy={"whitelisted_domains": ""}, + gcs={"bucket_name": "", "service_account_key": ""}, + dostorage={"bucket_name": "", "bucket_region": "", "access_key": "", "secret": "", "base_path": ""}, + wasabi={"bucket_name": "", "bucket_region": "", "access_key": "", "secret": "", "base_path": ""}, + cloudinary={"host_name": "", "cloud_name": ""}, + azure={"azure_account_name": "", "azure_container_name": "", "azure_shared_token": "", "azure_path": ""}, + linode={"bucket_name": "", "bucket_region": "", "access_key": "", "secret": ""}, + backblaze={ + "bucket_name": "", + "bucket_region": "", + "endpoint": "", + "access_key": "", + "secret": "", + "base_path": "", + }, + cloudflare={"bucket_name": "", "access_key": "", "account_id": "", "secret": "", "base_path": ""}, + zoom={"secret": "yourSecret"}, + ) + + +def _smoke_case_83() -> None: + video_workspace = client.video_workspaces.update( + workspace_id="workspaceId", + name="awsrename", + default_profile_id="646df1c9173a4a2fcac180b7", + type="aws", + aws={ + "bucket_name": "my-bucket-test", + "bucket_region": "ap-southeast-1", + "access_key": "BQUA6QFXVWHAAB6IO2X1", + "secret": "aws_secret", + }, + ) + + +def _smoke_case_84() -> None: + video_workspace = client.video_workspaces.update( + workspace_id="workspaceId", + name="awsrename", + default_profile_id="646df1c9173a4a2fcac180b7", + temp_cname=[""], + insight_property_id="", + player_config={ + "preload": False, + "autoplay": False, + "disable_seek": False, + "disable_player_controls": False, + "powered_by_gumlet_overlay": False, + "allow_drm_protected_videos": False, + "loop": False, + "player_color": "", + "include_seo": False, + "subtitle_enabled": False, + "pixel_tags": {}, + "logo_width": 0, + "logo_height": 0, + "dynamic_watermark": False, + "watermark_font_size": 0, + "watermark_font_color": "", + "watermark_bg_color": "", + "watermark_interval": 0, + "cast": False, + "show_video_title": False, + }, + video_protection={ + "signed_url": False, + "signed_url_secret": "", + "blacklisted_countries": [""], + "whitelisted_referrers": [""], + }, + channel_settings={ + "active": False, + "description": "", + "title": "", + "privacy_type": '"public"', + "featured_video": "", + "password": "", + }, + type="aws", + webfolder={"base_url": ""}, + aws={ + "bucket_name": "my-bucket-test", + "bucket_region": "ap-southeast-1", + "access_key": "BQUA6QFXVWHAAB6IO2X1", + "secret": "aws_secret", + }, + proxy={"whitelisted_domains": ""}, + gcs={"bucket_name": "", "service_account_key": ""}, + dostorage={"bucket_name": "", "bucket_region": "", "access_key": "", "secret": "", "base_path": ""}, + wasabi={"bucket_name": "", "bucket_region": "", "access_key": "", "secret": "", "base_path": ""}, + linode={"bucket_name": "", "bucket_region": "", "access_key": "", "secret": ""}, + backblaze={ + "bucket_name": "", + "bucket_region": "", + "endpoint": "", + "access_key": "", + "secret": "", + "base_path": "", + }, + cloudflare={"bucket_name": "", "access_key": "", "account_id": "", "secret": "", "base_path": ""}, + cloudinary={"host_name": "", "cloud_name": ""}, + azure={"azure_account_name": "", "azure_container_name": "", "azure_shared_token": "", "azure_path": ""}, + zoom={"secret": ""}, + ) + + +def _smoke_case_85() -> None: + video_workspace = client.video_workspaces.retrieve( + workspace_id="workspaceId", + ) + + +def _smoke_case_86() -> None: + video_workspace = client.video_workspaces.delete( + workspace_id="workspaceId", + ) + + +def _smoke_case_87() -> None: + folder = client.folders.create( + workspace_id="workspaceId", + name="Course Assets", + parent_id="", + ) + + +def _smoke_case_88() -> None: + folder = client.folders.list( + workspace_id="workspaceId", + ) + + +def _smoke_case_89() -> None: + folder = client.folders.list( + workspace_id="workspaceId", + parent_id="parent_id", + ) + + +def _smoke_case_90() -> None: + folder = client.folders.retrieve( + workspace_id="workspaceId", + folder_id="folderId", + ) + + +def _smoke_case_91() -> None: + folder = client.folders.update( + workspace_id="workspaceId", + folder_id="folderId", + name="Course Assets Updated", + ) + + +def _smoke_case_92() -> None: + folder = client.folders.update( + workspace_id="workspaceId", + folder_id="folderId", + name="Course Assets Updated", + parent_id="", + asset_ids=[""], + ) + + +def _smoke_case_93() -> None: + folder = client.folders.delete( + workspace_id="workspaceId", + folder_id="folderId", + ) + + +def _smoke_case_94() -> None: + folder = client.folders.delete_assets( + workspace_id="workspaceId", + asset_ids=["67e4f2b4403562dbea654301", "67e4f2bb403562dbea654302"], + ) + + +def _smoke_case_95() -> None: + channel_viewer = client.channel_viewers.invite( + video_workspace_id="videoWorkspaceId", + users=[ + {"email": "test@gumlet.com", "name": "Test User-0"}, + {"email": "test+1@gumlet.com", "name": "Test User-1"}, + {"email": "test+2@gumlet.com", "name": "Test User-2"}, + ], + ) + + +def _smoke_case_96() -> None: + channel_viewer = client.channel_viewers.delete( + video_workspace_id="videoWorkspaceId", + emails=["test@gumlet.com", "test+2@gumlet.com"], + ) + + +def _smoke_case_97() -> None: + channel_viewer = client.channel_viewers.invite_csv( + video_workspace_id="videoWorkspaceId", + viewers_csv=b"viewers.csv", + ) + + +def _smoke_case_98() -> None: + channel_viewer = client.channel_viewers.list_subscribers( + workspace_id="workspaceId", + page_number=1, + page_size=10, + ) + + +def _smoke_case_99() -> None: + video_analytic = client.video_analytics.chart_data( + metrics=[""], + workspace_id="", + date_range={"start_at": "2024-01-01", "end_at": "2024-01-01"}, + group_by="daily", + ) + + +def _smoke_case_100() -> None: + video_analytic = client.video_analytics.chart_data( + metrics=[""], + workspace_id="", + date_range={"start_at": "2024-01-01", "end_at": "2024-01-01"}, + filters=[{"name": "meta_browser", "value": "", "operator": "equals"}], + group_by="daily", + chart_dimension={"group_by": [{}]}, + ) + + +def _smoke_case_101() -> None: + video_analytic = client.video_analytics.breakdown_data( + date_range={"start_at": "2026-07-20", "end_at": "2026-08-20"}, + filters=[], + breakdowns=[ + {"name": "custom_video_id", "metric": "views", "page": 1, "page_size": 10}, + {"name": "custom_video_title", "metric": "completion_percent_by_views", "page": 1, "page_size": 10}, + ], + workspace_id="6694c405e63913eecf3cf5fb", + ) + + +def _smoke_case_102() -> None: + video_analytic = client.video_analytics.aggregated_data( + aggregate=[{"metric": "views", "function": "sum"}], + workspace_id="", + timeframe={}, + ) + + +def _smoke_case_103() -> None: + video_analytic = client.video_analytics.aggregated_data( + aggregate=[{"metric": "views", "function": "sum"}], + workspace_id="", + timeframe={"start_at": "2024-01-01", "end_at": "2024-01-01"}, + filters=[{"name": "meta_browser", "value": "", "operator": "equals"}], + ) + + +def _smoke_case_104() -> None: + organization_data = client.organization_data.fetch_org() + + +def _smoke_case_105() -> None: + user_data = client.user_data.fetch() + + +def _smoke_case_106() -> None: + audit_log = client.audit_logs.fetch( + date_range={"start_at": "2026-08-25", "end_at": "2026-08-29"}, + page_number=1, + page_size=100, + ) + + +def _smoke_case_107() -> None: + audit_log = client.audit_logs.fetch( + date_range={"start_at": "2026-08-25", "end_at": "2026-08-29"}, + page_number=1, + page_size=100, + user_email=["user@example.com"], + activity_type=["workspace_created"], + ) + + +def _smoke_case_108() -> None: + billing = client.billing.list_invoices() + + +def _smoke_case_109() -> None: + billing = client.billing.fetch_details() + + +def _smoke_case_110() -> None: + billing = client.billing.update_details( + address_line="", + city="", + company_name="", + country_code="", + gst_number="", + postal="", + state_code="", + ) + + +def _smoke_case_111() -> None: + billing = client.billing.fetch_upcoming_invoice() + + +def _smoke_case_112() -> None: + live_stream_workspace = client.live_stream_workspaces.list() + + +def _smoke_case_113() -> None: + live_stream_workspace = client.live_stream_workspaces.create( + name="", + ) + + +def _smoke_case_114() -> None: + live_stream_workspace = client.live_stream_workspaces.update( + live_workspace_id="liveWorkspaceId", + name="live-stream-collections", + video_source_id="67bea1d66ca0059a95bf7de9", + ) + + +def _smoke_case_115() -> None: + live_stream_workspace = client.live_stream_workspaces.delete( + live_workspace_id="liveWorkspaceId", + ) + + +cases: list[SmokeCase] = [ + { + "operation": "create", + "method": "POST", + "path": "/video/assets", + "label": "required params", + "run": _smoke_case_0, + }, + { + "operation": "create", + "method": "POST", + "path": "/video/assets", + "label": "all params", + "run": _smoke_case_1, + }, + { + "operation": "upload", + "method": "POST", + "path": "/video/assets/upload", + "label": "required params", + "run": _smoke_case_2, + }, + { + "operation": "upload", + "method": "POST", + "path": "/video/assets/upload", + "label": "all params", + "run": _smoke_case_3, + }, + { + "operation": "retrieveDetails", + "method": "GET", + "path": "/video/assets/{asset_id}", + "run": _smoke_case_4, + }, + { + "operation": "delete", + "method": "DELETE", + "path": "/video/assets/{asset_id}", + "run": _smoke_case_5, + }, + { + "operation": "update", + "method": "POST", + "path": "/video/assets/update", + "label": "required params", + "run": _smoke_case_6, + }, + { + "operation": "update", + "method": "POST", + "path": "/video/assets/update", + "label": "all params", + "run": _smoke_case_7, + }, + { + "operation": "thumbnailSelect", + "method": "POST", + "path": "/video/assets/{asset_id}/thumbnail-select", + "run": _smoke_case_8, + }, + { + "operation": "thumbnailUpload", + "method": "POST", + "path": "/video/assets/{asset_ID}/thumbnail", + "run": _smoke_case_9, + }, + { + "operation": "createUpdateChapter", + "method": "POST", + "path": "/video/assets/{asset_id}/chapters", + "run": _smoke_case_10, + }, + { + "operation": "list", + "method": "GET", + "path": "/video/workspaces/{workspace_id}/list", + "label": "required params", + "run": _smoke_case_11, + }, + { + "operation": "list", + "method": "GET", + "path": "/video/workspaces/{workspace_id}/list", + "label": "all params", + "run": _smoke_case_12, + }, + { + "operation": "listDeprecated", + "method": "GET", + "path": "/video/assets/list/{workspace_id}", + "label": "required params", + "run": _smoke_case_13, + }, + { + "operation": "listDeprecated", + "method": "GET", + "path": "/video/assets/list/{workspace_id}", + "label": "all params", + "run": _smoke_case_14, + }, + { + "operation": "deleteMany", + "method": "DELETE", + "path": "/video/assets/bulk/delete", + "run": _smoke_case_15, + }, + { + "operation": "tagMany", + "method": "POST", + "path": "/video/assets/bulk/tag", + "run": _smoke_case_16, + }, + { + "operation": "analytics", + "method": "POST", + "path": "/video/assets/{asset_id}/analytics", + "label": "required params", + "run": _smoke_case_17, + }, + { + "operation": "analytics", + "method": "POST", + "path": "/video/assets/{asset_id}/analytics", + "label": "all params", + "run": _smoke_case_18, + }, + { + "operation": "upload", + "method": "POST", + "path": "/video/assets/{asset_ID}/subtitle/upload", + "run": _smoke_case_19, + }, + { + "operation": "complete", + "method": "POST", + "path": "/video/assets/{asset_ID}/subtitle/upload/event", + "run": _smoke_case_20, + }, + { + "operation": "upload", + "method": "POST", + "path": "/video/assets/{asset_ID}/audio/upload", + "run": _smoke_case_21, + }, + { + "operation": "complete", + "method": "POST", + "path": "/video/assets/{asset_ID}/audio/upload/event", + "run": _smoke_case_22, + }, + { + "operation": "retrieve", + "method": "POST", + "path": "/video/analytics", + "label": "required params", + "run": _smoke_case_23, + }, + { + "operation": "retrieve", + "method": "POST", + "path": "/video/analytics", + "label": "all params", + "run": _smoke_case_24, + }, + { + "operation": "topAssets", + "method": "GET", + "path": "/video/streaming-duration", + "label": "required params", + "run": _smoke_case_25, + }, + { + "operation": "topAssets", + "method": "GET", + "path": "/video/streaming-duration", + "label": "all params", + "run": _smoke_case_26, + }, + { + "operation": "retrievePartUrl", + "method": "GET", + "path": "/video/assets/{asset_id}/multipartupload/{part_number}/sign", + "run": _smoke_case_27, + }, + { + "operation": "complete", + "method": "POST", + "path": "/video/assets/{asset_id}/multipartupload/complete", + "label": "required params", + "run": _smoke_case_28, + }, + { + "operation": "complete", + "method": "POST", + "path": "/video/assets/{asset_id}/multipartupload/complete", + "label": "all params", + "run": _smoke_case_29, + }, + { + "operation": "create", + "method": "POST", + "path": "/video/profiles", + "label": "required params", + "run": _smoke_case_30, + }, + { + "operation": "create", + "method": "POST", + "path": "/video/profiles", + "label": "all params", + "run": _smoke_case_31, + }, + { + "operation": "list", + "method": "GET", + "path": "/video/profiles", + "label": "required params", + "run": _smoke_case_32, + }, + { + "operation": "list", + "method": "GET", + "path": "/video/profiles", + "label": "all params", + "run": _smoke_case_33, + }, + { + "operation": "update", + "method": "POST", + "path": "/video/profiles/{profile_id}", + "label": "required params", + "run": _smoke_case_34, + }, + { + "operation": "update", + "method": "POST", + "path": "/video/profiles/{profile_id}", + "label": "all params", + "run": _smoke_case_35, + }, + { + "operation": "retrieve", + "method": "GET", + "path": "/video/profiles/{profile_id}", + "run": _smoke_case_36, + }, + { + "operation": "delete", + "method": "DELETE", + "path": "/video/profiles/{profile_id}", + "run": _smoke_case_37, + }, + { + "operation": "create", + "method": "POST", + "path": "/video/playlist", + "run": _smoke_case_38, + }, + { + "operation": "listAll", + "method": "GET", + "path": "/video/playlist", + "label": "required params", + "run": _smoke_case_39, + }, + { + "operation": "listAll", + "method": "GET", + "path": "/video/playlist", + "label": "all params", + "run": _smoke_case_40, + }, + { + "operation": "createAsset", + "method": "POST", + "path": "/video/playlist/{playlist_id}/asset", + "run": _smoke_case_41, + }, + { + "operation": "deleteAsset", + "method": "DELETE", + "path": "/video/playlist/{playlist_id}/asset", + "run": _smoke_case_42, + }, + { + "operation": "update", + "method": "POST", + "path": "/video/playlist/{playlist_id}", + "run": _smoke_case_43, + }, + { + "operation": "delete", + "method": "DELETE", + "path": "/video/playlist/{playlist_id}", + "run": _smoke_case_44, + }, + { + "operation": "listAssets", + "method": "GET", + "path": "/video/playlist/{playlist_id}/assets", + "label": "required params", + "run": _smoke_case_45, + }, + { + "operation": "listAssets", + "method": "GET", + "path": "/video/playlist/{playlist_id}/assets", + "label": "all params", + "run": _smoke_case_46, + }, + { + "operation": "reorderAsset", + "method": "POST", + "path": "/video/playlists/{playlist_id}/reorder", + "run": _smoke_case_47, + }, + { + "operation": "create", + "method": "POST", + "path": "/org/webhooks", + "run": _smoke_case_48, + }, + { + "operation": "list", + "method": "GET", + "path": "/org/webhooks", + "run": _smoke_case_49, + }, + { + "operation": "update", + "method": "POST", + "path": "/org/webhooks/{webhook_id}", + "label": "required params", + "run": _smoke_case_50, + }, + { + "operation": "update", + "method": "POST", + "path": "/org/webhooks/{webhook_id}", + "label": "all params", + "run": _smoke_case_51, + }, + { + "operation": "delete", + "method": "DELETE", + "path": "/org/webhooks/{webhook_id}", + "run": _smoke_case_52, + }, + { + "operation": "history", + "method": "GET", + "path": "/org/webhook/{webhook_id}/history", + "run": _smoke_case_53, + }, + { + "operation": "create", + "method": "POST", + "path": "/image/sources", + "label": "required params", + "run": _smoke_case_54, + }, + { + "operation": "create", + "method": "POST", + "path": "/image/sources", + "label": "all params", + "run": _smoke_case_55, + }, + { + "operation": "list", + "method": "GET", + "path": "/image/sources", + "run": _smoke_case_56, + }, + { + "operation": "retrieve", + "method": "GET", + "path": "/image/sources/{image_source_id}", + "run": _smoke_case_57, + }, + { + "operation": "update", + "method": "POST", + "path": "/image/sources/{image_source_id}", + "label": "required params", + "run": _smoke_case_58, + }, + { + "operation": "update", + "method": "POST", + "path": "/image/sources/{image_source_id}", + "label": "all params", + "run": _smoke_case_59, + }, + { + "operation": "delete", + "method": "DELETE", + "path": "/image/sources/{image_source_id}", + "run": _smoke_case_60, + }, + { + "operation": "purgeCache", + "method": "POST", + "path": "/purge/{subdomain}", + "run": _smoke_case_61, + }, + { + "operation": "purge", + "method": "POST", + "path": "/image/purge/{source_id}", + "run": _smoke_case_62, + }, + { + "operation": "retrieve", + "method": "POST", + "path": "/image/analytics", + "label": "required params", + "run": _smoke_case_63, + }, + { + "operation": "retrieve", + "method": "POST", + "path": "/image/analytics", + "label": "all params", + "run": _smoke_case_64, + }, + { + "operation": "create", + "method": "POST", + "path": "/video/live/assets", + "label": "required params", + "run": _smoke_case_65, + }, + { + "operation": "create", + "method": "POST", + "path": "/video/live/assets", + "label": "all params", + "run": _smoke_case_66, + }, + { + "operation": "update", + "method": "POST", + "path": "/video/live/assets/update", + "label": "required params", + "run": _smoke_case_67, + }, + { + "operation": "update", + "method": "POST", + "path": "/video/live/assets/update", + "label": "all params", + "run": _smoke_case_68, + }, + { + "operation": "retrieveStatus", + "method": "GET", + "path": "/video/live/assets/{live_asset_id}", + "run": _smoke_case_69, + }, + { + "operation": "delete", + "method": "DELETE", + "path": "/video/live/assets/{live_asset_id}", + "run": _smoke_case_70, + }, + { + "operation": "complete", + "method": "POST", + "path": "/video/live/assets/{live_asset_id}/complete", + "run": _smoke_case_71, + }, + { + "operation": "filter", + "method": "GET", + "path": "/video/live/assets/list/{live_source_id}", + "label": "required params", + "run": _smoke_case_72, + }, + { + "operation": "filter", + "method": "GET", + "path": "/video/live/assets/list/{live_source_id}", + "label": "all params", + "run": _smoke_case_73, + }, + { + "operation": "start", + "method": "POST", + "path": "/video/live/assets/{live_asset_id}/start", + "run": _smoke_case_74, + }, + { + "operation": "upload", + "method": "POST", + "path": "/video/live/assets/thumbnail/upload", + "run": _smoke_case_75, + }, + { + "operation": "statusHistory", + "method": "GET", + "path": "/video/live/assets/{live_asset_id}/history", + "run": _smoke_case_76, + }, + { + "operation": "recover", + "method": "POST", + "path": "/video/asset/recover", + "run": _smoke_case_77, + }, + { + "operation": "list", + "method": "GET", + "path": "/video/asset/recoverable/list", + "label": "required params", + "run": _smoke_case_78, + }, + { + "operation": "list", + "method": "GET", + "path": "/video/asset/recoverable/list", + "label": "all params", + "run": _smoke_case_79, + }, + { + "operation": "list", + "method": "GET", + "path": "/video/workspaces", + "run": _smoke_case_80, + }, + { + "operation": "create", + "method": "POST", + "path": "/video/workspaces", + "label": "required params", + "run": _smoke_case_81, + }, + { + "operation": "create", + "method": "POST", + "path": "/video/workspaces", + "label": "all params", + "run": _smoke_case_82, + }, + { + "operation": "update", + "method": "POST", + "path": "/video/workspaces/{workspace_id}", + "label": "required params", + "run": _smoke_case_83, + }, + { + "operation": "update", + "method": "POST", + "path": "/video/workspaces/{workspace_id}", + "label": "all params", + "run": _smoke_case_84, + }, + { + "operation": "retrieve", + "method": "GET", + "path": "/video/workspaces/{workspace_id}", + "run": _smoke_case_85, + }, + { + "operation": "delete", + "method": "DELETE", + "path": "/video/workspaces/{workspace_id}", + "run": _smoke_case_86, + }, + { + "operation": "create", + "method": "POST", + "path": "/video/workspaces/{workspace_id}/folders", + "run": _smoke_case_87, + }, + { + "operation": "list", + "method": "GET", + "path": "/video/workspaces/{workspace_id}/folders", + "label": "required params", + "run": _smoke_case_88, + }, + { + "operation": "list", + "method": "GET", + "path": "/video/workspaces/{workspace_id}/folders", + "label": "all params", + "run": _smoke_case_89, + }, + { + "operation": "retrieve", + "method": "GET", + "path": "/video/workspaces/{workspace_id}/folders/{folder_id}", + "run": _smoke_case_90, + }, + { + "operation": "update", + "method": "POST", + "path": "/video/workspaces/{workspace_id}/folders/{folder_id}", + "label": "required params", + "run": _smoke_case_91, + }, + { + "operation": "update", + "method": "POST", + "path": "/video/workspaces/{workspace_id}/folders/{folder_id}", + "label": "all params", + "run": _smoke_case_92, + }, + { + "operation": "delete", + "method": "DELETE", + "path": "/video/workspaces/{workspace_id}/folders/{folder_id}", + "run": _smoke_case_93, + }, + { + "operation": "deleteAssets", + "method": "POST", + "path": "/video/workspaces/{workspace_id}/remove-assets-from-folder", + "run": _smoke_case_94, + }, + { + "operation": "invite", + "method": "POST", + "path": "/channel/{video_workspace_id}/viewers/invite", + "run": _smoke_case_95, + }, + { + "operation": "delete", + "method": "POST", + "path": "/channel/{video_workspace_id}/viewers/remove", + "run": _smoke_case_96, + }, + { + "operation": "inviteCsv", + "method": "POST", + "path": "/channel/{video_workspace_id}/viewers/invite/csv", + "run": _smoke_case_97, + }, + { + "operation": "listSubscribers", + "method": "GET", + "path": "/channel/{workspace_id}/viewers", + "run": _smoke_case_98, + }, + { + "operation": "chartData", + "method": "POST", + "path": "/insights/viewer-analytics", + "label": "required params", + "run": _smoke_case_99, + }, + { + "operation": "chartData", + "method": "POST", + "path": "/insights/viewer-analytics", + "label": "all params", + "run": _smoke_case_100, + }, + { + "operation": "breakdownData", + "method": "POST", + "path": "/insights/breakdown-data", + "run": _smoke_case_101, + }, + { + "operation": "aggregatedData", + "method": "POST", + "path": "/insights/aggregated-data", + "label": "required params", + "run": _smoke_case_102, + }, + { + "operation": "aggregatedData", + "method": "POST", + "path": "/insights/aggregated-data", + "label": "all params", + "run": _smoke_case_103, + }, + { + "operation": "fetchOrg", + "method": "GET", + "path": "/org/data", + "run": _smoke_case_104, + }, + { + "operation": "fetch", + "method": "GET", + "path": "/user/data", + "run": _smoke_case_105, + }, + { + "operation": "fetch", + "method": "POST", + "path": "/user/audit-log", + "label": "required params", + "run": _smoke_case_106, + }, + { + "operation": "fetch", + "method": "POST", + "path": "/user/audit-log", + "label": "all params", + "run": _smoke_case_107, + }, + { + "operation": "listInvoices", + "method": "GET", + "path": "/mixed/billing/invoice/history", + "run": _smoke_case_108, + }, + { + "operation": "fetchDetails", + "method": "GET", + "path": "/mixed/billing/details", + "run": _smoke_case_109, + }, + { + "operation": "updateDetails", + "method": "POST", + "path": "/mixed/billing/details", + "run": _smoke_case_110, + }, + { + "operation": "fetchUpcomingInvoice", + "method": "GET", + "path": "/mixed/billing/invoice/upcoming", + "run": _smoke_case_111, + }, + { + "operation": "list", + "method": "GET", + "path": "/video/sources/live", + "run": _smoke_case_112, + }, + { + "operation": "create", + "method": "POST", + "path": "/video/sources/live", + "run": _smoke_case_113, + }, + { + "operation": "update", + "method": "POST", + "path": "/video/sources/live/{live_workspace_id}", + "run": _smoke_case_114, + }, + { + "operation": "delete", + "method": "DELETE", + "path": "/video/sources/live/{live_workspace_id}", + "run": _smoke_case_115, + }, +] + +DEFAULT_SMOKE_CONCURRENCY = 32 + + +def _selected_cases() -> list[SmokeCase]: + filter_value = os.environ.get("SCALAR_SMOKE_FILTER") + needles = [needle.strip() for needle in filter_value.split(",") if needle.strip()] if filter_value else [] + if not needles: + return cases + return [case for case in cases if any(needle in case["operation"] or needle in case["path"] for needle in needles)] + + +def _smoke_concurrency(case_count: int) -> int: + override = os.environ.get("SCALAR_SMOKE_CONCURRENCY") + if override: + try: + parsed = int(override) + if parsed > 0: + return min(parsed, case_count) + except ValueError: + pass + return min(DEFAULT_SMOKE_CONCURRENCY, case_count) + + +def _case_identity(case: SmokeCase) -> SmokeResult: + # `label` is carried through only when the operation contributed both of its calls, so a + # single-case operation reports exactly as it did before there were two. + identity: SmokeResult = { + "operation": case["operation"], + "method": case["method"], + "path": case["path"], + } + label = case.get("label") + if label: + identity["label"] = label + return identity + + +def _run_case(case: SmokeCase) -> SmokeResult: + started_at = time.monotonic() + identity = _case_identity(case) + try: + case["run"]() + return { + **identity, + "status": "passed", + "durationMs": int((time.monotonic() - started_at) * 1000), + } + except Exception: + return { + **identity, + "status": "failed", + "durationMs": int((time.monotonic() - started_at) * 1000), + "error": traceback.format_exc(), + } + + +def main() -> None: + selected = _selected_cases() + if selected: + # Keep enough parallelism to catch generated SDK concurrency bugs without overwhelming + # CI runners or the in-process mock server for large SDKs. + with ThreadPoolExecutor(max_workers=_smoke_concurrency(len(selected))) as executor: + results = list(executor.map(_run_case, selected)) + else: + results = [] + failed = [result for result in results if result["status"] == "failed"] + + report_path = os.environ.get("SCALAR_SMOKE_REPORT") + if report_path: + Path(report_path).write_text( + json.dumps({"total": len(results), "failed": len(failed), "results": results}), encoding="utf-8" + ) + else: + for result in results: + suffix = f" [{result['label']}]" if result.get("label") else "" + if result["status"] == "passed": + print( + f"PASS {result['operation']}{suffix} ({result['method']} {result['path']}) {result['durationMs']}ms" + ) + else: + print( + f"FAIL {result['operation']}{suffix} ({result['method']} {result['path']})\n{result.get('error', '')}", + file=sys.stderr, + ) + if not results: + print("No code samples ran (empty SDK or a SCALAR_SMOKE_FILTER that matched nothing).", file=sys.stderr) + else: + print(f"\n{len(results) - len(failed)}/{len(results)} samples passed") + + if failed or not results: + sys.exit(1) + + +if __name__ == "__main__": + main()