liminal-orm 2.0.3a1__py3-none-any.whl → 3.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- liminal/.DS_Store +0 -0
- liminal/base/base_operation.py +7 -7
- liminal/base/properties/base_field_properties.py +3 -1
- liminal/base/properties/base_schema_properties.py +8 -2
- liminal/entity_schemas/compare.py +40 -21
- liminal/entity_schemas/entity_schema_models.py +40 -13
- liminal/entity_schemas/generate_files.py +2 -2
- liminal/entity_schemas/operations.py +61 -11
- liminal/entity_schemas/tag_schema_models.py +72 -25
- liminal/entity_schemas/utils.py +36 -9
- liminal/enums/__init__.py +1 -0
- liminal/enums/benchling_entity_type.py +9 -0
- liminal/enums/benchling_field_type.py +8 -0
- liminal/enums/benchling_naming_strategy.py +1 -0
- liminal/enums/sequence_constraint.py +13 -0
- liminal/mappers.py +8 -11
- liminal/orm/base_model.py +46 -11
- liminal/orm/column.py +23 -5
- liminal/orm/schema_properties.py +14 -5
- liminal/tests/.DS_Store +0 -0
- liminal/unit_dictionary/utils.py +48 -0
- liminal/validation/__init__.py +2 -9
- {liminal_orm-2.0.3a1.dist-info → liminal_orm-3.0.0.dist-info}/METADATA +1 -1
- {liminal_orm-2.0.3a1.dist-info → liminal_orm-3.0.0.dist-info}/RECORD +27 -23
- {liminal_orm-2.0.3a1.dist-info → liminal_orm-3.0.0.dist-info}/LICENSE.md +0 -0
- {liminal_orm-2.0.3a1.dist-info → liminal_orm-3.0.0.dist-info}/WHEEL +0 -0
- {liminal_orm-2.0.3a1.dist-info → liminal_orm-3.0.0.dist-info}/entry_points.txt +0 -0
liminal/.DS_Store
ADDED
Binary file
|
liminal/base/base_operation.py
CHANGED
@@ -13,13 +13,13 @@ Order of operations based on order class var:
|
|
13
13
|
6. ArchiveDropdownOption
|
14
14
|
7. ReorderDropdownOptions
|
15
15
|
8. CreateSchema
|
16
|
-
9.
|
17
|
-
10.
|
18
|
-
11.
|
19
|
-
12.
|
20
|
-
13.
|
21
|
-
14.
|
22
|
-
15.
|
16
|
+
9. UnarchiveSchema
|
17
|
+
10. CreateField
|
18
|
+
11. UpdateEntitySchemaNameTemplate
|
19
|
+
12. UpdateSchema
|
20
|
+
13. UnarchiveField
|
21
|
+
14. UpdateField
|
22
|
+
15. ArchiveField
|
23
23
|
16. ReorderFields
|
24
24
|
17. ArchiveSchema
|
25
25
|
18. ArchiveDropdown
|
@@ -49,6 +49,8 @@ class BaseFieldProperties(BaseModel):
|
|
49
49
|
dropdown_link: str | None = None
|
50
50
|
entity_link: str | None = None
|
51
51
|
tooltip: str | None = None
|
52
|
+
decimal_places: int | None = None
|
53
|
+
unit_name: str | None = None
|
52
54
|
_archived: bool | None = PrivateAttr(default=None)
|
53
55
|
|
54
56
|
model_config = ConfigDict(arbitrary_types_allowed=True)
|
@@ -122,4 +124,4 @@ class BaseFieldProperties(BaseModel):
|
|
122
124
|
|
123
125
|
def __repr__(self) -> str:
|
124
126
|
"""Generates a string representation of the class so that it can be executed."""
|
125
|
-
return f"{self.__class__.__name__}({', '.join([f'{k}={v.__repr__()}' for k, v in self.model_dump(exclude_unset=True
|
127
|
+
return f"{self.__class__.__name__}({', '.join([f'{k}={v.__repr__()}' for k, v in self.model_dump(exclude_unset=True).items()])})"
|
@@ -66,8 +66,13 @@ class BaseSchemaProperties(BaseModel):
|
|
66
66
|
include_registry_id_in_chips : bool | None = None
|
67
67
|
Flag for configuring the chip label for entities. Determines if the chip will include the Registry ID in the chip label.
|
68
68
|
constraint_fields : set[str] | None
|
69
|
-
Set of constraints for field values for the schema. Must be a set of column names
|
70
|
-
|
69
|
+
Set of constraints for field values for the schema. Must be a set of warehouse column names. This specifies that their entity field values must be a unique combination within an entity.
|
70
|
+
The following sequence constraints are also supported:
|
71
|
+
- bases: only supported for nucleotide sequence entity types. hasUniqueResidues=True
|
72
|
+
- amino_acids_ignore_case: only supported for amino acid sequence entity types. hasUniqueResidues=True
|
73
|
+
- amino_acids_exact_match: only supported for amino acid sequence entity types. hasUniqueResidues=True, areUniqueResiduesCaseSensitive=True
|
74
|
+
show_bases_in_expanded_view : bool | None
|
75
|
+
Whether the bases should be shown in the expanded view of the entity.
|
71
76
|
_archived : bool | None
|
72
77
|
Whether the schema is archived in Benchling.
|
73
78
|
"""
|
@@ -81,6 +86,7 @@ class BaseSchemaProperties(BaseModel):
|
|
81
86
|
use_registry_id_as_label: bool | None = None
|
82
87
|
include_registry_id_in_chips: bool | None = None
|
83
88
|
constraint_fields: set[str] | None = None
|
89
|
+
show_bases_in_expanded_view: bool | None = None
|
84
90
|
_archived: bool | None = None
|
85
91
|
|
86
92
|
def __init__(self, **data: Any):
|
@@ -18,6 +18,7 @@ from liminal.entity_schemas.operations import (
|
|
18
18
|
UpdateEntitySchemaNameTemplate,
|
19
19
|
)
|
20
20
|
from liminal.entity_schemas.utils import get_converted_tag_schemas
|
21
|
+
from liminal.enums.benchling_naming_strategy import BenchlingNamingStrategy
|
21
22
|
from liminal.orm.base_model import BaseModel
|
22
23
|
from liminal.orm.column import Column
|
23
24
|
from liminal.utils import to_snake_case
|
@@ -275,6 +276,16 @@ def compare_entity_schemas(
|
|
275
276
|
col.properties.set_warehouse_name(wh_name)
|
276
277
|
for wh_name, col in model_columns.items()
|
277
278
|
]
|
279
|
+
template_based_naming_strategies = {
|
280
|
+
s
|
281
|
+
for s in model.__schema_properties__.naming_strategies
|
282
|
+
if BenchlingNamingStrategy.is_template_based(s)
|
283
|
+
}
|
284
|
+
model.__schema_properties__.naming_strategies = {
|
285
|
+
s
|
286
|
+
for s in model.__schema_properties__.naming_strategies
|
287
|
+
if not BenchlingNamingStrategy.is_template_based(s)
|
288
|
+
}
|
278
289
|
ops.append(
|
279
290
|
CompareOperation(
|
280
291
|
op=CreateEntitySchema(
|
@@ -288,28 +299,10 @@ def compare_entity_schemas(
|
|
288
299
|
),
|
289
300
|
)
|
290
301
|
)
|
291
|
-
benchling_given_wh_name = to_snake_case(model.__schema_properties__.name)
|
292
|
-
if model.__schema_properties__.warehouse_name != benchling_given_wh_name:
|
293
|
-
ops.append(
|
294
|
-
CompareOperation(
|
295
|
-
op=UpdateEntitySchema(
|
296
|
-
benchling_given_wh_name,
|
297
|
-
BaseSchemaProperties(
|
298
|
-
warehouse_name=model.__schema_properties__.warehouse_name
|
299
|
-
),
|
300
|
-
),
|
301
|
-
reverse_op=UpdateEntitySchema(
|
302
|
-
model.__schema_properties__.warehouse_name,
|
303
|
-
BaseSchemaProperties(
|
304
|
-
warehouse_name=benchling_given_wh_name
|
305
|
-
),
|
306
|
-
),
|
307
|
-
)
|
308
|
-
)
|
309
302
|
benchling_given_name_template = BaseNameTemplate(
|
310
303
|
parts=[], order_name_parts_by_sequence=False
|
311
304
|
)
|
312
|
-
if
|
305
|
+
if benchling_given_name_template != model.__name_template__:
|
313
306
|
ops.append(
|
314
307
|
CompareOperation(
|
315
308
|
op=UpdateEntitySchemaNameTemplate(
|
@@ -323,13 +316,39 @@ def compare_entity_schemas(
|
|
323
316
|
reverse_op=UpdateEntitySchemaNameTemplate(
|
324
317
|
model.__schema_properties__.warehouse_name,
|
325
318
|
BaseNameTemplate(
|
326
|
-
**
|
327
|
-
|
319
|
+
**model.__name_template__.merge(
|
320
|
+
benchling_given_name_template
|
328
321
|
)
|
329
322
|
),
|
330
323
|
),
|
331
324
|
)
|
332
325
|
)
|
326
|
+
benchling_given_wh_name = to_snake_case(model.__schema_properties__.name)
|
327
|
+
new_schema_props = BaseSchemaProperties()
|
328
|
+
rollback_schema_props = BaseSchemaProperties()
|
329
|
+
if model.__schema_properties__.warehouse_name != benchling_given_wh_name:
|
330
|
+
new_schema_props.warehouse_name = (
|
331
|
+
model.__schema_properties__.warehouse_name
|
332
|
+
)
|
333
|
+
rollback_schema_props.warehouse_name = benchling_given_wh_name
|
334
|
+
if template_based_naming_strategies:
|
335
|
+
new_schema_props.naming_strategies = template_based_naming_strategies
|
336
|
+
rollback_schema_props.naming_strategies = (
|
337
|
+
model.__schema_properties__.naming_strategies
|
338
|
+
)
|
339
|
+
if new_schema_props.model_dump(exclude_unset=True) != {}:
|
340
|
+
ops.append(
|
341
|
+
CompareOperation(
|
342
|
+
op=UpdateEntitySchema(
|
343
|
+
benchling_given_wh_name,
|
344
|
+
new_schema_props,
|
345
|
+
),
|
346
|
+
reverse_op=UpdateEntitySchema(
|
347
|
+
model.__schema_properties__.warehouse_name,
|
348
|
+
rollback_schema_props,
|
349
|
+
),
|
350
|
+
)
|
351
|
+
)
|
333
352
|
|
334
353
|
model_operations[model.__schema_properties__.warehouse_name] = ops
|
335
354
|
running_benchling_schema_names = [
|
@@ -9,8 +9,10 @@ from liminal.connection import BenchlingService
|
|
9
9
|
from liminal.dropdowns.utils import get_benchling_dropdown_summary_by_name
|
10
10
|
from liminal.entity_schemas.tag_schema_models import TagSchemaModel
|
11
11
|
from liminal.enums import BenchlingEntityType
|
12
|
+
from liminal.enums.sequence_constraint import SequenceConstraint
|
12
13
|
from liminal.mappers import convert_field_type_to_api_field_type
|
13
14
|
from liminal.orm.schema_properties import MixtureSchemaConfig, SchemaProperties
|
15
|
+
from liminal.unit_dictionary.utils import get_unit_id_from_name
|
14
16
|
|
15
17
|
|
16
18
|
class FieldLinkShortModel(BaseModel):
|
@@ -26,28 +28,44 @@ class EntitySchemaConstraint(BaseModel):
|
|
26
28
|
"""
|
27
29
|
|
28
30
|
areUniqueResiduesCaseSensitive: bool | None = None
|
29
|
-
fields: dict[str, Any] | None = None
|
30
|
-
|
31
|
+
fields: list[dict[str, Any]] | None = None
|
32
|
+
hasUniqueCanonicalSmiles: bool | None = None
|
31
33
|
hasUniqueResidues: bool | None = None
|
32
34
|
|
33
35
|
@classmethod
|
34
36
|
def from_constraint_fields(
|
35
|
-
cls,
|
37
|
+
cls,
|
38
|
+
constraint_fields: set[str],
|
39
|
+
benchling_service: BenchlingService | None = None,
|
36
40
|
) -> EntitySchemaConstraint:
|
37
41
|
"""
|
38
42
|
Generates a Constraint object from a set of constraint fields to create a constraint on a schema.
|
39
43
|
"""
|
40
|
-
if constraint_fields is None:
|
41
|
-
return None
|
42
44
|
hasUniqueResidues = False
|
43
|
-
|
44
|
-
|
45
|
+
areUniqueResiduesCaseSensitive = False
|
46
|
+
if SequenceConstraint.BASES in constraint_fields:
|
47
|
+
constraint_fields.discard(SequenceConstraint.BASES)
|
48
|
+
hasUniqueResidues = True
|
49
|
+
elif SequenceConstraint.AMINO_ACIDS_IGNORE_CASE in constraint_fields:
|
50
|
+
constraint_fields.discard(SequenceConstraint.AMINO_ACIDS_IGNORE_CASE)
|
45
51
|
hasUniqueResidues = True
|
52
|
+
elif SequenceConstraint.AMINO_ACIDS_EXACT_MATCH in constraint_fields:
|
53
|
+
constraint_fields.discard(SequenceConstraint.AMINO_ACIDS_EXACT_MATCH)
|
54
|
+
hasUniqueResidues = True
|
55
|
+
areUniqueResiduesCaseSensitive = True
|
56
|
+
fields = []
|
57
|
+
for field_name in constraint_fields:
|
58
|
+
if benchling_service is None:
|
59
|
+
raise ValueError(
|
60
|
+
"Benchling SDK must be provided to update constraint fields."
|
61
|
+
)
|
62
|
+
field = TagSchemaModel.get_one(benchling_service, field_name)
|
63
|
+
fields.append({"name": field.name, "id": field.id})
|
46
64
|
return cls(
|
47
|
-
fields=
|
65
|
+
fields=fields,
|
48
66
|
hasUniqueResidues=hasUniqueResidues,
|
49
|
-
|
50
|
-
areUniqueResiduesCaseSensitive=
|
67
|
+
hasUniqueCanonicalSmiles=False,
|
68
|
+
areUniqueResiduesCaseSensitive=areUniqueResiduesCaseSensitive,
|
51
69
|
)
|
52
70
|
|
53
71
|
|
@@ -63,6 +81,8 @@ class CreateEntitySchemaFieldModel(BaseModel):
|
|
63
81
|
isParentLink: bool = False
|
64
82
|
dropdownId: str | None = None
|
65
83
|
link: FieldLinkShortModel | None = None
|
84
|
+
unitId: str | None = None
|
85
|
+
decimalPrecision: int | None = None
|
66
86
|
|
67
87
|
@classmethod
|
68
88
|
def from_benchling_props(
|
@@ -107,6 +127,11 @@ class CreateEntitySchemaFieldModel(BaseModel):
|
|
107
127
|
dropdown_summary_id = get_benchling_dropdown_summary_by_name(
|
108
128
|
benchling_service, field_props.dropdown_link
|
109
129
|
).id
|
130
|
+
unit_id = None
|
131
|
+
if field_props.unit_name is not None:
|
132
|
+
if benchling_service is None:
|
133
|
+
raise ValueError("Benchling SDK must be provided to update unit field.")
|
134
|
+
unit_id = get_unit_id_from_name(benchling_service, field_props.unit_name)
|
110
135
|
return CreateEntitySchemaFieldModel(
|
111
136
|
name=field_props.name,
|
112
137
|
systemName=field_props.warehouse_name,
|
@@ -118,6 +143,8 @@ class CreateEntitySchemaFieldModel(BaseModel):
|
|
118
143
|
link=FieldLinkShortModel(
|
119
144
|
tagSchema=tag_schema, folderItemType=folder_item_type
|
120
145
|
),
|
146
|
+
unitId=unit_id,
|
147
|
+
decimalPrecision=field_props.decimal_places,
|
121
148
|
)
|
122
149
|
|
123
150
|
|
@@ -135,6 +162,7 @@ class CreateEntitySchemaModel(BaseModel):
|
|
135
162
|
useOrganizationCollectionAliasForDisplayLabel: bool | None = None
|
136
163
|
labelingStrategies: list[str] | None = None
|
137
164
|
constraint: EntitySchemaConstraint | None = None
|
165
|
+
showResidues: bool | None = None
|
138
166
|
|
139
167
|
@classmethod
|
140
168
|
def from_benchling_props(
|
@@ -168,11 +196,10 @@ class CreateEntitySchemaModel(BaseModel):
|
|
168
196
|
includeRegistryIdInChips=benchling_props.include_registry_id_in_chips,
|
169
197
|
useOrganizationCollectionAliasForDisplayLabel=benchling_props.use_registry_id_as_label,
|
170
198
|
labelingStrategies=[s.value for s in benchling_props.naming_strategies],
|
199
|
+
showResidues=benchling_props.show_bases_in_expanded_view,
|
171
200
|
constraint=EntitySchemaConstraint.from_constraint_fields(
|
172
201
|
benchling_props.constraint_fields
|
173
|
-
)
|
174
|
-
if benchling_props.constraint_fields
|
175
|
-
else None,
|
202
|
+
),
|
176
203
|
fields=[
|
177
204
|
CreateEntitySchemaFieldModel.from_benchling_props(
|
178
205
|
field_props, benchling_service
|
@@ -91,7 +91,7 @@ def generate_all_entity_schema_files(
|
|
91
91
|
dropdown_classname = dropdown_name_to_classname_map[col.dropdown_link]
|
92
92
|
dropdowns.append(dropdown_classname)
|
93
93
|
column_strings.append(
|
94
|
-
f"""{tab}{col_name}: SqlColumn = Column(name="{col.name}", type={str(col.type)}, required={col.required}{', is_multi=True' if col.is_multi else ''}{', parent_link=True' if col.parent_link else ''}{f', entity_link="{col.entity_link}"' if col.entity_link else ''}{f', dropdown={dropdown_classname}' if dropdown_classname else ''}{f', tooltip="{col.tooltip}"' if col.tooltip else ''})"""
|
94
|
+
f"""{tab}{col_name}: SqlColumn = Column(name="{col.name}", type={str(col.type)}, required={col.required}{', is_multi=True' if col.is_multi else ''}{', parent_link=True' if col.parent_link else ''}{f', entity_link="{col.entity_link}"' if col.entity_link else ''}{f', dropdown={dropdown_classname}' if dropdown_classname else ''}{f', tooltip="{col.tooltip}"' if col.tooltip else ''}{f', unit_name="{col.unit_name}"' if col.unit_name else ''}{f', decimal_places={col.decimal_places}' if col.decimal_places is not None else ''})"""
|
95
95
|
)
|
96
96
|
if col.required and col.type:
|
97
97
|
init_strings.append(
|
@@ -105,7 +105,7 @@ def generate_all_entity_schema_files(
|
|
105
105
|
if not has_date:
|
106
106
|
import_strings.append("from datetime import datetime")
|
107
107
|
if (
|
108
|
-
col.type
|
108
|
+
col.type in BenchlingFieldType.get_entity_link_types()
|
109
109
|
and col.entity_link is not None
|
110
110
|
):
|
111
111
|
if not col.is_multi:
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import logging
|
1
2
|
from typing import Any, ClassVar
|
2
3
|
|
3
4
|
from liminal.base.base_operation import BaseOperation
|
@@ -25,8 +26,14 @@ from liminal.entity_schemas.utils import (
|
|
25
26
|
)
|
26
27
|
from liminal.enums import BenchlingNamingStrategy
|
27
28
|
from liminal.orm.schema_properties import SchemaProperties
|
29
|
+
from liminal.unit_dictionary.utils import (
|
30
|
+
get_unit_id_to_name_map,
|
31
|
+
get_unit_name_to_id_map,
|
32
|
+
)
|
28
33
|
from liminal.utils import to_snake_case
|
29
34
|
|
35
|
+
LOGGER = logging.getLogger(__name__)
|
36
|
+
|
30
37
|
|
31
38
|
class CreateEntitySchema(BaseOperation):
|
32
39
|
order: ClassVar[int] = 80
|
@@ -39,7 +46,7 @@ class CreateEntitySchema(BaseOperation):
|
|
39
46
|
self.schema_properties = schema_properties
|
40
47
|
self.fields = fields
|
41
48
|
self._validated_schema_properties = SchemaProperties(
|
42
|
-
**schema_properties.model_dump()
|
49
|
+
**schema_properties.model_dump(exclude_unset=True)
|
43
50
|
)
|
44
51
|
|
45
52
|
def execute(self, benchling_service: BenchlingService) -> dict[str, Any]:
|
@@ -83,6 +90,15 @@ class CreateEntitySchema(BaseOperation):
|
|
83
90
|
Either set warehouse_access to True in BenchlingConnection or set the schema warehouse_name to the given Benchling warehouse name: {to_snake_case(self._validated_schema_properties.name)}. \
|
84
91
|
Reach out to Benchling support if you need help setting up warehouse access."
|
85
92
|
)
|
93
|
+
for field in self.fields:
|
94
|
+
if (
|
95
|
+
field.unit_name
|
96
|
+
and field.unit_name
|
97
|
+
not in get_unit_name_to_id_map(benchling_service).keys()
|
98
|
+
):
|
99
|
+
raise ValueError(
|
100
|
+
f"{self._validated_schema_properties.warehouse_name}: On field {field.warehouse_name}, unit {field.unit_name} not found in Benchling Unit Dictionary as a valid unit. Please check the field definition or your Unit Dictionary."
|
101
|
+
)
|
86
102
|
|
87
103
|
def _validate_create(self, benchling_service: BenchlingService) -> None:
|
88
104
|
all_schemas = TagSchemaModel.get_all_json(benchling_service)
|
@@ -123,8 +139,11 @@ class CreateEntitySchema(BaseOperation):
|
|
123
139
|
f"Entity schema {self._validated_schema_properties.warehouse_name} is already active in Benchling."
|
124
140
|
)
|
125
141
|
dropdowns_map = get_benchling_dropdown_id_name_map(benchling_service)
|
142
|
+
unit_id_to_name_map = get_unit_id_to_name_map(benchling_service)
|
126
143
|
benchling_schema_props, _, benchling_fields_props = (
|
127
|
-
convert_tag_schema_to_internal_schema(
|
144
|
+
convert_tag_schema_to_internal_schema(
|
145
|
+
schema, dropdowns_map, unit_id_to_name_map
|
146
|
+
)
|
128
147
|
)
|
129
148
|
if (
|
130
149
|
self._validated_schema_properties != benchling_schema_props
|
@@ -162,7 +181,7 @@ class ArchiveEntitySchema(BaseOperation):
|
|
162
181
|
|
163
182
|
|
164
183
|
class UnarchiveEntitySchema(BaseOperation):
|
165
|
-
order: ClassVar[int] =
|
184
|
+
order: ClassVar[int] = 90
|
166
185
|
|
167
186
|
def __init__(self, wh_schema_name: str) -> None:
|
168
187
|
self.wh_schema_name = wh_schema_name
|
@@ -187,7 +206,7 @@ class UnarchiveEntitySchema(BaseOperation):
|
|
187
206
|
|
188
207
|
|
189
208
|
class UpdateEntitySchema(BaseOperation):
|
190
|
-
order: ClassVar[int] =
|
209
|
+
order: ClassVar[int] = 120
|
191
210
|
|
192
211
|
def __init__(
|
193
212
|
self,
|
@@ -251,7 +270,7 @@ class UpdateEntitySchema(BaseOperation):
|
|
251
270
|
|
252
271
|
|
253
272
|
class UpdateEntitySchemaNameTemplate(BaseOperation):
|
254
|
-
order: ClassVar[int] =
|
273
|
+
order: ClassVar[int] = 110
|
255
274
|
|
256
275
|
def __init__(
|
257
276
|
self,
|
@@ -283,7 +302,7 @@ class UpdateEntitySchemaNameTemplate(BaseOperation):
|
|
283
302
|
|
284
303
|
|
285
304
|
class CreateEntitySchemaField(BaseOperation):
|
286
|
-
order: ClassVar[int] =
|
305
|
+
order: ClassVar[int] = 100
|
287
306
|
|
288
307
|
def __init__(
|
289
308
|
self,
|
@@ -316,13 +335,20 @@ class CreateEntitySchemaField(BaseOperation):
|
|
316
335
|
f"Field {self._wh_field_name} is already active on entity schema {self.wh_schema_name}."
|
317
336
|
)
|
318
337
|
dropdowns_map = get_benchling_dropdown_id_name_map(benchling_service)
|
338
|
+
unit_id_to_name_map = get_unit_id_to_name_map(benchling_service)
|
319
339
|
if self.field_props == convert_tag_schema_field_to_field_properties(
|
320
|
-
field, dropdowns_map
|
321
|
-
):
|
340
|
+
field, dropdowns_map, unit_id_to_name_map
|
341
|
+
).set_warehouse_name(self._wh_field_name):
|
322
342
|
return UnarchiveEntitySchemaField(
|
323
343
|
self.wh_schema_name, self._wh_field_name, self.index
|
324
344
|
).execute(benchling_service)
|
325
345
|
else:
|
346
|
+
print(self.field_props)
|
347
|
+
print(
|
348
|
+
convert_tag_schema_field_to_field_properties(
|
349
|
+
field, dropdowns_map, unit_id_to_name_map
|
350
|
+
)
|
351
|
+
)
|
326
352
|
raise ValueError(
|
327
353
|
f"Field {self._wh_field_name} on entity schema {self.wh_schema_name} is different in code versus Benchling."
|
328
354
|
)
|
@@ -367,10 +393,18 @@ class CreateEntitySchemaField(BaseOperation):
|
|
367
393
|
Either set warehouse_access to True in BenchlingConnection or set the column variable name to the given Benchling field warehouse name: {to_snake_case(self.field_props.name)}. \
|
368
394
|
Reach out to Benchling support if you need help setting up warehouse access."
|
369
395
|
)
|
396
|
+
if (
|
397
|
+
self.field_props.unit_name
|
398
|
+
and self.field_props.unit_name
|
399
|
+
not in get_unit_name_to_id_map(benchling_service).keys()
|
400
|
+
):
|
401
|
+
raise ValueError(
|
402
|
+
f"{self.wh_schema_name}: On field {self._wh_field_name}, unit {self.field_props.unit_name} not found in Benchling Unit Dictionary as a valid unit. Please check the field definition or your Unit Dictionary."
|
403
|
+
)
|
370
404
|
|
371
405
|
|
372
406
|
class ArchiveEntitySchemaField(BaseOperation):
|
373
|
-
order: ClassVar[int] =
|
407
|
+
order: ClassVar[int] = 150
|
374
408
|
|
375
409
|
def __init__(
|
376
410
|
self, wh_schema_name: str, wh_field_name: str, index: int | None = None
|
@@ -421,7 +455,7 @@ class ArchiveEntitySchemaField(BaseOperation):
|
|
421
455
|
|
422
456
|
|
423
457
|
class UnarchiveEntitySchemaField(BaseOperation):
|
424
|
-
order: ClassVar[int] =
|
458
|
+
order: ClassVar[int] = 130
|
425
459
|
|
426
460
|
def __init__(
|
427
461
|
self, wh_schema_name: str, wh_field_name: str, index: int | None = None
|
@@ -468,7 +502,7 @@ class UnarchiveEntitySchemaField(BaseOperation):
|
|
468
502
|
|
469
503
|
|
470
504
|
class UpdateEntitySchemaField(BaseOperation):
|
471
|
-
order: ClassVar[int] =
|
505
|
+
order: ClassVar[int] = 140
|
472
506
|
|
473
507
|
def __init__(
|
474
508
|
self,
|
@@ -500,6 +534,9 @@ class UpdateEntitySchemaField(BaseOperation):
|
|
500
534
|
return f"{self.wh_schema_name}: Entity schema field '{self.wh_field_name}' in Benchling is different than in code: {str(self.update_props)}."
|
501
535
|
|
502
536
|
def validate(self, benchling_service: BenchlingService) -> None:
|
537
|
+
tag_schema = TagSchemaModel.get_one_cached(
|
538
|
+
benchling_service, self.wh_schema_name
|
539
|
+
)
|
503
540
|
if (
|
504
541
|
not benchling_service.connection.warehouse_access
|
505
542
|
and self.update_props.warehouse_name is not None
|
@@ -509,6 +546,19 @@ class UpdateEntitySchemaField(BaseOperation):
|
|
509
546
|
Either set warehouse_access to True in BenchlingConnection or do not change the warehouse name. \
|
510
547
|
Reach out to Benchling support if you need help setting up warehouse access."
|
511
548
|
)
|
549
|
+
if "unit_name" in self.update_props.model_dump(exclude_unset=True):
|
550
|
+
no_change_message = f"{self.wh_schema_name}: On field {self.wh_field_name}, updating unit name to {self.update_props.unit_name}. The unit of this field CANNOT be changed once it's been set."
|
551
|
+
if tag_schema.get_field(self.wh_field_name).unitApiIdentifier:
|
552
|
+
raise ValueError(no_change_message)
|
553
|
+
else:
|
554
|
+
LOGGER.warning(no_change_message)
|
555
|
+
if (
|
556
|
+
self.update_props.unit_name
|
557
|
+
not in get_unit_name_to_id_map(benchling_service).keys()
|
558
|
+
):
|
559
|
+
raise ValueError(
|
560
|
+
f"{self.wh_schema_name}: On field {self.wh_field_name}, unit {self.update_props.unit_name} not found in Benchling Unit Dictionary as a valid unit. Please check the field definition or your Unit Dictionary."
|
561
|
+
)
|
512
562
|
|
513
563
|
def _validate(self, benchling_service: BenchlingService) -> TagSchemaModel:
|
514
564
|
tag_schema = TagSchemaModel.get_one(benchling_service, self.wh_schema_name)
|
@@ -1,5 +1,6 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
+
from functools import lru_cache
|
3
4
|
from typing import Any
|
4
5
|
|
5
6
|
import requests
|
@@ -17,6 +18,7 @@ from liminal.enums import (
|
|
17
18
|
BenchlingSequenceType,
|
18
19
|
)
|
19
20
|
from liminal.enums.name_template_part_type import NameTemplatePartType
|
21
|
+
from liminal.enums.sequence_constraint import SequenceConstraint
|
20
22
|
from liminal.mappers import (
|
21
23
|
convert_entity_type_to_api_entity_type,
|
22
24
|
convert_field_type_to_api_field_type,
|
@@ -25,6 +27,7 @@ from liminal.orm.name_template_parts import (
|
|
25
27
|
NameTemplatePart,
|
26
28
|
)
|
27
29
|
from liminal.orm.schema_properties import MixtureSchemaConfig
|
30
|
+
from liminal.unit_dictionary.utils import get_unit_id_from_name
|
28
31
|
|
29
32
|
|
30
33
|
class FieldRequiredLinkShortModel(BaseModel):
|
@@ -91,16 +94,28 @@ class TagSchemaConstraint(BaseModel):
|
|
91
94
|
|
92
95
|
@classmethod
|
93
96
|
def from_constraint_fields(
|
94
|
-
cls,
|
97
|
+
cls,
|
98
|
+
constraint_fields: list[TagSchemaFieldModel],
|
99
|
+
sequence_constraint: SequenceConstraint | None = None,
|
95
100
|
) -> TagSchemaConstraint:
|
96
101
|
"""
|
97
102
|
Generates a Constraint object from a set of constraint fields to create a constraint on a schema.
|
98
103
|
"""
|
104
|
+
uniqueResidues = False
|
105
|
+
areUniqueResiduesCaseSensitive = False
|
106
|
+
match sequence_constraint:
|
107
|
+
case SequenceConstraint.BASES:
|
108
|
+
uniqueResidues = True
|
109
|
+
case SequenceConstraint.AMINO_ACIDS_IGNORE_CASE:
|
110
|
+
uniqueResidues = True
|
111
|
+
case SequenceConstraint.AMINO_ACIDS_EXACT_MATCH:
|
112
|
+
uniqueResidues = True
|
113
|
+
areUniqueResiduesCaseSensitive = True
|
99
114
|
return cls(
|
100
115
|
fields=constraint_fields,
|
101
|
-
uniqueResidues=
|
116
|
+
uniqueResidues=uniqueResidues,
|
102
117
|
uniqueCanonicalSmilers=False,
|
103
|
-
areUniqueResiduesCaseSensitive=
|
118
|
+
areUniqueResiduesCaseSensitive=areUniqueResiduesCaseSensitive,
|
104
119
|
)
|
105
120
|
|
106
121
|
|
@@ -135,6 +150,8 @@ class CreateTagSchemaFieldModel(BaseModel):
|
|
135
150
|
name: str
|
136
151
|
requiredLink: FieldRequiredLinkShortModel | None = None
|
137
152
|
tooltipText: str | None = None
|
153
|
+
decimalPrecision: int | None = None
|
154
|
+
unitApiIdentifier: str | None = None
|
138
155
|
|
139
156
|
@classmethod
|
140
157
|
def from_props(
|
@@ -164,7 +181,7 @@ class CreateTagSchemaFieldModel(BaseModel):
|
|
164
181
|
)
|
165
182
|
|
166
183
|
tagSchema = None
|
167
|
-
if new_props.type
|
184
|
+
if new_props.type in BenchlingFieldType.get_entity_link_types():
|
168
185
|
if new_props.entity_link is not None:
|
169
186
|
if benchling_service is None:
|
170
187
|
raise ValueError(
|
@@ -184,6 +201,13 @@ class CreateTagSchemaFieldModel(BaseModel):
|
|
184
201
|
dropdown_summary_id = get_benchling_dropdown_summary_by_name(
|
185
202
|
benchling_service, new_props.dropdown_link
|
186
203
|
).id
|
204
|
+
unit_api_identifier = None
|
205
|
+
if new_props.unit_name:
|
206
|
+
if benchling_service is None:
|
207
|
+
raise ValueError("Benchling SDK must be provided to update unit field.")
|
208
|
+
unit_api_identifier = get_unit_id_from_name(
|
209
|
+
benchling_service, new_props.unit_name
|
210
|
+
)
|
187
211
|
return cls(
|
188
212
|
name=new_props.name,
|
189
213
|
systemName=new_props.warehouse_name,
|
@@ -197,6 +221,8 @@ class CreateTagSchemaFieldModel(BaseModel):
|
|
197
221
|
tagSchema=tagSchema,
|
198
222
|
),
|
199
223
|
tooltipText=new_props.tooltip,
|
224
|
+
unitApiIdentifier=unit_api_identifier,
|
225
|
+
decimalPrecision=new_props.decimal_places,
|
200
226
|
)
|
201
227
|
|
202
228
|
|
@@ -230,6 +256,8 @@ class TagSchemaFieldModel(BaseModel):
|
|
230
256
|
strictSelector: bool | None
|
231
257
|
systemName: str
|
232
258
|
tooltipText: str | None
|
259
|
+
unitApiIdentifier: str | None
|
260
|
+
unitSymbol: str | None = None
|
233
261
|
|
234
262
|
def update_from_props(
|
235
263
|
self,
|
@@ -307,6 +335,22 @@ class TagSchemaFieldModel(BaseModel):
|
|
307
335
|
benchling_service, update_props.dropdown_link
|
308
336
|
).id
|
309
337
|
self.schemaFieldSelectorId = dropdown_summary_id
|
338
|
+
self.decimalPrecision = (
|
339
|
+
update_props.decimal_places
|
340
|
+
if "decimal_places" in update_diff_names
|
341
|
+
else self.decimalPrecision
|
342
|
+
)
|
343
|
+
if "unit_name" in update_diff_names:
|
344
|
+
if update_props.unit_name is None:
|
345
|
+
self.unitApiIdentifier = None
|
346
|
+
else:
|
347
|
+
if benchling_service is None:
|
348
|
+
raise ValueError(
|
349
|
+
"Benchling SDK must be provided to update unit field."
|
350
|
+
)
|
351
|
+
self.unitApiIdentifier = get_unit_id_from_name(
|
352
|
+
benchling_service, update_props.unit_name
|
353
|
+
)
|
310
354
|
return self
|
311
355
|
|
312
356
|
|
@@ -405,6 +449,15 @@ class TagSchemaModel(BaseModel):
|
|
405
449
|
)
|
406
450
|
return cls.model_validate(schema)
|
407
451
|
|
452
|
+
@classmethod
|
453
|
+
@lru_cache(maxsize=100)
|
454
|
+
def get_one_cached(
|
455
|
+
cls,
|
456
|
+
benchling_service: BenchlingService,
|
457
|
+
wh_schema_name: str,
|
458
|
+
) -> TagSchemaModel:
|
459
|
+
return cls.get_one(benchling_service, wh_schema_name)
|
460
|
+
|
408
461
|
def get_field(self, wh_field_name: str) -> TagSchemaFieldModel:
|
409
462
|
"""Returns a field from the tag schema by its warehouse field name."""
|
410
463
|
for field in self.allFields:
|
@@ -437,37 +490,31 @@ class TagSchemaModel(BaseModel):
|
|
437
490
|
)
|
438
491
|
if "include_registry_id_in_chips" in update_diff_names:
|
439
492
|
self.includeRegistryIdInChips = update_props.include_registry_id_in_chips
|
493
|
+
if "show_bases_in_expanded_view" in update_diff_names:
|
494
|
+
self.showResidues = update_props.show_bases_in_expanded_view
|
440
495
|
|
441
496
|
if "constraint_fields" in update_diff_names:
|
442
497
|
if update_props.constraint_fields:
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
if f.systemName in update_props.constraint_fields
|
451
|
-
]
|
452
|
-
self.constraint = TagSchemaConstraint.from_constraint_fields(
|
453
|
-
constraint_fields, has_bases
|
498
|
+
sequence_constraint = next(
|
499
|
+
(
|
500
|
+
SequenceConstraint(c)
|
501
|
+
for c in update_props.constraint_fields
|
502
|
+
if SequenceConstraint.is_sequence_constraint(c)
|
503
|
+
),
|
504
|
+
None,
|
454
505
|
)
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
has_bases = False
|
461
|
-
if "bases" in update_props.constraint_fields:
|
462
|
-
has_bases = True
|
463
|
-
update_props.constraint_fields.discard("bases")
|
506
|
+
update_props.constraint_fields = {
|
507
|
+
c
|
508
|
+
for c in update_props.constraint_fields
|
509
|
+
if not SequenceConstraint.is_sequence_constraint(c)
|
510
|
+
}
|
464
511
|
constraint_fields = [
|
465
512
|
f
|
466
513
|
for f in self.fields
|
467
514
|
if f.systemName in update_props.constraint_fields
|
468
515
|
]
|
469
516
|
self.constraint = TagSchemaConstraint.from_constraint_fields(
|
470
|
-
constraint_fields,
|
517
|
+
constraint_fields, sequence_constraint
|
471
518
|
)
|
472
519
|
else:
|
473
520
|
self.constraint = None
|
liminal/entity_schemas/utils.py
CHANGED
@@ -5,12 +5,15 @@ from liminal.connection import BenchlingService
|
|
5
5
|
from liminal.dropdowns.utils import get_benchling_dropdown_id_name_map
|
6
6
|
from liminal.entity_schemas.tag_schema_models import TagSchemaFieldModel, TagSchemaModel
|
7
7
|
from liminal.enums import BenchlingAPIFieldType, BenchlingNamingStrategy
|
8
|
+
from liminal.enums.benchling_entity_type import BenchlingEntityType
|
9
|
+
from liminal.enums.sequence_constraint import SequenceConstraint
|
8
10
|
from liminal.mappers import (
|
9
11
|
convert_api_entity_type_to_entity_type,
|
10
12
|
convert_api_field_type_to_field_type,
|
11
13
|
)
|
12
14
|
from liminal.orm.name_template import NameTemplate
|
13
15
|
from liminal.orm.schema_properties import MixtureSchemaConfig, SchemaProperties
|
16
|
+
from liminal.unit_dictionary.utils import get_unit_id_to_name_map
|
14
17
|
|
15
18
|
|
16
19
|
def get_converted_tag_schemas(
|
@@ -24,6 +27,7 @@ def get_converted_tag_schemas(
|
|
24
27
|
"""
|
25
28
|
all_schemas = TagSchemaModel.get_all(benchling_service, wh_schema_names)
|
26
29
|
dropdowns_map = get_benchling_dropdown_id_name_map(benchling_service)
|
30
|
+
unit_id_to_name_map = get_unit_id_to_name_map(benchling_service)
|
27
31
|
all_schemas = (
|
28
32
|
all_schemas
|
29
33
|
if include_archived
|
@@ -31,7 +35,7 @@ def get_converted_tag_schemas(
|
|
31
35
|
)
|
32
36
|
return [
|
33
37
|
convert_tag_schema_to_internal_schema(
|
34
|
-
tag_schema, dropdowns_map, include_archived
|
38
|
+
tag_schema, dropdowns_map, unit_id_to_name_map, include_archived
|
35
39
|
)
|
36
40
|
for tag_schema in all_schemas
|
37
41
|
]
|
@@ -40,24 +44,38 @@ def get_converted_tag_schemas(
|
|
40
44
|
def convert_tag_schema_to_internal_schema(
|
41
45
|
tag_schema: TagSchemaModel,
|
42
46
|
dropdowns_map: dict[str, str],
|
47
|
+
unit_id_to_name_map: dict[str, str],
|
43
48
|
include_archived_fields: bool = False,
|
44
49
|
) -> tuple[SchemaProperties, NameTemplate, dict[str, BaseFieldProperties]]:
|
45
50
|
all_fields = tag_schema.allFields
|
46
51
|
if not include_archived_fields:
|
47
52
|
all_fields = [f for f in all_fields if not f.archiveRecord]
|
48
|
-
constraint_fields: set[str]
|
53
|
+
constraint_fields: set[str] = set()
|
54
|
+
entity_type = convert_api_entity_type_to_entity_type(
|
55
|
+
tag_schema.folderItemType, tag_schema.sequenceType
|
56
|
+
)
|
49
57
|
if tag_schema.constraint:
|
50
|
-
constraint_fields =
|
58
|
+
constraint_fields = constraint_fields.union(
|
59
|
+
[f.systemName for f in tag_schema.constraint.fields]
|
60
|
+
)
|
51
61
|
if tag_schema.constraint.uniqueResidues:
|
52
|
-
|
62
|
+
if entity_type.is_nt_sequence():
|
63
|
+
constraint_fields.add(SequenceConstraint.BASES.value)
|
64
|
+
elif entity_type == BenchlingEntityType.AA_SEQUENCE:
|
65
|
+
if tag_schema.constraint.areUniqueResiduesCaseSensitive:
|
66
|
+
constraint_fields.add(
|
67
|
+
SequenceConstraint.AMINO_ACIDS_EXACT_MATCH.value
|
68
|
+
)
|
69
|
+
else:
|
70
|
+
constraint_fields.add(
|
71
|
+
SequenceConstraint.AMINO_ACIDS_IGNORE_CASE.value
|
72
|
+
)
|
53
73
|
return (
|
54
74
|
SchemaProperties(
|
55
75
|
name=tag_schema.name,
|
56
76
|
prefix=tag_schema.prefix,
|
57
77
|
warehouse_name=tag_schema.sqlIdentifier,
|
58
|
-
entity_type=
|
59
|
-
tag_schema.folderItemType, tag_schema.sequenceType
|
60
|
-
),
|
78
|
+
entity_type=entity_type,
|
61
79
|
mixture_schema_config=MixtureSchemaConfig(
|
62
80
|
allowMeasuredIngredients=tag_schema.mixtureSchemaConfig.allowMeasuredIngredients,
|
63
81
|
componentLotStorageEnabled=tag_schema.mixtureSchemaConfig.componentLotStorageEnabled,
|
@@ -73,20 +91,25 @@ def convert_tag_schema_to_internal_schema(
|
|
73
91
|
_archived=tag_schema.archiveRecord is not None,
|
74
92
|
use_registry_id_as_label=tag_schema.useOrganizationCollectionAliasForDisplayLabel,
|
75
93
|
include_registry_id_in_chips=tag_schema.includeRegistryIdInChips,
|
94
|
+
show_bases_in_expanded_view=tag_schema.showResidues,
|
76
95
|
),
|
77
96
|
NameTemplate(
|
78
97
|
parts=tag_schema.get_internal_name_template_parts(),
|
79
98
|
order_name_parts_by_sequence=tag_schema.shouldOrderNamePartsBySequence,
|
80
99
|
),
|
81
100
|
{
|
82
|
-
f.systemName: convert_tag_schema_field_to_field_properties(
|
101
|
+
f.systemName: convert_tag_schema_field_to_field_properties(
|
102
|
+
f, dropdowns_map, unit_id_to_name_map
|
103
|
+
)
|
83
104
|
for f in all_fields
|
84
105
|
},
|
85
106
|
)
|
86
107
|
|
87
108
|
|
88
109
|
def convert_tag_schema_field_to_field_properties(
|
89
|
-
field: TagSchemaFieldModel,
|
110
|
+
field: TagSchemaFieldModel,
|
111
|
+
dropdowns_map: dict[str, str],
|
112
|
+
unit_id_to_name_map: dict[str, str],
|
90
113
|
) -> BaseFieldProperties:
|
91
114
|
return BaseFieldProperties(
|
92
115
|
name=field.name,
|
@@ -105,6 +128,10 @@ def convert_tag_schema_field_to_field_properties(
|
|
105
128
|
else None,
|
106
129
|
tooltip=field.tooltipText,
|
107
130
|
_archived=field.archiveRecord is not None,
|
131
|
+
unit_name=unit_id_to_name_map.get(field.unitApiIdentifier)
|
132
|
+
if field.unitApiIdentifier
|
133
|
+
else None,
|
134
|
+
decimal_places=field.decimalPrecision,
|
108
135
|
)
|
109
136
|
|
110
137
|
|
liminal/enums/__init__.py
CHANGED
@@ -5,3 +5,4 @@ from liminal.enums.benchling_field_type import BenchlingFieldType
|
|
5
5
|
from liminal.enums.benchling_folder_item_type import BenchlingFolderItemType
|
6
6
|
from liminal.enums.benchling_naming_strategy import BenchlingNamingStrategy
|
7
7
|
from liminal.enums.benchling_sequence_type import BenchlingSequenceType
|
8
|
+
from liminal.enums.sequence_constraint import SequenceConstraint
|
@@ -14,10 +14,19 @@ class BenchlingEntityType(StrEnum):
|
|
14
14
|
MIXTURE = "mixture"
|
15
15
|
MOLECULE = "molecule"
|
16
16
|
|
17
|
+
def is_nt_sequence(self) -> bool:
|
18
|
+
return self in [
|
19
|
+
self.DNA_SEQUENCE,
|
20
|
+
self.RNA_SEQUENCE,
|
21
|
+
self.DNA_OLIGO,
|
22
|
+
self.RNA_OLIGO,
|
23
|
+
]
|
24
|
+
|
17
25
|
def is_sequence(self) -> bool:
|
18
26
|
return self in [
|
19
27
|
self.DNA_SEQUENCE,
|
20
28
|
self.RNA_SEQUENCE,
|
21
29
|
self.DNA_OLIGO,
|
22
30
|
self.RNA_OLIGO,
|
31
|
+
self.AA_SEQUENCE,
|
23
32
|
]
|
@@ -32,3 +32,11 @@ class BenchlingFieldType(StrEnum):
|
|
32
32
|
cls.DATE,
|
33
33
|
cls.DATETIME,
|
34
34
|
]
|
35
|
+
|
36
|
+
@classmethod
|
37
|
+
def get_entity_link_types(cls) -> list[str]:
|
38
|
+
return [cls.ENTITY_LINK, cls.TRANSLATION_LINK, cls.PART_LINK]
|
39
|
+
|
40
|
+
@classmethod
|
41
|
+
def get_number_field_types(cls) -> list[str]:
|
42
|
+
return [cls.INTEGER, cls.DECIMAL]
|
@@ -11,6 +11,7 @@ class BenchlingNamingStrategy(StrEnum):
|
|
11
11
|
)
|
12
12
|
RENAME_WITH_TEMPLATE_WITH_ALIAS = "SET_FROM_NAME_PARTS" # Generate new registry IDs, rename according to name template, and keep old name as alias
|
13
13
|
REPLACE_NAMES_WITH_TEMPLATE = "REPLACE_NAMES_FROM_PARTS" # Generate new registry IDs, and replace name according to name template
|
14
|
+
KEEP_NAMES = "KEEP_NAMES" # Keeps the original name
|
14
15
|
|
15
16
|
@classmethod
|
16
17
|
def is_template_based(cls, strategy: BenchlingNamingStrategy) -> bool:
|
@@ -0,0 +1,13 @@
|
|
1
|
+
from liminal.base.str_enum import StrEnum
|
2
|
+
|
3
|
+
|
4
|
+
class SequenceConstraint(StrEnum):
|
5
|
+
"""This enum represents hardcoded sequence constraints."""
|
6
|
+
|
7
|
+
BASES = "bases"
|
8
|
+
AMINO_ACIDS_IGNORE_CASE = "amino_acids_ignore_case"
|
9
|
+
AMINO_ACIDS_EXACT_MATCH = "amino_acids_exact_match"
|
10
|
+
|
11
|
+
@classmethod
|
12
|
+
def is_sequence_constraint(cls, constraint: str) -> bool:
|
13
|
+
return constraint in cls._value2member_map_
|
liminal/mappers.py
CHANGED
@@ -22,6 +22,8 @@ def convert_benchling_type_to_python_type(benchling_type: BenchlingFieldType) ->
|
|
22
22
|
BenchlingFieldType.BLOB_LINK: dict[str, Any],
|
23
23
|
BenchlingFieldType.CUSTOM_ENTITY_LINK: str,
|
24
24
|
BenchlingFieldType.DNA_SEQUENCE_LINK: str,
|
25
|
+
BenchlingFieldType.AA_SEQUENCE_LINK: str,
|
26
|
+
BenchlingFieldType.TRANSLATION_LINK: str,
|
25
27
|
BenchlingFieldType.DROPDOWN: str,
|
26
28
|
BenchlingFieldType.ENTITY_LINK: str,
|
27
29
|
BenchlingFieldType.ENTRY_LINK: str,
|
@@ -48,6 +50,8 @@ def convert_benchling_type_to_sql_alchemy_type(
|
|
48
50
|
BenchlingFieldType.BLOB_LINK: JSON,
|
49
51
|
BenchlingFieldType.CUSTOM_ENTITY_LINK: String,
|
50
52
|
BenchlingFieldType.DNA_SEQUENCE_LINK: String,
|
53
|
+
BenchlingFieldType.AA_SEQUENCE_LINK: String,
|
54
|
+
BenchlingFieldType.TRANSLATION_LINK: String,
|
51
55
|
BenchlingFieldType.DROPDOWN: String,
|
52
56
|
BenchlingFieldType.ENTITY_LINK: String,
|
53
57
|
BenchlingFieldType.ENTRY_LINK: String,
|
@@ -55,7 +59,6 @@ def convert_benchling_type_to_sql_alchemy_type(
|
|
55
59
|
BenchlingFieldType.STORAGE_LINK: String,
|
56
60
|
BenchlingFieldType.PART_LINK: String,
|
57
61
|
BenchlingFieldType.MIXTURE_LINK: String,
|
58
|
-
BenchlingFieldType.AA_SEQUENCE_LINK: String,
|
59
62
|
BenchlingFieldType.TEXT: String,
|
60
63
|
}
|
61
64
|
if benchling_type in benchling_to_sql_alchemy_type_map:
|
@@ -88,13 +91,10 @@ def convert_field_type_to_api_field_type(
|
|
88
91
|
BenchlingAPIFieldType.FILE_LINK,
|
89
92
|
BenchlingFolderItemType.SEQUENCE,
|
90
93
|
),
|
91
|
-
BenchlingFieldType.PART_LINK: (
|
92
|
-
BenchlingAPIFieldType.PART_LINK,
|
93
|
-
BenchlingFolderItemType.SEQUENCE,
|
94
|
-
),
|
94
|
+
BenchlingFieldType.PART_LINK: (BenchlingAPIFieldType.PART_LINK, None),
|
95
95
|
BenchlingFieldType.TRANSLATION_LINK: (
|
96
96
|
BenchlingAPIFieldType.TRANSLATION_LINK,
|
97
|
-
|
97
|
+
None,
|
98
98
|
),
|
99
99
|
BenchlingFieldType.ENTITY_LINK: (BenchlingAPIFieldType.FILE_LINK, None),
|
100
100
|
BenchlingFieldType.DECIMAL: (BenchlingAPIFieldType.FLOAT, None),
|
@@ -135,13 +135,10 @@ def convert_api_field_type_to_field_type(
|
|
135
135
|
BenchlingAPIFieldType.FILE_LINK,
|
136
136
|
BenchlingFolderItemType.PROTEIN,
|
137
137
|
): BenchlingFieldType.AA_SEQUENCE_LINK,
|
138
|
-
(
|
139
|
-
BenchlingAPIFieldType.PART_LINK,
|
140
|
-
BenchlingFolderItemType.SEQUENCE,
|
141
|
-
): BenchlingFieldType.PART_LINK,
|
138
|
+
(BenchlingAPIFieldType.PART_LINK, None): BenchlingFieldType.PART_LINK,
|
142
139
|
(
|
143
140
|
BenchlingAPIFieldType.TRANSLATION_LINK,
|
144
|
-
|
141
|
+
None,
|
145
142
|
): BenchlingFieldType.TRANSLATION_LINK,
|
146
143
|
(BenchlingAPIFieldType.FILE_LINK, None): BenchlingFieldType.ENTITY_LINK,
|
147
144
|
(BenchlingAPIFieldType.FLOAT, None): BenchlingFieldType.DECIMAL,
|
liminal/orm/base_model.py
CHANGED
@@ -13,6 +13,8 @@ from sqlalchemy.orm.decl_api import declared_attr
|
|
13
13
|
|
14
14
|
from liminal.base.base_validation_filters import BaseValidatorFilters
|
15
15
|
from liminal.enums import BenchlingNamingStrategy
|
16
|
+
from liminal.enums.benchling_entity_type import BenchlingEntityType
|
17
|
+
from liminal.enums.sequence_constraint import SequenceConstraint
|
16
18
|
from liminal.orm.base import Base
|
17
19
|
from liminal.orm.base_tables.user import User
|
18
20
|
from liminal.orm.name_template import NameTemplate
|
@@ -63,16 +65,49 @@ class BaseModel(Generic[T], Base):
|
|
63
65
|
c[0] for c in cls.__dict__.items() if isinstance(c[1], SqlColumn)
|
64
66
|
]
|
65
67
|
# Validate constraints
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
)
|
68
|
+
invalid_constraints = [
|
69
|
+
c
|
70
|
+
for c in cls.__schema_properties__.constraint_fields
|
71
|
+
if c
|
72
|
+
not in set(column_wh_names)
|
73
|
+
| set(SequenceConstraint._value2member_map_.keys())
|
74
|
+
]
|
75
|
+
if invalid_constraints:
|
76
|
+
raise ValueError(
|
77
|
+
f"Constraints {', '.join(invalid_constraints)} are not fields on schema {cls.__schema_properties__.name}."
|
78
|
+
)
|
79
|
+
sequence_constraints = [
|
80
|
+
SequenceConstraint(c)
|
81
|
+
for c in cls.__schema_properties__.constraint_fields
|
82
|
+
if SequenceConstraint.is_sequence_constraint(c)
|
83
|
+
]
|
84
|
+
if len(sequence_constraints) > 1:
|
85
|
+
raise ValueError(
|
86
|
+
"Only one sequence constraint field can be set for a schema."
|
87
|
+
)
|
88
|
+
sequence_constraint = sequence_constraints[0] if sequence_constraints else None
|
89
|
+
match sequence_constraint:
|
90
|
+
case SequenceConstraint.BASES:
|
91
|
+
if not cls.__schema_properties__.entity_type.is_nt_sequence():
|
92
|
+
raise ValueError(
|
93
|
+
"`bases` constraint is only supported for nucleotide sequence entities."
|
94
|
+
)
|
95
|
+
case SequenceConstraint.AMINO_ACIDS_IGNORE_CASE:
|
96
|
+
if (
|
97
|
+
cls.__schema_properties__.entity_type
|
98
|
+
!= BenchlingEntityType.AA_SEQUENCE
|
99
|
+
):
|
100
|
+
raise ValueError(
|
101
|
+
"`amino_acids_ignore_case` constraint is only supported for aa_sequence entities."
|
102
|
+
)
|
103
|
+
case SequenceConstraint.AMINO_ACIDS_EXACT_MATCH:
|
104
|
+
if (
|
105
|
+
cls.__schema_properties__.entity_type
|
106
|
+
!= BenchlingEntityType.AA_SEQUENCE
|
107
|
+
):
|
108
|
+
raise ValueError(
|
109
|
+
"`amino_acids_exact_match` constraint is only supported for aa_sequence entities."
|
110
|
+
)
|
76
111
|
# Validate naming strategies
|
77
112
|
if any(
|
78
113
|
BenchlingNamingStrategy.is_template_based(strategy)
|
@@ -84,7 +119,7 @@ class BaseModel(Generic[T], Base):
|
|
84
119
|
)
|
85
120
|
# Validate name template
|
86
121
|
if cls.__name_template__:
|
87
|
-
if not cls.__schema_properties__.entity_type.
|
122
|
+
if not cls.__schema_properties__.entity_type.is_nt_sequence():
|
88
123
|
if cls.__name_template__.order_name_parts_by_sequence is True:
|
89
124
|
raise ValueError(
|
90
125
|
"order_name_parts_by_sequence is only supported for sequence entities. Must be set to False if entity type is not a sequence."
|
liminal/orm/column.py
CHANGED
@@ -32,6 +32,10 @@ class Column(SqlColumn):
|
|
32
32
|
The dropdown for the field.
|
33
33
|
entity_link : str | None = None
|
34
34
|
The warehouse name of the entity the field links to.
|
35
|
+
unit : str | None = None
|
36
|
+
The unit of the field. Searches for the unit warehouse name in the Unit Dictionary.
|
37
|
+
decimal_places : int | None = None
|
38
|
+
The number of decimal places for the field. Must be (0-15).
|
35
39
|
_warehouse_name : str | None = None
|
36
40
|
The warehouse name of the column. Necessary when the variable name is not the same as the warehouse name.
|
37
41
|
_archived : bool = False
|
@@ -48,6 +52,8 @@ class Column(SqlColumn):
|
|
48
52
|
tooltip: str | None = None,
|
49
53
|
dropdown: Type[BaseDropdown] | None = None, # noqa: UP006
|
50
54
|
entity_link: str | None = None,
|
55
|
+
unit_name: str | None = None,
|
56
|
+
decimal_places: int | None = None,
|
51
57
|
_warehouse_name: str | None = None,
|
52
58
|
_archived: bool = False,
|
53
59
|
**kwargs: Any,
|
@@ -64,6 +70,8 @@ class Column(SqlColumn):
|
|
64
70
|
entity_link=entity_link,
|
65
71
|
tooltip=tooltip,
|
66
72
|
_archived=_archived,
|
73
|
+
unit_name=unit_name,
|
74
|
+
decimal_places=decimal_places,
|
67
75
|
)
|
68
76
|
self.properties = properties
|
69
77
|
|
@@ -73,19 +81,29 @@ class Column(SqlColumn):
|
|
73
81
|
raise ValueError("Dropdown can only be set if the field type is DROPDOWN.")
|
74
82
|
if dropdown is None and type == BenchlingFieldType.DROPDOWN:
|
75
83
|
raise ValueError("Dropdown must be set if the field type is DROPDOWN.")
|
76
|
-
if
|
84
|
+
if unit_name and type not in BenchlingFieldType.get_number_field_types():
|
77
85
|
raise ValueError(
|
78
|
-
"
|
86
|
+
f"Unit can only be set if the field type is one of {BenchlingFieldType.get_number_field_types()}."
|
79
87
|
)
|
80
|
-
if
|
88
|
+
if decimal_places and type not in BenchlingFieldType.DECIMAL:
|
81
89
|
raise ValueError(
|
82
|
-
"
|
90
|
+
"Decimal places can only be set if the field type is DECIMAL."
|
91
|
+
)
|
92
|
+
if decimal_places and (decimal_places < 0 or decimal_places > 15):
|
93
|
+
raise ValueError("Decimal places must be between 0 and 15.")
|
94
|
+
if entity_link and type not in BenchlingFieldType.get_entity_link_types():
|
95
|
+
raise ValueError(
|
96
|
+
f"Entity link can only be set if the field type is one of {BenchlingFieldType.get_entity_link_types()}."
|
97
|
+
)
|
98
|
+
if parent_link and type not in BenchlingFieldType.get_entity_link_types():
|
99
|
+
raise ValueError(
|
100
|
+
f"Parent link can only be set if the field type is one of {BenchlingFieldType.get_entity_link_types()}."
|
83
101
|
)
|
84
102
|
if type in BenchlingFieldType.get_non_multi_select_types() and is_multi is True:
|
85
103
|
raise ValueError(f"Field type {type} cannot have multi-value set as True.")
|
86
104
|
self.sqlalchemy_type = sqlalchemy_type
|
87
105
|
foreign_key = None
|
88
|
-
if type
|
106
|
+
if type in BenchlingFieldType.get_entity_link_types() and entity_link:
|
89
107
|
foreign_key = ForeignKey(f"{entity_link}$raw.id")
|
90
108
|
if _warehouse_name:
|
91
109
|
kwargs["name"] = _warehouse_name
|
liminal/orm/schema_properties.py
CHANGED
@@ -35,9 +35,14 @@ class SchemaProperties(BaseSchemaProperties):
|
|
35
35
|
Flag for configuring the chip label for entities. Determines if the chip will use the Registry ID as the main label for items.
|
36
36
|
include_registry_id_in_chips : bool | None = None
|
37
37
|
Flag for configuring the chip label for entities. Determines if the chip will include the Registry ID in the chip label.
|
38
|
-
constraint_fields : set[str]
|
39
|
-
Set of constraints for field values for the schema. Must be a set of column names
|
40
|
-
|
38
|
+
constraint_fields : set[str]
|
39
|
+
Set of constraints for field values for the schema. Must be a set of warehouse column names. This specifies that their entity field values must be a unique combination within an entity.
|
40
|
+
The following sequence constraints are also supported:
|
41
|
+
- bases: only supported for nucleotide sequence entity types. hasUniqueResidues=True
|
42
|
+
- amino_acids_ignore_case: only supported for amino acid sequence entity types. hasUniqueResidues=True
|
43
|
+
- amino_acids_exact_match: only supported for amino acid sequence entity types. hasUniqueResidues=True, areUniqueResiduesCaseSensitive=True
|
44
|
+
show_bases_in_expanded_view : bool | None = None
|
45
|
+
Whether the bases should be shown in the expanded view of the entity.
|
41
46
|
_archived : bool | None
|
42
47
|
Whether the schema is archived in Benchling.
|
43
48
|
"""
|
@@ -50,7 +55,8 @@ class SchemaProperties(BaseSchemaProperties):
|
|
50
55
|
use_registry_id_as_label: bool | None = False
|
51
56
|
include_registry_id_in_chips: bool | None = False
|
52
57
|
mixture_schema_config: MixtureSchemaConfig | None = None
|
53
|
-
constraint_fields: set[str]
|
58
|
+
constraint_fields: set[str] = set()
|
59
|
+
show_bases_in_expanded_view: bool | None = False
|
54
60
|
_archived: bool = False
|
55
61
|
|
56
62
|
def __init__(self, **data: Any):
|
@@ -73,7 +79,10 @@ class SchemaProperties(BaseSchemaProperties):
|
|
73
79
|
raise ValueError(
|
74
80
|
"The entity type is not a Mixture. Remove the mixture schema config."
|
75
81
|
)
|
76
|
-
|
82
|
+
if not self.entity_type.is_sequence() and self.show_bases_in_expanded_view:
|
83
|
+
raise ValueError(
|
84
|
+
"show_bases_in_expanded_view can only be set for sequence entities."
|
85
|
+
)
|
77
86
|
if self.naming_strategies and len(self.naming_strategies) == 0:
|
78
87
|
raise ValueError(
|
79
88
|
"Schema must have at least 1 registry naming option enabled"
|
liminal/tests/.DS_Store
ADDED
Binary file
|
@@ -0,0 +1,48 @@
|
|
1
|
+
import json
|
2
|
+
from functools import lru_cache
|
3
|
+
|
4
|
+
from liminal.connection.benchling_service import BenchlingService
|
5
|
+
|
6
|
+
|
7
|
+
@lru_cache(maxsize=1)
|
8
|
+
def get_unit_name_to_id_map(benchling_service: BenchlingService) -> dict[str, str]:
|
9
|
+
response = benchling_service.api.get_response(
|
10
|
+
url="/api/v2-alpha/unit-types?pageSize=50"
|
11
|
+
)
|
12
|
+
unit_types = json.loads(response.content)["unitTypes"]
|
13
|
+
all_unit_types_flattened = {}
|
14
|
+
for unit_type in unit_types:
|
15
|
+
for unit in unit_type["units"]:
|
16
|
+
all_unit_types_flattened[unit["name"]] = unit["id"]
|
17
|
+
return all_unit_types_flattened
|
18
|
+
|
19
|
+
|
20
|
+
def get_unit_id_from_name(benchling_service: BenchlingService, unit_name: str) -> str:
|
21
|
+
unit_id = get_unit_name_to_id_map(benchling_service).get(unit_name)
|
22
|
+
if unit_id is None:
|
23
|
+
raise ValueError(
|
24
|
+
f"Unit {unit_name} not found in Benchling Unit Dictionary. Please check the field definition or your Unit Dictionary."
|
25
|
+
)
|
26
|
+
return unit_id
|
27
|
+
|
28
|
+
|
29
|
+
@lru_cache(maxsize=1)
|
30
|
+
def get_unit_id_to_name_map(benchling_service: BenchlingService) -> dict[str, str]:
|
31
|
+
response = benchling_service.api.get_response(
|
32
|
+
url="/api/v2-alpha/unit-types?pageSize=50"
|
33
|
+
)
|
34
|
+
unit_types = json.loads(response.content)["unitTypes"]
|
35
|
+
all_unit_types_flattened = {}
|
36
|
+
for unit_type in unit_types:
|
37
|
+
for unit in unit_type["units"]:
|
38
|
+
all_unit_types_flattened[unit["id"]] = unit["name"]
|
39
|
+
return all_unit_types_flattened
|
40
|
+
|
41
|
+
|
42
|
+
def get_unit_name_from_id(benchling_service: BenchlingService, unit_id: str) -> str:
|
43
|
+
unit_name = get_unit_id_to_name_map(benchling_service).get(unit_id)
|
44
|
+
if unit_name is None:
|
45
|
+
raise ValueError(
|
46
|
+
f"Unit {unit_id} not found in Benchling Unit Dictionary. Please check the field definition or your Unit Dictionary."
|
47
|
+
)
|
48
|
+
return unit_name
|
liminal/validation/__init__.py
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
import inspect
|
2
1
|
from datetime import datetime
|
3
2
|
from functools import partial, wraps
|
4
3
|
from typing import TYPE_CHECKING, Any, Callable
|
@@ -102,7 +101,7 @@ class BenchlingValidatorReport(BaseModel):
|
|
102
101
|
|
103
102
|
|
104
103
|
def liminal_validator(
|
105
|
-
func: Callable[[
|
104
|
+
func: Callable[["BenchlingBaseModel"], BenchlingValidatorReport | None]
|
106
105
|
| None = None,
|
107
106
|
*,
|
108
107
|
validator_level: ValidationSeverity = ValidationSeverity.LOW,
|
@@ -125,15 +124,9 @@ def liminal_validator(
|
|
125
124
|
validator_level=validator_level,
|
126
125
|
validator_name=validator_name,
|
127
126
|
)
|
128
|
-
elif not isinstance(
|
129
|
-
func, Callable[[["BenchlingBaseModel"]], BenchlingValidatorReport | None]
|
130
|
-
):
|
131
|
-
raise ValueError(
|
132
|
-
"Parameters passed to liminal_validator must be keyword arguments, not positional arguments."
|
133
|
-
)
|
134
127
|
|
135
128
|
@wraps(func)
|
136
|
-
def wrapper(self:
|
129
|
+
def wrapper(self: "BenchlingBaseModel") -> BenchlingValidatorReport:
|
137
130
|
"""Wrapper that runs the validator function and returns a BenchlingValidatorReport."""
|
138
131
|
try:
|
139
132
|
ret_val = func(self)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: liminal-orm
|
3
|
-
Version:
|
3
|
+
Version: 3.0.0
|
4
4
|
Summary: An ORM and toolkit that builds on top of Benchling's platform to keep your schemas and downstream code dependencies in sync.
|
5
5
|
Home-page: https://github.com/dynotx/liminal-orm
|
6
6
|
Author: DynoTx Open Source
|
@@ -1,12 +1,13 @@
|
|
1
|
+
liminal/.DS_Store,sha256=s_ehSI1aIzOjVRnFlcSzhtWS3irmEDSGHyS6l0QRcus,8196
|
1
2
|
liminal/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
3
|
liminal/base/base_dropdown.py,sha256=Unk4l_5Y8rj_eSWYqzFi2BAFSQToQDWW2qdXwiCHTg8,2523
|
3
|
-
liminal/base/base_operation.py,sha256=
|
4
|
+
liminal/base/base_operation.py,sha256=opQfFZeC49YAFkg5ahE6CFpeSUNPh1ootWZxXyEXfFI,3128
|
4
5
|
liminal/base/base_validation_filters.py,sha256=kHG3G5gXkuNHQosMTrxRc57OTmczcaoSx0DmkrScIr4,1043
|
5
6
|
liminal/base/compare_operation.py,sha256=hkpv4ewHhxy4dlTPKgJuzBjsAqO6Km7OrrKB44pRA_o,352
|
6
7
|
liminal/base/name_template_parts.py,sha256=dJeyrhhhZHDwKXe_p7AtgDlbZlzsnYQ8FoM8FXVF7q0,271
|
7
|
-
liminal/base/properties/base_field_properties.py,sha256=
|
8
|
+
liminal/base/properties/base_field_properties.py,sha256=ze8CLf0jPPYwklFYVKXkRvXiBBA9ilG5nK1_oP0rS04,4970
|
8
9
|
liminal/base/properties/base_name_template.py,sha256=AOtaW4QEDRC-mjZOZk6jgc_mopUMsHS2Fj6VVsO07WY,3150
|
9
|
-
liminal/base/properties/base_schema_properties.py,sha256=
|
10
|
+
liminal/base/properties/base_schema_properties.py,sha256=11QaxtUoHZx25QFv7HP8gQBpknrFthVeyWuMnI_lV5g,5891
|
10
11
|
liminal/base/str_enum.py,sha256=jF3d-Lo8zsHUe6GsctX2L-TSj92Y3qCYDrTD-saeJoc,210
|
11
12
|
liminal/cli/cli.py,sha256=JxWHLO9KMeMaOnOYwzdH0w71l0477ScFOkWNtTlc97Y,9045
|
12
13
|
liminal/cli/controller.py,sha256=QNj3QO9TMb9hfc6U-VhLuFa0_aohOHZUmvY4XkATPhw,10118
|
@@ -21,48 +22,51 @@ liminal/dropdowns/generate_files.py,sha256=IqnBs-IyLsIZE0NUkdB99zd5EAF-1f9CPBebl
|
|
21
22
|
liminal/dropdowns/operations.py,sha256=-TRIsxqnUtrIUjhrt5k_PdiBCDUXsXDzsOUmznJE-6Q,13516
|
22
23
|
liminal/dropdowns/utils.py,sha256=1-H7bTszCUeqeRBpiYXjRjreDzhn1Fd1MFwIsrEI-o4,4109
|
23
24
|
liminal/entity_schemas/api.py,sha256=Emn_Y95cAG9Wis6tpchw6QBVKQh4If86LOdgKk0Ndjw,3575
|
24
|
-
liminal/entity_schemas/compare.py,sha256=
|
25
|
-
liminal/entity_schemas/entity_schema_models.py,sha256=
|
26
|
-
liminal/entity_schemas/generate_files.py,sha256=
|
27
|
-
liminal/entity_schemas/operations.py,sha256=
|
28
|
-
liminal/entity_schemas/tag_schema_models.py,sha256=
|
29
|
-
liminal/entity_schemas/utils.py,sha256=
|
30
|
-
liminal/enums/__init__.py,sha256
|
25
|
+
liminal/entity_schemas/compare.py,sha256=t6tl67GWaMoNNcPxyLpCuNAlN3OWNqURTo3EUEMtETE,17549
|
26
|
+
liminal/entity_schemas/entity_schema_models.py,sha256=v5A1ELaiuBnUSl1HkUNAeMuIRQeQnIKzfpFxmsiKWh0,8349
|
27
|
+
liminal/entity_schemas/generate_files.py,sha256=u9SoDO9f4qL2nZaddln__2J0zJ3QMFBQhiUabn22aUY,9032
|
28
|
+
liminal/entity_schemas/operations.py,sha256=jd6Wiq_rW0UIjiVqUACio_Lwbv3fGrtoyQRGBHtXJHo,26654
|
29
|
+
liminal/entity_schemas/tag_schema_models.py,sha256=FlpoDWh083nA8iT7Pl84HVXKb7StIApV1ExvwHKiAfc,24002
|
30
|
+
liminal/entity_schemas/utils.py,sha256=2ZHyLxnYITVEuyAWxNdsq5hcNSgvN7pN3-uUzyocYSk,6161
|
31
|
+
liminal/enums/__init__.py,sha256=-szuqAwMED4ai0NaPVUfgihQJAJ27wPu_nDnj4cEgTk,518
|
31
32
|
liminal/enums/benchling_api_field_type.py,sha256=0QamSWEMnxZtedZXlh6zNhSRogS9ZqvWskdHHN19xJo,633
|
32
|
-
liminal/enums/benchling_entity_type.py,sha256=
|
33
|
-
liminal/enums/benchling_field_type.py,sha256=
|
33
|
+
liminal/enums/benchling_entity_type.py,sha256=H_6ZlHJsiVNMpezPBrNKo2eP0pDrt--HU-P7PgznaMA,846
|
34
|
+
liminal/enums/benchling_field_type.py,sha256=kKbLR6_gTP3qI-bNOWDO3csfOXI50Y6p6buQH7cQqUg,1194
|
34
35
|
liminal/enums/benchling_folder_item_type.py,sha256=Jb-YxCvB8O86_qTsfwtLQOkKGjTWGKHFwIKf24eemYk,248
|
35
|
-
liminal/enums/benchling_naming_strategy.py,sha256=
|
36
|
+
liminal/enums/benchling_naming_strategy.py,sha256=jmaR-Vfj3MWhna8tANBNjAgYUyoQ5wMbz1AIy2bv6Zk,1258
|
36
37
|
liminal/enums/benchling_sequence_type.py,sha256=TBI4C5c1XKE4ZXqsz1ApDUzy2wR-04u-M3VO_zLikjM,202
|
37
38
|
liminal/enums/name_template_part_type.py,sha256=Kv0phZIO_dPN3tLHM0lT2tjUd3zBGqpJQGahEpGjNcU,365
|
39
|
+
liminal/enums/sequence_constraint.py,sha256=CT3msm8qzJpcivfbQZ3NOWNRsedH4mSlfhzvQBLrHWA,407
|
38
40
|
liminal/external/__init__.py,sha256=nMpyzpBXpYhTvN3R3HQEiYb24_U2AYjz_20seAUUK9s,1264
|
39
|
-
liminal/mappers.py,sha256=
|
41
|
+
liminal/mappers.py,sha256=TgPMQsLrESAI6D7KBl0UoBBpnxYgcgGOT7a2faWsuhY,9587
|
40
42
|
liminal/migrate/components.py,sha256=2HuFp5KDNhofROMRI-BioUoA4CCjhQ_v_F0QmGJzUBU,3480
|
41
43
|
liminal/migrate/revision.py,sha256=KppU0u-d0JsfPsXsmncxy9Q_XBJyf-o4e16wNZAJODM,7774
|
42
44
|
liminal/migrate/revisions_timeline.py,sha256=G9VwxPrLhLqKOrIXyxrXyHpujc-72m7omsZjI5-0D0M,14520
|
43
45
|
liminal/migrate/utils.py,sha256=HdSr3N2WN_1S-PLRGVWSMYl-4gIcP-Ph2wPycGi2cGg,3404
|
44
46
|
liminal/orm/base.py,sha256=fFSpiNRYgK5UG7lbXdQGV8KgO8pwjMqt0pycM3rWJ2o,615
|
45
|
-
liminal/orm/base_model.py,sha256=
|
47
|
+
liminal/orm/base_model.py,sha256=FFkrB-lMAAgp77BUnQDu39IxZxmhHu52CtCLeBZVNGA,15527
|
46
48
|
liminal/orm/base_tables/registry_entity.py,sha256=4ET1cepTGjZ3AMFI5q-iMYxMObzXwuUDBD0jNNqCipE,2126
|
47
49
|
liminal/orm/base_tables/schema.py,sha256=7_btCVSUJxjVdGcKVRKL8sKcNw7-_gazTpfEh1jru3o,921
|
48
50
|
liminal/orm/base_tables/user.py,sha256=elRAHj7HgO3iVLK_pNCIwf_9Rl_9k6vkBgaYazoJSQc,818
|
49
|
-
liminal/orm/column.py,sha256=
|
51
|
+
liminal/orm/column.py,sha256=aK-MrKabOK5tf3UFPpRACq83YVVrjXITZF_rcOU-wPQ,6207
|
50
52
|
liminal/orm/mixins.py,sha256=yEeUDF1qEBLP523q8bZra4KtNVK0gwZN9mXJSNe3GEE,4802
|
51
53
|
liminal/orm/name_template.py,sha256=ftXZOiRR6gGGvGaZkFVDXKOboIHFWauhQENRguBGWMI,1739
|
52
54
|
liminal/orm/name_template_parts.py,sha256=KCGXAcCuOqCjlgYn-mw1K7fwDI92D20l-FnlpEVrbM8,2771
|
53
55
|
liminal/orm/relationship.py,sha256=Zl4bMHbtDSPx1psGHYnojGGJpA8B8hwcPJdgjB1lmW0,2490
|
54
|
-
liminal/orm/schema_properties.py,sha256=
|
56
|
+
liminal/orm/schema_properties.py,sha256=vqqjnxbh7AYh9ZvSmhCsl69BqSBPpQutNKImb-TBCGg,4167
|
55
57
|
liminal/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
58
|
+
liminal/tests/.DS_Store,sha256=0sTLf7flLKL2_3KGceYriAB8_gXTcYwn0c2RVSYmLZk,6148
|
56
59
|
liminal/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
57
60
|
liminal/tests/conftest.py,sha256=B463eOfe1uCHDJsUNvG-6tY8Qx8FJMByGDOtuyM87lA,17669
|
58
61
|
liminal/tests/from benchling_sdk.py,sha256=CjRUHFB3iaa4rUPLGOqDiBq5EPKldm-Fd8aQQr92zF4,147
|
59
62
|
liminal/tests/test_dropdown_compare.py,sha256=yHB0ovQlBLRu8-qYkqIPd8VtYEOmOft_93FQM86g_z8,8198
|
60
63
|
liminal/tests/test_entity_schema_compare.py,sha256=-26Bu5eYIuHRswB5kYjGDo5Wed5LUWjm1e6IRI1Q-lE,18952
|
64
|
+
liminal/unit_dictionary/utils.py,sha256=o3K06Yyt33iIUSMHPT8f1vSuUSgWjZLf51p78lx4SZs,1817
|
61
65
|
liminal/utils.py,sha256=radRtRsZmCiNblMvxOX1DH0rcO5TR09kFlp6OONIPBU,2951
|
62
|
-
liminal/validation/__init__.py,sha256=
|
66
|
+
liminal/validation/__init__.py,sha256=TVaHrSF3GnSd4mbZrPn8TBHscGWkAPKAUUPq7-symC8,5275
|
63
67
|
liminal/validation/validation_severity.py,sha256=ib03PTZCQHcbBDc01v4gJF53YtA-ANY6QSFnhTV-FbU,259
|
64
|
-
liminal_orm-
|
65
|
-
liminal_orm-
|
66
|
-
liminal_orm-
|
67
|
-
liminal_orm-
|
68
|
-
liminal_orm-
|
68
|
+
liminal_orm-3.0.0.dist-info/LICENSE.md,sha256=oVA877F_D1AV44dpjsv4f-4k690uNGApX1EtzOo3T8U,11353
|
69
|
+
liminal_orm-3.0.0.dist-info/METADATA,sha256=36MOv4Hh9h8gTmKFAtfn-XpJerX15rLphdD8WWDPsxQ,11032
|
70
|
+
liminal_orm-3.0.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
71
|
+
liminal_orm-3.0.0.dist-info/entry_points.txt,sha256=atIrU63rrzH81dWC2sjUbFLlc5FWMmYRdMxXEWexIZA,47
|
72
|
+
liminal_orm-3.0.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|