From dc2dad7a26a64b6b7a9ff8eb10ee8937592ad087 Mon Sep 17 00:00:00 2001 From: petrosmitseas Date: Thu, 4 Sep 2025 11:53:18 +0300 Subject: [PATCH 1/3] added s3_presigned_url endpoint capability --- src/behavioralsignals/base.py | 2 ++ src/behavioralsignals/behavioral.py | 43 +++++++++++++++++++++++++++++ src/behavioralsignals/deepfakes.py | 43 +++++++++++++++++++++++++++++ src/behavioralsignals/models.py | 21 ++++++++++++++ 4 files changed, 109 insertions(+) diff --git a/src/behavioralsignals/base.py b/src/behavioralsignals/base.py index efc040b..4c59898 100644 --- a/src/behavioralsignals/base.py +++ b/src/behavioralsignals/base.py @@ -46,6 +46,8 @@ def _send_request( 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( diff --git a/src/behavioralsignals/behavioral.py b/src/behavioralsignals/behavioral.py index 01a47d2..bea17e0 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,48 @@ 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 + + data = { + "url": params.url, + "name": job_name, + "embeddings": params.embeddings + } + headers = {"content-type": "application/x-www-form-urlencoded"} + + if params.meta: + data["meta"] = params.meta + + data = self._send_request( + path=f"clients/{self.config.cid}/processes/s3-presigned-url", + method="POST", + data=data, + headers=headers + ) + + return ProcessItem(**data) + def list_processes( self, page: int = 0, diff --git a/src/behavioralsignals/deepfakes.py b/src/behavioralsignals/deepfakes.py index 2786903..e902689 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,48 @@ 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 + + data = { + "url": params.url, + "name": job_name, + "embeddings": params.embeddings + } + headers = {"content-type": "application/x-www-form-urlencoded"} + + if params.meta: + data["meta"] = params.meta + + data = self._send_request( + path=f"detection/clients/{self.config.cid}/processes/s3-presigned-url", + method="POST", + data=data, + headers=headers + ) + + return ProcessItem(**data) + 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""" From 06f3db5567d3549ac73506088e6d623dc52aa7ff Mon Sep 17 00:00:00 2001 From: petrosmitseas Date: Thu, 4 Sep 2025 12:31:51 +0300 Subject: [PATCH 2/3] use JSON --- src/behavioralsignals/base.py | 3 ++- src/behavioralsignals/behavioral.py | 13 +++++++------ src/behavioralsignals/deepfakes.py | 13 +++++++------ 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/behavioralsignals/base.py b/src/behavioralsignals/base.py index 4c59898..d01d340 100644 --- a/src/behavioralsignals/base.py +++ b/src/behavioralsignals/base.py @@ -40,6 +40,7 @@ def _send_request( path: str, method: str = "GET", data: Optional[dict] = None, + json: Optional[dict] = None, headers: Optional[dict] = None, files: Optional[dict] = None, ): @@ -55,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 bea17e0..d182fd9 100644 --- a/src/behavioralsignals/behavioral.py +++ b/src/behavioralsignals/behavioral.py @@ -81,24 +81,25 @@ def upload_s3_presigned_url( # Use provided name or default to filename job_name = params.name - data = { + payload = { "url": params.url, "name": job_name, "embeddings": params.embeddings } - headers = {"content-type": "application/x-www-form-urlencoded"} if params.meta: - data["meta"] = params.meta + payload["meta"] = params.meta - data = self._send_request( + headers = {"content-type": "application/json"} + + response = self._send_request( path=f"clients/{self.config.cid}/processes/s3-presigned-url", method="POST", - data=data, + json=payload, headers=headers ) - return ProcessItem(**data) + return ProcessItem(**response) def list_processes( self, diff --git a/src/behavioralsignals/deepfakes.py b/src/behavioralsignals/deepfakes.py index e902689..7d7142c 100644 --- a/src/behavioralsignals/deepfakes.py +++ b/src/behavioralsignals/deepfakes.py @@ -81,24 +81,25 @@ def upload_s3_presigned_url( # Use provided name or default to filename job_name = params.name - data = { + payload = { "url": params.url, "name": job_name, "embeddings": params.embeddings } - headers = {"content-type": "application/x-www-form-urlencoded"} if params.meta: - data["meta"] = params.meta + payload["meta"] = params.meta - data = self._send_request( + headers = {"content-type": "application/json"} + + response = self._send_request( path=f"detection/clients/{self.config.cid}/processes/s3-presigned-url", method="POST", - data=data, + json=payload, headers=headers ) - return ProcessItem(**data) + return ProcessItem(**response) def list_processes( self, From 8043d26d59962de4c020653b7f5e5c0dd2778bc3 Mon Sep 17 00:00:00 2001 From: petrosmitseas Date: Mon, 8 Sep 2025 12:17:29 +0300 Subject: [PATCH 3/3] increment version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 = []