Skip to content

Commit f7eedc0

Browse files
committed
Update alignerr project factory and tests
1 parent 42ca995 commit f7eedc0

File tree

3 files changed

+567
-4
lines changed

3 files changed

+567
-4
lines changed

libs/labelbox/src/labelbox/alignerr/alignerr_project_factory.py

Lines changed: 132 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import datetime
2-
from typing import TYPE_CHECKING
2+
from typing import TYPE_CHECKING, Union, List
33
import yaml
44
from pathlib import Path
55
import logging
66

77
from labelbox.alignerr.schema.project_rate import BillingMode
8+
from labelbox.alignerr.schema.enchanced_resource_tags import ResourceTagType
89
from labelbox.schema.media_type import MediaType
910

1011
logger = logging.getLogger(__name__)
@@ -18,21 +19,43 @@ class AlignerrProjectFactory:
1819
def __init__(self, client: "Client"):
1920
self.client = client
2021

21-
def create(self, yaml_file_path: str, skip_validation: bool = False):
22+
def create(self, yaml_file_path: str, skip_validation: Union[bool, List] = False):
2223
"""
2324
Create an AlignerrProject from a YAML configuration file.
2425
2526
Args:
2627
yaml_file_path: Path to the YAML configuration file
27-
skip_validation: Whether to skip validation of required fields
28+
skip_validation: Whether to skip validation of required fields. Can be:
29+
- bool: Skip all validations (True) or run all validations (False)
30+
- List: Skip specific validations (e.g., [ValidationType.PROJECT_OWNER])
2831
2932
Returns:
30-
AlignerrProject: The created project with configured rates
33+
AlignerrProject: The created project with all configured attributes
3134
3235
Raises:
3336
FileNotFoundError: If the YAML file doesn't exist
3437
yaml.YAMLError: If the YAML file is invalid
3538
ValueError: If required fields are missing or invalid
39+
40+
YAML Configuration Structure:
41+
name: str (required) - Project name
42+
media_type: str (required) - Media type (e.g., "Image", "Video", "Text")
43+
rates: dict (optional) - Alignerr role rates
44+
role_name:
45+
rate: float
46+
billing_mode: str
47+
effective_since: str (ISO datetime)
48+
effective_until: str (optional, ISO datetime)
49+
customer_rate: dict (optional) - Customer billing rate
50+
rate: float
51+
billing_mode: str
52+
effective_since: str (ISO datetime)
53+
effective_until: str (optional, ISO datetime)
54+
domains: list[str] (optional) - Project domain names
55+
tags: list[dict] (optional) - Enhanced resource tags
56+
- text: str
57+
type: str (ResourceTagType enum value)
58+
project_owner: str (optional) - Project owner email address
3659
"""
3760
logger.info(f"Creating project from YAML file: {yaml_file_path}")
3861

@@ -150,5 +173,110 @@ def create(self, yaml_file_path: str, skip_validation: bool = False):
150173
effective_until=effective_until,
151174
)
152175

176+
# Set customer rate if provided
177+
if "customer_rate" in config:
178+
customer_rate_config = config["customer_rate"]
179+
if not isinstance(customer_rate_config, dict):
180+
raise ValueError("'customer_rate' must be a dictionary")
181+
182+
# Validate customer rate configuration
183+
required_customer_rate_fields = [
184+
"rate",
185+
"billing_mode",
186+
"effective_since",
187+
]
188+
for field in required_customer_rate_fields:
189+
if field not in customer_rate_config:
190+
raise ValueError(
191+
f"Required field '{field}' is missing for customer_rate"
192+
)
193+
194+
# Parse billing mode
195+
try:
196+
billing_mode = BillingMode(customer_rate_config["billing_mode"])
197+
except ValueError:
198+
raise ValueError(
199+
f"Invalid billing_mode '{customer_rate_config['billing_mode']}' for customer_rate. Must be one of: {[e.value for e in BillingMode]}"
200+
)
201+
202+
# Parse effective dates
203+
try:
204+
effective_since = datetime.datetime.fromisoformat(
205+
customer_rate_config["effective_since"]
206+
)
207+
except ValueError:
208+
raise ValueError(
209+
f"Invalid effective_since date format for customer_rate. Use ISO format (YYYY-MM-DDTHH:MM:SS)"
210+
)
211+
212+
effective_until = None
213+
if (
214+
"effective_until" in customer_rate_config
215+
and customer_rate_config["effective_until"]
216+
):
217+
try:
218+
effective_until = datetime.datetime.fromisoformat(
219+
customer_rate_config["effective_until"]
220+
)
221+
except ValueError:
222+
raise ValueError(
223+
f"Invalid effective_until date format for customer_rate. Use ISO format (YYYY-MM-DDTHH:MM:SS)"
224+
)
225+
226+
# Set the customer rate
227+
builder.set_customer_rate(
228+
rate=float(customer_rate_config["rate"]),
229+
billing_mode=billing_mode,
230+
effective_since=effective_since,
231+
effective_until=effective_until,
232+
)
233+
234+
# Set domains if provided
235+
if "domains" in config:
236+
domains_config = config["domains"]
237+
if not isinstance(domains_config, list):
238+
raise ValueError("'domains' must be a list")
239+
240+
if not all(isinstance(domain, str) for domain in domains_config):
241+
raise ValueError("All domain names must be strings")
242+
243+
builder.set_domains(domains_config)
244+
245+
# Set enhanced resource tags if provided
246+
if "tags" in config:
247+
tags_config = config["tags"]
248+
if not isinstance(tags_config, list):
249+
raise ValueError("'tags' must be a list")
250+
251+
for tag_config in tags_config:
252+
if not isinstance(tag_config, dict):
253+
raise ValueError("Each tag must be a dictionary")
254+
255+
required_tag_fields = ["text", "type"]
256+
for field in required_tag_fields:
257+
if field not in tag_config:
258+
raise ValueError(
259+
f"Required field '{field}' is missing for tag"
260+
)
261+
262+
# Validate tag type
263+
try:
264+
tag_type = ResourceTagType(tag_config["type"])
265+
except ValueError:
266+
raise ValueError(
267+
f"Invalid tag type '{tag_config['type']}'. Must be one of: {[e.value for e in ResourceTagType]}"
268+
)
269+
270+
# Set the tag
271+
builder.set_tags([tag_config["text"]], tag_type)
272+
273+
# Set project owner if provided
274+
if "project_owner" in config:
275+
project_owner_config = config["project_owner"]
276+
if not isinstance(project_owner_config, str):
277+
raise ValueError("'project_owner' must be a string (email address)")
278+
279+
builder.set_project_owner(project_owner_config)
280+
153281
# Create the project
154282
return builder.create(skip_validation=skip_validation)
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: "TestComprehensiveProject"
2+
media_type: "Image"
3+
4+
# Alignerr role rates
5+
rates:
6+
LABELER:
7+
rate: 15.0
8+
billing_mode: "BY_HOUR"
9+
effective_since: "2024-01-01T00:00:00"
10+
effective_until: "2024-12-31T23:59:59"
11+
REVIEWER:
12+
rate: 20.0
13+
billing_mode: "BY_HOUR"
14+
effective_since: "2024-01-01T00:00:00"
15+
16+
# Customer billing rate
17+
customer_rate:
18+
rate: 25.0
19+
billing_mode: "BY_HOUR"
20+
effective_since: "2024-01-01T00:00:00"
21+
effective_until: "2024-12-31T23:59:59"
22+
23+
# Project domains (will be created if they don't exist)
24+
domains:
25+
- "TestDomain1"
26+
- "TestDomain2"
27+
28+
# Enhanced resource tags
29+
tags:
30+
- text: "TestTag1"
31+
type: "Default"
32+
- text: "TestTag2"
33+
type: "Billing"
34+
- text: "TestTag3"
35+
type: "System"
36+
37+
# Project owner (will use current user's email in tests)
38+
project_owner: "test@example.com"

0 commit comments

Comments
 (0)