Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
f53e28f
Various custom template, project and task changes
TomAlbertInvent Aug 1, 2025
dd6d36f
Delete .vscode/launch.json
TomAlbertInvent Aug 1, 2025
4974bbb
Merge branch 'main' into ct-task-project-migration-updates
TomAlbertInvent Aug 1, 2025
6ac42f6
Merge
TomAlbertInvent Aug 4, 2025
62fef2c
Edits
TomAlbertInvent Aug 5, 2025
6671f1f
Version Bump
TomAlbertInvent Aug 7, 2025
dde33eb
Version Bump
TomAlbertInvent Aug 7, 2025
071561b
Formatting
TomAlbertInvent Aug 7, 2025
8b50cbe
Merge branch 'main' into ct-task-project-migration-updates
TomAlbertInvent Aug 11, 2025
cbae027
Formatting
TomAlbertInvent Aug 11, 2025
af3f028
Merge branch 'ct-task-project-migration-updates' of https://github.co…
TomAlbertInvent Aug 11, 2025
cfc9dd2
Update __init__.py
TomAlbertInvent Aug 11, 2025
72acc57
Updated NotebookData
TomAlbertInvent Aug 13, 2025
7812ddf
Version Bump
TomAlbertInvent Aug 13, 2025
432f869
pyTest Changes
TomAlbertInvent Aug 25, 2025
da87879
Merge remote-tracking branch 'origin/main' into ct-task-project-migra…
TomAlbertInvent Aug 25, 2025
2abc3d6
Update __init__.py
TomAlbertInvent Aug 25, 2025
8303e49
Formatting
TomAlbertInvent Aug 25, 2025
9713ffd
Merge branch 'ct-task-project-migration-updates' of https://github.co…
TomAlbertInvent Aug 25, 2025
4b0a7e4
Update seeding.py
TomAlbertInvent Aug 25, 2025
1ccce05
Update src/albert/collections/custom_templates.py
TomAlbertInvent Sep 8, 2025
9900eda
Update custom_templates.py
TomAlbertInvent Sep 8, 2025
d9bc7a2
Update custom_templates.py
TomAlbertInvent Sep 8, 2025
ae5b307
Update __init__.py
TomAlbertInvent Sep 8, 2025
9a97152
Update custom_templates.py
TomAlbertInvent Sep 8, 2025
69f9138
Merge branch 'ct-task-project-migration-updates' of https://github.co…
TomAlbertInvent Sep 8, 2025
ef78ee4
Update custom_templates.py
TomAlbertInvent Sep 8, 2025
33f317f
Update custom_templates.py
TomAlbertInvent Sep 8, 2025
2291c74
Merge branch 'main' into ct-task-project-migration-updates
TomAlbertInvent Sep 11, 2025
b870c02
Removed Custom Template Seed Function
TomAlbertInvent Sep 11, 2025
a1c0297
Removed Unused imports
TomAlbertInvent Sep 11, 2025
19cc1ea
Update conftest.py
TomAlbertInvent Sep 11, 2025
c1e8ac6
Update conftest.py
TomAlbertInvent Sep 11, 2025
5203b2d
Update conftest.py
TomAlbertInvent Sep 11, 2025
b5395e1
Update conftest.py
TomAlbertInvent Sep 12, 2025
82b2080
Update custom_templates.py
TomAlbertInvent Sep 18, 2025
b7e4000
Update __init__.py
TomAlbertInvent Sep 18, 2025
8f5ff2e
Update custom_templates.py
TomAlbertInvent Sep 18, 2025
17b2b7e
Update _patch.py
TomAlbertInvent Sep 18, 2025
4fadcb3
Update _patch.py
TomAlbertInvent Sep 18, 2025
cdfded9
Sheet and ACL edits
TomAlbertInvent Oct 2, 2025
a47f813
Update __init__.py
TomAlbertInvent Oct 2, 2025
a222774
Fixed data templates
TomAlbertInvent Oct 8, 2025
c5a3fcc
Fixed workflow return errors.
TomAlbertInvent Oct 9, 2025
50b6b2d
Update data_templates.py
TomAlbertInvent Oct 9, 2025
7564462
Update sheets.py
TomAlbertInvent Oct 10, 2025
81713b4
Add IMAGE type to DataType enum and refactor patch logic
tmtAlbert Oct 16, 2025
25aa71c
Update sheets.py
TomAlbertInvent Oct 17, 2025
e66c75e
Rename ACL field and extend parent_id types
TomAlbertInvent Oct 27, 2025
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
2 changes: 1 addition & 1 deletion src/albert/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@

__all__ = ["Albert", "AlbertClientCredentials", "AlbertSSOClient"]

__version__ = "1.5.5"
__version__ = "1.6.6"
45 changes: 45 additions & 0 deletions src/albert/collections/companies.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

from collections.abc import Iterator

from pydantic import validate_call
Expand Down Expand Up @@ -249,3 +251,46 @@ def update(self, *, company: Company) -> Company:
self.session.patch(url, json=patch_payload.model_dump(mode="json", by_alias=True))
updated_company = self.get_by_id(id=company.id)
return updated_company

def merge(self, *, parent_id: str, child_ids: str | list[str]) -> Company:
"""
Merge one or more child companies into a single parent company.

Parameters
----------
parent_id : str
The ID of the company that will remain as the parent.
child_ids : Union[str, List[str]]
A single company ID or a list of company IDs to merge into the parent.

Returns
-------
Company
The updated parent Company object.
"""
# allow passing a single ID as a string
if isinstance(child_ids, str):
child_ids = [child_ids]

if not child_ids:
msg = "At least one child company ID must be provided for merge."
logger.error(msg)
raise AlbertException(msg)

payload = {
"parentId": parent_id,
"ChildCompanies": [{"id": child_id} for child_id in child_ids],
}

url = f"{self.base_path}/merge"
response = self.session.post(url, json=payload)
if response.status_code == 206:
msg = "Merge returned partial content (206). Check that all ACLs are valid."
logger.error(msg)
raise AlbertException(msg)
response.raise_for_status()

try:
return Company(**response.json())
except (ValueError, TypeError):
return self.get_by_id(id=parent_id)
55 changes: 55 additions & 0 deletions src/albert/collections/custom_templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,44 @@ def search(
],
)

def create(self, *, custom_template: list[CustomTemplate]) -> list[CustomTemplate]:
"""Creates a new custom template.

Parameters
----------
custom_template : CustomTemplate
The custom template to create.

Returns
-------
CustomTemplate
The created CustomTemplate object.
"""

response = self.session.post(
url=self.base_path,
json=[
custom_template.model_dump(
mode="json", by_alias=True, exclude_unset=True, exclude_none=True
)
],
)
obj = response.json()[0]
tags = (obj.get("Data")).get("Tags") or []

def _resolve_tags(tid: str) -> dict:
r = self.session.get(url=f"/api/v3/tags/{tid}")
if r.ok:
d = r.json()
item = (d.get("Items") or [d])[0] if isinstance(d, dict) and "Items" in d else d
return {"albertId": item.get("albertId", tid), "name": item.get("name")}
return {"albertId": tid}

if tags:
obj["Data"]["Tags"] = [_resolve_tags(t.get("id")) for t in tags]

return CustomTemplate(**obj)

def get_all(
self,
*,
Expand Down Expand Up @@ -120,3 +158,20 @@ def get_all(
yield self.get_by_id(id=item.id)
except AlbertHTTPError as e:
logger.warning(f"Error hydrating custom template {item.id}: {e}")

def delete(self, *, id: CustomTemplateId) -> None:
"""
Delete a Custom Template by ID.

Parameters
----------
id : str
The Albert ID of the custom template to delete.

Raises
------
AlbertHTTPError
If the API responds with a non-2xx status (e.g., 404 if not found).
"""
url = f"{self.base_path}/{id}"
self.session.delete(url)
11 changes: 8 additions & 3 deletions src/albert/collections/data_templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,9 @@ def _add_param_enums(
data_template = self.get_by_id(id=data_template_id)
existing_parameters = data_template.parameter_values

for parameter in new_parameters:
all_results: list[EnumValidationValue] = []

for index, parameter in enumerate(new_parameters, start=1):
this_sequence = next(
(
p.sequence
Expand All @@ -102,6 +104,7 @@ def _add_param_enums(
),
None,
)
rowId = f"ROW{index}"
enum_patches = []
if (
parameter.validation
Expand Down Expand Up @@ -170,10 +173,12 @@ def _add_param_enums(

if len(enum_patches) > 0:
enum_response = self.session.put(
f"{self.base_path}/{data_template_id}/parameters/{this_sequence}/enums",
f"{self.base_path}/{data_template_id}/parameters/{rowId}/enums",
json=enum_patches,
)
return [EnumValidationValue(**x) for x in enum_response.json()]
all_results.extend([EnumValidationValue(**x) for x in enum_response.json()])

return all_results

@validate_call
def get_by_id(self, *, id: DataTemplateId) -> DataTemplate:
Expand Down
1 change: 1 addition & 0 deletions src/albert/collections/notebooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
PutBlockPayload,
PutOperation,
)
from albert.resources.notebooks import BlockType


class NotebookCollection(BaseCollection):
Expand Down
35 changes: 35 additions & 0 deletions src/albert/collections/parameter_groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,41 @@ def update(self, *, parameter_group: ParameterGroup) -> ParameterGroup:
url=enum_url,
json=ep,
)
required_params: list[dict] = []
for existing_param in existing.parameters:
# find the matching updated param by its row_id
updated_param = next(
(
parameter
for parameter in parameter_group.parameters
if parameter.sequence == existing_param.sequence
),
None,
)
if not updated_param:
continue

if existing_param.required != updated_param.required:
required_params = [
{
"operation": "update",
"attribute": "required",
"rowId": existing_param.sequence,
"oldValue": existing_param.required,
"newValue": updated_param.required,
}]

self.session.patch(
url=path,
json={"data": required_params},
)

# if required_params:
# self.session.patch(
# url=path,
# json={"data": required_params},
# )

if len(general_patches.data) > 0:
# patch the general patches
self.session.patch(
Expand Down
5 changes: 4 additions & 1 deletion src/albert/collections/workflows.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,10 @@ def create(self, *, workflows: list[Workflow]) -> list[Workflow]:
for x in workflows
],
)
return [Workflow(**x) for x in response.json()]
try:
return [Workflow(**x) for x in response.json()]
except Exception:
return [Workflow(id=response.json()[0].get('existingAlbertId') or response.json()[0].get('albertId'),name=response.json()[0].get('name'),ParameterGroups=[])]

def _hydrate_parameter_groups(self, *, workflow: Workflow) -> None:
"""Populate parameter setpoints when only an ID is provided."""
Expand Down
21 changes: 15 additions & 6 deletions src/albert/resources/custom_templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class GeneralData(BaseTaggedResource):
priority: Priority | None = Field(default=None)
sources: list[TaskSource] | None = Field(alias="Sources", default=None)
parent_id: str | None = Field(alias="parentId", default=None)
notes: str | None = Field(default=None)


class JobStatus(str, Enum):
Expand Down Expand Up @@ -114,6 +115,8 @@ class BatchData(BaseTaggedResource):
inventories: list[DataTemplateInventory] | None = Field(default=None, alias="Inventories")
priority: Priority # enum?!
workflow: list[EntityLink] = Field(default=None, alias="Workflow")
notes: str | None = Field(default=None)
due_date: str | None = Field(alias="dueDate", default=None)


class PropertyData(BaseTaggedResource):
Expand All @@ -126,6 +129,7 @@ class PropertyData(BaseTaggedResource):
project: SerializeAsEntityLink[Project] | None = Field(alias="Project", default=None)
inventories: list[DataTemplateInventory] | None = Field(default=None, alias="Inventories")
due_date: str | None = Field(alias="dueDate", default=None)
notes: str | None = Field(default=None)


class SheetData(BaseTaggedResource):
Expand All @@ -136,6 +140,7 @@ class SheetData(BaseTaggedResource):


class NotebookData(BaseTaggedResource):
id: str
category: Literal[TemplateCategory.NOTEBOOK] = TemplateCategory.NOTEBOOK


Expand All @@ -153,22 +158,26 @@ class ACLType(str, Enum):


class TeamACL(ACL):
type: Literal[ACLType.TEAM] = ACLType.TEAM
# accept either backend token or SDK enum value
type: Literal[ACLType.TEAM, "CustomTemplateTeam"] = ACLType.TEAM


class OwnerACL(ACL):
type: Literal[ACLType.OWNER] = ACLType.OWNER
type: Literal[ACLType.OWNER, "CustomTemplateOwner"] = ACLType.OWNER


class MemberACL(ACL):
type: Literal[ACLType.MEMBER] = ACLType.MEMBER
type: Literal[ACLType.MEMBER, "CustomTemplateMember"] = ACLType.MEMBER


class ViewerACL(ACL):
type: Literal[ACLType.VIEWER] = ACLType.VIEWER
type: Literal[ACLType.VIEWER, "CustomTemplateViewer"] = ACLType.VIEWER


ACLEntry = Annotated[TeamACL | OwnerACL | MemberACL | ViewerACL, Field(discriminator="type")]
ACLEntry = Annotated[
TeamACL | OwnerACL | MemberACL | ViewerACL,
Field(discriminator="type"),
]


class TemplateACL(BaseResource):
Expand Down Expand Up @@ -198,7 +207,7 @@ class CustomTemplate(BaseTaggedResource):
"""

name: str
id: CustomTemplateId = Field(alias="albertId")
id: str | None = Field(default=None, alias="albertId")
category: TemplateCategory = Field(default=TemplateCategory.GENERAL)
metadata: dict[str, MetadataItem] | None = Field(default=None, alias="Metadata")
data: CustomTemplateData | None = Field(default=None, alias="Data")
Expand Down
2 changes: 1 addition & 1 deletion src/albert/resources/data_templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class DataTemplate(BaseTaggedResource):
description: str | None = None
security_class: SecurityClass | None = None
verified: bool = False
users_with_access: list[SerializeAsEntityLink[User]] | None = Field(alias="ACL", default=None)
acl: list[SerializeAsEntityLink[User]] | None = Field(alias="ACL", default=None)
data_column_values: list[DataColumnValue] | None = Field(alias="DataColumns", default=None)
parameter_values: list[ParameterValue] | None = Field(alias="Parameters", default=None)
deleted_parameters: list[ParameterValue] | None = Field(
Expand Down
4 changes: 2 additions & 2 deletions src/albert/resources/notebooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from pydantic import Field, model_validator

from albert.core.base import BaseAlbertModel
from albert.core.shared.identifiers import LinkId, NotebookId, ProjectId, SynthesisId, TaskId
from albert.core.shared.identifiers import LinkId, NotebookId, ProjectId, SynthesisId, TaskId, CustomTemplateId
from albert.core.shared.models.base import BaseResource, EntityLink
from albert.exceptions import AlbertException
from albert.resources.acls import ACL
Expand Down Expand Up @@ -220,7 +220,7 @@ class NotebookLink(BaseAlbertModel):
class Notebook(BaseResource):
id: NotebookId | None = Field(default=None, alias="albertId")
name: str = Field(default="Untitled Notebook")
parent_id: ProjectId | TaskId = Field(..., alias="parentId")
parent_id: ProjectId | TaskId | CustomTemplateId = Field(..., alias="parentId")
version: datetime | None = Field(default=None)
blocks: list[NotebookBlock] = Field(default_factory=list)
links: list[NotebookLink] | None = Field(default=None)
Expand Down
2 changes: 2 additions & 0 deletions src/albert/resources/parameter_groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class DataType(str, Enum):
NUMBER = "number"
STRING = "string"
ENUM = "enum"
IMAGE = "image"


class Operator(str, Enum):
Expand Down Expand Up @@ -99,6 +100,7 @@ class ParameterValue(BaseAlbertModel):
unit: SerializeAsEntityLink[Unit] | None = Field(alias="Unit", default=None)
added: AuditFields | None = Field(alias="Added", default=None, exclude=True)
validation: list[ValueValidation] | None = Field(default_factory=list)
required: bool | None = Field(default=False)

# Read-only fields
name: str | None = Field(default=None, exclude=True, frozen=True)
Expand Down
Loading