Skip to content

Commit e77fbc7

Browse files
authored
Merge pull request #812 from CitrineInformatics/feature/pla-11071-local-attribute
PLA-11071: Add LocalAttribute to variable definition set
2 parents cf5345b + 1c95e24 commit e77fbc7

File tree

4 files changed

+73
-17
lines changed

4 files changed

+73
-17
lines changed

docs/source/data_extraction.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ There are several ways to define variables that take their values from Attribute
233233
* :class:`~citrine.gemtables.variables.AttributeInOutput`: for when attributes occur both in a process output and one or more of its inputs
234234
* :class:`~citrine.gemtables.variables.IngredientQuantityByProcessAndName`: for the specific case of the volume fraction, mass fraction, number fraction, or absolute quantity of an ingredient
235235
* :class:`~citrine.gemtables.variables.IngredientQuantityInOutput`: for the quantity of an ingredient between the terminal material and a given set of processes (useful for ingredients used in multiple processes)
236+
* :class:`~citrine.gemtables.variables.LocalAttribute`: for retrieving the attribute from the terminal material or its attached process or measurements (useful for attributes found on multiple materials)
236237
* :class:`~citrine.gemtables.variables.LocalIngredientQuantity`: for the quantity of an ingredient used in the process creating the terminal material (useful for ingredients used in multiple processes)
237238

238239
* Identifiers

src/citrine/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '1.57.0'
1+
__version__ = '1.58.0'

src/citrine/gemtables/variables.py

Lines changed: 70 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Variable definitions for GEM Tables."""
22
from abc import abstractmethod
3-
from typing import Type, Optional, List, Union
3+
from typing import Type, Optional, List, Union, Tuple
44
from uuid import UUID
55

66
from deprecation import deprecated
@@ -78,9 +78,9 @@ def get_type(cls, data) -> Type[Serializable]:
7878
raise ValueError("Can only get types from dicts with a 'type' key")
7979
types: List[Type[Serializable]] = [
8080
TerminalMaterialInfo, AttributeByTemplate, AttributeByTemplateAfterProcessTemplate,
81-
AttributeByTemplateAndObjectTemplate, IngredientIdentifierByProcessTemplateAndName,
82-
IngredientLabelByProcessAndName, IngredientLabelsSetByProcessAndName,
83-
IngredientQuantityByProcessAndName,
81+
AttributeByTemplateAndObjectTemplate, LocalAttribute,
82+
IngredientIdentifierByProcessTemplateAndName, IngredientLabelByProcessAndName,
83+
IngredientLabelsSetByProcessAndName, IngredientQuantityByProcessAndName,
8484
TerminalMaterialIdentifier, AttributeInOutput,
8585
IngredientIdentifierInOutput, IngredientLabelsSetInOutput, IngredientQuantityInOutput,
8686
LocalIngredientIdentifier, LocalIngredientLabelsSet, LocalIngredientQuantity,
@@ -143,7 +143,7 @@ class AttributeByTemplate(Serializable['AttributeByTemplate'], Variable):
143143
sequence of column headers
144144
template: Union[UUID, str, LinkByUID, AttributeTemplate]
145145
attribute template that identifies the attribute to assign to the variable
146-
attribute_constraints: list[list[Union[UUID, str, LinkByUID, AttributeTemplate], Bounds]]
146+
attribute_constraints: List[Tuple[Union[UUID, str, LinkByUID, AttributeTemplate], Bounds]]
147147
Optional
148148
constraints on object attributes in the target object that must be satisfied. Constraints
149149
are expressed as Bounds. Attributes are expressed with links. The attribute that the
@@ -166,7 +166,7 @@ class AttributeByTemplate(Serializable['AttributeByTemplate'], Variable):
166166
typ = properties.String('type', default="attribute_by_template", deserializable=False)
167167

168168
attribute_type = Union[UUID, str, LinkByUID, AttributeTemplate]
169-
constraint_type = Union[attribute_type, BaseBounds]
169+
constraint_type = Tuple[attribute_type, BaseBounds]
170170

171171
def _attrs(self) -> List[str]:
172172
return ["name", "headers", "template", "attribute_constraints", "type_selector", "typ"]
@@ -176,7 +176,7 @@ def __init__(self,
176176
*,
177177
headers: List[str],
178178
template: attribute_type,
179-
attribute_constraints: Optional[List[List[constraint_type]]] = None,
179+
attribute_constraints: Optional[List[constraint_type]] = None,
180180
type_selector: DataObjectTypeSelector = DataObjectTypeSelector.PREFER_RUN):
181181
self.name = name
182182
self.headers = headers
@@ -200,7 +200,7 @@ class AttributeByTemplateAfterProcessTemplate(
200200
attribute template that identifies the attribute to assign to the variable
201201
process_template: Union[UUID, str, LinkByUID, ProcessTemplate]
202202
process template that identifies the originating process
203-
attribute_constraints: list[list[Union[UUID, str, LinkByUID, AttributeTemplate], Bounds]]
203+
attribute_constraints: List[Tuple[Union[UUID, str, LinkByUID, AttributeTemplate], Bounds]]
204204
Optional
205205
constraints on object attributes in the target object that must be satisfied. Constraints
206206
are expressed as Bounds. Attributes are expressed with links. The attribute that the
@@ -225,7 +225,7 @@ class AttributeByTemplateAfterProcessTemplate(
225225

226226
attribute_type = Union[UUID, str, LinkByUID, AttributeTemplate]
227227
process_type = Union[UUID, str, LinkByUID, ProcessTemplate]
228-
constraint_type = Union[attribute_type, BaseBounds]
228+
constraint_type = Tuple[attribute_type, BaseBounds]
229229

230230
def _attrs(self) -> List[str]:
231231
return ["name", "headers", "attribute_template", "process_template",
@@ -237,7 +237,7 @@ def __init__(self,
237237
headers: List[str],
238238
attribute_template: attribute_type,
239239
process_template: process_type,
240-
attribute_constraints: Optional[List[List[constraint_type]]] = None,
240+
attribute_constraints: Optional[List[constraint_type]] = None,
241241
type_selector: DataObjectTypeSelector = DataObjectTypeSelector.PREFER_RUN):
242242
self.name = name
243243
self.headers = headers
@@ -267,7 +267,7 @@ class AttributeByTemplateAndObjectTemplate(
267267
attribute template that identifies the attribute to assign to the variable
268268
object_template: Union[UUID, str, LinkByUID, BaseTemplate]
269269
template that identifies the associated object
270-
attribute_constraints: list[list[Union[UUID, str, LinkByUID, AttributeTemplate], Bounds]]
270+
attribute_constraints: List[Tuple[Union[UUID, str, LinkByUID, AttributeTemplate], Bounds]]
271271
Optional
272272
constraints on object attributes in the target object that must be satisfied. Constraints
273273
are expressed as Bounds. Attributes are expressed with links. The attribute that the
@@ -292,7 +292,7 @@ class AttributeByTemplateAndObjectTemplate(
292292

293293
attribute_type = Union[UUID, str, LinkByUID, AttributeTemplate]
294294
object_type = Union[UUID, str, LinkByUID, BaseTemplate]
295-
constraint_type = Union[attribute_type, BaseBounds]
295+
constraint_type = Tuple[attribute_type, BaseBounds]
296296

297297
def _attrs(self) -> List[str]:
298298
return ["name", "headers", "attribute_template", "object_template",
@@ -304,7 +304,7 @@ def __init__(self,
304304
headers: List[str],
305305
attribute_template: attribute_type,
306306
object_template: object_type,
307-
attribute_constraints: Optional[List[List[constraint_type]]] = None,
307+
attribute_constraints: Optional[List[constraint_type]] = None,
308308
type_selector: DataObjectTypeSelector = DataObjectTypeSelector.PREFER_RUN):
309309
self.name = name
310310
self.headers = headers
@@ -315,6 +315,60 @@ def __init__(self,
315315
self.type_selector = type_selector
316316

317317

318+
class LocalAttribute(Serializable['LocalAttribute'], Variable):
319+
"""[ALPHA] Attribute marked by an attribute template for the root of a material history tree.
320+
321+
Parameters
322+
----------
323+
name: str
324+
a short human-readable name to use when referencing the variable
325+
headers: list[str]
326+
sequence of column headers
327+
template: Union[UUID, str, LinkByUID, AttributeTemplate]
328+
attribute template that identifies the attribute to assign to the variable
329+
attribute_constraints: List[Tuple[Union[UUID, str, LinkByUID, AttributeTemplate], Bounds]]
330+
Optional
331+
constraints on object attributes in the target object that must be satisfied. Constraints
332+
are expressed as Bounds. Attributes are expressed with links. The attribute that the
333+
variable is being set to may be the target of a constraint as well.
334+
type_selector: DataObjectTypeSelector
335+
strategy for selecting data object types to consider when matching, defaults to PREFER_RUN
336+
337+
"""
338+
339+
name = properties.String('name')
340+
headers = properties.List(properties.String, 'headers')
341+
template = properties.Object(LinkByUID, 'template')
342+
attribute_constraints = properties.Optional(
343+
properties.List(
344+
properties.SpecifiedMixedList(
345+
[properties.Object(LinkByUID), properties.Object(BaseBounds)]
346+
)
347+
), 'attribute_constraints')
348+
type_selector = properties.Enumeration(DataObjectTypeSelector, "type_selector")
349+
typ = properties.String('type', default="local_attribute", deserializable=False)
350+
351+
attribute_type = Union[UUID, str, LinkByUID, AttributeTemplate]
352+
constraint_type = Tuple[attribute_type, BaseBounds]
353+
354+
def _attrs(self) -> List[str]:
355+
return ["name", "headers", "template", "attribute_constraints", "type_selector", "typ"]
356+
357+
def __init__(self,
358+
name: str,
359+
*,
360+
headers: List[str],
361+
template: attribute_type,
362+
attribute_constraints: Optional[List[constraint_type]] = None,
363+
type_selector: DataObjectTypeSelector = DataObjectTypeSelector.PREFER_RUN):
364+
self.name = name
365+
self.headers = headers
366+
self.template = _make_link_by_uid(template)
367+
self.attribute_constraints = None if attribute_constraints is None \
368+
else [(_make_link_by_uid(x[0]), x[1]) for x in attribute_constraints]
369+
self.type_selector = type_selector
370+
371+
318372
class IngredientIdentifierByProcessTemplateAndName(
319373
Serializable['IngredientIdentifierByProcessAndName'], Variable):
320374
"""[ALPHA] Ingredient identifier associated with a process template and a name.
@@ -614,7 +668,7 @@ class AttributeInOutput(Serializable['AttributeInOutput'], Variable):
614668
process_templates: list[LinkByUID]
615669
process templates that should not be traversed through when searching for a matching
616670
attribute. The attribute may be present in these processes but not their ingredients.
617-
attribute_constraints: list[list[Union[UUID, str, LinkByUID, AttributeTemplate], Bounds]]
671+
attribute_constraints: List[Tuple[Union[UUID, str, LinkByUID, AttributeTemplate], Bounds]]
618672
Optional
619673
constraints on object attributes in the target object that must be satisfied. Constraints
620674
are expressed as Bounds. Attributes are expressed with links. The attribute that the
@@ -639,7 +693,7 @@ class AttributeInOutput(Serializable['AttributeInOutput'], Variable):
639693

640694
attribute_type = Union[UUID, str, LinkByUID, AttributeTemplate]
641695
process_type = Union[UUID, str, LinkByUID, ProcessTemplate]
642-
constraint_type = Union[attribute_type, BaseBounds]
696+
constraint_type = Tuple[attribute_type, BaseBounds]
643697

644698
def _attrs(self) -> List[str]:
645699
return ["name", "headers", "attribute_template", "process_templates",
@@ -651,7 +705,7 @@ def __init__(self,
651705
headers: List[str],
652706
attribute_template: attribute_type,
653707
process_templates: List[process_type],
654-
attribute_constraints: Optional[List[List[constraint_type]]] = None,
708+
attribute_constraints: Optional[List[constraint_type]] = None,
655709
type_selector: DataObjectTypeSelector = DataObjectTypeSelector.PREFER_RUN):
656710
self.name = name
657711
self.headers = headers

tests/gemtable/test_variables.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
AttributeByTemplateAfterProcessTemplate(name="density", headers=["density"], attribute_template=LinkByUID(scope="template", id="density"), process_template=LinkByUID(scope="template", id="process")),
1414
AttributeByTemplateAndObjectTemplate(name="density", headers=["density"], attribute_template=LinkByUID(scope="template", id="density"), object_template=LinkByUID(scope="template", id="object")),
1515
AttributeInOutput(name="density", headers=["density"], attribute_template=LinkByUID(scope="template", id="density"), process_templates=[LinkByUID(scope="template", id="object")]),
16+
LocalAttribute(name="density", headers=["density"], template=LinkByUID(scope="templates", id="density"), attribute_constraints=[[LinkByUID(scope="templates", id="density"), RealBounds(0, 100, "g/cm**3")]]),
1617
IngredientIdentifierByProcessTemplateAndName(name="ingredient id", headers=["density"], process_template=LinkByUID(scope="template", id="process"), ingredient_name="ingredient", scope="scope"),
1718
IngredientIdentifierInOutput(name="ingredient id", headers=["ingredient id"], ingredient_name="ingredient", process_templates=[LinkByUID(scope="template", id="object")], scope="scope"),
1819
LocalIngredientIdentifier(name="ingredient id", headers=["ingredient id"], ingredient_name="ingredient", scope="scope"),

0 commit comments

Comments
 (0)