From 1d0273f6aae76ff1610a6f20451a8d259bb3bbb7 Mon Sep 17 00:00:00 2001 From: Brian Summa Date: Tue, 20 May 2025 08:42:19 -0500 Subject: [PATCH 1/4] Initial commit --- pythonik/models/transcode.py | 699 ++++++++++++++++++++++++++ pythonik/specs/transcode.py | 928 +++++++++++++++++++++++++++++++++++ 2 files changed, 1627 insertions(+) create mode 100644 pythonik/models/transcode.py create mode 100644 pythonik/specs/transcode.py diff --git a/pythonik/models/transcode.py b/pythonik/models/transcode.py new file mode 100644 index 0000000..ebdbcc6 --- /dev/null +++ b/pythonik/models/transcode.py @@ -0,0 +1,699 @@ +""" +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 +from uuid import UUID + +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[UUID] = 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[UUID] = None + job_id: Optional[UUID] = None + media_info: Optional[str] = None + object_id: Optional[UUID] = 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[UUID] = None + system_name: Optional[str] = None + type: Optional[str] = None + user_id: Optional[UUID] = None + version_id: Optional[UUID] = None + + +class TranscodeQueueObjectSchema(BaseModel): + """Represents a TranscodeQueueObjectSchema in the Iconik system.""" + + date_created: Optional[datetime] = None + date_updated: Optional[datetime] = None + id: Optional[UUID] = None + job_id: Optional[UUID] = 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[UUID] = None + system_domain_timestamp: Optional[float] = None + system_name: Optional[str] = None + type: Optional[str] = None + user_id: Optional[UUID] = 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[UUID] + + +class JobsPrioritySchema(BaseModel): + """Represents a JobsPrioritySchema in the Iconik system.""" + + job_ids: List[UUID] + 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[UUID] = None + force: Optional[bool] = None + specified_asset_ids: Optional[List[UUID]] = 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[UUID] = None + last_update_date: Optional[datetime] = None + status: Literal["ACTIVE", "INACTIVE"] + storage_id: UUID + + +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[UUID] + object_type: Literal["assets", "collections", "saved_searches"] + profile_id: Optional[UUID] = 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[UUID] + object_type: Literal["assets", "collections", "saved_searches"] + profile_id: Optional[UUID] = None + + +class BulkActionSchema(BaseModel): + """Represents a BulkActionSchema in the Iconik system.""" + + object_ids: List[UUID] + object_type: Literal["assets", "collections", "saved_searches"] + + +class AssetLinkURLSchema(BaseModel): + """Represents a AssetLinkURLSchema in the Iconik system.""" + + url: 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/transcode.py b/pythonik/specs/transcode.py new file mode 100644 index 0000000..b399f20 --- /dev/null +++ b/pythonik/specs/transcode.py @@ -0,0 +1,928 @@ +from typing import Any, Dict, Literal, Optional, Union + +from pythonik.models.base import Response + +from aiopythonik import is_pydantic_model +from aiopythonik._pythonik_patches import Spec + +from .._typing import ObjectID +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: ObjectID, + 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: ObjectID, + 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: ObjectID, + 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: ObjectID, + profile_id: ObjectID, + 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: ObjectID, + **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 fetch_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: ObjectID, + **kwargs, + ) -> Response: + """ + Get a 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: ObjectID, + **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: ObjectID, + worker_schema: Union[EdgeTranscodeWorkerSchema, Dict[str, Any]], + exclude_defaults: bool = True, + **kwargs, + ) -> Response: + """ + Update a 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: ObjectID, + worker_schema: Union[EdgeTranscodeWorkerSchema, Dict[str, Any]], + exclude_defaults: bool = True, + **kwargs, + ) -> Response: + """ + Update a 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: ObjectID, + 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: ObjectID, + 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 fetch_storage_edge_transcode_jobs( + self, + storage_id: ObjectID, + 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: ObjectID, + file_id: ObjectID, + **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 fetch_storage_transcode_jobs( + self, + storage_id: ObjectID, + 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: ObjectID, + record_id: ObjectID, + **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: ObjectID, + record_id: ObjectID, + **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 fetch_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 fetch_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 fetch_transcode_object_queue_records( + self, + object_type: str, + object_id: ObjectID, + **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 fetch_transcode_version_queue_records( + self, + object_type: str, + object_id: ObjectID, + version_id: ObjectID, + **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: ObjectID, + **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: ObjectID, + **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: ObjectID, + 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: ObjectID, + 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: ObjectID, + 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) From b28fe3e6f13111ae315ef8f118cbedc375e85554 Mon Sep 17 00:00:00 2001 From: Brian Summa Date: Tue, 20 May 2025 11:31:48 -0500 Subject: [PATCH 2/4] feat(transcode): implement TranscodeSpec API integration Add comprehensive transcode functionality with models and API endpoints for: - Asset transcoding and analysis - Media file processing - Edge transcoding - Keyframe generation - Queue management - Transcription services --- pythonik/client.py | 4 + pythonik/models/transcode.py | 198 ++--- pythonik/specs/_internal_utils.py | 31 + pythonik/specs/transcode.py | 179 ++--- pythonik/tests/test_internal_utils.py | 96 +++ pythonik/tests/test_transcode.py | 1059 +++++++++++++++++++++++++ 6 files changed, 1374 insertions(+), 193 deletions(-) create mode 100644 pythonik/specs/_internal_utils.py create mode 100644 pythonik/tests/test_internal_utils.py create mode 100644 pythonik/tests/test_transcode.py 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 index ebdbcc6..4ce7933 100644 --- a/pythonik/models/transcode.py +++ b/pythonik/models/transcode.py @@ -6,10 +6,21 @@ from __future__ import annotations from datetime import datetime -from typing import Any, Dict, List, Literal, Optional +from typing import ( + Any, + Dict, + List, + Literal, + Optional, + Union, +) from uuid import UUID -from pydantic import BaseModel, Field, HttpUrl +from pydantic import ( + BaseModel, + Field, + HttpUrl, +) class TranscribeSchema(BaseModel): @@ -18,7 +29,7 @@ class TranscribeSchema(BaseModel): engine: Optional[str] = None force: Optional[bool] = None language: Optional[str] = None - profile_id: Optional[UUID] = None + profile_id: Optional[Union[str, UUID]] = None speakers: Optional[int] = Field(None, ge=1, le=100) summary: Optional[bool] = None topics_extraction: Optional[bool] = None @@ -48,15 +59,15 @@ class TranscodeQueueSchema(BaseModel): first_url: Optional[str] = None last_url: Optional[str] = None next_url: Optional[str] = None - objects: Optional[List["TranscodeQueueObjectSchema"] - ] = Field(default_factory=list) + 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 - ) + total: Optional[int] = Field(None, + ge=-9223372036854775808, + le=9223372036854775807) class TranscodeQueueRecordSchema(BaseModel): @@ -65,21 +76,21 @@ class TranscodeQueueRecordSchema(BaseModel): bytes_params: Optional[Any] = None date_created: Optional[str] = None date_updated: Optional[str] = None - id: Optional[UUID] = None - job_id: Optional[UUID] = None + id: Optional[Union[str, UUID]] = None + job_id: Optional[Union[str, UUID]] = None media_info: Optional[str] = None - object_id: Optional[UUID] = None + object_id: Optional[Union[str, UUID]] = 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[UUID] = None + system_domain_id: Optional[Union[str, UUID]] = None system_name: Optional[str] = None type: Optional[str] = None - user_id: Optional[UUID] = None - version_id: Optional[UUID] = None + user_id: Optional[Union[str, UUID]] = None + version_id: Optional[Union[str, UUID]] = None class TranscodeQueueObjectSchema(BaseModel): @@ -87,17 +98,17 @@ class TranscodeQueueObjectSchema(BaseModel): date_created: Optional[datetime] = None date_updated: Optional[datetime] = None - id: Optional[UUID] = None - job_id: Optional[UUID] = None + id: Optional[Union[str, UUID]] = None + job_id: Optional[Union[str, UUID]] = 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[UUID] = None + system_domain_id: Optional[Union[str, UUID]] = None system_domain_timestamp: Optional[float] = None system_name: Optional[str] = None type: Optional[str] = None - user_id: Optional[UUID] = None + user_id: Optional[Union[str, UUID]] = None class TranscodeElasticQueueRecordSchema(BaseModel): @@ -125,8 +136,8 @@ class TranscodeElasticQueueRecordSchema(BaseModel): class TranscodeESQueueRecordsSchema(BaseModel): """Represents a TranscodeESQueueRecordsSchema in the Iconik system.""" - objects: Optional[List["TranscodeElasticQueueRecord"] - ] = Field(default_factory=list) + objects: Optional[List["TranscodeElasticQueueRecord"]] = Field( + default_factory=list) class TranscodeElasticQueueRecord(BaseModel): @@ -157,20 +168,20 @@ class ThumbnailJobSchema(BaseModel): 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 - ) + 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 - ) + 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) @@ -210,8 +221,8 @@ class LocalTranscodeJobSchema(BaseModel): 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) + valid_transcoders: Optional[List["Transcoders"]] = Field( + default_factory=list) version_id: Optional[str] = None @@ -245,15 +256,15 @@ class LocalStorageFileTranscodeJobsSchema(BaseModel): first_url: Optional[str] = None last_url: Optional[str] = None next_url: Optional[str] = None - objects: Optional[List["LocalStorageFileTranscodeJobSchema"] - ] = Field(default_factory=list) + 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 - ) + total: Optional[int] = Field(None, + ge=-9223372036854775808, + le=9223372036854775807) class LocalStorageFileTranscodeJobSchema(BaseModel): @@ -285,22 +296,22 @@ class ListObjectsSchema(BaseModel): 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 - ) + 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[UUID] + job_ids: List[Union[str, UUID]] class JobsPrioritySchema(BaseModel): """Represents a JobsPrioritySchema in the Iconik system.""" - job_ids: List[UUID] + job_ids: List[Union[str, UUID]] priority: int = Field(..., ge=1, le=10) @@ -331,11 +342,12 @@ class JobBaseSchema(BaseModel): class GenerateCollectionKeyframeSchema(BaseModel): """Represents a GenerateCollectionKeyframeSchema in the Iconik system.""" - deleted_asset_id: Optional[UUID] = None + deleted_asset_id: Optional[Union[str, UUID]] = None force: Optional[bool] = None - specified_asset_ids: Optional[List[UUID]] = Field(default_factory=list) - specified_keyframes: Optional[List["SpecifiedKeyframes"] - ] = Field(default_factory=list) + specified_asset_ids: Optional[List[Union[str, UUID]]] = Field( + default_factory=list) + specified_keyframes: Optional[List["SpecifiedKeyframes"]] = Field( + default_factory=list) class SpecifiedKeyframes(BaseModel): @@ -348,44 +360,44 @@ 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 - ) + 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 - ) + 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) + objects: Optional[List["EdgeTranscodeWorkerSchema"]] = Field( + default_factory=list) class EdgeTranscodeWorkerSchema(BaseModel): """Represents a EdgeTranscodeWorkerSchema in the Iconik system.""" - id: Optional[UUID] = None + id: Optional[Union[str, UUID]] = None last_update_date: Optional[datetime] = None status: Literal["ACTIVE", "INACTIVE"] - storage_id: UUID + storage_id: Union[str, UUID] class EdgeTranscodeJobsSchema(BaseModel): """Represents a EdgeTranscodeJobsSchema in the Iconik system.""" - objects: Optional[List["EdgeTranscodeJobSchema"] - ] = Field(default_factory=list) + objects: Optional[List["EdgeTranscodeJobSchema"]] = Field( + default_factory=list) class TranscodeJobSchema(BaseModel): @@ -428,8 +440,8 @@ class JobSchema(BaseModel): 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) + valid_transcoders: Optional[List["Transcoders"]] = Field( + default_factory=list) version_id: Optional[str] = None @@ -451,10 +463,10 @@ class EdgeTranscodeJobSchema(BaseModel): 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) + thumbnail: Optional[List["EdgeThumbnailJobFieldSchema"]] = Field( + default_factory=list) + transcode: Optional[List["EdgeTranscodeJobFieldSchema"]] = Field( + default_factory=list) class JobStep(BaseModel): @@ -474,20 +486,20 @@ class ThumbnailJob(BaseModel): 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 - ) + 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 - ) + 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) @@ -578,12 +590,12 @@ class EdgeThumbnailJobFieldSchema(BaseModel): 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 - ) + 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) @@ -593,9 +605,9 @@ class BulkTranscribeSchema(BaseModel): engine: Optional[str] = None force: Optional[bool] = None language: Optional[str] = None - object_ids: List[UUID] + object_ids: List[Union[str, UUID]] object_type: Literal["assets", "collections", "saved_searches"] - profile_id: Optional[UUID] = None + profile_id: Optional[Union[str, UUID]] = None speakers: Optional[int] = Field(None, ge=1, le=100) summary: Optional[bool] = None topics_extraction: Optional[bool] = None @@ -607,22 +619,22 @@ class BulkAnalyzeSchema(BaseModel): force: Optional[bool] = None force_type: Optional[Literal["OVERWRITE", "APPEND"]] = None - object_ids: List[UUID] + object_ids: List[Union[str, UUID]] object_type: Literal["assets", "collections", "saved_searches"] - profile_id: Optional[UUID] = None + profile_id: Optional[Union[str, UUID]] = None class BulkActionSchema(BaseModel): """Represents a BulkActionSchema in the Iconik system.""" - object_ids: List[UUID] + object_ids: List[Union[str, UUID]] object_type: Literal["assets", "collections", "saved_searches"] class AssetLinkURLSchema(BaseModel): """Represents a AssetLinkURLSchema in the Iconik system.""" - url: HttpUrl + url: Union[str, HttpUrl] class AssetLinkData(BaseModel): 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 index b399f20..bd9fe0a 100644 --- a/pythonik/specs/transcode.py +++ b/pythonik/specs/transcode.py @@ -1,11 +1,16 @@ -from typing import Any, Dict, Literal, Optional, Union +from typing import ( + Any, + Dict, + Literal, + Optional, + Union, +) +from uuid import UUID from pythonik.models.base import Response +from pythonik.specs._internal_utils import is_pydantic_model +from pythonik.specs.base import Spec -from aiopythonik import is_pydantic_model -from aiopythonik._pythonik_patches import Spec - -from .._typing import ObjectID from ..models.transcode import ( AbortStorageTranscodeJobsSchema, AnalyzeSchema, @@ -31,7 +36,7 @@ class TranscodeSpec(Spec): def analyze_asset( self, - asset_id: ObjectID, + asset_id: Union[str, UUID], analyze_schema: Union[AnalyzeSchema, Dict[str, Any]], exclude_defaults: bool = True, **kwargs, @@ -53,17 +58,15 @@ def analyze_asset( - 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 - ) + 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: ObjectID, + asset_id: Union[str, UUID], analyze_schema: Union[AnalyzeSchema, Dict[str, Any]], exclude_defaults: bool = True, **kwargs, @@ -86,17 +89,15 @@ def analyze_asset_default_profile( - 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 - ) + 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: ObjectID, + asset_id: Union[str, UUID], media_type: str, analyze_schema: Union[AnalyzeSchema, Dict[str, Any]], exclude_defaults: bool = True, @@ -121,20 +122,17 @@ def analyze_asset_default_profile_media_type( - 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 - ) + 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}/" - ) + 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: ObjectID, - profile_id: ObjectID, + asset_id: Union[str, UUID], + profile_id: Union[str, UUID], analyze_schema: Union[AnalyzeSchema, Dict[str, Any]], exclude_defaults: bool = True, **kwargs, @@ -158,10 +156,8 @@ def analyze_asset_custom_profile( - 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 - ) + 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) @@ -192,8 +188,7 @@ def analyze_bulk( """ body = ( bulk_analyze_schema.model_dump(exclude_defaults=exclude_defaults) - if is_pydantic_model(bulk_analyze_schema) else bulk_analyze_schema - ) + 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) @@ -221,18 +216,17 @@ def get_asset_link_metadata( - 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 - ) + 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: ObjectID, + job_id: Union[str, UUID], **kwargs, ) -> Response: """ @@ -297,21 +291,19 @@ def create_edge_transcode_worker( - 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 - ) + 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: ObjectID, + worker_id: Union[str, UUID], **kwargs, ) -> Response: """ - Get a edge transcode worker + Get an edge transcode worker Args: worker_id: ID of the edge transcode worker @@ -331,7 +323,7 @@ def get_edge_transcode_worker( def delete_edge_transcode_worker( self, - worker_id: ObjectID, + worker_id: Union[str, UUID], **kwargs, ) -> Response: """ @@ -355,13 +347,13 @@ def delete_edge_transcode_worker( def update_edge_transcode_worker( self, - worker_id: ObjectID, + worker_id: Union[str, UUID], worker_schema: Union[EdgeTranscodeWorkerSchema, Dict[str, Any]], exclude_defaults: bool = True, **kwargs, ) -> Response: """ - Update a edge transcode worker + Update an edge transcode worker Args: worker_id: ID of the edge transcode worker @@ -378,23 +370,21 @@ def update_edge_transcode_worker( - 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 - ) + 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: ObjectID, + worker_id: Union[str, UUID], worker_schema: Union[EdgeTranscodeWorkerSchema, Dict[str, Any]], exclude_defaults: bool = True, **kwargs, ) -> Response: """ - Update a edge transcode worker partially + Update an edge transcode worker partially Args: worker_id: ID of the edge transcode worker @@ -411,18 +401,16 @@ def partial_update_edge_transcode_worker( - 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 - ) + 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: ObjectID, + collection_id: Union[str, UUID], keyframe_schema: Union[GenerateCollectionKeyframeSchema, Dict[str, Any]], exclude_defaults: bool = True, @@ -445,17 +433,15 @@ def generate_collection_keyframe( - 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 - ) + 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: ObjectID, + storage_id: Union[str, UUID], abort_schema: Union[AbortStorageTranscodeJobsSchema, Dict[str, Any]], exclude_defaults: bool = True, **kwargs, @@ -478,17 +464,15 @@ def abort_storage_transcode_jobs( - 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 - ) + 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 fetch_storage_edge_transcode_jobs( self, - storage_id: ObjectID, + storage_id: Union[str, UUID], limit: int = 10, **kwargs, ) -> Response: @@ -514,8 +498,8 @@ def fetch_storage_edge_transcode_jobs( def delete_storage_file_transcode( self, - storage_id: ObjectID, - file_id: ObjectID, + storage_id: Union[str, UUID], + file_id: Union[str, UUID], **kwargs, ) -> Response: """ @@ -538,7 +522,7 @@ def delete_storage_file_transcode( def fetch_storage_transcode_jobs( self, - storage_id: ObjectID, + storage_id: Union[str, UUID], per_page: int = 10, last_id: Optional[str] = None, **kwargs, @@ -564,8 +548,8 @@ def fetch_storage_transcode_jobs( def get_storage_transcode_job( self, - storage_id: ObjectID, - record_id: ObjectID, + storage_id: Union[str, UUID], + record_id: Union[str, UUID], **kwargs, ) -> Response: """ @@ -588,8 +572,8 @@ def get_storage_transcode_job( def delete_storage_transcode_job( self, - storage_id: ObjectID, - record_id: ObjectID, + storage_id: Union[str, UUID], + record_id: Union[str, UUID], **kwargs, ) -> Response: """ @@ -631,10 +615,8 @@ def create_transcode( - 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 - ) + 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) @@ -715,7 +697,7 @@ def fetch_transcode_queue_system( def fetch_transcode_object_queue_records( self, object_type: str, - object_id: ObjectID, + object_id: Union[str, UUID], **kwargs, ) -> Response: """ @@ -739,8 +721,8 @@ def fetch_transcode_object_queue_records( def fetch_transcode_version_queue_records( self, object_type: str, - object_id: ObjectID, - version_id: ObjectID, + object_id: Union[str, UUID], + version_id: Union[str, UUID], **kwargs, ) -> Response: """ @@ -759,14 +741,13 @@ def fetch_transcode_version_queue_records( - 400 Bad request - malformed parameters """ url = self.gen_url( - f"transcode/{object_type}/{object_id}/versions/{version_id}/" - ) + 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: ObjectID, + transcode_job_id: Union[str, UUID], **kwargs, ) -> Response: """ @@ -789,7 +770,7 @@ def get_transcode_job( def delete_transcode_job( self, - transcode_job_id: ObjectID, + transcode_job_id: Union[str, UUID], **kwargs, ) -> Response: """ @@ -813,7 +794,7 @@ def delete_transcode_job( def move_transcode_job_position( self, - transcode_job_id: ObjectID, + transcode_job_id: Union[str, UUID], position: Literal["top", "bottom"], **kwargs, ) -> Response: @@ -833,13 +814,14 @@ def move_transcode_job_position( - 401 Token is invalid - 404 Transcode does not exist """ - url = self.gen_url(f"transcode/{transcode_job_id}/position/{position}/") + 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: ObjectID, + transcode_job_id: Union[str, UUID], priority: int, **kwargs, ) -> Response: @@ -859,13 +841,14 @@ def update_transcode_job_priority( - 401 Token is invalid - 404 Transcode does not exist """ - url = self.gen_url(f"transcode/{transcode_job_id}/priority/{priority}/") + 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: ObjectID, + asset_id: Union[str, UUID], transcribe_schema: Union[TranscribeSchema, Dict[str, Any]], exclude_defaults: bool = True, **kwargs, @@ -887,10 +870,8 @@ def transcribe_asset_default_profile( - 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 - ) + 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) @@ -917,12 +898,10 @@ def transcribe_bulk( - 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 - ) + 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..8adc450 --- /dev/null +++ b/pythonik/tests/test_transcode.py @@ -0,0 +1,1059 @@ +# pythonik/tests/test_transcode.py +import json +import uuid +from datetime import datetime +from typing import ( + Any, + Dict, +) +from uuid import UUID + +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 + + +class UUIDEncoder(json.JSONEncoder): + """JSON encoder that handles UUID and Pydantic URL objects.""" + + def default(self, obj): + """Convert UUID objects to strings for JSON serialization.""" + if isinstance(obj, UUID): + return str(obj) + # Handle HttpUrl specially + if str(type(obj)) == "": + return str(obj) + return super().default(obj) + + +def serialize_model_for_json(model): + """ + Convert a Pydantic model to a JSON-serializable dict with UUID support. + + Args: + model: The Pydantic model to convert + + Returns: + A dict that can be serialized to JSON + """ + if hasattr(model, "model_dump"): # Pydantic v2 + # Convert model to dict + model_dict = model.model_dump(exclude_unset=True) + # Serialize to JSON string and back to handle UUIDs + return json.loads(json.dumps(model_dict, cls=UUIDEncoder)) + # Handle dict case + if isinstance(model, dict): + return json.loads(json.dumps(model, cls=UUIDEncoder)) + return model + + +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().fetch_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) + + # No need to patch for dict inputs, but we'll keep the pattern consistent + original_patch = client.transcode()._patch + + def patched_patch(path, json=None, **kwargs): + if json is not None: + json = serialize_model_for_json(json) + return original_patch(path, json=json, **kwargs) + + client.transcode()._patch = patched_patch + + 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().fetch_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().fetch_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().fetch_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().fetch_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().fetch_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().fetch_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 From 37bffda76265aeca37bba441d20413ea6288a336 Mon Sep 17 00:00:00 2001 From: Brian Summa Date: Tue, 20 May 2025 12:01:04 -0500 Subject: [PATCH 3/4] fix(transcode): replace UUID type with str for improved compatibility Update Transcode models and specs to use string type instead of UUID for all identifiers. This change ensures better compatibility with existing models and aligns with user expectations when calling API methods. --- pythonik/models/transcode.py | 46 ++++++++++++-------------- pythonik/specs/transcode.py | 57 ++++++++++++++++---------------- pythonik/tests/test_transcode.py | 46 -------------------------- 3 files changed, 50 insertions(+), 99 deletions(-) diff --git a/pythonik/models/transcode.py b/pythonik/models/transcode.py index 4ce7933..17869a9 100644 --- a/pythonik/models/transcode.py +++ b/pythonik/models/transcode.py @@ -14,7 +14,6 @@ Optional, Union, ) -from uuid import UUID from pydantic import ( BaseModel, @@ -29,7 +28,7 @@ class TranscribeSchema(BaseModel): engine: Optional[str] = None force: Optional[bool] = None language: Optional[str] = None - profile_id: Optional[Union[str, UUID]] = None + profile_id: Optional[str] = None speakers: Optional[int] = Field(None, ge=1, le=100) summary: Optional[bool] = None topics_extraction: Optional[bool] = None @@ -76,21 +75,21 @@ class TranscodeQueueRecordSchema(BaseModel): bytes_params: Optional[Any] = None date_created: Optional[str] = None date_updated: Optional[str] = None - id: Optional[Union[str, UUID]] = None - job_id: Optional[Union[str, UUID]] = None + id: Optional[str] = None + job_id: Optional[str] = None media_info: Optional[str] = None - object_id: Optional[Union[str, UUID]] = 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[Union[str, UUID]] = None + system_domain_id: Optional[str] = None system_name: Optional[str] = None type: Optional[str] = None - user_id: Optional[Union[str, UUID]] = None - version_id: Optional[Union[str, UUID]] = None + user_id: Optional[str] = None + version_id: Optional[str] = None class TranscodeQueueObjectSchema(BaseModel): @@ -98,17 +97,17 @@ class TranscodeQueueObjectSchema(BaseModel): date_created: Optional[datetime] = None date_updated: Optional[datetime] = None - id: Optional[Union[str, UUID]] = None - job_id: Optional[Union[str, UUID]] = 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[Union[str, UUID]] = 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[Union[str, UUID]] = None + user_id: Optional[str] = None class TranscodeElasticQueueRecordSchema(BaseModel): @@ -305,13 +304,13 @@ class JobsStateSchema(BaseModel): """Represents a JobsStateSchema in the Iconik system.""" action: Literal["ABORT", "RESTART"] - job_ids: List[Union[str, UUID]] + job_ids: List[str] class JobsPrioritySchema(BaseModel): """Represents a JobsPrioritySchema in the Iconik system.""" - job_ids: List[Union[str, UUID]] + job_ids: List[str] priority: int = Field(..., ge=1, le=10) @@ -342,10 +341,9 @@ class JobBaseSchema(BaseModel): class GenerateCollectionKeyframeSchema(BaseModel): """Represents a GenerateCollectionKeyframeSchema in the Iconik system.""" - deleted_asset_id: Optional[Union[str, UUID]] = None + deleted_asset_id: Optional[str] = None force: Optional[bool] = None - specified_asset_ids: Optional[List[Union[str, UUID]]] = Field( - default_factory=list) + specified_asset_ids: Optional[List[str]] = Field(default_factory=list) specified_keyframes: Optional[List["SpecifiedKeyframes"]] = Field( default_factory=list) @@ -387,10 +385,10 @@ class EdgeTranscodeWorkersSchema(BaseModel): class EdgeTranscodeWorkerSchema(BaseModel): """Represents a EdgeTranscodeWorkerSchema in the Iconik system.""" - id: Optional[Union[str, UUID]] = None + id: Optional[str] = None last_update_date: Optional[datetime] = None status: Literal["ACTIVE", "INACTIVE"] - storage_id: Union[str, UUID] + storage_id: str class EdgeTranscodeJobsSchema(BaseModel): @@ -605,9 +603,9 @@ class BulkTranscribeSchema(BaseModel): engine: Optional[str] = None force: Optional[bool] = None language: Optional[str] = None - object_ids: List[Union[str, UUID]] + object_ids: List[str] object_type: Literal["assets", "collections", "saved_searches"] - profile_id: Optional[Union[str, UUID]] = None + profile_id: Optional[str] = None speakers: Optional[int] = Field(None, ge=1, le=100) summary: Optional[bool] = None topics_extraction: Optional[bool] = None @@ -619,15 +617,15 @@ class BulkAnalyzeSchema(BaseModel): force: Optional[bool] = None force_type: Optional[Literal["OVERWRITE", "APPEND"]] = None - object_ids: List[Union[str, UUID]] + object_ids: List[str] object_type: Literal["assets", "collections", "saved_searches"] - profile_id: Optional[Union[str, UUID]] = None + profile_id: Optional[str] = None class BulkActionSchema(BaseModel): """Represents a BulkActionSchema in the Iconik system.""" - object_ids: List[Union[str, UUID]] + object_ids: List[str] object_type: Literal["assets", "collections", "saved_searches"] diff --git a/pythonik/specs/transcode.py b/pythonik/specs/transcode.py index bd9fe0a..86d46cc 100644 --- a/pythonik/specs/transcode.py +++ b/pythonik/specs/transcode.py @@ -5,7 +5,6 @@ Optional, Union, ) -from uuid import UUID from pythonik.models.base import Response from pythonik.specs._internal_utils import is_pydantic_model @@ -36,7 +35,7 @@ class TranscodeSpec(Spec): def analyze_asset( self, - asset_id: Union[str, UUID], + asset_id: str, analyze_schema: Union[AnalyzeSchema, Dict[str, Any]], exclude_defaults: bool = True, **kwargs, @@ -66,7 +65,7 @@ def analyze_asset( def analyze_asset_default_profile( self, - asset_id: Union[str, UUID], + asset_id: str, analyze_schema: Union[AnalyzeSchema, Dict[str, Any]], exclude_defaults: bool = True, **kwargs, @@ -97,7 +96,7 @@ def analyze_asset_default_profile( def analyze_asset_default_profile_media_type( self, - asset_id: Union[str, UUID], + asset_id: str, media_type: str, analyze_schema: Union[AnalyzeSchema, Dict[str, Any]], exclude_defaults: bool = True, @@ -131,8 +130,8 @@ def analyze_asset_default_profile_media_type( def analyze_asset_custom_profile( self, - asset_id: Union[str, UUID], - profile_id: Union[str, UUID], + asset_id: str, + profile_id: str, analyze_schema: Union[AnalyzeSchema, Dict[str, Any]], exclude_defaults: bool = True, **kwargs, @@ -226,7 +225,7 @@ def get_asset_link_metadata( def acknowledge_edge_transcode_job( self, - job_id: Union[str, UUID], + job_id: str, **kwargs, ) -> Response: """ @@ -299,7 +298,7 @@ def create_edge_transcode_worker( def get_edge_transcode_worker( self, - worker_id: Union[str, UUID], + worker_id: str, **kwargs, ) -> Response: """ @@ -323,7 +322,7 @@ def get_edge_transcode_worker( def delete_edge_transcode_worker( self, - worker_id: Union[str, UUID], + worker_id: str, **kwargs, ) -> Response: """ @@ -347,7 +346,7 @@ def delete_edge_transcode_worker( def update_edge_transcode_worker( self, - worker_id: Union[str, UUID], + worker_id: str, worker_schema: Union[EdgeTranscodeWorkerSchema, Dict[str, Any]], exclude_defaults: bool = True, **kwargs, @@ -378,7 +377,7 @@ def update_edge_transcode_worker( def partial_update_edge_transcode_worker( self, - worker_id: Union[str, UUID], + worker_id: str, worker_schema: Union[EdgeTranscodeWorkerSchema, Dict[str, Any]], exclude_defaults: bool = True, **kwargs, @@ -410,7 +409,7 @@ def partial_update_edge_transcode_worker( def generate_collection_keyframe( self, - collection_id: Union[str, UUID], + collection_id: str, keyframe_schema: Union[GenerateCollectionKeyframeSchema, Dict[str, Any]], exclude_defaults: bool = True, @@ -441,7 +440,7 @@ def generate_collection_keyframe( def abort_storage_transcode_jobs( self, - storage_id: Union[str, UUID], + storage_id: str, abort_schema: Union[AbortStorageTranscodeJobsSchema, Dict[str, Any]], exclude_defaults: bool = True, **kwargs, @@ -472,7 +471,7 @@ def abort_storage_transcode_jobs( def fetch_storage_edge_transcode_jobs( self, - storage_id: Union[str, UUID], + storage_id: str, limit: int = 10, **kwargs, ) -> Response: @@ -498,8 +497,8 @@ def fetch_storage_edge_transcode_jobs( def delete_storage_file_transcode( self, - storage_id: Union[str, UUID], - file_id: Union[str, UUID], + storage_id: str, + file_id: str, **kwargs, ) -> Response: """ @@ -522,7 +521,7 @@ def delete_storage_file_transcode( def fetch_storage_transcode_jobs( self, - storage_id: Union[str, UUID], + storage_id: str, per_page: int = 10, last_id: Optional[str] = None, **kwargs, @@ -548,8 +547,8 @@ def fetch_storage_transcode_jobs( def get_storage_transcode_job( self, - storage_id: Union[str, UUID], - record_id: Union[str, UUID], + storage_id: str, + record_id: str, **kwargs, ) -> Response: """ @@ -572,8 +571,8 @@ def get_storage_transcode_job( def delete_storage_transcode_job( self, - storage_id: Union[str, UUID], - record_id: Union[str, UUID], + storage_id: str, + record_id: str, **kwargs, ) -> Response: """ @@ -697,7 +696,7 @@ def fetch_transcode_queue_system( def fetch_transcode_object_queue_records( self, object_type: str, - object_id: Union[str, UUID], + object_id: str, **kwargs, ) -> Response: """ @@ -721,8 +720,8 @@ def fetch_transcode_object_queue_records( def fetch_transcode_version_queue_records( self, object_type: str, - object_id: Union[str, UUID], - version_id: Union[str, UUID], + object_id: str, + version_id: str, **kwargs, ) -> Response: """ @@ -747,7 +746,7 @@ def fetch_transcode_version_queue_records( def get_transcode_job( self, - transcode_job_id: Union[str, UUID], + transcode_job_id: str, **kwargs, ) -> Response: """ @@ -770,7 +769,7 @@ def get_transcode_job( def delete_transcode_job( self, - transcode_job_id: Union[str, UUID], + transcode_job_id: str, **kwargs, ) -> Response: """ @@ -794,7 +793,7 @@ def delete_transcode_job( def move_transcode_job_position( self, - transcode_job_id: Union[str, UUID], + transcode_job_id: str, position: Literal["top", "bottom"], **kwargs, ) -> Response: @@ -821,7 +820,7 @@ def move_transcode_job_position( def update_transcode_job_priority( self, - transcode_job_id: Union[str, UUID], + transcode_job_id: str, priority: int, **kwargs, ) -> Response: @@ -848,7 +847,7 @@ def update_transcode_job_priority( def transcribe_asset_default_profile( self, - asset_id: Union[str, UUID], + asset_id: str, transcribe_schema: Union[TranscribeSchema, Dict[str, Any]], exclude_defaults: bool = True, **kwargs, diff --git a/pythonik/tests/test_transcode.py b/pythonik/tests/test_transcode.py index 8adc450..57a6513 100644 --- a/pythonik/tests/test_transcode.py +++ b/pythonik/tests/test_transcode.py @@ -1,12 +1,10 @@ # pythonik/tests/test_transcode.py -import json import uuid from datetime import datetime from typing import ( Any, Dict, ) -from uuid import UUID import requests_mock @@ -33,40 +31,6 @@ from pythonik.specs.transcode import TranscodeSpec -class UUIDEncoder(json.JSONEncoder): - """JSON encoder that handles UUID and Pydantic URL objects.""" - - def default(self, obj): - """Convert UUID objects to strings for JSON serialization.""" - if isinstance(obj, UUID): - return str(obj) - # Handle HttpUrl specially - if str(type(obj)) == "": - return str(obj) - return super().default(obj) - - -def serialize_model_for_json(model): - """ - Convert a Pydantic model to a JSON-serializable dict with UUID support. - - Args: - model: The Pydantic model to convert - - Returns: - A dict that can be serialized to JSON - """ - if hasattr(model, "model_dump"): # Pydantic v2 - # Convert model to dict - model_dict = model.model_dump(exclude_unset=True) - # Serialize to JSON string and back to handle UUIDs - return json.loads(json.dumps(model_dict, cls=UUIDEncoder)) - # Handle dict case - if isinstance(model, dict): - return json.loads(json.dumps(model, cls=UUIDEncoder)) - return model - - def test_analyze_asset(): """Test analyze_asset method.""" with requests_mock.Mocker() as m: @@ -444,16 +408,6 @@ def test_partial_update_edge_transcode_worker(): auth_token=auth_token, timeout=3) - # No need to patch for dict inputs, but we'll keep the pattern consistent - original_patch = client.transcode()._patch - - def patched_patch(path, json=None, **kwargs): - if json is not None: - json = serialize_model_for_json(json) - return original_patch(path, json=json, **kwargs) - - client.transcode()._patch = patched_patch - response = client.transcode().partial_update_edge_transcode_worker( worker_id, worker_schema) From 45c1093ddb12da07093db54f55e6cbcaa9a6b751 Mon Sep 17 00:00:00 2001 From: Brian Summa Date: Tue, 20 May 2025 15:21:33 -0500 Subject: [PATCH 4/4] Refactor to use `list_` prefix instead of `fetch_` when getting a list of objects --- pythonik/specs/transcode.py | 14 +++++++------- pythonik/tests/test_transcode.py | 18 +++++++++--------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/pythonik/specs/transcode.py b/pythonik/specs/transcode.py index 86d46cc..64869a4 100644 --- a/pythonik/specs/transcode.py +++ b/pythonik/specs/transcode.py @@ -247,7 +247,7 @@ def acknowledge_edge_transcode_job( resp = self._post(url, **kwargs) return self.parse_response(resp, None) - def fetch_edge_transcode_workers( + def list_edge_transcode_workers( self, **kwargs, ) -> Response: @@ -469,7 +469,7 @@ def abort_storage_transcode_jobs( resp = self._delete(url, json=body, **kwargs) return self.parse_response(resp, None) - def fetch_storage_edge_transcode_jobs( + def list_storage_edge_transcode_jobs( self, storage_id: str, limit: int = 10, @@ -519,7 +519,7 @@ def delete_storage_file_transcode( resp = self._delete(url, **kwargs) return self.parse_response(resp, None) - def fetch_storage_transcode_jobs( + def list_storage_transcode_jobs( self, storage_id: str, per_page: int = 10, @@ -620,7 +620,7 @@ def create_transcode( resp = self._post(url, json=body, **kwargs) return self.parse_response(resp, JobSchema) - def fetch_transcode_queue( + def list_transcode_queue( self, per_page: Optional[int] = None, page: Optional[int] = None, @@ -655,7 +655,7 @@ def fetch_transcode_queue( resp = self._get(url, params=params, **kwargs) return self.parse_response(resp, TranscodeQueueSchema) - def fetch_transcode_queue_system( + def list_transcode_queue_system( self, per_domain_id: Optional[bool] = None, per_page: Optional[int] = None, @@ -693,7 +693,7 @@ def fetch_transcode_queue_system( resp = self._get(url, params=params, **kwargs) return self.parse_response(resp, TranscodeQueueSchema) - def fetch_transcode_object_queue_records( + def list_transcode_object_queue_records( self, object_type: str, object_id: str, @@ -717,7 +717,7 @@ def fetch_transcode_object_queue_records( resp = self._get(url, **kwargs) return self.parse_response(resp, TranscodeESQueueRecordsSchema) - def fetch_transcode_version_queue_records( + def list_transcode_version_queue_records( self, object_type: str, object_id: str, diff --git a/pythonik/tests/test_transcode.py b/pythonik/tests/test_transcode.py index 57a6513..24b18b3 100644 --- a/pythonik/tests/test_transcode.py +++ b/pythonik/tests/test_transcode.py @@ -249,7 +249,7 @@ def test_fetch_edge_transcode_workers(): client = PythonikClient(app_id=app_id, auth_token=auth_token, timeout=3) - response = client.transcode().fetch_edge_transcode_workers() + response = client.transcode().list_edge_transcode_workers() assert response.response.ok assert m.last_request.url == mock_address @@ -515,7 +515,7 @@ def test_fetch_storage_edge_transcode_jobs(): client = PythonikClient(app_id=app_id, auth_token=auth_token, timeout=3) - response = client.transcode().fetch_storage_edge_transcode_jobs( + response = client.transcode().list_storage_edge_transcode_jobs( storage_id, limit=5) assert response.response.ok @@ -584,7 +584,7 @@ def test_fetch_storage_transcode_jobs(): client = PythonikClient(app_id=app_id, auth_token=auth_token, timeout=3) - response = client.transcode().fetch_storage_transcode_jobs( + response = client.transcode().list_storage_transcode_jobs( storage_id, per_page=10, last_id="last-job-id") assert response.response.ok @@ -723,9 +723,9 @@ def test_fetch_transcode_queue(): client = PythonikClient(app_id=app_id, auth_token=auth_token, timeout=3) - response = client.transcode().fetch_transcode_queue(per_page=10, - page=1, - sort="priority") + 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) @@ -765,7 +765,7 @@ def test_fetch_transcode_queue_system(): client = PythonikClient(app_id=app_id, auth_token=auth_token, timeout=3) - response = client.transcode().fetch_transcode_queue_system( + response = client.transcode().list_transcode_queue_system( per_domain_id=True, per_page=10, page=1, sort="priority") assert response.response.ok @@ -804,7 +804,7 @@ def test_fetch_transcode_object_queue_records(): client = PythonikClient(app_id=app_id, auth_token=auth_token, timeout=3) - response = client.transcode().fetch_transcode_object_queue_records( + response = client.transcode().list_transcode_object_queue_records( object_type, object_id) assert response.response.ok @@ -841,7 +841,7 @@ def test_fetch_transcode_version_queue_records(): client = PythonikClient(app_id=app_id, auth_token=auth_token, timeout=3) - response = client.transcode().fetch_transcode_version_queue_records( + response = client.transcode().list_transcode_version_queue_records( object_type, object_id, version_id) assert response.response.ok