liminal-orm 1.0.5__py3-none-any.whl → 1.1.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/base/base_operation.py +9 -11
- liminal/base/properties/base_field_properties.py +11 -0
- liminal/base/properties/base_schema_properties.py +15 -2
- liminal/cli/cli.py +4 -1
- liminal/cli/live_test_dropdown_migration.py +8 -9
- liminal/cli/live_test_entity_schema_migration.py +24 -27
- liminal/connection/benchling_connection.py +8 -2
- liminal/connection/benchling_service.py +18 -19
- liminal/dropdowns/operations.py +1 -1
- liminal/entity_schemas/compare.py +17 -7
- liminal/entity_schemas/entity_schema_models.py +6 -9
- liminal/entity_schemas/generate_files.py +9 -8
- liminal/entity_schemas/operations.py +77 -106
- liminal/entity_schemas/tag_schema_models.py +9 -2
- liminal/entity_schemas/utils.py +4 -2
- liminal/enums/benchling_api_field_type.py +3 -0
- liminal/enums/benchling_entity_type.py +1 -0
- liminal/enums/benchling_field_type.py +4 -0
- liminal/enums/benchling_folder_item_type.py +1 -0
- liminal/external/__init__.py +0 -2
- liminal/mappers.py +25 -1
- liminal/migrate/components.py +9 -4
- liminal/orm/base_model.py +8 -7
- liminal/orm/column.py +11 -4
- liminal/orm/mixins.py +9 -0
- liminal/orm/schema_properties.py +8 -2
- liminal/tests/conftest.py +52 -22
- liminal/tests/test_entity_schema_compare.py +11 -5
- liminal/utils.py +11 -4
- {liminal_orm-1.0.5.dist-info → liminal_orm-1.1.0.dist-info}/METADATA +12 -5
- liminal_orm-1.1.0.dist-info/RECORD +61 -0
- liminal_orm-1.0.5.dist-info/RECORD +0 -61
- {liminal_orm-1.0.5.dist-info → liminal_orm-1.1.0.dist-info}/LICENSE.md +0 -0
- {liminal_orm-1.0.5.dist-info → liminal_orm-1.1.0.dist-info}/WHEEL +0 -0
- {liminal_orm-1.0.5.dist-info → liminal_orm-1.1.0.dist-info}/entry_points.txt +0 -0
liminal/base/base_operation.py
CHANGED
@@ -14,16 +14,14 @@ Order of operations based on order class var:
|
|
14
14
|
7. ReorderDropdownOptions
|
15
15
|
8. CreateSchema
|
16
16
|
9. UpdateSchema
|
17
|
-
10.
|
18
|
-
11.
|
19
|
-
12.
|
20
|
-
13.
|
21
|
-
14.
|
22
|
-
15.
|
23
|
-
16.
|
24
|
-
17.
|
25
|
-
18. ArchiveSchema
|
26
|
-
19. ArchiveDropdown
|
17
|
+
10. UnarchiveSchema
|
18
|
+
11. CreateField
|
19
|
+
12. UnarchiveField
|
20
|
+
13. UpdateField
|
21
|
+
14. ArchiveField
|
22
|
+
15. ReorderFields
|
23
|
+
16. ArchiveSchema
|
24
|
+
17. ArchiveDropdown
|
27
25
|
"""
|
28
26
|
|
29
27
|
|
@@ -36,7 +34,7 @@ class BaseOperation(ABC):
|
|
36
34
|
self.args = args
|
37
35
|
self.kwargs = kwargs
|
38
36
|
|
39
|
-
def validate(self) -> None:
|
37
|
+
def validate(self, benchling_service: BenchlingService) -> None:
|
40
38
|
"""Validate the operation before running it. This is not called at runtime,
|
41
39
|
but is used to validate operations before a migration is run."""
|
42
40
|
pass
|
@@ -18,6 +18,8 @@ class BaseFieldProperties(BaseModel):
|
|
18
18
|
----------
|
19
19
|
name : str | None
|
20
20
|
The external facing name of the field.
|
21
|
+
warehouse_name : str | None
|
22
|
+
The sql column name in the benchling warehouse.
|
21
23
|
type : BenchlingFieldType | None
|
22
24
|
The type of the field.
|
23
25
|
required : bool | None
|
@@ -39,6 +41,7 @@ class BaseFieldProperties(BaseModel):
|
|
39
41
|
"""
|
40
42
|
|
41
43
|
name: str | None = None
|
44
|
+
warehouse_name: str | None = None
|
42
45
|
type: BenchlingFieldType | None = None
|
43
46
|
required: bool | None = None
|
44
47
|
is_multi: bool | None = None
|
@@ -50,10 +53,18 @@ class BaseFieldProperties(BaseModel):
|
|
50
53
|
|
51
54
|
model_config = ConfigDict(arbitrary_types_allowed=True)
|
52
55
|
|
56
|
+
def __init__(self, **data: Any):
|
57
|
+
super().__init__(**data)
|
58
|
+
self._archived = data.get("_archived", None)
|
59
|
+
|
53
60
|
def set_archived(self, value: bool) -> BaseFieldProperties:
|
54
61
|
self._archived = value
|
55
62
|
return self
|
56
63
|
|
64
|
+
def set_warehouse_name(self, wh_name: str) -> BaseFieldProperties:
|
65
|
+
self.warehouse_name = wh_name
|
66
|
+
return self
|
67
|
+
|
57
68
|
def validate_column(self, wh_name: str) -> bool:
|
58
69
|
"""If the Field Properties are meant to represent a column in Benchling,
|
59
70
|
this will validate the properties and ensure that the entity_link and dropdowns are valid names that exist in our code.
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
from typing import Any
|
4
4
|
|
5
|
-
from pydantic import BaseModel, ConfigDict,
|
5
|
+
from pydantic import BaseModel, ConfigDict, model_validator
|
6
6
|
|
7
7
|
from liminal.enums import BenchlingEntityType, BenchlingNamingStrategy
|
8
8
|
|
@@ -69,7 +69,11 @@ class BaseSchemaProperties(BaseModel):
|
|
69
69
|
entity_type: BenchlingEntityType | None = None
|
70
70
|
naming_strategies: set[BenchlingNamingStrategy] | None = None
|
71
71
|
mixture_schema_config: MixtureSchemaConfig | None = None
|
72
|
-
_archived: bool | None =
|
72
|
+
_archived: bool | None = None
|
73
|
+
|
74
|
+
def __init__(self, **data: Any):
|
75
|
+
super().__init__(**data)
|
76
|
+
self._archived = data.get("_archived", None)
|
73
77
|
|
74
78
|
model_config = ConfigDict(arbitrary_types_allowed=True)
|
75
79
|
|
@@ -97,3 +101,12 @@ class BaseSchemaProperties(BaseModel):
|
|
97
101
|
if not isinstance(other, BaseSchemaProperties):
|
98
102
|
return False
|
99
103
|
return self.model_dump() == other.model_dump()
|
104
|
+
|
105
|
+
def __str__(self) -> str:
|
106
|
+
return ", ".join(
|
107
|
+
[f"{k}={v}" for k, v in self.model_dump(exclude_unset=True).items()]
|
108
|
+
)
|
109
|
+
|
110
|
+
def __repr__(self) -> str:
|
111
|
+
"""Generates a string representation of the class so that it can be executed."""
|
112
|
+
return f"{self.__class__.__name__}({', '.join([f'{k}={v.__repr__()}' for k, v in self.model_dump(exclude_defaults=True).items()])})"
|
liminal/cli/cli.py
CHANGED
@@ -79,7 +79,10 @@ def generate_files(
|
|
79
79
|
..., help="Benchling tenant (or alias) to connect to."
|
80
80
|
),
|
81
81
|
write_path: Path = typer.Option(
|
82
|
-
Path("."),
|
82
|
+
Path("."),
|
83
|
+
"-p",
|
84
|
+
"--write-path",
|
85
|
+
help="The path to write the generated files to.",
|
83
86
|
),
|
84
87
|
) -> None:
|
85
88
|
current_revision_id, benchling_connection = read_local_env_file(
|
@@ -1,5 +1,3 @@
|
|
1
|
-
import uuid
|
2
|
-
|
3
1
|
from liminal.base.base_operation import BaseOperation
|
4
2
|
from liminal.connection.benchling_service import BenchlingService
|
5
3
|
from liminal.dropdowns.operations import (
|
@@ -13,6 +11,7 @@ from liminal.dropdowns.operations import (
|
|
13
11
|
UpdateDropdownOption,
|
14
12
|
)
|
15
13
|
from liminal.migrate.components import execute_operations, execute_operations_dry_run
|
14
|
+
from liminal.utils import generate_random_id
|
16
15
|
|
17
16
|
|
18
17
|
def mock_dropdown_full_migration(
|
@@ -24,15 +23,15 @@ def mock_dropdown_full_migration(
|
|
24
23
|
If dry_run is set to False, the operations will be executed.
|
25
24
|
If dry_run is set to True, the operations will be executed in dry run mode and only the description of the operations will be printed.
|
26
25
|
"""
|
27
|
-
random_id =
|
26
|
+
random_id = generate_random_id()
|
27
|
+
test_dropdown_name = f"{test_dropdown_name}_{random_id}"
|
28
28
|
|
29
29
|
dropdown_options = ["Option 1", "Option 2"]
|
30
30
|
create_dropdown_op = CreateDropdown(test_dropdown_name, dropdown_options)
|
31
31
|
|
32
|
-
|
33
|
-
|
34
|
-
)
|
35
|
-
test_dropdown_name = f"{test_dropdown_name}_{random_id}"
|
32
|
+
test_dropdown_name = f"{test_dropdown_name}_arch"
|
33
|
+
|
34
|
+
update_dropdown_op = UpdateDropdownName(test_dropdown_name, test_dropdown_name)
|
36
35
|
|
37
36
|
create_dropdown_option_op = CreateDropdownOption(
|
38
37
|
test_dropdown_name, "Option 3 New", 2
|
@@ -70,7 +69,7 @@ def mock_dropdown_full_migration(
|
|
70
69
|
unarchive_dropdown_op,
|
71
70
|
rearchive_dropdown_op,
|
72
71
|
]
|
73
|
-
execute_operations_dry_run(
|
72
|
+
execute_operations_dry_run(
|
74
73
|
benchling_service, ops
|
75
|
-
)
|
74
|
+
) if dry_run else execute_operations(benchling_service, ops)
|
76
75
|
print("Dry run migration complete!") if dry_run else print("Migration complete!")
|
@@ -1,5 +1,3 @@
|
|
1
|
-
import uuid
|
2
|
-
|
3
1
|
from liminal.base.base_operation import BaseOperation
|
4
2
|
from liminal.base.properties.base_field_properties import BaseFieldProperties
|
5
3
|
from liminal.base.properties.base_schema_properties import BaseSchemaProperties
|
@@ -14,7 +12,6 @@ from liminal.entity_schemas.operations import (
|
|
14
12
|
UnarchiveEntitySchemaField,
|
15
13
|
UpdateEntitySchema,
|
16
14
|
UpdateEntitySchemaField,
|
17
|
-
UpdateEntitySchemaFieldName,
|
18
15
|
)
|
19
16
|
from liminal.enums import (
|
20
17
|
BenchlingEntityType,
|
@@ -23,6 +20,7 @@ from liminal.enums import (
|
|
23
20
|
)
|
24
21
|
from liminal.migrate.components import execute_operations, execute_operations_dry_run
|
25
22
|
from liminal.orm.schema_properties import SchemaProperties
|
23
|
+
from liminal.utils import generate_random_id
|
26
24
|
|
27
25
|
|
28
26
|
def mock_entity_schema_full_migration(
|
@@ -34,8 +32,9 @@ def mock_entity_schema_full_migration(
|
|
34
32
|
If dry_run is set to False, the operations will be executed.
|
35
33
|
If dry_run is set to True, the operations will be executed in dry run mode and only the description of the operations will be printed.
|
36
34
|
"""
|
37
|
-
random_id =
|
38
|
-
|
35
|
+
random_id = generate_random_id()
|
36
|
+
test_model_wh_name = f"{test_model_wh_name}_{random_id}"
|
37
|
+
if len(test_model_wh_name) > 32:
|
39
38
|
raise ValueError(
|
40
39
|
f"Test model warehouse name is too long. Must be less than {32 - len(random_id)} characters."
|
41
40
|
)
|
@@ -49,37 +48,37 @@ def mock_entity_schema_full_migration(
|
|
49
48
|
)
|
50
49
|
create_entity_schema_op = CreateEntitySchema(
|
51
50
|
schema_properties=schema_properties,
|
52
|
-
fields=
|
53
|
-
|
51
|
+
fields=[
|
52
|
+
BaseFieldProperties(
|
54
53
|
name="Test Column 1",
|
54
|
+
warehouse_name="test_column_1",
|
55
55
|
type=BenchlingFieldType.TEXT,
|
56
56
|
required=False,
|
57
57
|
is_multi=False,
|
58
58
|
parent_link=False,
|
59
59
|
),
|
60
|
-
|
60
|
+
BaseFieldProperties(
|
61
61
|
name="Test Column 2",
|
62
|
+
warehouse_name="test_column_2",
|
62
63
|
type=BenchlingFieldType.INTEGER,
|
63
64
|
required=False,
|
64
65
|
is_multi=False,
|
65
66
|
parent_link=False,
|
66
67
|
),
|
67
|
-
|
68
|
+
BaseFieldProperties(
|
68
69
|
name="Test Column 4",
|
69
|
-
|
70
|
-
|
70
|
+
warehouse_name="test_column_4",
|
71
|
+
type=BenchlingFieldType.CUSTOM_ENTITY_LINK,
|
71
72
|
required=False,
|
72
73
|
is_multi=False,
|
73
|
-
parent_link=
|
74
|
+
parent_link=False,
|
74
75
|
),
|
75
|
-
|
76
|
+
],
|
76
77
|
)
|
77
78
|
|
78
|
-
test_model_wh_name = f"{test_model_wh_name}_{random_id}"
|
79
79
|
update_schema_properties = BaseSchemaProperties(
|
80
|
-
|
81
|
-
|
82
|
-
prefix=test_model_wh_name,
|
80
|
+
name=f"{test_model_wh_name}_arch",
|
81
|
+
prefix=f"{test_model_wh_name}_arch",
|
83
82
|
entity_type=BenchlingEntityType.AA_SEQUENCE,
|
84
83
|
naming_strategies=[
|
85
84
|
BenchlingNamingStrategy.IDS_FROM_NAMES,
|
@@ -92,24 +91,23 @@ def mock_entity_schema_full_migration(
|
|
92
91
|
|
93
92
|
test_column_3_field_properties = BaseFieldProperties(
|
94
93
|
name="Test Column 3 New",
|
94
|
+
warehouse_name="test_column_3",
|
95
95
|
type=BenchlingFieldType.TEXT,
|
96
96
|
required=False,
|
97
97
|
is_multi=False,
|
98
98
|
parent_link=False,
|
99
99
|
)
|
100
100
|
create_entity_schema_field_op = CreateEntitySchemaField(
|
101
|
-
test_model_wh_name,
|
101
|
+
test_model_wh_name, test_column_3_field_properties, 2
|
102
102
|
)
|
103
103
|
|
104
104
|
update_test_column_3_field_properties = BaseFieldProperties(
|
105
|
-
type=BenchlingFieldType.CUSTOM_ENTITY_LINK,
|
105
|
+
type=BenchlingFieldType.CUSTOM_ENTITY_LINK,
|
106
|
+
is_multi=True,
|
107
|
+
name="Test Column 3",
|
106
108
|
)
|
107
109
|
update_entity_schema_field_op = UpdateEntitySchemaField(
|
108
|
-
test_model_wh_name, "
|
109
|
-
)
|
110
|
-
|
111
|
-
update_entity_schema_field_name_op = UpdateEntitySchemaFieldName(
|
112
|
-
test_model_wh_name, "test_column_3_new", "test_column_3"
|
110
|
+
test_model_wh_name, "test_column_3", update_test_column_3_field_properties
|
113
111
|
)
|
114
112
|
|
115
113
|
archive_entity_schema_field_op = ArchiveEntitySchemaField(
|
@@ -134,7 +132,6 @@ def mock_entity_schema_full_migration(
|
|
134
132
|
update_entity_schema_op,
|
135
133
|
create_entity_schema_field_op,
|
136
134
|
update_entity_schema_field_op,
|
137
|
-
update_entity_schema_field_name_op,
|
138
135
|
archive_entity_schema_field_op,
|
139
136
|
unarchive_entity_schema_field_op,
|
140
137
|
reorder_entity_schema_field_op,
|
@@ -142,7 +139,7 @@ def mock_entity_schema_full_migration(
|
|
142
139
|
unarchive_entity_schema_op,
|
143
140
|
rearchive_entity_schema_op,
|
144
141
|
]
|
145
|
-
execute_operations_dry_run(
|
142
|
+
execute_operations_dry_run(
|
146
143
|
benchling_service, ops
|
147
|
-
)
|
144
|
+
) if dry_run else execute_operations(benchling_service, ops)
|
148
145
|
print("Dry run migration complete!") if dry_run else print("Migration complete!")
|
@@ -27,20 +27,26 @@ class BenchlingConnection(BaseModel):
|
|
27
27
|
The email of the internal API admin.
|
28
28
|
internal_api_admin_password: str | None = None
|
29
29
|
The password of the internal API admin.
|
30
|
+
warehouse_access: bool = False
|
31
|
+
Whether your Benchling tenant has access to the warehouse. If warehouse_connection_string is provided, this will default to True.
|
32
|
+
warehouse_access is required to set a custom warehouse names on entity schemas and their fields.
|
30
33
|
"""
|
31
34
|
|
32
35
|
tenant_name: str
|
33
36
|
tenant_alias: str | None = None
|
34
37
|
current_revision_id_var_name: str = ""
|
35
|
-
api_client_id: str
|
36
|
-
api_client_secret: str
|
38
|
+
api_client_id: str
|
39
|
+
api_client_secret: str
|
37
40
|
warehouse_connection_string: str | None = None
|
38
41
|
internal_api_admin_email: str | None = None
|
39
42
|
internal_api_admin_password: str | None = None
|
43
|
+
warehouse_access: bool = False
|
40
44
|
|
41
45
|
@model_validator(mode="before")
|
42
46
|
@classmethod
|
43
47
|
def set_current_revision_id_var_name(cls, values: dict) -> dict:
|
48
|
+
if values.get("warehouse_connection_string"):
|
49
|
+
values["warehouse_access"] = True
|
44
50
|
if not values.get("current_revision_id_var_name"):
|
45
51
|
tenant_alias = values.get("tenant_alias")
|
46
52
|
tenant_name = values.get("tenant_name")
|
@@ -9,7 +9,12 @@ from bs4 import BeautifulSoup
|
|
9
9
|
from sqlalchemy import create_engine
|
10
10
|
from sqlalchemy.engine import Engine
|
11
11
|
from sqlalchemy.orm import Session, configure_mappers
|
12
|
-
from tenacity import
|
12
|
+
from tenacity import (
|
13
|
+
retry,
|
14
|
+
retry_if_exception_type,
|
15
|
+
stop_after_attempt,
|
16
|
+
wait_exponential,
|
17
|
+
)
|
13
18
|
|
14
19
|
from liminal.connection.benchling_connection import BenchlingConnection
|
15
20
|
|
@@ -45,24 +50,17 @@ class BenchlingService(Benchling):
|
|
45
50
|
self.use_api = use_api
|
46
51
|
self.benchling_tenant = connection.tenant_name
|
47
52
|
if use_api:
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
logger.info(
|
60
|
-
f"Tenant {connection.tenant_name}: Connected to Benchling API."
|
61
|
-
)
|
62
|
-
else:
|
63
|
-
raise ValueError(
|
64
|
-
"use_api is True but api_client_id and api_client_secret not provided in BenchlingConnection."
|
65
|
-
)
|
53
|
+
retry_strategy = RetryStrategy(max_tries=10)
|
54
|
+
auth_method = ClientCredentialsOAuth2(
|
55
|
+
client_id=connection.api_client_id,
|
56
|
+
client_secret=connection.api_client_secret,
|
57
|
+
token_url=f"https://{connection.tenant_name}.benchling.com/api/v2/token",
|
58
|
+
)
|
59
|
+
url = f"https://{connection.tenant_name}.benchling.com"
|
60
|
+
super().__init__(
|
61
|
+
url=url, auth_method=auth_method, retry_strategy=retry_strategy
|
62
|
+
)
|
63
|
+
logger.info(f"Tenant {connection.tenant_name}: Connected to Benchling API.")
|
66
64
|
self.use_db = use_db
|
67
65
|
if use_db:
|
68
66
|
if connection.warehouse_connection_string:
|
@@ -144,6 +142,7 @@ class BenchlingService(Benchling):
|
|
144
142
|
@retry(
|
145
143
|
stop=stop_after_attempt(3),
|
146
144
|
retry=retry_if_exception_type(ValueError),
|
145
|
+
wait=wait_exponential(multiplier=1, min=1, max=8),
|
147
146
|
reraise=True,
|
148
147
|
)
|
149
148
|
def autogenerate_auth(
|
liminal/dropdowns/operations.py
CHANGED
@@ -87,7 +87,7 @@ class ArchiveDropdown(BaseOperation):
|
|
87
87
|
raise ValueError(f"Dropdown {self.dropdown_name} is already archived.")
|
88
88
|
return archive_dropdown(benchling_service, dropdown.id)
|
89
89
|
|
90
|
-
def validate(self) -> None:
|
90
|
+
def validate(self, benchling_service: BenchlingService) -> None:
|
91
91
|
if schemas_with_dropdown := get_schemas_with_dropdown(self.dropdown_name):
|
92
92
|
raise ValueError(
|
93
93
|
f"Dropdown {self.dropdown_name} is used in schemas {schemas_with_dropdown}. Cannot archive a dropdown that is in use in non-archived fields."
|
@@ -41,9 +41,13 @@ def compare_entity_schemas(
|
|
41
41
|
)
|
42
42
|
# If models are provided, filter the schemas from benchling so that only the models passed in are compared.
|
43
43
|
# If you don't filter, it will compare to all the schemas in benchling and think that they are missing from code and should be archived.
|
44
|
-
models =
|
44
|
+
models = [
|
45
|
+
m
|
46
|
+
for m in BaseModel.get_all_subclasses(schema_names)
|
47
|
+
if not m.__schema_properties__._archived
|
48
|
+
]
|
45
49
|
archived_benchling_schema_wh_names = [
|
46
|
-
s
|
50
|
+
s.warehouse_name for s, _ in benchling_schemas if s._archived is True
|
47
51
|
]
|
48
52
|
# Running list of schema names from benchling. As each model is checked, remove the schema name from this list.
|
49
53
|
# This is used at the end to check if there are any schemas left (schemas that exist in benchling but not in code) and archive them if they are.
|
@@ -63,7 +67,9 @@ def compare_entity_schemas(
|
|
63
67
|
s.warehouse_name for s, _ in benchling_schemas
|
64
68
|
]:
|
65
69
|
benchling_schema_props, benchling_schema_fields = next(
|
66
|
-
|
70
|
+
(s, lof)
|
71
|
+
for s, lof in benchling_schemas
|
72
|
+
if s.warehouse_name == model_wh_name
|
67
73
|
)
|
68
74
|
archived_benchling_schema_fields = {
|
69
75
|
k: v for k, v in benchling_schema_fields.items() if v._archived is True
|
@@ -193,12 +199,14 @@ def compare_entity_schemas(
|
|
193
199
|
)
|
194
200
|
)
|
195
201
|
else:
|
202
|
+
new_field_props = model_columns[
|
203
|
+
column_name
|
204
|
+
].properties.set_warehouse_name(column_name)
|
196
205
|
ops.append(
|
197
206
|
CompareOperation(
|
198
207
|
op=CreateEntitySchemaField(
|
199
208
|
model_wh_name,
|
200
|
-
|
201
|
-
field_props=model_columns[column_name].properties,
|
209
|
+
field_props=new_field_props,
|
202
210
|
index=list(model_columns.keys()).index(column_name),
|
203
211
|
),
|
204
212
|
reverse_op=ArchiveEntitySchemaField(
|
@@ -233,8 +241,10 @@ def compare_entity_schemas(
|
|
233
241
|
# Benchling api does not allow for setting a custom warehouse_name,
|
234
242
|
# so we need to run another UpdateEntitySchema to set the warehouse_name if it is different from the snakecase version of the model name.
|
235
243
|
else:
|
236
|
-
model_props =
|
237
|
-
|
244
|
+
model_props = [
|
245
|
+
col.properties.set_warehouse_name(wh_name)
|
246
|
+
for wh_name, col in model_columns.items()
|
247
|
+
]
|
238
248
|
ops.append(
|
239
249
|
CompareOperation(
|
240
250
|
op=CreateEntitySchema(
|
@@ -34,7 +34,6 @@ class CreateEntitySchemaFieldModel(BaseModel):
|
|
34
34
|
@classmethod
|
35
35
|
def from_benchling_props(
|
36
36
|
cls,
|
37
|
-
wh_field_name: str,
|
38
37
|
field_props: BaseFieldProperties,
|
39
38
|
benchling_service: BenchlingService | None = None,
|
40
39
|
) -> CreateEntitySchemaFieldModel:
|
@@ -42,8 +41,6 @@ class CreateEntitySchemaFieldModel(BaseModel):
|
|
42
41
|
|
43
42
|
Parameters
|
44
43
|
----------
|
45
|
-
wh_field_name : str
|
46
|
-
The warehouse name of the field.
|
47
44
|
field_props : BaseFieldProperties
|
48
45
|
The field properties.
|
49
46
|
benchling_service : BenchlingService | None
|
@@ -79,7 +76,7 @@ class CreateEntitySchemaFieldModel(BaseModel):
|
|
79
76
|
).id
|
80
77
|
return CreateEntitySchemaFieldModel(
|
81
78
|
name=field_props.name,
|
82
|
-
systemName=
|
79
|
+
systemName=field_props.warehouse_name,
|
83
80
|
isMulti=field_props.is_multi,
|
84
81
|
isRequired=field_props.required,
|
85
82
|
isParentLink=field_props.parent_link,
|
@@ -107,7 +104,7 @@ class CreateEntitySchemaModel(BaseModel):
|
|
107
104
|
def from_benchling_props(
|
108
105
|
cls,
|
109
106
|
benchling_props: SchemaProperties,
|
110
|
-
fields:
|
107
|
+
fields: list[BaseFieldProperties],
|
111
108
|
benchling_service: BenchlingService,
|
112
109
|
) -> CreateEntitySchemaModel:
|
113
110
|
"""Generates a CreateEntitySchemaModel from the given internal definition of benchling schema properties.
|
@@ -116,8 +113,8 @@ class CreateEntitySchemaModel(BaseModel):
|
|
116
113
|
----------
|
117
114
|
benchling_props : Benchling SchemaProperties
|
118
115
|
The schema properties.
|
119
|
-
fields :
|
120
|
-
|
116
|
+
fields : list[Benchling FieldProperties]
|
117
|
+
List of field properties.
|
121
118
|
benchling_service : BenchlingService | None
|
122
119
|
The Benchling service instance used to fetch additional data if needed.
|
123
120
|
|
@@ -135,8 +132,8 @@ class CreateEntitySchemaModel(BaseModel):
|
|
135
132
|
labelingStrategies=[s.value for s in benchling_props.naming_strategies],
|
136
133
|
fields=[
|
137
134
|
CreateEntitySchemaFieldModel.from_benchling_props(
|
138
|
-
|
135
|
+
field_props, benchling_service
|
139
136
|
)
|
140
|
-
for
|
137
|
+
for field_props in fields
|
141
138
|
],
|
142
139
|
)
|
@@ -14,6 +14,7 @@ def get_entity_mixin(entity_type: BenchlingEntityType) -> str:
|
|
14
14
|
type_to_mixin_map = {
|
15
15
|
BenchlingEntityType.ENTRY: "EntryMixin",
|
16
16
|
BenchlingEntityType.MIXTURE: "MixtureMixin",
|
17
|
+
BenchlingEntityType.MOLECULE: "MoleculeMixin",
|
17
18
|
BenchlingEntityType.CUSTOM_ENTITY: "CustomEntityMixin",
|
18
19
|
BenchlingEntityType.DNA_SEQUENCE: "DnaSequenceMixin",
|
19
20
|
BenchlingEntityType.DNA_OLIGO: "DnaOligoMixin",
|
@@ -36,6 +37,7 @@ def get_file_subdirectory(entity_type: BenchlingEntityType) -> str:
|
|
36
37
|
BenchlingEntityType.AA_SEQUENCE: "aa_sequences",
|
37
38
|
BenchlingEntityType.ENTRY: "entries",
|
38
39
|
BenchlingEntityType.MIXTURE: "mixtures",
|
40
|
+
BenchlingEntityType.MOLECULE: "molecules",
|
39
41
|
}
|
40
42
|
if entity_type not in type_to_subdir_map:
|
41
43
|
raise ValueError(f"Unknown entity type: {entity_type}")
|
@@ -97,7 +99,10 @@ def generate_all_entity_schema_files(
|
|
97
99
|
f"""{tab}{col_name}: {convert_benchling_type_to_python_type(col.type).__name__},"""
|
98
100
|
)
|
99
101
|
|
100
|
-
if
|
102
|
+
if (
|
103
|
+
col.type == BenchlingFieldType.DATE
|
104
|
+
or col.type == BenchlingFieldType.DATETIME
|
105
|
+
):
|
101
106
|
if not has_date:
|
102
107
|
import_strings.append("from datetime import datetime")
|
103
108
|
if (
|
@@ -106,14 +111,14 @@ def generate_all_entity_schema_files(
|
|
106
111
|
):
|
107
112
|
if not col.is_multi:
|
108
113
|
relationship_strings.append(
|
109
|
-
f"""{tab}single_relationship("{wh_name_to_classname[col.entity_link]}", {col_name})"""
|
114
|
+
f"""{tab}{col_name}_entity = single_relationship("{wh_name_to_classname[col.entity_link]}", {col_name})"""
|
110
115
|
)
|
111
116
|
import_strings.append(
|
112
117
|
"from liminal.orm.relationship import single_relationship"
|
113
118
|
)
|
114
119
|
else:
|
115
120
|
relationship_strings.append(
|
116
|
-
f"""{tab}multi_relationship("{wh_name_to_classname[col.entity_link]}", "{classname}", "{col_name}")"""
|
121
|
+
f"""{tab}{col_name}_entities = multi_relationship("{wh_name_to_classname[col.entity_link]}", "{classname}", "{col_name}")"""
|
117
122
|
)
|
118
123
|
import_strings.append(
|
119
124
|
"from liminal.orm.relationship import multi_relationship"
|
@@ -140,11 +145,7 @@ def generate_all_entity_schema_files(
|
|
140
145
|
relationship_string = "\n".join(relationship_strings)
|
141
146
|
import_string = "\n".join(list(set(import_strings)))
|
142
147
|
init_string = f"\n{tab}".join(init_strings) if len(columns) > 0 else ""
|
143
|
-
functions_string =
|
144
|
-
@classmethod
|
145
|
-
def query(self, session: Session) -> Query:
|
146
|
-
return session.query({classname})
|
147
|
-
|
148
|
+
functions_string = """
|
148
149
|
def get_validators(self) -> list[BenchlingValidator]:
|
149
150
|
return []"""
|
150
151
|
|