Skip to content

Commit af74642

Browse files
authored
Merge pull request #926 from CitrineInformatics/feature/pla-13437-add-gemd-query
Implement GemdQuery obejcts for table building
2 parents 3e19066 + ef55b55 commit af74642

File tree

9 files changed

+442
-1
lines changed

9 files changed

+442
-1
lines changed

src/citrine/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "3.0.2"
1+
__version__ = "3.1.0"

src/citrine/gemd_queries/__init__.py

Whitespace-only changes.
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
"""Definitions for GemdQuery objects, and their sub-objects."""
2+
from typing import List, Type
3+
4+
from gemd.enumeration.base_enumeration import BaseEnumeration
5+
6+
from citrine._serialization.serializable import Serializable
7+
from citrine._serialization.polymorphic_serializable import PolymorphicSerializable
8+
from citrine._serialization import properties
9+
from citrine.gemd_queries.filter import PropertyFilterType
10+
11+
__all__ = ['MaterialClassification', 'TextSearchType',
12+
'AndOperator', 'OrOperator',
13+
'PropertiesCriteria', 'NameCriteria',
14+
'MaterialRunClassificationCriteria', 'MaterialTemplatesCriteria'
15+
]
16+
17+
18+
class MaterialClassification(BaseEnumeration):
19+
"""A classification based on where in a Material History you find a Material."""
20+
21+
ATOMIC_INGREDIENT = "atomic_ingredient"
22+
INTERMEDIATE_INGREDIENT = "intermediate_ingredient"
23+
TERMINAL_MATERIAL = "terminal_material"
24+
25+
26+
class TextSearchType(BaseEnumeration):
27+
"""The style of text search to run."""
28+
29+
EXACT = "exact"
30+
PREFIX = "prefix"
31+
SUFFIX = "suffix"
32+
SUBSTRING = "substring"
33+
34+
35+
class Criteria(PolymorphicSerializable):
36+
"""Abstract concept of a criteria to apply when searching for materials."""
37+
38+
@classmethod
39+
def get_type(cls, data) -> Type[Serializable]:
40+
"""Return the subtype."""
41+
classes: List[Type[Criteria]] = [
42+
AndOperator, OrOperator,
43+
PropertiesCriteria, NameCriteria, MaterialRunClassificationCriteria,
44+
MaterialTemplatesCriteria
45+
]
46+
return {klass.typ: klass for klass in classes}[data['type']]
47+
48+
49+
class AndOperator(Serializable['AndOperator'], Criteria):
50+
"""
51+
Combine multiple criteria, requiring EACH to be true for a match.
52+
53+
Parameters
54+
----------
55+
criteria: Criteria
56+
List of conditions all responses must satisfy (i.e., joined with an AND).
57+
58+
"""
59+
60+
criteria = properties.List(properties.Object(Criteria), "criteria")
61+
typ = properties.String('type', default="and_operator", deserializable=False)
62+
63+
64+
class OrOperator(Serializable['OrOperator'], Criteria):
65+
"""
66+
Combine multiple criteria, requiring ANY to be true for a match.
67+
68+
Parameters
69+
----------
70+
criteria: Criteria
71+
List of conditions, at least one of which must match (i.e., joined with an OR).
72+
73+
"""
74+
75+
criteria = properties.List(properties.Object(Criteria), "criteria")
76+
typ = properties.String('type', default="or_operator", deserializable=False)
77+
78+
79+
class PropertiesCriteria(Serializable['PropertiesCriteria'], Criteria):
80+
"""
81+
Look for materials with a particular Property and optionally Value types & ranges.
82+
83+
Parameters
84+
----------
85+
property_templates_filter: Set[UUID]
86+
The citrine IDs of the property templates matches must reference.
87+
value_type_filter: Optional[PropertyFilterType]
88+
The value range matches must conform to.
89+
90+
"""
91+
92+
property_templates_filter = properties.Set(properties.UUID, "property_templates_filter")
93+
value_type_filter = properties.Optional(
94+
properties.Object(PropertyFilterType), "value_type_filter"
95+
)
96+
typ = properties.String('type', default="properties_criteria", deserializable=False)
97+
98+
99+
class NameCriteria(Serializable['NameCriteria'], Criteria):
100+
"""
101+
Look for materials with particular names.
102+
103+
Parameters
104+
----------
105+
name: str
106+
The name the returned objects must have.
107+
search_type: TextSearchType
108+
What kind of string match to use (exact, substring, ...).
109+
110+
"""
111+
112+
name = properties.String('name')
113+
search_type = properties.Enumeration(TextSearchType, 'search_type')
114+
typ = properties.String('type', default="name_criteria", deserializable=False)
115+
116+
117+
class MaterialRunClassificationCriteria(
118+
Serializable['MaterialRunClassificationCriteria'],
119+
Criteria
120+
):
121+
"""
122+
Look for materials with particular classification, defined by MaterialClassification.
123+
124+
Parameters
125+
----------
126+
classifications: Set[MaterialClassification]
127+
The classification, based on where in a material history an object appears.
128+
129+
"""
130+
131+
classifications = properties.Set(
132+
properties.Enumeration(MaterialClassification), 'classifications'
133+
)
134+
typ = properties.String(
135+
'type',
136+
default="material_run_classification_criteria",
137+
deserializable=False
138+
)
139+
140+
141+
class MaterialTemplatesCriteria(Serializable['MaterialTemplatesCriteria'], Criteria):
142+
"""
143+
Look for materials with particular Material Templates and tags.
144+
145+
This has a similar behavior to the old [[MaterialRunByTemplate]] Row definition
146+
147+
Parameters
148+
----------
149+
material_templates_identifiers: Set[UUID]
150+
Which material templates to filter by.
151+
tag_filters: Set[str]
152+
Which tags to filter by.
153+
154+
"""
155+
156+
material_templates_identifiers = properties.Set(
157+
properties.UUID,
158+
"material_templates_identifiers"
159+
)
160+
tag_filters = properties.Set(properties.String, 'tag_filters')
161+
typ = properties.String('type', default="material_template_criteria", deserializable=False)

src/citrine/gemd_queries/filter.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
"""Definitions for GemdQuery objects, and their sub-objects."""
2+
from typing import List, Type
3+
4+
from citrine._serialization.serializable import Serializable
5+
from citrine._serialization.polymorphic_serializable import PolymorphicSerializable
6+
from citrine._serialization import properties
7+
8+
__all__ = ['AllRealFilter', 'AllIntegerFilter', 'NominalCategoricalFilter']
9+
10+
11+
class PropertyFilterType(PolymorphicSerializable):
12+
"""Abstract concept of a criteria to apply when searching for materials."""
13+
14+
@classmethod
15+
def get_type(cls, data) -> Type[Serializable]:
16+
"""Return the subtype."""
17+
classes: List[Type[PropertyFilterType]] = [
18+
NominalCategoricalFilter,
19+
AllRealFilter, AllIntegerFilter
20+
]
21+
return {klass.typ: klass for klass in classes}[data['type']]
22+
23+
24+
class AllRealFilter(Serializable['AllRealFilter'], PropertyFilterType):
25+
"""
26+
Filter for any real value that fits certain constraints.
27+
28+
Parameters
29+
----------
30+
lower: str
31+
The lower bound on this filter range.
32+
upper: str
33+
The upper bound on this filter range.
34+
unit: str
35+
The units associated with the floating point values for this filter.
36+
37+
"""
38+
39+
lower = properties.Float('lower')
40+
upper = properties.Float('upper')
41+
unit = properties.String('unit')
42+
typ = properties.String('type', default="all_real_filter", deserializable=False)
43+
44+
45+
class AllIntegerFilter(Serializable['AllIntegerFilter'], PropertyFilterType):
46+
"""
47+
Filter for any integer value that fits certain constraints.
48+
49+
Parameters
50+
----------
51+
lower: str
52+
The lower bound on this filter range.
53+
upper: str
54+
The upper bound on this filter range.
55+
56+
"""
57+
58+
lower = properties.Float('lower')
59+
upper = properties.Float('upper')
60+
typ = properties.String('type', default="all_integer_filter", deserializable=False)
61+
62+
63+
class NominalCategoricalFilter(Serializable['NominalCategoricalFilter'], PropertyFilterType):
64+
"""
65+
Filter based upon a fixed list of Categorical Values.
66+
67+
Parameters
68+
----------
69+
categories: Set[str]
70+
Which categorical values match.
71+
72+
"""
73+
74+
categories = properties.Set(properties.String, 'categories')
75+
typ = properties.String('type', default="nominal_categorical_filter", deserializable=False)
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
"""Definitions for GemdQuery objects, and their sub-objects."""
2+
from gemd.enumeration.base_enumeration import BaseEnumeration
3+
4+
from citrine._serialization.serializable import Serializable
5+
from citrine._serialization import properties
6+
from citrine.gemd_queries.criteria import Criteria
7+
8+
9+
class GemdObjectType(BaseEnumeration):
10+
"""The style of text search to run."""
11+
12+
# An old defect has some old GemdQuery values stored with invalid enums
13+
# The synonyms will allow invalid old values to be read, but not emitted
14+
MEASUREMENT_TEMPLATE_TYPE = "measurement_template", "MEASUREMENT_TEMPLATE_TYPE"
15+
MATERIAL_TEMPLATE_TYPE = "material_template", "MATERIAL_TEMPLATE_TYPE"
16+
PROCESS_TEMPLATE_TYPE = "process_template", "PROCESS_TEMPLATE_TYPE"
17+
PROPERTY_TEMPLATE_TYPE = "property_template", "PROPERTY_TEMPLATE_TYPE"
18+
CONDITION_TEMPLATE_TYPE = "condition_template", "CONDITION_TEMPLATE_TYPE"
19+
PARAMETER_TEMPLATE_TYPE = "parameter_template", "PARAMETER_TEMPLATE_TYPE"
20+
PROCESS_RUN_TYPE = "process_run", "PROCESS_RUN_TYPE"
21+
PROCESS_SPEC_TYPE = "process_spec", "PROCESS_SPEC_TYPE"
22+
MATERIAL_RUN_TYPE = "material_run", "MATERIAL_RUN_TYPE"
23+
MATERIAL_SPEC_TYPE = "material_spec", "MATERIAL_SPEC_TYPE"
24+
INGREDIENT_RUN_TYPE = "ingredient_run", "INGREDIENT_RUN_TYPE"
25+
INGREDIENT_SPEC_TYPE = "ingredient_spec", "INGREDIENT_SPEC_TYPE"
26+
MEASUREMENT_RUN_TYPE = "measurement_run", "MEASUREMENT_RUN_TYPE"
27+
MEASUREMENT_SPEC_TYPE = "measurement_spec", "MEASUREMENT_SPEC_TYPE"
28+
29+
30+
class GemdQuery(Serializable['GemdQuery']):
31+
"""
32+
This describes what data objects to fetch (or graph of data objects).
33+
34+
Parameters
35+
----------
36+
criteria: Criteria
37+
List of conditions all responses must satisfy (i.e., joined with an AND).
38+
datasets: UUID
39+
Set of datasets to look in for matching objects.
40+
object_types: GemdObjectType
41+
Classes of objects to consider when searching.
42+
schema_version: Int
43+
What version of the query schema this package represents.
44+
45+
"""
46+
47+
criteria = properties.List(properties.Object(Criteria), "criteria", default=[])
48+
datasets = properties.Set(properties.UUID, "datasets", default=set())
49+
object_types = properties.Set(
50+
properties.Enumeration(GemdObjectType),
51+
'object_types',
52+
default={x for x in GemdObjectType}
53+
)
54+
schema_version = properties.Integer('schema_version', default=1)
55+
56+
@classmethod
57+
def _pre_build(cls, data: dict) -> dict:
58+
"""Run data modification before building."""
59+
version = data.get('schema_version')
60+
if data.get('schema_version') != 1:
61+
raise ValueError(
62+
f"This version of the library only supports schema_version 1, not '{version}'"
63+
)
64+
return data

src/citrine/resources/table_config.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from citrine._utils.functions import format_escaped_url
1515
from citrine.resources.data_concepts import CITRINE_SCOPE, _make_link_by_uid
1616
from citrine.resources.process_template import ProcessTemplate
17+
from citrine.gemd_queries.gemd_query import GemdQuery
1718
from citrine.gemtables.columns import Column, MeanColumn, IdentityColumn, OriginalUnitsColumn, \
1819
ConcatColumn
1920
from citrine.gemtables.rows import Row
@@ -53,6 +54,8 @@ class TableConfig(Resource["TableConfig"]):
5354
List of row definitions that define the rows of the table
5455
columns: list[Column]
5556
Column definitions, which describe how the variables are shaped into the table
57+
gemd_query: Optional[GemdQuery]
58+
The query used to define the materials underpinning this table
5659
5760
"""
5861

@@ -79,6 +82,7 @@ def _get_dups(lst: List) -> List:
7982
variables = properties.List(properties.Object(Variable), "variables")
8083
rows = properties.List(properties.Object(Row), "rows")
8184
columns = properties.List(properties.Object(Column), "columns")
85+
gemd_query = properties.Optional(properties.Object(GemdQuery), "gemd_query")
8286

8387
def __init__(self, name: str, *, description: str, datasets: List[UUID],
8488
variables: List[Variable], rows: List[Row], columns: List[Column]):

tests/gemd_query/__init__.py

Whitespace-only changes.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from uuid import uuid4
2+
import pytest
3+
4+
from citrine.gemd_queries.criteria import PropertiesCriteria
5+
from citrine.gemd_queries.filter import AllRealFilter
6+
from citrine.gemd_queries.gemd_query import GemdQuery
7+
8+
from tests.utils.factories import GemdQueryDataFactory
9+
10+
11+
def test_gemd_query_version():
12+
valid = GemdQueryDataFactory()
13+
assert GemdQuery.build(valid) is not None
14+
15+
invalid = GemdQueryDataFactory()
16+
invalid['schema_version'] = 2
17+
with pytest.raises(ValueError):
18+
GemdQuery.build(invalid)
19+
20+
21+
def test_criteria_rebuild():
22+
value_filter = AllRealFilter()
23+
value_filter.unit = 'm'
24+
value_filter.lower = 0
25+
value_filter.upper = 1
26+
27+
crit = PropertiesCriteria()
28+
crit.property_templates_filter = {uuid4()}
29+
crit.value_type_filter = value_filter
30+
31+
query = GemdQuery()
32+
query.criteria.append(crit)
33+
query.datasets.add(uuid4())
34+
query.object_types = {'material_run'}
35+
36+
query_copy = GemdQuery.build(query.dump())
37+
38+
assert len(query.criteria) == len(query_copy.criteria)
39+
assert query.criteria[0].property_templates_filter == query_copy.criteria[0].property_templates_filter
40+
assert query.criteria[0].value_type_filter.unit == query_copy.criteria[0].value_type_filter.unit
41+
assert query.criteria[0].value_type_filter.lower == query_copy.criteria[0].value_type_filter.lower
42+
assert query.criteria[0].value_type_filter.upper == query_copy.criteria[0].value_type_filter.upper
43+
assert query.datasets == query_copy.datasets
44+
assert query.object_types == query_copy.object_types
45+
assert query.schema_version == query_copy.schema_version

0 commit comments

Comments
 (0)