diff --git a/pyproject.toml b/pyproject.toml index a2cea90..a232f96 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "behavioralsignals" -version = "0.1.1" +version = "0.2.0" description = "Python SDK for Behavioral Signals API" readme = "README.md" authors = [] diff --git a/src/behavioralsignals/base.py b/src/behavioralsignals/base.py index efc040b..d01d340 100644 --- a/src/behavioralsignals/base.py +++ b/src/behavioralsignals/base.py @@ -40,12 +40,15 @@ def _send_request( path: str, method: str = "GET", data: Optional[dict] = None, + json: Optional[dict] = None, headers: Optional[dict] = None, files: Optional[dict] = None, ): url = self.config.api_url + "/" + path if headers is None: headers = self._get_default_headers() + else: + headers = {**self._get_default_headers(), **headers} if method == "GET": response = self.session.get( @@ -53,7 +56,7 @@ def _send_request( ) elif method == "POST": response = self.session.post( - url, headers=headers, data=data, files=files, timeout=self.config.timeout + url, headers=headers, data=data, files=files, json=json, timeout=self.config.timeout ) else: raise ValueError(f"Unsupported method: {method}") diff --git a/src/behavioralsignals/behavioral.py b/src/behavioralsignals/behavioral.py index 01a47d2..d182fd9 100644 --- a/src/behavioralsignals/behavioral.py +++ b/src/behavioralsignals/behavioral.py @@ -9,6 +9,7 @@ ResultResponse, StreamingOptions, AudioUploadParams, + S3UrlUploadParams, ProcessListParams, ProcessListResponse, StreamingResultResponse, @@ -57,6 +58,49 @@ def upload_audio( return ProcessItem(**data) + def upload_s3_presigned_url( + self, + url: str, + name: Optional[str] = None, + embeddings: bool = False, + meta: Optional[str] = None, + ) -> ProcessItem: + """Uploads an S3 presigned url pointing to an audio file and returns the process item. + + Args: + url (str): The S3 presigned url. + name (str, optional): Optional name for the job request. Defaults to filename. + embeddings (bool): Whether to include speaker and behavioral embeddings. Defaults to False. + meta (str, optional): Metadata json containing any extra user-defined metadata. + Returns: + ProcessItem: The process item containing details about the submitted process. + """ + # Create and validate parameters + params = S3UrlUploadParams(url=url, name=name, embeddings=embeddings, meta=meta) + + # Use provided name or default to filename + job_name = params.name + + payload = { + "url": params.url, + "name": job_name, + "embeddings": params.embeddings + } + + if params.meta: + payload["meta"] = params.meta + + headers = {"content-type": "application/json"} + + response = self._send_request( + path=f"clients/{self.config.cid}/processes/s3-presigned-url", + method="POST", + json=payload, + headers=headers + ) + + return ProcessItem(**response) + def list_processes( self, page: int = 0, diff --git a/src/behavioralsignals/deepfakes.py b/src/behavioralsignals/deepfakes.py index 2786903..7d7142c 100644 --- a/src/behavioralsignals/deepfakes.py +++ b/src/behavioralsignals/deepfakes.py @@ -9,6 +9,7 @@ ResultResponse, StreamingOptions, AudioUploadParams, + S3UrlUploadParams, ProcessListParams, ProcessListResponse, StreamingResultResponse, @@ -57,6 +58,49 @@ def upload_audio( return ProcessItem(**data) + def upload_s3_presigned_url( + self, + url: str, + name: Optional[str] = None, + embeddings: bool = False, + meta: Optional[str] = None, + ) -> ProcessItem: + """Uploads an S3 presigned url pointing to an audio file and returns the process item. + + Args: + url (str): The S3 presigned url. + name (str, optional): Optional name for the job request. Defaults to filename. + embeddings (bool): Whether to include speaker and behavioral embeddings. Defaults to False. + meta (str, optional): Metadata json containing any extra user-defined metadata. + Returns: + ProcessItem: The process item containing details about the submitted process. + """ + # Create and validate parameters + params = S3UrlUploadParams(url=url, name=name, embeddings=embeddings, meta=meta) + + # Use provided name or default to filename + job_name = params.name + + payload = { + "url": params.url, + "name": job_name, + "embeddings": params.embeddings + } + + if params.meta: + payload["meta"] = params.meta + + headers = {"content-type": "application/json"} + + response = self._send_request( + path=f"detection/clients/{self.config.cid}/processes/s3-presigned-url", + method="POST", + json=payload, + headers=headers + ) + + return ProcessItem(**response) + def list_processes( self, page: int = 0, diff --git a/src/behavioralsignals/models.py b/src/behavioralsignals/models.py index 904ea86..7941276 100644 --- a/src/behavioralsignals/models.py +++ b/src/behavioralsignals/models.py @@ -80,6 +80,27 @@ def validate_meta_json(cls, v): return v +class S3UrlUploadParams(BaseModel): + url: str = Field(..., description="The S3 presigned url containing the audio") + name: Optional[str] = Field(None, description="Optional name for the job request") + embeddings: bool = Field( + False, description="Whether to include speaker and behavioral embeddings in the result" + ) + meta: Optional[str] = Field( + None, description="Metadata json containing any extra user-defined metadata" + ) + + @field_validator("meta") + @classmethod + def validate_meta_json(cls, v): + if v is not None: + try: + json.loads(v) + except json.JSONDecodeError: + raise ValueError("meta must be valid JSON string") + return v + + class ProcessItem(BaseModel): """Individual process in the list"""