diff --git a/pythonik/client.py b/pythonik/client.py index d76989c..003ea08 100644 --- a/pythonik/client.py +++ b/pythonik/client.py @@ -8,6 +8,7 @@ from pythonik.specs.metadata import MetadataSpec from pythonik.specs.search import SearchSpec from pythonik.specs.collection import CollectionSpec +from pythonik.specs.transcode import TranscodeSpec # Iconik APIs @@ -50,3 +51,6 @@ def search(self): def jobs(self): return JobSpec(self.session, self.timeout, self.base_url) + + def transcode(self) -> TranscodeSpec: + return TranscodeSpec(self.session, self.timeout, self.base_url) diff --git a/pythonik/models/transcode.py b/pythonik/models/transcode.py new file mode 100644 index 0000000..17869a9 --- /dev/null +++ b/pythonik/models/transcode.py @@ -0,0 +1,709 @@ +""" +Iconik Transcode Models +This module contains Pydantic models for the Iconik Transcode API. +""" + +from __future__ import annotations + +from datetime import datetime +from typing import ( + Any, + Dict, + List, + Literal, + Optional, + Union, +) + +from pydantic import ( + BaseModel, + Field, + HttpUrl, +) + + +class TranscribeSchema(BaseModel): + """Represents a TranscribeSchema in the Iconik system.""" + + engine: Optional[str] = None + force: Optional[bool] = None + language: Optional[str] = None + profile_id: Optional[str] = None + speakers: Optional[int] = Field(None, ge=1, le=100) + summary: Optional[bool] = None + topics_extraction: Optional[bool] = None + translate_languages: Optional[List[str]] = Field(default_factory=list) + + +class TranscodersSchema(BaseModel): + """Represents a TranscodersSchema in the Iconik system.""" + + id: Optional[str] = None + name: Optional[str] = None + settings: Optional[Dict[str, Any]] = Field(default_factory=dict) + type: Optional[str] = None + + +class TranscodeValidateMediaInfoSchema(BaseModel): + """Represents a TranscodeValidateMediaInfoSchema in the Iconik system.""" + + filename: Optional[str] = None + media_info: str + + +class TranscodeQueueSchema(BaseModel): + """Represents a TranscodeQueueSchema in the Iconik system.""" + + facets: Optional[Dict[str, "FacetSchema"]] = Field(default_factory=dict) + first_url: Optional[str] = None + last_url: Optional[str] = None + next_url: Optional[str] = None + objects: Optional[List["TranscodeQueueObjectSchema"]] = Field( + default_factory=list) + page: Optional[int] = Field(None, ge=-2147483648, le=2147483647) + pages: Optional[int] = Field(None, ge=-2147483648, le=2147483647) + per_page: Optional[int] = Field(None, ge=-2147483648, le=2147483647) + prev_url: Optional[str] = None + total: Optional[int] = Field(None, + ge=-9223372036854775808, + le=9223372036854775807) + + +class TranscodeQueueRecordSchema(BaseModel): + """Represents a TranscodeQueueRecordSchema in the Iconik system.""" + + bytes_params: Optional[Any] = None + date_created: Optional[str] = None + date_updated: Optional[str] = None + id: Optional[str] = None + job_id: Optional[str] = None + media_info: Optional[str] = None + object_id: Optional[str] = None + object_type: Optional[str] = None + params: Optional[str] = None + priority: Optional[int] = None + retry_count: Optional[int] = None + spec: Optional[str] = None + status: Optional[str] = None + system_domain_id: Optional[str] = None + system_name: Optional[str] = None + type: Optional[str] = None + user_id: Optional[str] = None + version_id: Optional[str] = None + + +class TranscodeQueueObjectSchema(BaseModel): + """Represents a TranscodeQueueObjectSchema in the Iconik system.""" + + date_created: Optional[datetime] = None + date_updated: Optional[datetime] = None + id: Optional[str] = None + job_id: Optional[str] = None + priority: Optional[int] = Field(None, ge=1, le=10) + retry_count: Optional[int] = Field(None, ge=-2147483648, le=2147483647) + status: Optional[str] = None + system_domain: Optional[str] = None + system_domain_id: Optional[str] = None + system_domain_timestamp: Optional[float] = None + system_name: Optional[str] = None + type: Optional[str] = None + user_id: Optional[str] = None + + +class TranscodeElasticQueueRecordSchema(BaseModel): + """Represents a TranscodeElasticQueueRecordSchema in the Iconik system.""" + + date_created: Optional[str] = None + date_updated: Optional[str] = None + id: Optional[str] = None + job_id: Optional[str] = None + object_id: Optional[str] = None + object_type: Optional[str] = None + priority: Optional[str] = None + queue: Optional[str] = None + retry_count: Optional[str] = None + status: Optional[str] = None + storage_id: Optional[str] = None + system_domain_id: Optional[str] = None + system_domain_timestamp: Optional[str] = None + system_name: Optional[str] = None + type: Optional[str] = None + user_id: Optional[str] = None + version_id: Optional[str] = None + + +class TranscodeESQueueRecordsSchema(BaseModel): + """Represents a TranscodeESQueueRecordsSchema in the Iconik system.""" + + objects: Optional[List["TranscodeElasticQueueRecord"]] = Field( + default_factory=list) + + +class TranscodeElasticQueueRecord(BaseModel): + """Represents a TranscodeElasticQueueRecord in the Iconik system.""" + + date_created: Optional[str] = None + date_updated: Optional[str] = None + id: Optional[str] = None + job_id: Optional[str] = None + object_id: Optional[str] = None + object_type: Optional[str] = None + priority: Optional[str] = None + queue: Optional[str] = None + retry_count: Optional[str] = None + status: Optional[str] = None + storage_id: Optional[str] = None + system_domain_id: Optional[str] = None + system_domain_timestamp: Optional[str] = None + system_name: Optional[str] = None + type: Optional[str] = None + user_id: Optional[str] = None + version_id: Optional[str] = None + + +class ThumbnailJobSchema(BaseModel): + """Represents a ThumbnailJobSchema in the Iconik system.""" + + height: Optional[int] = Field(None, ge=-2147483648, le=2147483647) + id: Optional[str] = None + max_number: Optional[int] = Field(None, ge=-2147483648, le=2147483647) + min_interval: Optional[int] = Field(None, + ge=-9223372036854775808, + le=9223372036854775807) + output_endpoint: "OutputEndpointSchema" + set_as_custom_keyframe: Optional[bool] = None + time_end_milliseconds: Optional[int] = Field(None, + ge=-9223372036854775808, + le=9223372036854775807) + time_start_milliseconds: Optional[int] = Field(None, + ge=-9223372036854775808, + le=9223372036854775807) + timestamp: Optional[int] = Field(None, + ge=-9223372036854775808, + le=9223372036854775807) + width: Optional[int] = Field(None, ge=-2147483648, le=2147483647) + + +class SpecifiedKeyframesSchema(BaseModel): + """Represents a SpecifiedKeyframesSchema in the Iconik system.""" + + url: str + + +class ReindexQueueRecordSchema(BaseModel): + """Represents a ReindexQueueRecordSchema in the Iconik system.""" + + sync_to_another_dc: Optional[bool] = None + + +class LocalTranscodeJobSchema(BaseModel): + """Represents a LocalTranscodeJobSchema in the Iconik system.""" + + amazon_rekognition: Optional[bool] = None + analysis_data: Optional[Dict[str, Any]] = Field(default_factory=dict) + analysis_query_default_service_account: Optional[bool] = None + analyzed_before: Optional[bool] = None + asset_id: Optional[str] = None + asset_link: Optional[str] = None + collection_id: Optional[str] = None + create_transcription: Optional[bool] = None + delete_old_transcriptions: Optional[bool] = None + force_transcoder: Optional[str] = None + google_cloud_video_intelligence: Optional[bool] = None + input: "LocalTranscodeInputSchema" + job_id: Optional[str] = None + job_steps: Optional[List["JobStepSchema"]] = Field(default_factory=list) + language: Optional[str] = None + media_info: Optional[str] = None + overwrite: Optional[bool] = None + priority: Optional[int] = Field(None, ge=1, le=10) + speakers: Optional[int] = Field(None, ge=2, le=100) + thumbnail: Optional[List["ThumbnailJob"]] = Field(default_factory=list) + transcode: Optional[List["TranscodeJob"]] = Field(default_factory=list) + valid_transcoders: Optional[List["Transcoders"]] = Field( + default_factory=list) + version_id: Optional[str] = None + + +class LocalTranscodeInputSchema(BaseModel): + """Represents a LocalTranscodeInputSchema in the Iconik system.""" + + asset_id: str + checksum: Optional[str] = None + closed_captions: Optional[bool] = None + collection_id: Optional[str] = None + component_ids: Optional[List[str]] = Field(default_factory=list) + directory_path: str + endpoint: "EndpointSchema" + engine: Optional[str] = None + file_id: str + file_set_id: str + filename: str + format_id: str + language: Optional[str] = None + original_name: Optional[str] = None + proxy_id: Optional[str] = None + size: int = Field(..., ge=-9223372036854775808, le=9223372036854775807) + storage_id: str + update_proxy_mediainfo: Optional[bool] = None + version_id: str + + +class LocalStorageFileTranscodeJobsSchema(BaseModel): + """Represents a LocalStorageFileTranscodeJobsSchema in the Iconik system.""" + + first_url: Optional[str] = None + last_url: Optional[str] = None + next_url: Optional[str] = None + objects: Optional[List["LocalStorageFileTranscodeJobSchema"]] = Field( + default_factory=list) + page: Optional[int] = Field(None, ge=-2147483648, le=2147483647) + pages: Optional[int] = Field(None, ge=-2147483648, le=2147483647) + per_page: Optional[int] = Field(None, ge=-2147483648, le=2147483647) + prev_url: Optional[str] = None + total: Optional[int] = Field(None, + ge=-9223372036854775808, + le=9223372036854775807) + + +class LocalStorageFileTranscodeJobSchema(BaseModel): + """Represents a LocalStorageFileTranscodeJobSchema in the Iconik system.""" + + asset_id: str + checksum: Optional[str] = None + collection_id: Optional[str] = None + component_ids: Optional[List[str]] = Field(default_factory=list) + directory_path: str + file_id: str + file_set_id: str + filename: str + format_id: str + id: Optional[str] = None + job_id: str + priority: Optional[int] = Field(None, ge=1, le=10) + size: int = Field(..., ge=-9223372036854775808, le=9223372036854775807) + version_id: str + + +class ListObjectsSchema(BaseModel): + """Represents a ListObjectsSchema in the Iconik system.""" + + first_url: Optional[str] = None + last_url: Optional[str] = None + next_url: Optional[str] = None + page: Optional[int] = Field(None, ge=-2147483648, le=2147483647) + pages: Optional[int] = Field(None, ge=-2147483648, le=2147483647) + per_page: Optional[int] = Field(None, ge=-2147483648, le=2147483647) + prev_url: Optional[str] = None + total: Optional[int] = Field(None, + ge=-9223372036854775808, + le=9223372036854775807) + + +class JobsStateSchema(BaseModel): + """Represents a JobsStateSchema in the Iconik system.""" + + action: Literal["ABORT", "RESTART"] + job_ids: List[str] + + +class JobsPrioritySchema(BaseModel): + """Represents a JobsPrioritySchema in the Iconik system.""" + + job_ids: List[str] + priority: int = Field(..., ge=1, le=10) + + +class JobStepSchema(BaseModel): + """Represents a JobStepSchema in the Iconik system.""" + + date_created: Optional[datetime] = None + date_updated: Optional[datetime] = None + id: Optional[str] = None + label: Optional[str] = None + message: Optional[str] = None + status: Optional[str] = None + + +class JobBaseSchema(BaseModel): + """Represents a JobBaseSchema in the Iconik system.""" + + asset_id: Optional[str] = None + collection_id: Optional[str] = None + input: Optional["InputSchema"] = None + job_id: Optional[str] = None + job_steps: Optional[List["JobStep"]] = Field(default_factory=list) + media_info: Optional[str] = None + thumbnail: Optional[List["ThumbnailJob"]] = Field(default_factory=list) + transcode: Optional[List["TranscodeJob"]] = Field(default_factory=list) + + +class GenerateCollectionKeyframeSchema(BaseModel): + """Represents a GenerateCollectionKeyframeSchema in the Iconik system.""" + + deleted_asset_id: Optional[str] = None + force: Optional[bool] = None + specified_asset_ids: Optional[List[str]] = Field(default_factory=list) + specified_keyframes: Optional[List["SpecifiedKeyframes"]] = Field( + default_factory=list) + + +class SpecifiedKeyframes(BaseModel): + """Represents a SpecifiedKeyframes in the Iconik system.""" + + url: str + + +class FacetSchema(BaseModel): + """Represents a FacetSchema in the Iconik system.""" + + buckets: Optional[List["FacetBucketSchema"]] = Field(default_factory=list) + doc_count_error_upper_bound: Optional[int] = Field(None, + ge=-9223372036854775808, + le=9223372036854775807) + sum_other_doc_count: Optional[int] = Field(None, + ge=-9223372036854775808, + le=9223372036854775807) + + +class FacetBucketSchema(BaseModel): + """Represents a FacetBucketSchema in the Iconik system.""" + + doc_count: Optional[int] = Field(None, + ge=-9223372036854775808, + le=9223372036854775807) + key: Optional[str] = None + + +class EdgeTranscodeWorkersSchema(BaseModel): + """Represents a EdgeTranscodeWorkersSchema in the Iconik system.""" + + objects: Optional[List["EdgeTranscodeWorkerSchema"]] = Field( + default_factory=list) + + +class EdgeTranscodeWorkerSchema(BaseModel): + """Represents a EdgeTranscodeWorkerSchema in the Iconik system.""" + + id: Optional[str] = None + last_update_date: Optional[datetime] = None + status: Literal["ACTIVE", "INACTIVE"] + storage_id: str + + +class EdgeTranscodeJobsSchema(BaseModel): + """Represents a EdgeTranscodeJobsSchema in the Iconik system.""" + + objects: Optional[List["EdgeTranscodeJobSchema"]] = Field( + default_factory=list) + + +class TranscodeJobSchema(BaseModel): + """Represents a TranscodeJobSchema in the Iconik system.""" + + height: Optional[int] = Field(None, ge=-2147483648, le=2147483647) + id: Optional[str] = None + output_endpoint: "OutputEndpointSchema" + width: Optional[int] = Field(None, ge=-2147483648, le=2147483647) + + +class OutputEndpointSchema(BaseModel): + """Represents a OutputEndpointSchema in the Iconik system.""" + + headers: Optional[Dict[str, Any]] = Field(default_factory=dict) + key: str + + +class JobSchema(BaseModel): + """Represents a JobSchema in the Iconik system.""" + + amazon_rekognition: Optional[bool] = None + analysis_data: Optional[Dict[str, Any]] = Field(default_factory=dict) + analysis_query_default_service_account: Optional[bool] = None + analyzed_before: Optional[bool] = None + asset_id: Optional[str] = None + asset_link: Optional[str] = None + collection_id: Optional[str] = None + create_transcription: Optional[bool] = None + delete_old_transcriptions: Optional[bool] = None + force_transcoder: Optional[str] = None + google_cloud_video_intelligence: Optional[bool] = None + input: Optional["InputSchema"] = None + job_id: Optional[str] = None + job_steps: Optional[List["JobStep"]] = Field(default_factory=list) + language: Optional[str] = None + media_info: Optional[str] = None + overwrite: Optional[bool] = None + priority: Optional[int] = Field(None, ge=1, le=10) + speakers: Optional[int] = Field(None, ge=2, le=100) + thumbnail: Optional[List["ThumbnailJob"]] = Field(default_factory=list) + transcode: Optional[List["TranscodeJob"]] = Field(default_factory=list) + valid_transcoders: Optional[List["Transcoders"]] = Field( + default_factory=list) + version_id: Optional[str] = None + + +class Transcoders(BaseModel): + """Represents a Transcoders in the Iconik system.""" + + id: Optional[str] = None + name: Optional[str] = None + settings: Optional[Dict[str, Any]] = Field(default_factory=dict) + type: Optional[str] = None + + +class EdgeTranscodeJobSchema(BaseModel): + """Represents a EdgeTranscodeJobSchema in the Iconik system.""" + + asset_id: Optional[str] = None + collection_id: Optional[str] = None + input: "EdgeTranscodeInputSchema" + job_id: Optional[str] = None + job_steps: Optional[List["JobStep"]] = Field(default_factory=list) + media_info: Optional[str] = None + thumbnail: Optional[List["EdgeThumbnailJobFieldSchema"]] = Field( + default_factory=list) + transcode: Optional[List["EdgeTranscodeJobFieldSchema"]] = Field( + default_factory=list) + + +class JobStep(BaseModel): + """Represents a JobStep in the Iconik system.""" + + date_created: Optional[datetime] = None + date_updated: Optional[datetime] = None + id: Optional[str] = None + label: Optional[str] = None + message: Optional[str] = None + status: Optional[str] = None + + +class ThumbnailJob(BaseModel): + """Represents a ThumbnailJob in the Iconik system.""" + + height: Optional[int] = Field(None, ge=-2147483648, le=2147483647) + id: Optional[str] = None + max_number: Optional[int] = Field(None, ge=-2147483648, le=2147483647) + min_interval: Optional[int] = Field(None, + ge=-9223372036854775808, + le=9223372036854775807) + output_endpoint: "OutputEndpoint" + set_as_custom_keyframe: Optional[bool] = None + time_end_milliseconds: Optional[int] = Field(None, + ge=-9223372036854775808, + le=9223372036854775807) + time_start_milliseconds: Optional[int] = Field(None, + ge=-9223372036854775808, + le=9223372036854775807) + timestamp: Optional[int] = Field(None, + ge=-9223372036854775808, + le=9223372036854775807) + width: Optional[int] = Field(None, ge=-2147483648, le=2147483647) + + +class InputSchema(BaseModel): + """Represents a InputSchema in the Iconik system.""" + + asset_id: Optional[str] = None + closed_captions: Optional[bool] = None + collection_id: Optional[str] = None + directory_path: Optional[str] = None + endpoint: "EndpointSchema" + engine: Optional[str] = None + file_id: Optional[str] = None + file_set_id: Optional[str] = None + format_id: Optional[str] = None + language: Optional[str] = None + original_name: Optional[str] = None + proxy_id: Optional[str] = None + storage_id: Optional[str] = None + update_proxy_mediainfo: Optional[bool] = None + version_id: Optional[str] = None + + +class TranscodeJob(BaseModel): + """Represents a TranscodeJob in the Iconik system.""" + + height: Optional[int] = Field(None, ge=-2147483648, le=2147483647) + id: Optional[str] = None + output_endpoint: "OutputEndpoint" + width: Optional[int] = Field(None, ge=-2147483648, le=2147483647) + + +class OutputEndpoint(BaseModel): + """Represents a OutputEndpoint in the Iconik system.""" + + headers: Optional[Dict[str, Any]] = Field(default_factory=dict) + key: str + + +class EdgeTranscodeJobFieldSchema(BaseModel): + """Represents a EdgeTranscodeJobFieldSchema in the Iconik system.""" + + height: Optional[int] = Field(None, ge=-2147483648, le=2147483647) + id: Optional[str] = None + width: Optional[int] = Field(None, ge=-2147483648, le=2147483647) + + +class EdgeTranscodeInputSchema(BaseModel): + """Represents a EdgeTranscodeInputSchema in the Iconik system.""" + + asset_id: Optional[str] = None + closed_captions: Optional[bool] = None + directory_path: Optional[str] = None + endpoint: "EdgeTranscodeEndpointSchema" + file_id: Optional[str] = None + file_set_id: Optional[str] = None + format_id: Optional[str] = None + language: Optional[str] = None + original_name: Optional[str] = None + proxy_id: Optional[str] = None + storage_id: Optional[str] = None + + +class EndpointSchema(BaseModel): + """Represents a EndpointSchema in the Iconik system.""" + + data: Optional[Dict[str, Any]] = Field(default_factory=dict) + headers: Optional[Dict[str, Any]] = Field(default_factory=dict) + method: Optional[str] = None + storage_method: Optional[str] = None + type: Optional[str] = None + url: str + + +class EdgeTranscodeEndpointSchema(BaseModel): + """Represents a EdgeTranscodeEndpointSchema in the Iconik system.""" + + data: Optional[Dict[str, Any]] = Field(default_factory=dict) + method: Optional[str] = None + storage_method: Optional[str] = None + type: Optional[str] = None + url: str + + +class EdgeThumbnailJobFieldSchema(BaseModel): + """Represents a EdgeThumbnailJobFieldSchema in the Iconik system.""" + + height: Optional[int] = Field(None, ge=-2147483648, le=2147483647) + id: Optional[str] = None + max_number: Optional[int] = Field(None, ge=-2147483648, le=2147483647) + min_interval: Optional[int] = Field(None, + ge=-9223372036854775808, + le=9223372036854775807) + timestamp: Optional[int] = Field(None, + ge=-9223372036854775808, + le=9223372036854775807) + width: Optional[int] = Field(None, ge=-2147483648, le=2147483647) + + +class BulkTranscribeSchema(BaseModel): + """Represents a BulkTranscribeSchema in the Iconik system.""" + + engine: Optional[str] = None + force: Optional[bool] = None + language: Optional[str] = None + object_ids: List[str] + object_type: Literal["assets", "collections", "saved_searches"] + profile_id: Optional[str] = None + speakers: Optional[int] = Field(None, ge=1, le=100) + summary: Optional[bool] = None + topics_extraction: Optional[bool] = None + translate_languages: Optional[List[str]] = Field(default_factory=list) + + +class BulkAnalyzeSchema(BaseModel): + """Represents a BulkAnalyzeSchema in the Iconik system.""" + + force: Optional[bool] = None + force_type: Optional[Literal["OVERWRITE", "APPEND"]] = None + object_ids: List[str] + object_type: Literal["assets", "collections", "saved_searches"] + profile_id: Optional[str] = None + + +class BulkActionSchema(BaseModel): + """Represents a BulkActionSchema in the Iconik system.""" + + object_ids: List[str] + object_type: Literal["assets", "collections", "saved_searches"] + + +class AssetLinkURLSchema(BaseModel): + """Represents a AssetLinkURLSchema in the Iconik system.""" + + url: Union[str, HttpUrl] + + +class AssetLinkData(BaseModel): + """Represents a AssetLinkData in the Iconik system.""" + + site_name: Optional[str] = None + title: Optional[str] = None + + +class AnalyzeSchema(BaseModel): + """Represents a AnalyzeSchema in the Iconik system.""" + + force: Optional[bool] = None + force_type: Optional[Literal["OVERWRITE", "APPEND"]] = None + service_name: Optional[str] = None + + +class AbortStorageTranscodeJobsSchema(BaseModel): + """Represents a AbortStorageTranscodeJobsSchema in the Iconik system.""" + + error_message: Optional[str] = None + + +# Update forward references +TranscribeSchema.model_rebuild() +TranscodersSchema.model_rebuild() +TranscodeValidateMediaInfoSchema.model_rebuild() +TranscodeQueueSchema.model_rebuild() +TranscodeQueueRecordSchema.model_rebuild() +TranscodeQueueObjectSchema.model_rebuild() +TranscodeElasticQueueRecordSchema.model_rebuild() +TranscodeESQueueRecordsSchema.model_rebuild() +TranscodeElasticQueueRecord.model_rebuild() +ThumbnailJobSchema.model_rebuild() +SpecifiedKeyframesSchema.model_rebuild() +ReindexQueueRecordSchema.model_rebuild() +LocalTranscodeJobSchema.model_rebuild() +LocalTranscodeInputSchema.model_rebuild() +LocalStorageFileTranscodeJobsSchema.model_rebuild() +LocalStorageFileTranscodeJobSchema.model_rebuild() +ListObjectsSchema.model_rebuild() +JobsStateSchema.model_rebuild() +JobsPrioritySchema.model_rebuild() +JobStepSchema.model_rebuild() +JobBaseSchema.model_rebuild() +GenerateCollectionKeyframeSchema.model_rebuild() +SpecifiedKeyframes.model_rebuild() +FacetSchema.model_rebuild() +FacetBucketSchema.model_rebuild() +EdgeTranscodeWorkersSchema.model_rebuild() +EdgeTranscodeWorkerSchema.model_rebuild() +EdgeTranscodeJobsSchema.model_rebuild() +TranscodeJobSchema.model_rebuild() +OutputEndpointSchema.model_rebuild() +JobSchema.model_rebuild() +Transcoders.model_rebuild() +EdgeTranscodeJobSchema.model_rebuild() +JobStep.model_rebuild() +ThumbnailJob.model_rebuild() +InputSchema.model_rebuild() +TranscodeJob.model_rebuild() +OutputEndpoint.model_rebuild() +EdgeTranscodeJobFieldSchema.model_rebuild() +EdgeTranscodeInputSchema.model_rebuild() +EndpointSchema.model_rebuild() +EdgeTranscodeEndpointSchema.model_rebuild() +EdgeThumbnailJobFieldSchema.model_rebuild() +BulkTranscribeSchema.model_rebuild() +BulkAnalyzeSchema.model_rebuild() +BulkActionSchema.model_rebuild() +AssetLinkURLSchema.model_rebuild() +AssetLinkData.model_rebuild() +AnalyzeSchema.model_rebuild() +AbortStorageTranscodeJobsSchema.model_rebuild() diff --git a/pythonik/specs/_internal_utils.py b/pythonik/specs/_internal_utils.py new file mode 100644 index 0000000..58fd9e6 --- /dev/null +++ b/pythonik/specs/_internal_utils.py @@ -0,0 +1,31 @@ +from typing import Any + +from pythonik.exceptions import PythonikException + + +def is_pydantic_model(obj: Any) -> bool: + """ + Checks if an object is a Pydantic model instance. + + Args: + obj: The object to check. + + Returns: + True if the object is a Pydantic model instance, False otherwise. + """ + # Check for common Pydantic model attributes/methods + if obj is None: + return False + try: + # Pydantic v1 + has_dict_method = hasattr(obj, "dict") and callable( + getattr(obj, "dict", None)) + # Pydantic v2 + has_model_dump = hasattr(obj, "model_dump") and callable( + getattr(obj, "model_dump", None)) + # Check for schema-related attributes that are common in Pydantic models + has_schema_attrs = hasattr(obj, "__fields__") or hasattr( + obj, "model_fields") + return (has_dict_method or has_model_dump) and has_schema_attrs + except PythonikException: + return False diff --git a/pythonik/specs/transcode.py b/pythonik/specs/transcode.py new file mode 100644 index 0000000..64869a4 --- /dev/null +++ b/pythonik/specs/transcode.py @@ -0,0 +1,906 @@ +from typing import ( + Any, + Dict, + Literal, + Optional, + Union, +) + +from pythonik.models.base import Response +from pythonik.specs._internal_utils import is_pydantic_model +from pythonik.specs.base import Spec + +from ..models.transcode import ( + AbortStorageTranscodeJobsSchema, + AnalyzeSchema, + AssetLinkData, + AssetLinkURLSchema, + BulkAnalyzeSchema, + BulkTranscribeSchema, + EdgeTranscodeJobsSchema, + EdgeTranscodeWorkerSchema, + EdgeTranscodeWorkersSchema, + GenerateCollectionKeyframeSchema, + JobSchema, + LocalStorageFileTranscodeJobSchema, + LocalStorageFileTranscodeJobsSchema, + TranscodeESQueueRecordsSchema, + TranscodeQueueSchema, + TranscribeSchema, +) + + +class TranscodeSpec(Spec): + server = "API/transcode/" + + def analyze_asset( + self, + asset_id: str, + analyze_schema: Union[AnalyzeSchema, Dict[str, Any]], + exclude_defaults: bool = True, + **kwargs, + ) -> Response: + """ + Start a job that sends an asset for analysis + + Args: + asset_id: ID of the asset + analyze_schema: Analysis parameters (either as AnalyzeSchema + or dict) + exclude_defaults: Whether to exclude default values when dumping + **kwargs: Additional kwargs to pass to the request + + Returns: + Response(model=None) + + Raises: + - 400 Bad request + - 401 Token is invalid + """ + body = (analyze_schema.model_dump(exclude_defaults=exclude_defaults) + if is_pydantic_model(analyze_schema) else analyze_schema) + url = self.gen_url(f"analyze/assets/{asset_id}/") + resp = self._post(url, json=body, **kwargs) + return self.parse_response(resp, None) + + def analyze_asset_default_profile( + self, + asset_id: str, + analyze_schema: Union[AnalyzeSchema, Dict[str, Any]], + exclude_defaults: bool = True, + **kwargs, + ) -> Response: + """ + Start a job that sends an asset for analysis with a default + analysis profile + + Args: + asset_id: ID of the asset + analyze_schema: Analysis parameters (either as AnalyzeSchema + or dict) + exclude_defaults: Whether to exclude default values when dumping + **kwargs: Additional kwargs to pass to the request + + Returns: + Response(model=None) + + Raises: + - 400 Bad request + - 401 Token is invalid + """ + body = (analyze_schema.model_dump(exclude_defaults=exclude_defaults) + if is_pydantic_model(analyze_schema) else analyze_schema) + url = self.gen_url(f"analyze/assets/{asset_id}/profiles/default/") + resp = self._post(url, json=body, **kwargs) + return self.parse_response(resp, None) + + def analyze_asset_default_profile_media_type( + self, + asset_id: str, + media_type: str, + analyze_schema: Union[AnalyzeSchema, Dict[str, Any]], + exclude_defaults: bool = True, + **kwargs, + ) -> Response: + """ + Start a job that sends an asset for analysis with a default + analysis profile of specified media type + + Args: + asset_id: ID of the asset + media_type: Type of media + analyze_schema: Analysis parameters (either as AnalyzeSchema + or dict) + exclude_defaults: Whether to exclude default values when dumping + **kwargs: Additional kwargs to pass to the request + + Returns: + Response(model=None) + + Raises: + - 400 Bad request + - 401 Token is invalid + """ + body = (analyze_schema.model_dump(exclude_defaults=exclude_defaults) + if is_pydantic_model(analyze_schema) else analyze_schema) + url = self.gen_url( + f"analyze/assets/{asset_id}/profiles/default/{media_type}/") + resp = self._post(url, json=body, **kwargs) + return self.parse_response(resp, None) + + def analyze_asset_custom_profile( + self, + asset_id: str, + profile_id: str, + analyze_schema: Union[AnalyzeSchema, Dict[str, Any]], + exclude_defaults: bool = True, + **kwargs, + ) -> Response: + """ + Start a job that sends an asset for analysis with a custom + analysis profile + + Args: + asset_id: ID of the asset + profile_id: ID of the analysis profile + analyze_schema: Analysis parameters (either as AnalyzeSchema + or dict) + exclude_defaults: Whether to exclude default values when dumping + **kwargs: Additional kwargs to pass to the request + + Returns: + Response(model=None) + + Raises: + - 400 Bad request + - 401 Token is invalid + """ + body = (analyze_schema.model_dump(exclude_defaults=exclude_defaults) + if is_pydantic_model(analyze_schema) else analyze_schema) + url = self.gen_url(f"analyze/assets/{asset_id}/profiles/{profile_id}/") + resp = self._post(url, json=body, **kwargs) + return self.parse_response(resp, None) + + def analyze_bulk( + self, + bulk_analyze_schema: Union[BulkAnalyzeSchema, Dict[str, Any]], + exclude_defaults: bool = True, + **kwargs, + ) -> Response: + """ + Start a job that sends objects for analysis using a custom + analysis profile + + Args: + bulk_analyze_schema: Bulk analysis parameters (either as + BulkAnalyzeSchema or dict) + exclude_defaults: Whether to exclude default values when dumping + **kwargs: Additional kwargs to pass to the request + + Returns: + Response(model=None) + + Raises: + - 400 Bad request + - 401 Token is invalid + - 404 Analysis account/profile was not found + """ + body = ( + bulk_analyze_schema.model_dump(exclude_defaults=exclude_defaults) + if is_pydantic_model(bulk_analyze_schema) else bulk_analyze_schema) + url = self.gen_url("analyze/bulk/") + resp = self._post(url, json=body, **kwargs) + return self.parse_response(resp, None) + + def get_asset_link_metadata( + self, + asset_link_url_schema: Union[AssetLinkURLSchema, Dict[str, Any]], + exclude_defaults: bool = True, + **kwargs, + ) -> Response: + """ + Gets metadata info from the link + + Args: + asset_link_url_schema: URL parameters (either as + AssetLinkURLSchema or dict) + exclude_defaults: Whether to exclude default values when dumping + **kwargs: Additional kwargs to pass to the request + + Returns: + Response(model=AssetLinkData) + + Raises: + - 400 Bad URL + - 401 Token is invalid + - 404 Could not extract data + """ + body = (asset_link_url_schema.model_dump( + exclude_defaults=exclude_defaults) + if is_pydantic_model(asset_link_url_schema) else + asset_link_url_schema) + url = self.gen_url("assets/link/metadata/") + resp = self._post(url, json=body, **kwargs) + return self.parse_response(resp, AssetLinkData) + + def acknowledge_edge_transcode_job( + self, + job_id: str, + **kwargs, + ) -> Response: + """ + Acknowledge an edge transcode job + + Args: + job_id: ID of the edge transcode job + **kwargs: Additional kwargs to pass to the request + + Returns: + Response(model=None) + + Raises: + - 400 Bad request + - 401 Token is invalid + - 404 Edge transcode job doesn't exist + """ + url = self.gen_url(f"edge_transcode/jobs/{job_id}/acknowledge/") + resp = self._post(url, **kwargs) + return self.parse_response(resp, None) + + def list_edge_transcode_workers( + self, + **kwargs, + ) -> Response: + """ + Get edge transcode workers + + Args: + **kwargs: Additional kwargs to pass to the request + + Returns: + Response(model=EdgeTranscodeWorkersSchema) + + Raises: + - 400 Bad request + - 401 Token is invalid + """ + url = self.gen_url("edge_transcode/workers/") + resp = self._get(url, **kwargs) + return self.parse_response(resp, EdgeTranscodeWorkersSchema) + + def create_edge_transcode_worker( + self, + worker_schema: Union[EdgeTranscodeWorkerSchema, Dict[str, Any]], + exclude_defaults: bool = True, + **kwargs, + ) -> Response: + """ + Create a new edge transcode worker + + Args: + worker_schema: Edge transcode worker parameters (either as + EdgeTranscodeWorkerSchema or dict) + exclude_defaults: Whether to exclude default values when dumping + **kwargs: Additional kwargs to pass to the request + + Returns: + Response(model=EdgeTranscodeWorkerSchema) + + Raises: + - 400 Bad request + - 401 Token is invalid + """ + body = (worker_schema.model_dump(exclude_defaults=exclude_defaults) + if is_pydantic_model(worker_schema) else worker_schema) + url = self.gen_url("edge_transcode/workers/") + resp = self._post(url, json=body, **kwargs) + return self.parse_response(resp, EdgeTranscodeWorkerSchema) + + def get_edge_transcode_worker( + self, + worker_id: str, + **kwargs, + ) -> Response: + """ + Get an edge transcode worker + + Args: + worker_id: ID of the edge transcode worker + **kwargs: Additional kwargs to pass to the request + + Returns: + Response(model=EdgeTranscodeWorkerSchema) + + Raises: + - 400 Bad request + - 401 Token is invalid + - 404 Edge transcode worker doesn't exist + """ + url = self.gen_url(f"edge_transcode/workers/{worker_id}/") + resp = self._get(url, **kwargs) + return self.parse_response(resp, EdgeTranscodeWorkerSchema) + + def delete_edge_transcode_worker( + self, + worker_id: str, + **kwargs, + ) -> Response: + """ + Delete a edge transcode worker + + Args: + worker_id: ID of the edge transcode worker + **kwargs: Additional kwargs to pass to the request + + Returns: + Response(model=None) + + Raises: + - 400 Bad request + - 401 Token is invalid + - 404 Edge transcode worker doesn't exist + """ + url = self.gen_url(f"edge_transcode/workers/{worker_id}/") + resp = self._delete(url, **kwargs) + return self.parse_response(resp, None) + + def update_edge_transcode_worker( + self, + worker_id: str, + worker_schema: Union[EdgeTranscodeWorkerSchema, Dict[str, Any]], + exclude_defaults: bool = True, + **kwargs, + ) -> Response: + """ + Update an edge transcode worker + + Args: + worker_id: ID of the edge transcode worker + worker_schema: Edge transcode worker parameters (either as + EdgeTranscodeWorkerSchema or dict) + exclude_defaults: Whether to exclude default values when dumping + **kwargs: Additional kwargs to pass to the request + + Returns: + Response(model=EdgeTranscodeWorkerSchema) + + Raises: + - 400 Bad request + - 401 Token is invalid + - 404 Edge transcode worker doesn't exist + """ + body = (worker_schema.model_dump(exclude_defaults=exclude_defaults) + if is_pydantic_model(worker_schema) else worker_schema) + url = self.gen_url(f"edge_transcode/workers/{worker_id}/") + resp = self._put(url, json=body, **kwargs) + return self.parse_response(resp, EdgeTranscodeWorkerSchema) + + def partial_update_edge_transcode_worker( + self, + worker_id: str, + worker_schema: Union[EdgeTranscodeWorkerSchema, Dict[str, Any]], + exclude_defaults: bool = True, + **kwargs, + ) -> Response: + """ + Update an edge transcode worker partially + + Args: + worker_id: ID of the edge transcode worker + worker_schema: Edge transcode worker parameters to update + (either as EdgeTranscodeWorkerSchema or dict) + exclude_defaults: Whether to exclude default values when dumping + **kwargs: Additional kwargs to pass to the request + + Returns: + Response(model=EdgeTranscodeWorkerSchema) + + Raises: + - 400 Bad request + - 401 Token is invalid + - 404 Edge transcode worker doesn't exist + """ + body = (worker_schema.model_dump(exclude_defaults=exclude_defaults, + exclude_unset=True) + if is_pydantic_model(worker_schema) else worker_schema) + url = self.gen_url(f"edge_transcode/workers/{worker_id}/") + resp = self._patch(url, json=body, **kwargs) + return self.parse_response(resp, EdgeTranscodeWorkerSchema) + + def generate_collection_keyframe( + self, + collection_id: str, + keyframe_schema: Union[GenerateCollectionKeyframeSchema, Dict[str, + Any]], + exclude_defaults: bool = True, + **kwargs, + ) -> Response: + """ + Start a job that creates a collection keyframe + + Args: + collection_id: ID of the collection + keyframe_schema: Keyframe generation parameters (either as + GenerateCollectionKeyframeSchema or dict) + exclude_defaults: Whether to exclude default values when dumping + **kwargs: Additional kwargs to pass to the request + + Returns: + Response(model=None) + + Raises: + - 400 Bad request + - 401 Token is invalid + """ + body = (keyframe_schema.model_dump(exclude_defaults=exclude_defaults) + if is_pydantic_model(keyframe_schema) else keyframe_schema) + url = self.gen_url(f"keyframes/collections/{collection_id}/") + resp = self._post(url, json=body, **kwargs) + return self.parse_response(resp, None) + + def abort_storage_transcode_jobs( + self, + storage_id: str, + abort_schema: Union[AbortStorageTranscodeJobsSchema, Dict[str, Any]], + exclude_defaults: bool = True, + **kwargs, + ) -> Response: + """ + Cancel all transcode jobs linked to the storage + + Args: + storage_id: ID of the storage + abort_schema: Abort parameters (either as + AbortStorageTranscodeJobsSchema or dict) + exclude_defaults: Whether to exclude default values when dumping + **kwargs: Additional kwargs to pass to the request + + Returns: + Response(model=None) + + Raises: + - 400 Bad request + - 401 Token is invalid + - 404 User does not exist + """ + body = (abort_schema.model_dump(exclude_defaults=exclude_defaults) + if is_pydantic_model(abort_schema) else abort_schema) + url = self.gen_url(f"storages/{storage_id}/") + resp = self._delete(url, json=body, **kwargs) + return self.parse_response(resp, None) + + def list_storage_edge_transcode_jobs( + self, + storage_id: str, + limit: int = 10, + **kwargs, + ) -> Response: + """ + Get edge transcode jobs from the job queue + + Args: + storage_id: ID of the storage + limit: The max number of items to fetch + **kwargs: Additional kwargs to pass to the request + + Returns: + Response(model=EdgeTranscodeJobsSchema) + + Raises: + - 400 Bad request + - 401 Token is invalid + """ + params = {"limit": limit} + url = self.gen_url(f"storages/{storage_id}/edge_transcode/jobs/") + resp = self._get(url, params=params, **kwargs) + return self.parse_response(resp, EdgeTranscodeJobsSchema) + + def delete_storage_file_transcode( + self, + storage_id: str, + file_id: str, + **kwargs, + ) -> Response: + """ + Delete local storage transcode job. + + Args: + storage_id: ID of the storage + file_id: ID of the file + **kwargs: Additional kwargs to pass to the request + + Returns: + Response(model=None) + + Raises: + - 404 Transcode job does not exist + """ + url = self.gen_url(f"storages/{storage_id}/files/{file_id}/transcode/") + resp = self._delete(url, **kwargs) + return self.parse_response(resp, None) + + def list_storage_transcode_jobs( + self, + storage_id: str, + per_page: int = 10, + last_id: Optional[str] = None, + **kwargs, + ) -> Response: + """ + Get pending local storage transcode jobs. + + Args: + storage_id: ID of the storage + per_page: The number of items for each page + last_id: ID of a last transcode job entity on previous page + **kwargs: Additional kwargs to pass to the request + + Returns: + Response(model=LocalStorageFileTranscodeJobsSchema) + """ + params: Dict[str, Any] = {"per_page": per_page} + if last_id is not None: + params["last_id"] = last_id + url = self.gen_url(f"storages/{storage_id}/transcode/") + resp = self._get(url, params=params, **kwargs) + return self.parse_response(resp, LocalStorageFileTranscodeJobsSchema) + + def get_storage_transcode_job( + self, + storage_id: str, + record_id: str, + **kwargs, + ) -> Response: + """ + Get local storage transcode job. + + Args: + storage_id: ID of the storage + record_id: ID of the transcode record + **kwargs: Additional kwargs to pass to the request + + Returns: + Response(model=LocalStorageFileTranscodeJobSchema) + + Raises: + - 404 Transcode job does not exist + """ + url = self.gen_url(f"storages/{storage_id}/transcode/{record_id}/") + resp = self._get(url, **kwargs) + return self.parse_response(resp, LocalStorageFileTranscodeJobSchema) + + def delete_storage_transcode_job( + self, + storage_id: str, + record_id: str, + **kwargs, + ) -> Response: + """ + Delete local storage transcode job. + + Args: + storage_id: ID of the storage + record_id: ID of the transcode record + **kwargs: Additional kwargs to pass to the request + + Returns: + Response(model=None) + + Raises: + - 404 Transcode job does not exist + """ + url = self.gen_url(f"storages/{storage_id}/transcode/{record_id}/") + resp = self._delete(url, **kwargs) + return self.parse_response(resp, None) + + def create_transcode( + self, + job_schema: Union[JobSchema, Dict[str, Any]], + exclude_defaults: bool = True, + **kwargs, + ) -> Response: + """ + Starts a new transcode. + + Args: + job_schema: Job parameters (either as JobSchema or dict) + exclude_defaults: Whether to exclude default values when dumping + **kwargs: Additional kwargs to pass to the request + + Returns: + Response(model=JobSchema) + + Raises: + - 400 Bad request + - 401 Token is invalid + """ + body = (job_schema.model_dump(exclude_defaults=exclude_defaults) + if is_pydantic_model(job_schema) else job_schema) + url = self.gen_url("transcode/") + resp = self._post(url, json=body, **kwargs) + return self.parse_response(resp, JobSchema) + + def list_transcode_queue( + self, + per_page: Optional[int] = None, + page: Optional[int] = None, + sort: Optional[str] = None, + **kwargs, + ) -> Response: + """ + Get all the statuses of the queued transcode jobs + + Args: + per_page: The number of items for each page + page: Which page number to fetch + sort: A comma separated list of fieldnames without spaces. + object_type,user_id,retry_count,priority,type,status + **kwargs: Additional kwargs to pass to the request + + Returns: + Response(model=TranscodeQueueSchema) + + Raises: + - 400 Bad request + - 401 Token is invalid + """ + params = {} + if per_page is not None: + params["per_page"] = per_page + if page is not None: + params["page"] = page + if sort is not None: + params["sort"] = sort + url = self.gen_url("transcode/queue/") + resp = self._get(url, params=params, **kwargs) + return self.parse_response(resp, TranscodeQueueSchema) + + def list_transcode_queue_system( + self, + per_domain_id: Optional[bool] = None, + per_page: Optional[int] = None, + page: Optional[int] = None, + sort: Optional[str] = None, + **kwargs, + ) -> Response: + """ + Get the status of the transcode job queues + + Args: + per_domain_id: Whether to group by domain ID + per_page: The number of items for each page + page: Which page number to fetch + sort: Sort order + **kwargs: Additional kwargs to pass to the request + + Returns: + Response(model=TranscodeQueueSchema) + + Raises: + - 400 Bad request + - 401 Token is invalid + """ + params = {} + if per_domain_id is not None: + params["per_domain_id"] = per_domain_id + if per_page is not None: + params["per_page"] = per_page + if page is not None: + params["page"] = page + if sort is not None: + params["sort"] = sort + url = self.gen_url("transcode/queue/system/") + resp = self._get(url, params=params, **kwargs) + return self.parse_response(resp, TranscodeQueueSchema) + + def list_transcode_object_queue_records( + self, + object_type: str, + object_id: str, + **kwargs, + ) -> Response: + """ + Returns list of transcode queue records by object_id + + Args: + object_type: Type of object + object_id: ID of the object + **kwargs: Additional kwargs to pass to the request + + Returns: + Response(model=TranscodeESQueueRecordsSchema) + + Raises: + - 400 Bad request - malformed parameters + """ + url = self.gen_url(f"transcode/{object_type}/{object_id}/") + resp = self._get(url, **kwargs) + return self.parse_response(resp, TranscodeESQueueRecordsSchema) + + def list_transcode_version_queue_records( + self, + object_type: str, + object_id: str, + version_id: str, + **kwargs, + ) -> Response: + """ + Returns list of transcode queue records by version_id + + Args: + object_type: Type of object + object_id: ID of the object + version_id: ID of the version + **kwargs: Additional kwargs to pass to the request + + Returns: + Response(model=TranscodeESQueueRecordsSchema) + + Raises: + - 400 Bad request - malformed parameters + """ + url = self.gen_url( + f"transcode/{object_type}/{object_id}/versions/{version_id}/") + resp = self._get(url, **kwargs) + return self.parse_response(resp, TranscodeESQueueRecordsSchema) + + def get_transcode_job( + self, + transcode_job_id: str, + **kwargs, + ) -> Response: + """ + Get transcode job + + Args: + transcode_job_id: ID of the transcode job + **kwargs: Additional kwargs to pass to the request + + Returns: + Response(model=JobSchema) + + Raises: + - 400 Bad request + - 401 Token is invalid + """ + url = self.gen_url(f"transcode/{transcode_job_id}/") + resp = self._get(url, **kwargs) + return self.parse_response(resp, JobSchema) + + def delete_transcode_job( + self, + transcode_job_id: str, + **kwargs, + ) -> Response: + """ + Cancel a particular transcode job by id + + Args: + transcode_job_id: ID of the transcode job + **kwargs: Additional kwargs to pass to the request + + Returns: + Response(model=None) + + Raises: + - 400 Bad request + - 401 Token is invalid + - 404 Transcode does not exist + """ + url = self.gen_url(f"transcode/{transcode_job_id}/") + resp = self._delete(url, **kwargs) + return self.parse_response(resp, None) + + def move_transcode_job_position( + self, + transcode_job_id: str, + position: Literal["top", "bottom"], + **kwargs, + ) -> Response: + """ + Move transcode job to top or bottom of the queue + + Args: + transcode_job_id: ID of the transcode job + position: Position in the queue ("top" or "bottom") + **kwargs: Additional kwargs to pass to the request + + Returns: + Response(model=None) + + Raises: + - 400 Bad request + - 401 Token is invalid + - 404 Transcode does not exist + """ + url = self.gen_url( + f"transcode/{transcode_job_id}/position/{position}/") + resp = self._post(url, **kwargs) + return self.parse_response(resp, None) + + def update_transcode_job_priority( + self, + transcode_job_id: str, + priority: int, + **kwargs, + ) -> Response: + """ + Change transcode job priority + + Args: + transcode_job_id: ID of the transcode job + priority: Priority level + **kwargs: Additional kwargs to pass to the request + + Returns: + Response(model=None) + + Raises: + - 400 Bad request + - 401 Token is invalid + - 404 Transcode does not exist + """ + url = self.gen_url( + f"transcode/{transcode_job_id}/priority/{priority}/") + resp = self._put(url, **kwargs) + return self.parse_response(resp, None) + + def transcribe_asset_default_profile( + self, + asset_id: str, + transcribe_schema: Union[TranscribeSchema, Dict[str, Any]], + exclude_defaults: bool = True, + **kwargs, + ) -> Response: + """ + Start a job that sends an asset to default transcription service + + Args: + asset_id: ID of the asset + transcribe_schema: Transcribe parameters (either as + TranscribeSchema or dict) + exclude_defaults: Whether to exclude default values when dumping + **kwargs: Additional kwargs to pass to the request + + Returns: + Response(model=None) + + Raises: + - 400 Bad request + - 401 Token is invalid + """ + body = (transcribe_schema.model_dump(exclude_defaults=exclude_defaults) + if is_pydantic_model(transcribe_schema) else transcribe_schema) + url = self.gen_url(f"transcribe/assets/{asset_id}/profiles/default/") + resp = self._post(url, json=body, **kwargs) + return self.parse_response(resp, None) + + def transcribe_bulk( + self, + bulk_transcribe_schema: Union[BulkTranscribeSchema, Dict[str, Any]], + exclude_defaults: bool = True, + **kwargs, + ) -> Response: + """ + Start a job that sends multiple objects to transcription service + + Args: + bulk_transcribe_schema: Bulk transcribe parameters (either as + BulkTranscribeSchema or dict) + exclude_defaults: Whether to exclude default values when dumping + **kwargs: Additional kwargs to pass to the request + + Returns: + Response(model=None) + + Raises: + - 400 Bad request + - 401 Token is invalid + """ + body = (bulk_transcribe_schema.model_dump( + exclude_defaults=exclude_defaults) + if is_pydantic_model(bulk_transcribe_schema) else + bulk_transcribe_schema) + url = self.gen_url("transcribe/bulk/") + resp = self._post(url, json=body, **kwargs) + return self.parse_response(resp, None) diff --git a/pythonik/tests/test_internal_utils.py b/pythonik/tests/test_internal_utils.py new file mode 100644 index 0000000..314c72d --- /dev/null +++ b/pythonik/tests/test_internal_utils.py @@ -0,0 +1,96 @@ +# pythonik/tests/test_internal_utils.py +import uuid + +from pydantic import BaseModel + +from pythonik.exceptions import PythonikException +from pythonik.specs._internal_utils import is_pydantic_model + + +class PydanticV1StyleModel: + """Mock class that mimics a Pydantic v1 model.""" + + __fields__ = {"test": "field"} + + def dict(self): + return {"test": "value"} + + +class PydanticV2StyleModel: + """Mock class that mimics a Pydantic v2 model.""" + + model_fields = {"test": "field"} + + def model_dump(self): + return {"test": "value"} + + +class RealPydanticModel(BaseModel): + """A real Pydantic model for testing.""" + + id: str + name: str + + +class NonPydanticClass: + """A regular class that is not a Pydantic model.""" + + def __init__(self): + self.value = "test" + + +def test_is_pydantic_model_with_real_model(): + """Test is_pydantic_model with a real Pydantic model.""" + model = RealPydanticModel(id=str(uuid.uuid4()), name="Test Model") + assert is_pydantic_model(model) is True + + +def test_is_pydantic_model_with_none(): + """Test is_pydantic_model with None.""" + assert is_pydantic_model(None) is False + + +def test_is_pydantic_model_with_non_model(): + """Test is_pydantic_model with a non-model class instance.""" + non_model = NonPydanticClass() + assert is_pydantic_model(non_model) is False + + +def test_is_pydantic_model_with_v1_style_mock(): + """Test is_pydantic_model with a class that looks like a Pydantic v1 model.""" + v1_model = PydanticV1StyleModel() + assert is_pydantic_model(v1_model) is True + + +def test_is_pydantic_model_with_v2_style_mock(): + """Test is_pydantic_model with a class that looks like a Pydantic v2 model.""" + v2_model = PydanticV2StyleModel() + assert is_pydantic_model(v2_model) is True + + +def test_is_pydantic_model_with_dict(): + """Test is_pydantic_model with a dictionary.""" + dict_data = {"id": str(uuid.uuid4()), "name": "Test Dict"} + assert is_pydantic_model(dict_data) is False + + +def test_is_pydantic_model_with_exception(): + """Test is_pydantic_model when an exception is raised.""" + + class ExceptionRaisingModel: + """A model that raises an exception when properties are accessed.""" + + @property + def dict(self): + raise PythonikException("Test exception") + + @property + def model_dump(self): + raise PythonikException("Test exception") + + @property + def __fields__(self): + raise PythonikException("Test exception") + + model = ExceptionRaisingModel() + assert is_pydantic_model(model) is False diff --git a/pythonik/tests/test_transcode.py b/pythonik/tests/test_transcode.py new file mode 100644 index 0000000..24b18b3 --- /dev/null +++ b/pythonik/tests/test_transcode.py @@ -0,0 +1,1013 @@ +# pythonik/tests/test_transcode.py +import uuid +from datetime import datetime +from typing import ( + Any, + Dict, +) + +import requests_mock + +from pythonik.client import PythonikClient +from pythonik.models.transcode import ( + AbortStorageTranscodeJobsSchema, + AnalyzeSchema, + AssetLinkData, + AssetLinkURLSchema, + BulkAnalyzeSchema, + BulkTranscribeSchema, + EdgeTranscodeJobsSchema, + EdgeTranscodeWorkerSchema, + EdgeTranscodeWorkersSchema, + GenerateCollectionKeyframeSchema, + JobSchema, + LocalStorageFileTranscodeJobSchema, + LocalStorageFileTranscodeJobsSchema, + SpecifiedKeyframes, + TranscodeESQueueRecordsSchema, + TranscodeQueueSchema, + TranscribeSchema, +) +from pythonik.specs.transcode import TranscodeSpec + + +def test_analyze_asset(): + """Test analyze_asset method.""" + with requests_mock.Mocker() as m: + app_id = str(uuid.uuid4()) + auth_token = str(uuid.uuid4()) + asset_id = str(uuid.uuid4()) + + analyze_schema = AnalyzeSchema(force=True) + mock_address = TranscodeSpec.gen_url(f"analyze/assets/{asset_id}/") + m.post(mock_address, status_code=204) + + client = PythonikClient(app_id=app_id, + auth_token=auth_token, + timeout=3) + + response = client.transcode().analyze_asset(asset_id, analyze_schema) + + assert response.response.ok + assert response.response.status_code == 204 + assert m.last_request.url == mock_address + assert m.last_request.json() == {"force": True} + + +def test_analyze_asset_with_dict(): + """Test analyze_asset method with a dictionary input.""" + with requests_mock.Mocker() as m: + app_id = str(uuid.uuid4()) + auth_token = str(uuid.uuid4()) + asset_id = str(uuid.uuid4()) + + analyze_schema = {"force": True, "force_type": "OVERWRITE"} + mock_address = TranscodeSpec.gen_url(f"analyze/assets/{asset_id}/") + m.post(mock_address, status_code=204) + + client = PythonikClient(app_id=app_id, + auth_token=auth_token, + timeout=3) + + response = client.transcode().analyze_asset(asset_id, analyze_schema) + + assert response.response.ok + assert response.response.status_code == 204 + assert m.last_request.url == mock_address + assert m.last_request.json() == analyze_schema + + +def test_analyze_asset_default_profile(): + """Test analyze_asset_default_profile method.""" + with requests_mock.Mocker() as m: + app_id = str(uuid.uuid4()) + auth_token = str(uuid.uuid4()) + asset_id = str(uuid.uuid4()) + + analyze_schema = AnalyzeSchema(force=True) + mock_address = TranscodeSpec.gen_url( + f"analyze/assets/{asset_id}/profiles/default/") + m.post(mock_address, status_code=204) + + client = PythonikClient(app_id=app_id, + auth_token=auth_token, + timeout=3) + + response = client.transcode().analyze_asset_default_profile( + asset_id, analyze_schema) + + assert response.response.ok + assert response.response.status_code == 204 + assert m.last_request.url == mock_address + assert m.last_request.json() == {"force": True} + + +def test_analyze_asset_default_profile_media_type(): + """Test analyze_asset_default_profile_media_type method.""" + with requests_mock.Mocker() as m: + app_id = str(uuid.uuid4()) + auth_token = str(uuid.uuid4()) + asset_id = str(uuid.uuid4()) + media_type = "video" + + analyze_schema = AnalyzeSchema(force=True) + mock_address = TranscodeSpec.gen_url( + f"analyze/assets/{asset_id}/profiles/default/{media_type}/") + m.post(mock_address, status_code=204) + + client = PythonikClient(app_id=app_id, + auth_token=auth_token, + timeout=3) + + response = client.transcode().analyze_asset_default_profile_media_type( + asset_id, media_type, analyze_schema) + + assert response.response.ok + assert response.response.status_code == 204 + assert m.last_request.url == mock_address + assert m.last_request.json() == {"force": True} + + +def test_analyze_asset_custom_profile(): + """Test analyze_asset_custom_profile method.""" + with requests_mock.Mocker() as m: + app_id = str(uuid.uuid4()) + auth_token = str(uuid.uuid4()) + asset_id = str(uuid.uuid4()) + profile_id = str(uuid.uuid4()) + + analyze_schema = AnalyzeSchema(force=True) + mock_address = TranscodeSpec.gen_url( + f"analyze/assets/{asset_id}/profiles/{profile_id}/") + m.post(mock_address, status_code=204) + + client = PythonikClient(app_id=app_id, + auth_token=auth_token, + timeout=3) + + response = client.transcode().analyze_asset_custom_profile( + asset_id, profile_id, analyze_schema) + + assert response.response.ok + assert response.response.status_code == 204 + assert m.last_request.url == mock_address + assert m.last_request.json() == {"force": True} + + +def test_analyze_bulk(): + """Test analyze_bulk method.""" + with requests_mock.Mocker() as m: + app_id = str(uuid.uuid4()) + auth_token = str(uuid.uuid4()) + object_id = str(uuid.uuid4()) + + analyze_schema = BulkAnalyzeSchema(force=True, + object_ids=[object_id], + object_type="assets") + mock_address = TranscodeSpec.gen_url("analyze/bulk/") + m.post(mock_address, status_code=204) + + client = PythonikClient(app_id=app_id, + auth_token=auth_token, + timeout=3) + + response = client.transcode().analyze_bulk(analyze_schema) + + assert response.response.ok + assert response.response.status_code == 204 + assert m.last_request.url == mock_address + assert m.last_request.json()["force"] is True + assert m.last_request.json()["object_type"] == "assets" + assert m.last_request.json()["object_ids"] == [object_id] + + +def test_get_asset_link_metadata(): + """Test get_asset_link_metadata method.""" + with requests_mock.Mocker() as m: + app_id = str(uuid.uuid4()) + auth_token = str(uuid.uuid4()) + + url_schema = AssetLinkURLSchema(url="https://example.com/video.mp4") + response_data = {"site_name": "Example Site", "title": "Sample Video"} + mock_address = TranscodeSpec.gen_url("assets/link/metadata/") + m.post(mock_address, json=response_data) + + client = PythonikClient(app_id=app_id, + auth_token=auth_token, + timeout=3) + + response = client.transcode().get_asset_link_metadata(url_schema) + + assert response.response.ok + assert m.last_request.url == mock_address + assert m.last_request.json()["url"] == "https://example.com/video.mp4" + assert isinstance(response.data, AssetLinkData) + assert response.data.site_name == "Example Site" + assert response.data.title == "Sample Video" + + +def test_acknowledge_edge_transcode_job(): + """Test acknowledge_edge_transcode_job method.""" + with requests_mock.Mocker() as m: + app_id = str(uuid.uuid4()) + auth_token = str(uuid.uuid4()) + job_id = str(uuid.uuid4()) + + mock_address = TranscodeSpec.gen_url( + f"edge_transcode/jobs/{job_id}/acknowledge/") + m.post(mock_address, status_code=204) + + client = PythonikClient(app_id=app_id, + auth_token=auth_token, + timeout=3) + response = client.transcode().acknowledge_edge_transcode_job(job_id) + + assert response.response.ok + assert response.response.status_code == 204 + assert m.last_request.url == mock_address + + +def test_fetch_edge_transcode_workers(): + """Test fetch_edge_transcode_workers method.""" + with requests_mock.Mocker() as m: + app_id = str(uuid.uuid4()) + auth_token = str(uuid.uuid4()) + worker_id = str(uuid.uuid4()) + storage_id = str(uuid.uuid4()) + + response_data = { + "objects": [{ + "id": worker_id, + "status": "ACTIVE", + "storage_id": storage_id, + "last_update_date": datetime.now().isoformat(), + }] + } + mock_address = TranscodeSpec.gen_url("edge_transcode/workers/") + m.get(mock_address, json=response_data) + + client = PythonikClient(app_id=app_id, + auth_token=auth_token, + timeout=3) + response = client.transcode().list_edge_transcode_workers() + + assert response.response.ok + assert m.last_request.url == mock_address + assert isinstance(response.data, EdgeTranscodeWorkersSchema) + assert len(response.data.objects) == 1 + assert str(response.data.objects[0].id) == worker_id + assert response.data.objects[0].status == "ACTIVE" + assert str(response.data.objects[0].storage_id) == storage_id + + +def test_create_edge_transcode_worker(): + """Test create_edge_transcode_worker method.""" + with requests_mock.Mocker() as m: + app_id = str(uuid.uuid4()) + auth_token = str(uuid.uuid4()) + worker_id = str(uuid.uuid4()) + storage_id = str(uuid.uuid4()) + + worker_schema = EdgeTranscodeWorkerSchema(status="ACTIVE", + storage_id=storage_id) + response_data = { + "id": worker_id, + "status": "ACTIVE", + "storage_id": storage_id, + "last_update_date": datetime.now().isoformat(), + } + mock_address = TranscodeSpec.gen_url("edge_transcode/workers/") + m.post(mock_address, json=response_data) + + client = PythonikClient(app_id=app_id, + auth_token=auth_token, + timeout=3) + + response = client.transcode().create_edge_transcode_worker( + worker_schema) + + assert response.response.ok + assert m.last_request.url == mock_address + assert m.last_request.json()["status"] == "ACTIVE" + assert m.last_request.json()["storage_id"] == storage_id + assert isinstance(response.data, EdgeTranscodeWorkerSchema) + assert str(response.data.id) == worker_id + assert response.data.status == "ACTIVE" + assert str(response.data.storage_id) == storage_id + + +def test_get_edge_transcode_worker(): + """Test get_edge_transcode_worker method.""" + with requests_mock.Mocker() as m: + app_id = str(uuid.uuid4()) + auth_token = str(uuid.uuid4()) + worker_id = str(uuid.uuid4()) + storage_id = str(uuid.uuid4()) + + response_data = { + "id": worker_id, + "status": "ACTIVE", + "storage_id": storage_id, + "last_update_date": datetime.now().isoformat(), + } + mock_address = TranscodeSpec.gen_url( + f"edge_transcode/workers/{worker_id}/") + m.get(mock_address, json=response_data) + + client = PythonikClient(app_id=app_id, + auth_token=auth_token, + timeout=3) + response = client.transcode().get_edge_transcode_worker(worker_id) + + assert response.response.ok + assert m.last_request.url == mock_address + assert isinstance(response.data, EdgeTranscodeWorkerSchema) + assert str(response.data.id) == worker_id + assert response.data.status == "ACTIVE" + assert str(response.data.storage_id) == storage_id + + +def test_delete_edge_transcode_worker(): + """Test delete_edge_transcode_worker method.""" + with requests_mock.Mocker() as m: + app_id = str(uuid.uuid4()) + auth_token = str(uuid.uuid4()) + worker_id = str(uuid.uuid4()) + + mock_address = TranscodeSpec.gen_url( + f"edge_transcode/workers/{worker_id}/") + m.delete(mock_address, status_code=204) + + client = PythonikClient(app_id=app_id, + auth_token=auth_token, + timeout=3) + response = client.transcode().delete_edge_transcode_worker(worker_id) + + assert response.response.ok + assert response.response.status_code == 204 + assert m.last_request.url == mock_address + + +def test_update_edge_transcode_worker(): + """Test update_edge_transcode_worker method.""" + with requests_mock.Mocker() as m: + app_id = str(uuid.uuid4()) + auth_token = str(uuid.uuid4()) + worker_id = str(uuid.uuid4()) + storage_id = str(uuid.uuid4()) + + worker_schema = EdgeTranscodeWorkerSchema(status="ACTIVE", + storage_id=storage_id) + response_data = { + "id": worker_id, + "status": "ACTIVE", + "storage_id": storage_id, + "last_update_date": datetime.now().isoformat(), + } + mock_address = TranscodeSpec.gen_url( + f"edge_transcode/workers/{worker_id}/") + m.put(mock_address, json=response_data) + + client = PythonikClient(app_id=app_id, + auth_token=auth_token, + timeout=3) + + response = client.transcode().update_edge_transcode_worker( + worker_id, worker_schema) + + assert response.response.ok + assert m.last_request.url == mock_address + assert m.last_request.json()["status"] == "ACTIVE" + assert m.last_request.json()["storage_id"] == storage_id + assert isinstance(response.data, EdgeTranscodeWorkerSchema) + assert str(response.data.id) == worker_id + assert response.data.status == "ACTIVE" + assert str(response.data.storage_id) == storage_id + + +def test_partial_update_edge_transcode_worker(): + """Test partial_update_edge_transcode_worker method.""" + with requests_mock.Mocker() as m: + app_id = str(uuid.uuid4()) + auth_token = str(uuid.uuid4()) + worker_id = str(uuid.uuid4()) + storage_id = str(uuid.uuid4()) + + worker_schema = {"status": "INACTIVE"} + response_data = { + "id": worker_id, + "status": "INACTIVE", + "storage_id": storage_id, + "last_update_date": datetime.now().isoformat(), + } + mock_address = TranscodeSpec.gen_url( + f"edge_transcode/workers/{worker_id}/") + m.patch(mock_address, json=response_data) + + client = PythonikClient(app_id=app_id, + auth_token=auth_token, + timeout=3) + + response = client.transcode().partial_update_edge_transcode_worker( + worker_id, worker_schema) + + assert response.response.ok + assert m.last_request.url == mock_address + assert m.last_request.json()["status"] == "INACTIVE" + assert isinstance(response.data, EdgeTranscodeWorkerSchema) + assert str(response.data.id) == worker_id + assert response.data.status == "INACTIVE" + assert str(response.data.storage_id) == storage_id + + +def test_generate_collection_keyframe(): + """Test generate_collection_keyframe method.""" + with requests_mock.Mocker() as m: + app_id = str(uuid.uuid4()) + auth_token = str(uuid.uuid4()) + collection_id = str(uuid.uuid4()) + asset_id = str(uuid.uuid4()) + + keyframe_schema = GenerateCollectionKeyframeSchema( + force=True, + specified_asset_ids=[asset_id], + specified_keyframes=[ + SpecifiedKeyframes(url="https://example.com/image.jpg") + ], + ) + mock_address = TranscodeSpec.gen_url( + f"keyframes/collections/{collection_id}/") + m.post(mock_address, status_code=204) + + client = PythonikClient(app_id=app_id, + auth_token=auth_token, + timeout=3) + + response = client.transcode().generate_collection_keyframe( + collection_id, keyframe_schema) + + assert response.response.ok + assert response.response.status_code == 204 + assert m.last_request.url == mock_address + request_json = m.last_request.json() + assert request_json["force"] is True + assert request_json["specified_asset_ids"] == [asset_id] + assert (request_json["specified_keyframes"][0]["url"] == + "https://example.com/image.jpg") + + +def test_abort_storage_transcode_jobs(): + """Test abort_storage_transcode_jobs method.""" + with requests_mock.Mocker() as m: + app_id = str(uuid.uuid4()) + auth_token = str(uuid.uuid4()) + storage_id = str(uuid.uuid4()) + + abort_schema = AbortStorageTranscodeJobsSchema( + error_message="Test abort all jobs") + mock_address = TranscodeSpec.gen_url(f"storages/{storage_id}/") + m.delete(mock_address, status_code=204) + + client = PythonikClient(app_id=app_id, + auth_token=auth_token, + timeout=3) + + response = client.transcode().abort_storage_transcode_jobs( + storage_id, abort_schema) + + assert response.response.ok + assert response.response.status_code == 204 + assert m.last_request.url == mock_address + assert m.last_request.json()["error_message"] == "Test abort all jobs" + + +def test_fetch_storage_edge_transcode_jobs(): + """Test fetch_storage_edge_transcode_jobs method.""" + with requests_mock.Mocker() as m: + app_id = str(uuid.uuid4()) + auth_token = str(uuid.uuid4()) + storage_id = str(uuid.uuid4()) + job_id = str(uuid.uuid4()) + asset_id = str(uuid.uuid4()) + collection_id = str(uuid.uuid4()) + + # Create a complete response that meets EdgeTranscodeJobSchema requirements + response_data = { + "objects": [{ + "job_id": job_id, + "asset_id": asset_id, + "collection_id": collection_id, + "input": { + "asset_id": asset_id, + "endpoint": { + "url": "https://example.com/file.mp4", + "type": "http", + }, + }, + # Add any other required fields + "job_steps": [], + }] + } + mock_address = TranscodeSpec.gen_url( + f"storages/{storage_id}/edge_transcode/jobs/") + m.get(mock_address, json=response_data) + + client = PythonikClient(app_id=app_id, + auth_token=auth_token, + timeout=3) + response = client.transcode().list_storage_edge_transcode_jobs( + storage_id, limit=5) + + assert response.response.ok + assert m.last_request.url.startswith(mock_address) + assert "limit=5" in m.last_request.url + assert isinstance(response.data, EdgeTranscodeJobsSchema) + assert len(response.data.objects) == 1 + assert response.data.objects[0].job_id == job_id + + +def test_delete_storage_file_transcode(): + """Test delete_storage_file_transcode method.""" + with requests_mock.Mocker() as m: + app_id = str(uuid.uuid4()) + auth_token = str(uuid.uuid4()) + storage_id = str(uuid.uuid4()) + file_id = str(uuid.uuid4()) + + mock_address = TranscodeSpec.gen_url( + f"storages/{storage_id}/files/{file_id}/transcode/") + m.delete(mock_address, status_code=204) + + client = PythonikClient(app_id=app_id, + auth_token=auth_token, + timeout=3) + response = client.transcode().delete_storage_file_transcode( + storage_id, file_id) + + assert response.response.ok + assert response.response.status_code == 204 + assert m.last_request.url == mock_address + + +def test_fetch_storage_transcode_jobs(): + """Test fetch_storage_transcode_jobs method.""" + with requests_mock.Mocker() as m: + app_id = str(uuid.uuid4()) + auth_token = str(uuid.uuid4()) + storage_id = str(uuid.uuid4()) + job_id = str(uuid.uuid4()) + + response_data = { + "objects": [{ + "id": str(uuid.uuid4()), + "job_id": job_id, + "asset_id": str(uuid.uuid4()), + "file_id": str(uuid.uuid4()), + "file_set_id": str(uuid.uuid4()), + "format_id": str(uuid.uuid4()), + "version_id": str(uuid.uuid4()), + "filename": "test.mp4", + "directory_path": "/path/to/file", + "size": 1024, + }], + "per_page": + 10, + "page": + 1, + "total": + 1, + } + mock_address = TranscodeSpec.gen_url( + f"storages/{storage_id}/transcode/") + m.get(mock_address, json=response_data) + + client = PythonikClient(app_id=app_id, + auth_token=auth_token, + timeout=3) + response = client.transcode().list_storage_transcode_jobs( + storage_id, per_page=10, last_id="last-job-id") + + assert response.response.ok + assert m.last_request.url.startswith(mock_address) + assert "per_page=10" in m.last_request.url + assert "last_id=last-job-id" in m.last_request.url + assert isinstance(response.data, LocalStorageFileTranscodeJobsSchema) + assert len(response.data.objects) == 1 + assert response.data.objects[0].job_id == job_id + + +def test_get_storage_transcode_job(): + """Test get_storage_transcode_job method.""" + with requests_mock.Mocker() as m: + app_id = str(uuid.uuid4()) + auth_token = str(uuid.uuid4()) + storage_id = str(uuid.uuid4()) + record_id = str(uuid.uuid4()) + job_id = str(uuid.uuid4()) + + response_data = { + "id": record_id, + "job_id": job_id, + "asset_id": str(uuid.uuid4()), + "file_id": str(uuid.uuid4()), + "file_set_id": str(uuid.uuid4()), + "format_id": str(uuid.uuid4()), + "version_id": str(uuid.uuid4()), + "filename": "test.mp4", + "directory_path": "/path/to/file", + "size": 1024, + } + mock_address = TranscodeSpec.gen_url( + f"storages/{storage_id}/transcode/{record_id}/") + m.get(mock_address, json=response_data) + + client = PythonikClient(app_id=app_id, + auth_token=auth_token, + timeout=3) + response = client.transcode().get_storage_transcode_job( + storage_id, record_id) + + assert response.response.ok + assert m.last_request.url == mock_address + assert isinstance(response.data, LocalStorageFileTranscodeJobSchema) + assert response.data.id == record_id + assert response.data.job_id == job_id + + +def test_delete_storage_transcode_job(): + """Test delete_storage_transcode_job method.""" + with requests_mock.Mocker() as m: + app_id = str(uuid.uuid4()) + auth_token = str(uuid.uuid4()) + storage_id = str(uuid.uuid4()) + record_id = str(uuid.uuid4()) + + mock_address = TranscodeSpec.gen_url( + f"storages/{storage_id}/transcode/{record_id}/") + m.delete(mock_address, status_code=204) + + client = PythonikClient(app_id=app_id, + auth_token=auth_token, + timeout=3) + response = client.transcode().delete_storage_transcode_job( + storage_id, record_id) + + assert response.response.ok + assert response.response.status_code == 204 + assert m.last_request.url == mock_address + + +def test_create_transcode(): + """Test create_transcode method.""" + with requests_mock.Mocker() as m: + app_id = str(uuid.uuid4()) + auth_token = str(uuid.uuid4()) + job_id = str(uuid.uuid4()) + asset_id = str(uuid.uuid4()) + + # Creating a simplified job schema - a real one would be more complex + job_schema: Dict[str, Any] = { + "asset_id": asset_id, + "priority": 5, + "input": { + "file_id": str(uuid.uuid4()), + "endpoint": { + "url": "https://example.com/file.mp4", + "type": "http", + }, + }, + } + + response_data = {"job_id": job_id, "asset_id": asset_id, "priority": 5} + mock_address = TranscodeSpec.gen_url("transcode/") + m.post(mock_address, json=response_data) + + client = PythonikClient(app_id=app_id, + auth_token=auth_token, + timeout=3) + + response = client.transcode().create_transcode(job_schema) + + assert response.response.ok + assert m.last_request.url == mock_address + assert m.last_request.json()["asset_id"] == asset_id + assert m.last_request.json()["priority"] == 5 + assert isinstance(response.data, JobSchema) + assert response.data.asset_id == asset_id + assert response.data.priority == 5 + + +def test_fetch_transcode_queue(): + """Test fetch_transcode_queue method.""" + with requests_mock.Mocker() as m: + app_id = str(uuid.uuid4()) + auth_token = str(uuid.uuid4()) + + response_data = { + "objects": [{ + "id": str(uuid.uuid4()), + "status": "READY", + "priority": 5, + "type": "TRANSCODE", + }], + "per_page": + 10, + "page": + 1, + "total": + 1, + } + mock_address = TranscodeSpec.gen_url("transcode/queue/") + m.get(mock_address, json=response_data) + + client = PythonikClient(app_id=app_id, + auth_token=auth_token, + timeout=3) + response = client.transcode().list_transcode_queue(per_page=10, + page=1, + sort="priority") + + assert response.response.ok + assert m.last_request.url.startswith(mock_address) + assert "per_page=10" in m.last_request.url + assert "page=1" in m.last_request.url + assert "sort=priority" in m.last_request.url + assert isinstance(response.data, TranscodeQueueSchema) + assert len(response.data.objects) == 1 + assert response.data.objects[0].status == "READY" + assert response.data.objects[0].priority == 5 + + +def test_fetch_transcode_queue_system(): + """Test fetch_transcode_queue_system method.""" + with requests_mock.Mocker() as m: + app_id = str(uuid.uuid4()) + auth_token = str(uuid.uuid4()) + + response_data = { + "objects": [{ + "id": str(uuid.uuid4()), + "status": "READY", + "priority": 5, + "type": "TRANSCODE", + "system_domain": "default", + }], + "per_page": + 10, + "page": + 1, + "total": + 1, + } + mock_address = TranscodeSpec.gen_url("transcode/queue/system/") + m.get(mock_address, json=response_data) + + client = PythonikClient(app_id=app_id, + auth_token=auth_token, + timeout=3) + response = client.transcode().list_transcode_queue_system( + per_domain_id=True, per_page=10, page=1, sort="priority") + + assert response.response.ok + assert m.last_request.url.startswith(mock_address) + assert "per_domain_id=true" in m.last_request.url.lower() + assert "per_page=10" in m.last_request.url + assert "page=1" in m.last_request.url + assert "sort=priority" in m.last_request.url + assert isinstance(response.data, TranscodeQueueSchema) + assert len(response.data.objects) == 1 + assert response.data.objects[0].status == "READY" + assert response.data.objects[0].priority == 5 + assert response.data.objects[0].system_domain == "default" + + +def test_fetch_transcode_object_queue_records(): + """Test fetch_transcode_object_queue_records method.""" + with requests_mock.Mocker() as m: + app_id = str(uuid.uuid4()) + auth_token = str(uuid.uuid4()) + object_id = str(uuid.uuid4()) + object_type = "assets" + + response_data = { + "objects": [{ + "id": str(uuid.uuid4()), + "object_id": object_id, + "object_type": object_type, + "status": "READY", + }] + } + mock_address = TranscodeSpec.gen_url( + f"transcode/{object_type}/{object_id}/") + m.get(mock_address, json=response_data) + + client = PythonikClient(app_id=app_id, + auth_token=auth_token, + timeout=3) + response = client.transcode().list_transcode_object_queue_records( + object_type, object_id) + + assert response.response.ok + assert m.last_request.url == mock_address + assert isinstance(response.data, TranscodeESQueueRecordsSchema) + assert len(response.data.objects) == 1 + assert response.data.objects[0].object_id == object_id + assert response.data.objects[0].object_type == object_type + assert response.data.objects[0].status == "READY" + + +def test_fetch_transcode_version_queue_records(): + """Test fetch_transcode_version_queue_records method.""" + with requests_mock.Mocker() as m: + app_id = str(uuid.uuid4()) + auth_token = str(uuid.uuid4()) + object_id = str(uuid.uuid4()) + object_type = "assets" + version_id = str(uuid.uuid4()) + + response_data = { + "objects": [{ + "id": str(uuid.uuid4()), + "object_id": object_id, + "object_type": object_type, + "version_id": version_id, + "status": "READY", + }] + } + mock_address = TranscodeSpec.gen_url( + f"transcode/{object_type}/{object_id}/versions/{version_id}/") + m.get(mock_address, json=response_data) + + client = PythonikClient(app_id=app_id, + auth_token=auth_token, + timeout=3) + response = client.transcode().list_transcode_version_queue_records( + object_type, object_id, version_id) + + assert response.response.ok + assert m.last_request.url == mock_address + assert isinstance(response.data, TranscodeESQueueRecordsSchema) + assert len(response.data.objects) == 1 + assert response.data.objects[0].object_id == object_id + assert response.data.objects[0].object_type == object_type + assert response.data.objects[0].version_id == version_id + assert response.data.objects[0].status == "READY" + + +def test_get_transcode_job(): + """Test get_transcode_job method.""" + with requests_mock.Mocker() as m: + app_id = str(uuid.uuid4()) + auth_token = str(uuid.uuid4()) + job_id = str(uuid.uuid4()) + asset_id = str(uuid.uuid4()) + + response_data = { + "job_id": job_id, + "asset_id": asset_id, + "priority": 5, + "status": "READY", + } + mock_address = TranscodeSpec.gen_url(f"transcode/{job_id}/") + m.get(mock_address, json=response_data) + + client = PythonikClient(app_id=app_id, + auth_token=auth_token, + timeout=3) + response = client.transcode().get_transcode_job(job_id) + + assert response.response.ok + assert m.last_request.url == mock_address + assert isinstance(response.data, JobSchema) + assert response.data.job_id == job_id + assert response.data.asset_id == asset_id + assert response.data.priority == 5 + + +def test_delete_transcode_job(): + """Test delete_transcode_job method.""" + with requests_mock.Mocker() as m: + app_id = str(uuid.uuid4()) + auth_token = str(uuid.uuid4()) + job_id = str(uuid.uuid4()) + + mock_address = TranscodeSpec.gen_url(f"transcode/{job_id}/") + m.delete(mock_address, status_code=204) + + client = PythonikClient(app_id=app_id, + auth_token=auth_token, + timeout=3) + response = client.transcode().delete_transcode_job(job_id) + + assert response.response.ok + assert response.response.status_code == 204 + assert m.last_request.url == mock_address + + +def test_move_transcode_job_position(): + """Test move_transcode_job_position method.""" + with requests_mock.Mocker() as m: + app_id = str(uuid.uuid4()) + auth_token = str(uuid.uuid4()) + job_id = str(uuid.uuid4()) + + mock_address = TranscodeSpec.gen_url( + f"transcode/{job_id}/position/top/") + m.post(mock_address, status_code=204) + + client = PythonikClient(app_id=app_id, + auth_token=auth_token, + timeout=3) + response = client.transcode().move_transcode_job_position( + job_id, "top") + + assert response.response.ok + assert response.response.status_code == 204 + assert m.last_request.url == mock_address + + +def test_update_transcode_job_priority(): + """Test update_transcode_job_priority method.""" + with requests_mock.Mocker() as m: + app_id = str(uuid.uuid4()) + auth_token = str(uuid.uuid4()) + job_id = str(uuid.uuid4()) + priority = 8 + + mock_address = TranscodeSpec.gen_url( + f"transcode/{job_id}/priority/{priority}/") + m.put(mock_address, status_code=204) + + client = PythonikClient(app_id=app_id, + auth_token=auth_token, + timeout=3) + response = client.transcode().update_transcode_job_priority( + job_id, priority) + + assert response.response.ok + assert response.response.status_code == 204 + assert m.last_request.url == mock_address + + +def test_transcribe_asset_default_profile(): + """Test transcribe_asset_default_profile method.""" + with requests_mock.Mocker() as m: + app_id = str(uuid.uuid4()) + auth_token = str(uuid.uuid4()) + asset_id = str(uuid.uuid4()) + + transcribe_schema = TranscribeSchema(language="en", + speakers=2, + force=True) + mock_address = TranscodeSpec.gen_url( + f"transcribe/assets/{asset_id}/profiles/default/") + m.post(mock_address, status_code=204) + + client = PythonikClient(app_id=app_id, + auth_token=auth_token, + timeout=3) + + response = client.transcode().transcribe_asset_default_profile( + asset_id, transcribe_schema) + + assert response.response.ok + assert response.response.status_code == 204 + assert m.last_request.url == mock_address + request_json = m.last_request.json() + assert request_json["language"] == "en" + assert request_json["speakers"] == 2 + assert request_json["force"] is True + + +def test_transcribe_bulk(): + """Test transcribe_bulk method.""" + with requests_mock.Mocker() as m: + app_id = str(uuid.uuid4()) + auth_token = str(uuid.uuid4()) + object_id = str(uuid.uuid4()) + + transcribe_schema = BulkTranscribeSchema( + object_ids=[object_id], + object_type="assets", + language="en", + speakers=2, + force=True, + ) + mock_address = TranscodeSpec.gen_url("transcribe/bulk/") + m.post(mock_address, status_code=204) + + client = PythonikClient(app_id=app_id, + auth_token=auth_token, + timeout=3) + + response = client.transcode().transcribe_bulk(transcribe_schema) + + assert response.response.ok + assert response.response.status_code == 204 + assert m.last_request.url == mock_address + request_json = m.last_request.json() + assert request_json["object_type"] == "assets" + assert request_json["object_ids"] == [object_id] + assert request_json["language"] == "en" + assert request_json["speakers"] == 2 + assert request_json["force"] is True