Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Python
.venv
venv
__pycache__
*.pyc
*/*.pyc
Expand Down Expand Up @@ -40,3 +41,11 @@ htmlcov/
.pytest_cache/
*.xml
*.html

test.py
test2.py
test_.py
test_list_files_format.py
testviatimestamp.py


2 changes: 1 addition & 1 deletion labellerr/core/annotation_templates/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def create_template(
question_dict.pop("question_type", None)
questions_data.append(question_dict)

payload = {"templateName": params.template_name, "questions": questions_data}
payload = {"template_name": params.template_name, "questions": questions_data}
url = (
f"{constants.BASE_URL}/annotations/create_template?client_id={client.client_id}&data_type={params.data_type.value}"
f"&uuid={unique_id}"
Expand Down
107 changes: 69 additions & 38 deletions labellerr/core/projects/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,73 @@ def upload_preannotations(
else:
return future.result()

def _build_export_search_queries(self, export_config: schemas.CreateExportParams) -> list:
import time
search_queries = []
valid_statuses = [s for s in (export_config.statuses or []) if s]
if valid_statuses:
search_queries.append({
"op": "OR",
"id": "file_status",
"values": [{"p": "in", "v": valid_statuses}],
})
if export_config.updated_after_timestamp:
now_ms = int(time.time() * 1000)
search_queries.append({
"op": "OR",
"id": "last_updated_date",
"values": [{"p": "between", "v": [{"start": export_config.updated_after_timestamp, "end": now_ms}]}],
})
return search_queries

def _fetch_slice_id(self, search_queries: list) -> str:
files_res = self.list_files(search_queries=search_queries, size=10)
response_data = files_res.get("response", {})
slice_id = response_data.get("slice_id")
if not slice_id:
raise LabellerrError("Could not retrieve slice_id from project files.")
return slice_id

def _submit_export_job(self, export_config: schemas.CreateExportParams):
# 1. Get slice_id based on user search filters
search_queries = self._build_export_search_queries(export_config)
slice_id = self._fetch_slice_id(search_queries)

# 2. Build the payload
unique_id = client_utils.generate_request_id()
payload = {
"export_name": export_config.export_name,
"export_description": export_config.export_description or "",
"export_format": export_config.export_format,
"connection_id": export_config.connection_id,
"destination": export_config.export_destination.value,
"export_all": True,
"export_status": "Generating Export",
"file_activity": False,
"question_ids": export_config.question_ids or ["all"],
"slice_id": slice_id,
"export_path": export_config.export_folder_path,
}

# 3. Call the UI exports endpoint
url = f"{constants.BASE_URL}/exports/files?project_id={self.project_id}&client_id={self.client.client_id}&uuid={unique_id}"
headers = {
"Origin": constants.ALLOWED_ORIGINS,
"Content-Type": "application/json",
}

response = self.client.make_request(
"POST",
url,
extra_headers=headers,
request_id=unique_id,
data=json.dumps(payload),
)

# 4. Extract report_id from response
report_id = response.get("response") if isinstance(response, dict) else response
return Export(report_id=report_id, project=self)

def create_export(self, export_config: schemas.CreateExportParams):
"""
Creates an export with the given configuration.
Expand All @@ -451,25 +518,10 @@ def create_export(self, export_config: schemas.CreateExportParams):
"""
if export_config.export_destination == schemas.ExportDestination.LOCAL:
return self.create_local_export(export_config)

else:
payload = export_config.model_dump()
if not export_config.connection_id or export_config.connection_id == "":
raise LabellerrError("connection_id is required")
payload.update(
{
"question_ids": ["all"],
}
)

response = self.client.make_request(
"POST",
f"{constants.BASE_URL}/sdk/export/files?project_id={self.project_id}&client_id={self.client.client_id}",
extra_headers={"Content-Type": "application/json"},
data=json.dumps(payload),
)
report_id = response.get("response", {}).get("report_id")
return Export(report_id=report_id, project=self)
return self._submit_export_job(export_config)

def create_local_export(self, export_config: schemas.CreateExportParams):
"""
Expand All @@ -479,28 +531,7 @@ def create_local_export(self, export_config: schemas.CreateExportParams):
:return: Export instance with report_id and status tracking
:raises LabellerrError: If the export creation fails
"""

unique_id = client_utils.generate_request_id()

export_config_dict = export_config.model_dump()
export_config_dict.update(
{"export_destination": schemas.ExportDestination.LOCAL.value}
)

payload = json.dumps(export_config_dict)

response = self.client.make_request(
"POST",
f"{constants.BASE_URL}/sdk/export/files?project_id={self.project_id}&client_id={self.client.client_id}",
extra_headers={
"Origin": constants.ALLOWED_ORIGINS,
"Content-Type": "application/json",
},
request_id=unique_id,
data=payload,
)
report_id = response.get("response", {}).get("report_id")
return Export(report_id=report_id, project=self)
return self._submit_export_job(export_config)

def list_exports(self):
"""
Expand Down
4 changes: 2 additions & 2 deletions labellerr/core/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import os
from enum import Enum
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, field_validator
Expand Down Expand Up @@ -340,7 +340,7 @@ class ListFileParams(BaseModel):

client_id: str = Field(min_length=1)
project_id: str = Field(min_length=1)
search_queries: Dict[str, Any]
search_queries: Union[List[Any], Dict[str, Any]]
size: int = Field(default=10, gt=0)
next_search_after: Optional[Any] = None

Expand Down
4 changes: 2 additions & 2 deletions labellerr/core/schemas/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Schema models for file operations.
"""

from typing import Any, Dict, List, Optional
from typing import Any, Dict, List, Optional, Union

from pydantic import BaseModel, Field

Expand All @@ -12,7 +12,7 @@ class ListFileParams(BaseModel):

client_id: str = Field(min_length=1)
project_id: str = Field(min_length=1)
search_queries: Dict[str, Any]
search_queries: Union[List[Any], Dict[str, Any]]
size: int = Field(default=10, gt=0)
next_search_after: Optional[Any] = None

Expand Down
2 changes: 1 addition & 1 deletion tests/unit/test_template_creation.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def test_create_template_single_bbox_question(self, mock_client):
# Verify the create request payload
create_call = mock_request.call_args_list[0]
payload = create_call[1]["json"]
assert payload["templateName"] == "Single BBox Template"
assert payload["template_name"] == "Single BBox Template"
assert len(payload["questions"]) == 1
assert payload["questions"][0]["option_type"] == "BoundingBox"
assert payload["questions"][0]["color"] == "#FF0000"
Expand Down
Loading