Skip to content

Commit 23fa39f

Browse files
Fix pytest
Fix pytest Fix pytest Fix pytest Fix pytest Fix pytest Fix pytest Fix pytest
1 parent f16055b commit 23fa39f

25 files changed

+1348
-832
lines changed

pydatalab/schemas/cell.json

Lines changed: 163 additions & 111 deletions
Large diffs are not rendered by default.

pydatalab/schemas/equipment.json

Lines changed: 140 additions & 97 deletions
Large diffs are not rendered by default.

pydatalab/schemas/sample.json

Lines changed: 144 additions & 101 deletions
Large diffs are not rendered by default.

pydatalab/schemas/startingmaterial.json

Lines changed: 158 additions & 115 deletions
Large diffs are not rendered by default.

pydatalab/src/pydatalab/config.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ class ServerConfig(BaseSettings):
174174

175175
REMOTE_FILESYSTEMS: list[RemoteFilesystem] = Field(
176176
[],
177-
descripton="A list of dictionaries describing remote filesystems to be accessible from the server.",
177+
description="A list of dictionaries describing remote filesystems to be accessible from the server.",
178178
)
179179

180180
REMOTE_CACHE_MAX_AGE: int = Field(
@@ -346,6 +346,15 @@ def make_missing_log_directory(cls, v):
346346
raise RuntimeError(f"Unable to create log file at {v}") from exc
347347
return v
348348

349+
def update(self, values: dict):
350+
"""Update the configuration with new values, following Pydantic v1 behavior."""
351+
for key, value in values.items():
352+
key_upper = key.upper()
353+
if hasattr(self, key_upper):
354+
setattr(self, key_upper, value)
355+
else:
356+
setattr(self, key_upper, value)
357+
349358

350359
CONFIG: ServerConfig = ServerConfig()
351360
"""The global server configuration object.

pydatalab/src/pydatalab/main.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,25 +94,33 @@ def _check_secret_and_warn(secret: str, error: str, environ: bool = False) -> bo
9494
"No GitHub OAuth client secret provided, GitHub login will not work",
9595
):
9696
FEATURE_FLAGS.auth_mechanisms.github = True
97+
else:
98+
FEATURE_FLAGS.auth_mechanisms.github = False
9799
if _check_secret_and_warn(
98100
"ORCID_OAUTH_CLIENT_SECRET",
99101
"No ORCID OAuth client secret provided, ORCID login will not work",
100102
) and _check_secret_and_warn(
101103
"ORCID_OAUTH_CLIENT_ID", "No ORCID OAuth client ID provided, ORCID login will not work"
102104
):
103105
FEATURE_FLAGS.auth_mechanisms.orcid = True
106+
else:
107+
FEATURE_FLAGS.auth_mechanisms.orcid = False
104108
if _check_secret_and_warn(
105109
"OPENAI_API_KEY",
106110
"No OpenAI API key provided, OpenAI-based ChatBlock will not work",
107111
environ=True,
108112
):
109113
FEATURE_FLAGS.ai_integrations.openai = True
114+
else:
115+
FEATURE_FLAGS.ai_integrations.openai = False
110116
if _check_secret_and_warn(
111117
"ANTHROPIC_API_KEY",
112118
"No Anthropic API key provided, Claude-based ChatBlock will not work",
113119
environ=True,
114120
):
115121
FEATURE_FLAGS.ai_integrations.anthropic = True
122+
else:
123+
FEATURE_FLAGS.ai_integrations.anthropic = False
116124

117125
if CONFIG.DEBUG:
118126
LOGGER.warning("Running with debug logs enabled")

pydatalab/src/pydatalab/models/cells.py

Lines changed: 69 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
from typing import Literal
33

44
from pydantic import (
5+
Field,
56
field_validator,
67
model_validator,
78
)
89

9-
from pydatalab.models.entries import EntryReference
1010
from pydatalab.models.items import Item
1111
from pydatalab.models.utils import Constituent
1212

@@ -33,31 +33,33 @@ class Cell(Item):
3333

3434
type: Literal["cells"] = "cells"
3535

36-
cell_format: CellFormat | None
36+
cell_format: CellFormat | None = None
3737
"""The form factor of the cell, e.g., coin, pouch, in situ or otherwise."""
3838

39-
cell_format_description: str | None
39+
cell_format_description: str | None = None
4040
"""Additional human-readable description of the cell form factor, e.g., 18650, AMPIX, CAMPIX"""
4141

42-
cell_preparation_description: str | None
42+
cell_preparation_description: str | None = None
43+
"""Description of how the cell was prepared."""
4344

44-
characteristic_mass: float | None
45+
characteristic_mass: float | None = None
4546
"""The characteristic mass of the cell in milligrams. Can be used to normalize capacities."""
4647

47-
characteristic_chemical_formula: str | None
48+
characteristic_chemical_formula: str | None = None
4849
"""The chemical formula of the active material. Can be used to calculated molar mass in g/mol for normalizing capacities."""
4950

50-
characteristic_molar_mass: float | None
51+
characteristic_molar_mass: float | None = None
5152
"""The molar mass of the active material, in g/mol. Will be inferred from the chemical formula, or can be supplied if it cannot be supplied"""
5253

53-
positive_electrode: list[CellComponent] = []
54-
55-
negative_electrode: list[CellComponent] = []
56-
57-
electrolyte: list[CellComponent] = []
54+
positive_electrode: list[CellComponent] = Field(default_factory=list)
55+
negative_electrode: list[CellComponent] = Field(default_factory=list)
56+
electrolyte: list[CellComponent] = Field(default_factory=list)
5857

5958
active_ion_charge: float = 1
6059

60+
active_ion: str | None = None
61+
"""The active ion species."""
62+
6163
@field_validator("characteristic_molar_mass", mode="before")
6264
@classmethod
6365
def set_molar_mass(cls, v, info):
@@ -75,33 +77,68 @@ def set_molar_mass(cls, v, info):
7577
@model_validator(mode="before")
7678
@classmethod
7779
def add_missing_electrode_relationships(cls, values):
78-
"""Add any missing sample synthesis constituents to parent relationships"""
79-
from pydatalab.models.relationships import RelationshipType, TypedRelationship
80+
"""Add any missing electrode constituents to parent relationships"""
81+
from pydatalab.models.relationships import RelationshipType
8082

8183
existing_parthood_relationship_ids = set()
8284
if values.get("relationships") is not None:
83-
existing_parthood_relationship_ids = {
84-
relationship.refcode or relationship.item_id
85-
for relationship in values["relationships"]
86-
if relationship.relation == RelationshipType.PARTHOOD
87-
}
85+
for relationship in values["relationships"]:
86+
if isinstance(relationship, dict):
87+
relation = relationship.get("relation")
88+
if relation == RelationshipType.PARTHOOD or relation == "is_part_of":
89+
ref_id = relationship.get("refcode") or relationship.get("item_id")
90+
if ref_id:
91+
existing_parthood_relationship_ids.add(ref_id)
92+
else:
93+
if (
94+
hasattr(relationship, "relation")
95+
and relationship.relation == RelationshipType.PARTHOOD
96+
):
97+
ref_id = getattr(relationship, "refcode", None) or getattr(
98+
relationship, "item_id", None
99+
)
100+
if ref_id:
101+
existing_parthood_relationship_ids.add(ref_id)
88102
else:
89103
values["relationships"] = []
90104

91105
for component in ("positive_electrode", "negative_electrode", "electrolyte"):
92106
for constituent in values.get(component, []):
93-
if (
94-
isinstance(constituent.item, EntryReference)
95-
and (constituent.item.refcode or constituent.item.item_id)
96-
not in existing_parthood_relationship_ids
97-
):
98-
relationship = TypedRelationship(
99-
relation=RelationshipType.PARTHOOD,
100-
refcode=constituent.item.refcode,
101-
item_id=constituent.item.item_id,
102-
type=constituent.item.type,
103-
description="Is a constituent of",
104-
)
105-
values["relationships"].append(relationship)
107+
if isinstance(constituent, dict):
108+
item_data = constituent.get("item")
109+
else:
110+
item_data = getattr(constituent, "item", None)
111+
112+
if item_data is None:
113+
continue
114+
115+
if isinstance(item_data, dict):
116+
item_id = item_data.get("item_id")
117+
refcode = item_data.get("refcode")
118+
item_type = item_data.get("type")
119+
120+
if not item_id and not refcode:
121+
continue
122+
123+
constituent_id = refcode or item_id
124+
else:
125+
item_id = getattr(item_data, "item_id", None)
126+
refcode = getattr(item_data, "refcode", None)
127+
item_type = getattr(item_data, "type", None)
128+
129+
if not item_id and not refcode:
130+
continue
131+
132+
constituent_id = refcode or item_id
133+
134+
if constituent_id and constituent_id not in existing_parthood_relationship_ids:
135+
relationship_dict = {
136+
"relation": RelationshipType.PARTHOOD,
137+
"refcode": refcode,
138+
"item_id": item_id,
139+
"type": item_type,
140+
"description": "Is a constituent of",
141+
}
142+
values["relationships"].append(relationship_dict)
106143

107144
return values

pydatalab/src/pydatalab/models/collections.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ class Collection(Entry, HasOwner, HasBlocks):
1616
collection_id: HumanReadableIdentifier = Field(None)
1717
"""A short human-readable/usable name for the collection."""
1818

19-
title: str | None
19+
title: str | None = None
2020
"""A descriptive title for the collection."""
2121

22-
description: str | None
22+
description: str | None = None
2323
"""A description of the collection, either in plain-text or a markup language."""
2424

2525
num_items: int | None = Field(None)

pydatalab/src/pydatalab/models/entries.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,18 @@ class Entry(BaseModel, abc.ABC):
1919
type: str
2020
"""The resource type of the entry."""
2121

22-
immutable_id: PyObjectId = Field(
22+
immutable_id: PyObjectId | None = Field(
2323
None,
2424
title="Immutable ID",
2525
alias="_id",
26-
format="uuid",
26+
json_schema_extra={"format": "uuid"},
2727
)
2828
"""The immutable database ID of the entry."""
2929

3030
last_modified: IsoformatDateTime | None = None
3131
"""The timestamp at which the entry was last modified."""
3232

33-
relationships: list[TypedRelationship] | None = None
33+
relationships: list[TypedRelationship] = Field(default_factory=list)
3434
"""A list of related entries and their types."""
3535

3636
@model_validator(mode="before")
@@ -44,6 +44,13 @@ def check_id_names(cls, values):
4444

4545
return values
4646

47+
@model_validator(mode="after")
48+
def validate_relationships(self):
49+
"""Ensure relationships is always a list."""
50+
if self.relationships is None:
51+
self.relationships = []
52+
return self
53+
4754
def to_reference(self, additional_fields: list[str] | None = None) -> "EntryReference":
4855
"""Populate an EntryReference model from this entry, selecting additional fields to inline.
4956

pydatalab/src/pydatalab/models/equipment.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@ class Equipment(Item):
88

99
type: Literal["equipment"] = "equipment"
1010

11-
serial_numbers: str | None
11+
serial_numbers: str | None = None
1212
"""A string describing one or more serial numbers for the instrument."""
1313

14-
manufacturer: str | None
14+
manufacturer: str | None = None
1515
"""The manufacturer of this piece of equipment"""
1616

17-
location: str | None
17+
location: str | None = None
1818
"""Place where the equipment is located"""
1919

20-
contact: str | None
20+
contact: str | None = None
2121
"""Contact information for equipment (e.g., email address or phone number)."""

0 commit comments

Comments
 (0)