liminal-orm 3.1.1__tar.gz → 3.2.1__tar.gz

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.
Files changed (169) hide show
  1. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/PKG-INFO +2 -3
  2. liminal_orm-3.2.1/liminal/.DS_Store +0 -0
  3. liminal_orm-3.2.1/liminal/base/__pycache__/__init__.cpython-310.pyc +0 -0
  4. liminal_orm-3.2.1/liminal/base/__pycache__/base.cpython-310.pyc +0 -0
  5. liminal_orm-3.2.1/liminal/base/__pycache__/base_dropdown.cpython-310.pyc +0 -0
  6. liminal_orm-3.2.1/liminal/base/__pycache__/base_operation.cpython-310.pyc +0 -0
  7. liminal_orm-3.2.1/liminal/base/__pycache__/base_validation_filters.cpython-310.pyc +0 -0
  8. liminal_orm-3.2.1/liminal/base/__pycache__/benchling_base_model.cpython-310.pyc +0 -0
  9. liminal_orm-3.2.1/liminal/base/__pycache__/benchling_column.cpython-310.pyc +0 -0
  10. liminal_orm-3.2.1/liminal/base/__pycache__/benchling_dropdown.cpython-310.pyc +0 -0
  11. liminal_orm-3.2.1/liminal/base/__pycache__/benchling_field_properties.cpython-310.pyc +0 -0
  12. liminal_orm-3.2.1/liminal/base/__pycache__/benchling_schema_properties.cpython-310.pyc +0 -0
  13. liminal_orm-3.2.1/liminal/base/__pycache__/callable_operation.cpython-310.pyc +0 -0
  14. liminal_orm-3.2.1/liminal/base/__pycache__/column.cpython-310.pyc +0 -0
  15. liminal_orm-3.2.1/liminal/base/__pycache__/compare_operation.cpython-310.pyc +0 -0
  16. liminal_orm-3.2.1/liminal/base/__pycache__/field_properties.cpython-310.pyc +0 -0
  17. liminal_orm-3.2.1/liminal/base/__pycache__/name_template_components.cpython-310.pyc +0 -0
  18. liminal_orm-3.2.1/liminal/base/__pycache__/name_template_parts.cpython-310.pyc +0 -0
  19. liminal_orm-3.2.1/liminal/base/__pycache__/schema_properties.cpython-310.pyc +0 -0
  20. liminal_orm-3.2.1/liminal/base/__pycache__/str_enum.cpython-310.pyc +0 -0
  21. liminal_orm-3.2.1/liminal/base/__pycache__/unset.cpython-310.pyc +0 -0
  22. liminal_orm-3.2.1/liminal/base/__pycache__/user.cpython-310.pyc +0 -0
  23. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/base/name_template_parts.py +2 -3
  24. liminal_orm-3.2.1/liminal/base/properties/__pycache__/NoDropdown.cpython-310.pyc +0 -0
  25. liminal_orm-3.2.1/liminal/base/properties/__pycache__/__init__.cpython-310.pyc +0 -0
  26. liminal_orm-3.2.1/liminal/base/properties/__pycache__/base_field_properties.cpython-310.pyc +0 -0
  27. liminal_orm-3.2.1/liminal/base/properties/__pycache__/base_name_template.cpython-310.pyc +0 -0
  28. liminal_orm-3.2.1/liminal/base/properties/__pycache__/base_schema_properties.cpython-310.pyc +0 -0
  29. liminal_orm-3.2.1/liminal/base/properties/__pycache__/field_properties.cpython-310.pyc +0 -0
  30. liminal_orm-3.2.1/liminal/base/properties/__pycache__/schema_properties.cpython-310.pyc +0 -0
  31. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/base/properties/base_field_properties.py +0 -22
  32. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/base/properties/base_schema_properties.py +1 -1
  33. liminal_orm-3.2.1/liminal/cli/__pycache__/cli.cpython-310.pyc +0 -0
  34. liminal_orm-3.2.1/liminal/cli/__pycache__/controller.cpython-310.pyc +0 -0
  35. liminal_orm-3.2.1/liminal/cli/__pycache__/live_test_dropdown_migration.cpython-310.pyc +0 -0
  36. liminal_orm-3.2.1/liminal/cli/__pycache__/live_test_entity_schema_migration.cpython-310.pyc +0 -0
  37. liminal_orm-3.2.1/liminal/cli/__pycache__/utils.cpython-310.pyc +0 -0
  38. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/cli/cli.py +119 -26
  39. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/cli/controller.py +11 -6
  40. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/cli/utils.py +8 -5
  41. liminal_orm-3.2.1/liminal/connection/__init__.py +5 -0
  42. liminal_orm-3.2.1/liminal/connection/__pycache__/__init__.cpython-310.pyc +0 -0
  43. liminal_orm-3.2.1/liminal/connection/__pycache__/benchling_connection.cpython-310.pyc +0 -0
  44. liminal_orm-3.2.1/liminal/connection/__pycache__/benchling_service.cpython-310.pyc +0 -0
  45. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/connection/benchling_service.py +103 -0
  46. liminal_orm-3.2.1/liminal/dropdowns/__pycache__/api.cpython-310.pyc +0 -0
  47. liminal_orm-3.2.1/liminal/dropdowns/__pycache__/compare.cpython-310.pyc +0 -0
  48. liminal_orm-3.2.1/liminal/dropdowns/__pycache__/generate_files.cpython-310.pyc +0 -0
  49. liminal_orm-3.2.1/liminal/dropdowns/__pycache__/operations.cpython-310.pyc +0 -0
  50. liminal_orm-3.2.1/liminal/dropdowns/__pycache__/utils.cpython-310.pyc +0 -0
  51. liminal_orm-3.2.1/liminal/entity_schemas/__pycache__/api.cpython-310.pyc +0 -0
  52. liminal_orm-3.2.1/liminal/entity_schemas/__pycache__/compare.cpython-310.pyc +0 -0
  53. liminal_orm-3.2.1/liminal/entity_schemas/__pycache__/entity_schema_models.cpython-310.pyc +0 -0
  54. liminal_orm-3.2.1/liminal/entity_schemas/__pycache__/generate_files.cpython-310.pyc +0 -0
  55. liminal_orm-3.2.1/liminal/entity_schemas/__pycache__/operations.cpython-310.pyc +0 -0
  56. liminal_orm-3.2.1/liminal/entity_schemas/__pycache__/schemas.cpython-310.pyc +0 -0
  57. liminal_orm-3.2.1/liminal/entity_schemas/__pycache__/tag_schema_models.cpython-310.pyc +0 -0
  58. liminal_orm-3.2.1/liminal/entity_schemas/__pycache__/utils.cpython-310.pyc +0 -0
  59. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/entity_schemas/generate_files.py +1 -1
  60. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/entity_schemas/tag_schema_models.py +5 -2
  61. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/entity_schemas/utils.py +1 -0
  62. liminal_orm-3.2.1/liminal/enums/__pycache__/__init__.cpython-310.pyc +0 -0
  63. liminal_orm-3.2.1/liminal/enums/__pycache__/benchling_api_field_type.cpython-310.pyc +0 -0
  64. liminal_orm-3.2.1/liminal/enums/__pycache__/benchling_dropdown.cpython-310.pyc +0 -0
  65. liminal_orm-3.2.1/liminal/enums/__pycache__/benchling_entity_type.cpython-310.pyc +0 -0
  66. liminal_orm-3.2.1/liminal/enums/__pycache__/benchling_enum.cpython-310.pyc +0 -0
  67. liminal_orm-3.2.1/liminal/enums/__pycache__/benchling_field_api_type.cpython-310.pyc +0 -0
  68. liminal_orm-3.2.1/liminal/enums/__pycache__/benchling_field_type.cpython-310.pyc +0 -0
  69. liminal_orm-3.2.1/liminal/enums/__pycache__/benchling_folder_item_type.cpython-310.pyc +0 -0
  70. liminal_orm-3.2.1/liminal/enums/__pycache__/benchling_naming_strategy.cpython-310.pyc +0 -0
  71. liminal_orm-3.2.1/liminal/enums/__pycache__/benchling_report_level.cpython-310.pyc +0 -0
  72. liminal_orm-3.2.1/liminal/enums/__pycache__/benchling_sequence_type.cpython-310.pyc +0 -0
  73. liminal_orm-3.2.1/liminal/enums/__pycache__/name_template_part_type.cpython-310.pyc +0 -0
  74. liminal_orm-3.2.1/liminal/enums/__pycache__/sequence_constraint.cpython-310.pyc +0 -0
  75. liminal_orm-3.2.1/liminal/enums/__pycache__/sequence_constraints.cpython-310.pyc +0 -0
  76. liminal_orm-3.2.1/liminal/enums/__pycache__/str_enum.cpython-310.pyc +0 -0
  77. liminal_orm-3.2.1/liminal/enums/__pycache__/undefined.cpython-310.pyc +0 -0
  78. liminal_orm-3.2.1/liminal/external/__pycache__/__init__.cpython-310.pyc +0 -0
  79. liminal_orm-3.2.1/liminal/migrate/__pycache__/components.cpython-310.pyc +0 -0
  80. liminal_orm-3.2.1/liminal/migrate/__pycache__/revision.cpython-310.pyc +0 -0
  81. liminal_orm-3.2.1/liminal/migrate/__pycache__/revisions_timeline.cpython-310.pyc +0 -0
  82. liminal_orm-3.2.1/liminal/migrate/__pycache__/utils.cpython-310.pyc +0 -0
  83. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/migrate/components.py +3 -1
  84. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/migrate/revisions_timeline.py +1 -3
  85. liminal_orm-3.2.1/liminal/orm/__pycache__/__init__.cpython-310.pyc +0 -0
  86. liminal_orm-3.2.1/liminal/orm/__pycache__/base.cpython-310.pyc +0 -0
  87. liminal_orm-3.2.1/liminal/orm/__pycache__/base_model.cpython-310.pyc +0 -0
  88. liminal_orm-3.2.1/liminal/orm/__pycache__/base_results_schema_model.cpython-310.pyc +0 -0
  89. liminal_orm-3.2.1/liminal/orm/__pycache__/column.cpython-310.pyc +0 -0
  90. liminal_orm-3.2.1/liminal/orm/__pycache__/mixins.cpython-310.pyc +0 -0
  91. liminal_orm-3.2.1/liminal/orm/__pycache__/name_template.cpython-310.pyc +0 -0
  92. liminal_orm-3.2.1/liminal/orm/__pycache__/name_template_parts.cpython-310.pyc +0 -0
  93. liminal_orm-3.2.1/liminal/orm/__pycache__/relationship.cpython-310.pyc +0 -0
  94. liminal_orm-3.2.1/liminal/orm/__pycache__/results_schema_properties.cpython-310.pyc +0 -0
  95. liminal_orm-3.2.1/liminal/orm/__pycache__/schema_properties.cpython-310.pyc +0 -0
  96. liminal_orm-3.2.1/liminal/orm/__pycache__/user.cpython-310.pyc +0 -0
  97. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/orm/base_model.py +26 -3
  98. liminal_orm-3.2.1/liminal/orm/base_tables/__pycache__/__init__.cpython-310.pyc +0 -0
  99. liminal_orm-3.2.1/liminal/orm/base_tables/__pycache__/registry_entity.cpython-310.pyc +0 -0
  100. liminal_orm-3.2.1/liminal/orm/base_tables/__pycache__/schema.cpython-310.pyc +0 -0
  101. liminal_orm-3.2.1/liminal/orm/base_tables/__pycache__/user.cpython-310.pyc +0 -0
  102. liminal_orm-3.2.1/liminal/orm/relationship.py +114 -0
  103. liminal_orm-3.2.1/liminal/results_schemas/__pycache__/generate_files.cpython-310.pyc +0 -0
  104. liminal_orm-3.2.1/liminal/results_schemas/__pycache__/utils.cpython-310.pyc +0 -0
  105. liminal_orm-3.2.1/liminal/results_schemas/models/__pycache__/results_schema_model.cpython-310.pyc +0 -0
  106. liminal_orm-3.2.1/liminal/tests/.DS_Store +0 -0
  107. liminal_orm-3.2.1/liminal/tests/__pycache__/__init__.cpython-310.pyc +0 -0
  108. liminal_orm-3.2.1/liminal/tests/__pycache__/conftest.cpython-310-pytest-8.3.3.pyc +0 -0
  109. liminal_orm-3.2.1/liminal/tests/__pycache__/test_dropdown_compare.cpython-310-pytest-8.3.3.pyc +0 -0
  110. liminal_orm-3.2.1/liminal/tests/__pycache__/test_entity_schema_compare.cpython-310-pytest-8.3.3.pyc +0 -0
  111. liminal_orm-3.2.1/liminal/unit_dictionary/__pycache__/utils.cpython-310.pyc +0 -0
  112. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/utils.py +6 -2
  113. liminal_orm-3.2.1/liminal/validation/__pycache__/__init__.cpython-310.pyc +0 -0
  114. liminal_orm-3.2.1/liminal/validation/__pycache__/validation_report_level.cpython-310.pyc +0 -0
  115. liminal_orm-3.2.1/liminal/validation/__pycache__/validation_severity.cpython-310.pyc +0 -0
  116. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/pyproject.toml +2 -2
  117. liminal_orm-3.1.1/liminal/connection/__init__.py +0 -2
  118. liminal_orm-3.1.1/liminal/orm/relationship.py +0 -62
  119. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/LICENSE.md +0 -0
  120. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/README.md +0 -0
  121. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/__init__.py +0 -0
  122. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/base/base_dropdown.py +0 -0
  123. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/base/base_operation.py +0 -0
  124. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/base/base_validation_filters.py +0 -0
  125. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/base/compare_operation.py +0 -0
  126. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/base/properties/base_name_template.py +0 -0
  127. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/base/str_enum.py +0 -0
  128. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/cli/live_test_dropdown_migration.py +0 -0
  129. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/cli/live_test_entity_schema_migration.py +0 -0
  130. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/connection/benchling_connection.py +0 -0
  131. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/dropdowns/api.py +0 -0
  132. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/dropdowns/compare.py +0 -0
  133. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/dropdowns/generate_files.py +0 -0
  134. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/dropdowns/operations.py +0 -0
  135. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/dropdowns/utils.py +0 -0
  136. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/entity_schemas/api.py +0 -0
  137. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/entity_schemas/compare.py +0 -0
  138. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/entity_schemas/entity_schema_models.py +0 -0
  139. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/entity_schemas/operations.py +0 -0
  140. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/enums/__init__.py +0 -0
  141. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/enums/benchling_api_field_type.py +0 -0
  142. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/enums/benchling_entity_type.py +0 -0
  143. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/enums/benchling_field_type.py +0 -0
  144. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/enums/benchling_folder_item_type.py +0 -0
  145. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/enums/benchling_naming_strategy.py +0 -0
  146. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/enums/benchling_sequence_type.py +0 -0
  147. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/enums/name_template_part_type.py +0 -0
  148. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/enums/sequence_constraint.py +0 -0
  149. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/external/__init__.py +0 -0
  150. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/mappers.py +0 -0
  151. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/migrate/revision.py +0 -0
  152. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/orm/base.py +0 -0
  153. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/orm/base_tables/registry_entity.py +0 -0
  154. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/orm/base_tables/schema.py +0 -0
  155. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/orm/base_tables/user.py +0 -0
  156. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/orm/column.py +0 -0
  157. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/orm/mixins.py +0 -0
  158. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/orm/name_template.py +0 -0
  159. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/orm/name_template_parts.py +0 -0
  160. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/orm/schema_properties.py +0 -0
  161. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/py.typed +0 -0
  162. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/tests/__init__.py +0 -0
  163. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/tests/conftest.py +0 -0
  164. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/tests/from benchling_sdk.py +0 -0
  165. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/tests/test_dropdown_compare.py +0 -0
  166. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/tests/test_entity_schema_compare.py +0 -0
  167. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/unit_dictionary/utils.py +0 -0
  168. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/validation/__init__.py +0 -0
  169. {liminal_orm-3.1.1 → liminal_orm-3.2.1}/liminal/validation/validation_severity.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: liminal-orm
3
- Version: 3.1.1
3
+ Version: 3.2.1
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
@@ -11,8 +11,7 @@ Classifier: Programming Language :: Python :: 3.9
11
11
  Classifier: Programming Language :: Python :: 3.10
12
12
  Classifier: Programming Language :: Python :: 3.11
13
13
  Classifier: Programming Language :: Python :: 3.12
14
- Classifier: Programming Language :: Python :: 3.13
15
- Requires-Dist: benchling-sdk (>=1.8.0)
14
+ Requires-Dist: benchling-sdk (>=1.21.2,<2.0.0)
16
15
  Requires-Dist: bs4 (>=0.0.2,<0.0.3)
17
16
  Requires-Dist: lxml (>=5.3.0,<6.0.0)
18
17
  Requires-Dist: numpy (>=1.23.5,<2.0.0)
Binary file
@@ -3,7 +3,6 @@ import warnings
3
3
  from liminal.orm.name_template_parts import * # noqa: F403
4
4
 
5
5
  warnings.warn(
6
- "Importing from 'liminal.base.name_template_parts' is deprecated. Please import from 'liminal.orm.name_template_parts' instead.",
7
- DeprecationWarning,
8
- stacklevel=2,
6
+ "Importing from 'liminal.base.name_template_parts' is deprecated. Please import from 'liminal.orm.name_template_parts' instead. This will be removed in v4.",
7
+ FutureWarning,
9
8
  )
@@ -4,10 +4,7 @@ from typing import Any
4
4
 
5
5
  from pydantic import BaseModel, ConfigDict, PrivateAttr
6
6
 
7
- from liminal.base.base_dropdown import BaseDropdown
8
7
  from liminal.enums import BenchlingFieldType
9
- from liminal.orm.base_model import BaseModel as BenchlingBaseModel
10
- from liminal.utils import is_valid_wh_name
11
8
 
12
9
 
13
10
  class BaseFieldProperties(BaseModel):
@@ -71,25 +68,6 @@ class BaseFieldProperties(BaseModel):
71
68
  """If the Field Properties are meant to represent a column in Benchling,
72
69
  this will validate the properties and ensure that the entity_link and dropdowns are valid names that exist in our code.
73
70
  """
74
- if self.entity_link:
75
- if self.entity_link not in [
76
- s.__schema_properties__.warehouse_name
77
- for s in BenchlingBaseModel.get_all_subclasses()
78
- ]:
79
- raise ValueError(
80
- f"Field {wh_name}: could not find entity link {self.entity_link} as a warehouse name for any currently defined schemas."
81
- )
82
- if self.dropdown_link:
83
- if self.dropdown_link not in [
84
- d.__benchling_name__ for d in BaseDropdown.get_all_subclasses()
85
- ]:
86
- raise ValueError(
87
- f"Field {wh_name}: could not find dropdown link {self.dropdown_link} as a name to any defined dropdowns."
88
- )
89
- if not is_valid_wh_name(wh_name):
90
- raise ValueError(
91
- f"Field {wh_name}: invalid warehouse name '{wh_name}'. It should only contain alphanumeric characters and underscores."
92
- )
93
71
  return True
94
72
 
95
73
  def merge(self, new_props: BaseFieldProperties) -> dict[str, Any]:
@@ -91,7 +91,7 @@ class BaseSchemaProperties(BaseModel):
91
91
 
92
92
  def __init__(self, **data: Any):
93
93
  super().__init__(**data)
94
- self._archived = data.get("_archived", None)
94
+ self._archived = data.get("_archived", None) # TODO: WHY??
95
95
 
96
96
  model_config = ConfigDict(arbitrary_types_allowed=True)
97
97
 
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env python
2
+ import warnings
2
3
  from pathlib import Path
3
4
 
4
5
  import typer
@@ -17,7 +18,9 @@ from liminal.cli.live_test_entity_schema_migration import (
17
18
  mock_entity_schema_full_migration,
18
19
  )
19
20
  from liminal.cli.utils import read_local_liminal_dir, update_env_revision_id
20
- from liminal.connection.benchling_service import BenchlingService
21
+ from liminal.connection.benchling_service import (
22
+ BenchlingService,
23
+ )
21
24
  from liminal.migrate.revisions_timeline import RevisionsTimeline
22
25
 
23
26
 
@@ -54,15 +57,22 @@ def init() -> None:
54
57
  new_revision_file_path = RevisionsTimeline(VERSIONS_DIR_PATH).init_versions(
55
58
  VERSIONS_DIR_PATH
56
59
  )
57
- revision_id = new_revision_file_path.name.split("_")[0]
58
- env_file = f"""# This file is auto-generated by Liminal.
60
+
61
+ env_file = """# This file is auto-generated by Liminal.
59
62
  # Import the models and dropdowns you want to keep in sync here.
60
63
  # Instantiate the BenchlingConnection(s) with the correct parameters for your tenant(s).
61
- # There must be a revision_id variable defined for each BenchlingConnection instance. The variable name should match the `current_revision_id_var_name` variable passed into the BenchlingConnection instance.
62
- # The revision_id variable name can also match the format `<tenant_name>_CURRENT_REVISION_ID` / `<tenant_alias>_CURRENT_REVISION_ID`.
63
- from liminal.connection import BenchlingConnection
64
+ from liminal.connection import BenchlingConnection, TenantConfigFlags
64
65
 
65
- CURRENT_REVISION_ID = "{revision_id}"
66
+ connection = BenchlingConnection(
67
+ tenant_name="pizzahouse-prod",
68
+ tenant_alias="prod",
69
+ api_client_id="my-secret-api-client-id",
70
+ api_client_secret="my-secret-api-client-secret",
71
+ warehouse_connection_string="...",
72
+ internal_api_admin_email="my-secret-internal-api-admin-email",
73
+ internal_api_admin_password="my-secret-internal-api-admin-password",
74
+ config_flags=TenantConfigFlags(...)
75
+ )
66
76
  """
67
77
  with open(ENV_FILE_PATH, "w") as file:
68
78
  file.write(env_file)
@@ -86,9 +96,7 @@ def generate_files(
86
96
  help="The path to write the generated files to.",
87
97
  ),
88
98
  ) -> None:
89
- current_revision_id, benchling_connection = read_local_liminal_dir(
90
- LIMINAL_DIR_PATH, benchling_tenant
91
- )
99
+ _, benchling_connection = read_local_liminal_dir(LIMINAL_DIR_PATH, benchling_tenant)
92
100
  benchling_service = BenchlingService(benchling_connection, use_internal_api=True)
93
101
  if not write_path.exists():
94
102
  write_path.mkdir()
@@ -98,7 +106,7 @@ def generate_files(
98
106
 
99
107
  @app.command(
100
108
  name="current",
101
- help="Returns the CURRENT_REVISION_ID that your Benchling tenant is on. Reads from liminal/env.py.",
109
+ help="Returns the remote revision_id that your Benchling tenant is currently on. Reads this from the name on the '_liminal_remote' schema.",
102
110
  )
103
111
  def current(
104
112
  benchling_tenant: str = typer.Argument(
@@ -108,16 +116,34 @@ def current(
108
116
  current_revision_id, benchling_connection = read_local_liminal_dir(
109
117
  LIMINAL_DIR_PATH, benchling_tenant
110
118
  )
111
- print(
112
- f"[blue]{benchling_connection.current_revision_id_var_name}: {current_revision_id}[/blue]"
113
- )
119
+ benchling_service = BenchlingService(benchling_connection, use_internal_api=True)
120
+ try:
121
+ remote_revision_id = benchling_service.get_remote_revision_id()
122
+ if current_revision_id is not None:
123
+ warnings.warn(
124
+ f"Accessing and using the revision_id variable in {LIMINAL_DIR_PATH/'env.py'} is deprecated. Delete the variable set in the env.py file, the revision_id is now stored in your Benchling tenant within the '_liminal_remote' schema. Support for reading/writing the local revision_id will end with the v4 release.",
125
+ FutureWarning,
126
+ )
127
+ current_revision_id = remote_revision_id
128
+ except Exception:
129
+ pass
130
+ print(f"[blue]Current revision_id: {current_revision_id}.")
114
131
 
115
132
 
116
133
  @app.command(
117
- name="autogenerate",
134
+ name="head",
135
+ help="Returns the local heads, or the latest revision_id, in your linear revision timeline.",
136
+ )
137
+ def head() -> None:
138
+ revision_timeline = RevisionsTimeline(VERSIONS_DIR_PATH)
139
+ print(f"[blue]{revision_timeline.get_latest_revision().id}: (head)[/blue]")
140
+
141
+
142
+ @app.command(
143
+ name="revision",
118
144
  help="Generates a revision file with a list of operations to bring the given Benchling tenant up to date with the locally defined schemas. Writes revision file to liminal/versions/.",
119
145
  )
120
- def autogenerate(
146
+ def revision(
121
147
  benchling_tenant: str = typer.Argument(
122
148
  ..., help="Benchling tenant (or alias) to connect to."
123
149
  ),
@@ -125,19 +151,56 @@ def autogenerate(
125
151
  ...,
126
152
  help="A description of the revision being generated. This will also be included in the file name.",
127
153
  ),
154
+ autogenerate: bool = typer.Option(
155
+ True,
156
+ "--autogenerate",
157
+ help="Automatically generate the revision file based on comparisons.",
158
+ ),
128
159
  ) -> None:
129
160
  current_revision_id, benchling_connection = read_local_liminal_dir(
130
161
  LIMINAL_DIR_PATH, benchling_tenant
131
162
  )
132
163
  benchling_service = BenchlingService(benchling_connection, use_internal_api=True)
164
+ try:
165
+ remote_revision_id = benchling_service.get_remote_revision_id()
166
+ if current_revision_id is not None:
167
+ warnings.warn(
168
+ f"Accessing and using the revision_id variable in {LIMINAL_DIR_PATH/'env.py'} is deprecated. Delete the variable set in the env.py file, the revision_id is now stored in your Benchling tenant within the '_liminal_remote' schema. Support for reading/writing the local revision_id will end with the v4 release.",
169
+ FutureWarning,
170
+ )
171
+ current_revision_id = remote_revision_id
172
+ except Exception:
173
+ assert current_revision_id is not None
133
174
  autogenerate_revision_file(
134
- benchling_service, VERSIONS_DIR_PATH, description, current_revision_id
175
+ benchling_service,
176
+ VERSIONS_DIR_PATH,
177
+ description,
178
+ current_revision_id,
179
+ autogenerate,
180
+ )
181
+
182
+
183
+ @app.command(
184
+ name="autogenerate",
185
+ hidden=True,
186
+ )
187
+ def autogenerate(
188
+ benchling_tenant: str = typer.Argument(
189
+ ..., help="Benchling tenant (or alias) to connect to."
190
+ ),
191
+ description: str = typer.Argument(
192
+ ...,
193
+ help="A description of the revision being generated. This will also be included in the file name.",
194
+ ),
195
+ ) -> None:
196
+ raise DeprecationWarning(
197
+ "CLI command `liminal autogenerate ...` is deprecated and will be removed in v4. Please use `liminal revision ...` instead."
135
198
  )
136
199
 
137
200
 
138
201
  @app.command(
139
202
  name="upgrade",
140
- help="Upgrades the Benchling tenant by running revision file(s) based on the CURRENT_REVISION_ID and the passed in parameters. Runs the upgrade operations of each revision file in order.",
203
+ help="Upgrades the Benchling tenant by running revision file(s) based on the passed in parameters. Uses the remote revision_id in Benchling as the starting point, and upgrades to the given revision by running the operations in the revision file(s).",
141
204
  context_settings={"ignore_unknown_options": True},
142
205
  )
143
206
  def upgrade(
@@ -146,19 +209,35 @@ def upgrade(
146
209
  ),
147
210
  upgrade_descriptor: str = typer.Argument(
148
211
  ...,
149
- help="Determines the revision files that get run. Pass in the 'revision_id' to upgrade to that revision. Pass in 'head' to upgrade to the latest revision. Pass in '+n' to make a relative revision based on the current revision id.",
212
+ help="Determines the revision files that get run. Pass in the 'revision_id' to upgrade to that revision. Pass in 'head' to upgrade to the latest revision. Pass in '+n' to make a relative revision based on the current remote revision id.",
150
213
  ),
151
214
  ) -> None:
152
215
  current_revision_id, benchling_connection = read_local_liminal_dir(
153
216
  LIMINAL_DIR_PATH, benchling_tenant
154
217
  )
218
+ local_revision_id_exists = current_revision_id is not None
155
219
  benchling_service = BenchlingService(benchling_connection, use_internal_api=True)
220
+ try:
221
+ remote_revision_id = benchling_service.get_remote_revision_id()
222
+ if current_revision_id is not None:
223
+ warnings.warn(
224
+ f"Accessing and using the revision_id variable in {LIMINAL_DIR_PATH/'env.py'} is deprecated. Delete the variable set in the env.py file, the revision_id is now stored in your Benchling tenant within the '_liminal_remote' schema. Support for reading/writing the local revision_id will end with the v4 release.",
225
+ FutureWarning,
226
+ )
227
+ current_revision_id = remote_revision_id
228
+ except Exception:
229
+ assert current_revision_id is not None
156
230
  upgrade_revision_id = upgrade_benchling_tenant(
157
231
  benchling_service, VERSIONS_DIR_PATH, current_revision_id, upgrade_descriptor
158
232
  )
159
- update_env_revision_id(ENV_FILE_PATH, benchling_tenant, upgrade_revision_id)
233
+ benchling_service.upsert_remote_revision_id(upgrade_revision_id)
234
+ if local_revision_id_exists:
235
+ update_env_revision_id(ENV_FILE_PATH, benchling_tenant, upgrade_revision_id)
236
+ print(
237
+ f"[dim red]Set local {benchling_tenant}_CURRENT_REVISION_ID to {upgrade_revision_id} in liminal/env.py"
238
+ )
160
239
  print(
161
- f"[dim]Set {benchling_tenant}_CURRENT_REVISION_ID to {upgrade_revision_id} in liminal/env.py"
240
+ f"[dim]Set revision_id to {upgrade_revision_id} withinn '_liminal_remote' schema."
162
241
  )
163
242
  print("[bold green]Migration complete")
164
243
 
@@ -180,13 +259,29 @@ def downgrade(
180
259
  current_revision_id, benchling_connection = read_local_liminal_dir(
181
260
  LIMINAL_DIR_PATH, benchling_tenant
182
261
  )
262
+ local_revision_id_exists = current_revision_id is not None
183
263
  benchling_service = BenchlingService(benchling_connection, use_internal_api=True)
264
+ try:
265
+ remote_revision_id = benchling_service.get_remote_revision_id()
266
+ if current_revision_id is not None:
267
+ warnings.warn(
268
+ f"Accessing and using the revision_id variable in {LIMINAL_DIR_PATH/'env.py'} is deprecated. Delete the variable set in the env.py file, the revision_id is now stored in your Benchling tenant within the '_liminal_remote' schema. Support for reading/writing the local revision_id will end with the v4 release.",
269
+ FutureWarning,
270
+ )
271
+ current_revision_id = remote_revision_id
272
+ except Exception:
273
+ assert current_revision_id is not None
184
274
  downgrade_revision_id = downgrade_benchling_tenant(
185
275
  benchling_service, VERSIONS_DIR_PATH, current_revision_id, downgrade_descriptor
186
276
  )
187
- update_env_revision_id(ENV_FILE_PATH, benchling_tenant, downgrade_revision_id)
277
+ benchling_service.upsert_remote_revision_id(downgrade_revision_id)
278
+ if local_revision_id_exists:
279
+ update_env_revision_id(ENV_FILE_PATH, benchling_tenant, downgrade_revision_id)
280
+ print(
281
+ f"[dim red]Set local {benchling_tenant}_CURRENT_REVISION_ID to {downgrade_revision_id} in liminal/env.py"
282
+ )
188
283
  print(
189
- f"[dim]Set {benchling_tenant}_CURRENT_REVISION_ID to {downgrade_revision_id} in liminal/env.py"
284
+ f"[dim]Set revision_id to {downgrade_revision_id} withinn '_liminal_remote' schema."
190
285
  )
191
286
  print("[bold green]Migration complete")
192
287
 
@@ -217,9 +312,7 @@ def live_test(
217
312
  raise ValueError(
218
313
  "Only one of --entity-schema-migration or --dropdown-migration can be set."
219
314
  )
220
- current_revision_id, benchling_connection = read_local_liminal_dir(
221
- LIMINAL_DIR_PATH, benchling_tenant
222
- )
315
+ _, benchling_connection = read_local_liminal_dir(LIMINAL_DIR_PATH, benchling_tenant)
223
316
  benchling_service = BenchlingService(benchling_connection, use_internal_api=True)
224
317
  if test_entity_schema_migration:
225
318
  mock_entity_schema_full_migration(
@@ -30,6 +30,7 @@ def autogenerate_revision_file(
30
30
  write_dir_path: Path,
31
31
  description: str,
32
32
  current_revision_id: str,
33
+ compare: bool = True,
33
34
  ) -> None:
34
35
  """Generates a revision file by comparing locally defined schemas to the given Benchling tenant.
35
36
  The revision file contains a unique revision id, the down_revision_id (which is the latest revision id that this revision is based on),
@@ -46,16 +47,20 @@ def autogenerate_revision_file(
46
47
  A description of the revision being generated. This will also be included in the file name.
47
48
  """
48
49
  revision_timeline = RevisionsTimeline(write_dir_path)
50
+ if current_revision_id not in revision_timeline.revisions_map.keys():
51
+ raise Exception(
52
+ f"Your target Benchling tenant is currently at revision_id {current_revision_id}. This does not exist within your revision timeline in any of your revision files. Ensure your current revision_id for your tenant is correct. The current local head revision is {revision_timeline.get_latest_revision().id}"
53
+ )
49
54
  if current_revision_id != revision_timeline.get_latest_revision().id:
50
55
  raise Exception(
51
- f"Your target Benchling tenant is not up to date with the latest revision ({revision_timeline.get_latest_revision().id}). Please upgrade to the latest revision before generating a new revision."
56
+ f"Your target Benchling tenant is currently at revision_id {current_revision_id}, which is not up to date with the local head revision ({revision_timeline.get_latest_revision().id}). Please upgrade your tenant to the latest revision before generating a new revision."
52
57
  )
53
- compare_ops = get_full_migration_operations(benchling_service)
54
- write_path = revision_timeline.write_new_revision(description, compare_ops)
55
- if write_path is None:
56
- print("[bold green]No changes needed. Skipping revision file generation.")
58
+ if compare:
59
+ compare_ops = get_full_migration_operations(benchling_service)
57
60
  else:
58
- print(f"[bold green]Revision file generated at {write_path}")
61
+ compare_ops = []
62
+ write_path = revision_timeline.write_new_revision(description, compare_ops)
63
+ print(f"[bold green]Revision file generated at {write_path}")
59
64
 
60
65
 
61
66
  def upgrade_benchling_tenant(
@@ -1,8 +1,11 @@
1
1
  import importlib.util
2
+ from logging import Logger
2
3
  from pathlib import Path
3
4
 
4
5
  from liminal.connection.benchling_connection import BenchlingConnection
5
6
 
7
+ LOGGER = Logger(name=__name__)
8
+
6
9
 
7
10
  def _check_liminal_directory_initialized(liminal_dir_path: Path) -> None:
8
11
  """Raises an exception if the liminal directory does not exist at the given path."""
@@ -19,7 +22,7 @@ def _check_liminal_directory_initialized(liminal_dir_path: Path) -> None:
19
22
 
20
23
  def read_local_liminal_dir(
21
24
  liminal_dir_path: Path, benchling_tenant: str
22
- ) -> tuple[str, BenchlingConnection]:
25
+ ) -> tuple[str | None, BenchlingConnection]:
23
26
  """Imports the env.py file from /liminal/env.py and returns the CURRENT_REVISION_ID variable along with the BenchlingConnection object.
24
27
  The env.py file is expected to have the CURRENT_REVISION_ID variable set to the revision id you are currently on.
25
28
  The BenchlingConnection object is expected to be defined and have connection information for the Benchling API client and internal API.
@@ -34,7 +37,7 @@ def read_local_liminal_dir(
34
37
 
35
38
  def _read_local_env_file(
36
39
  env_file_path: Path, benchling_tenant: str
37
- ) -> tuple[str, BenchlingConnection]:
40
+ ) -> tuple[str | None, BenchlingConnection]:
38
41
  """Imports the env.py file from the current working directory and returns the CURRENT_REVISION_ID variable along with the BenchlingConnection object.
39
42
  The env.py file is expected to have the CURRENT_REVISION_ID variable set to the revision id you are currently on.
40
43
  The BenchlingConnection object is expected to be defined and have connection information for the Benchling API client and internal API.
@@ -62,8 +65,8 @@ def _read_local_env_file(
62
65
  "internal_api_admin_email and internal_api_admin_password must be provided in BenchlingConnection in liminal/env.py. This is necessary for the migration service."
63
66
  )
64
67
  try:
65
- current_revision_id: str = getattr(
66
- module, bc.current_revision_id_var_name
68
+ current_revision_id: str | None = getattr(
69
+ module, bc.current_revision_id_var_name, None
67
70
  )
68
71
  return current_revision_id, bc
69
72
  except Exception as e:
@@ -78,7 +81,7 @@ def _read_local_env_file(
78
81
  def update_env_revision_id(
79
82
  env_file_path: Path, benchling_env: str, revision_id: str
80
83
  ) -> None:
81
- """Updates the CURRENT_REVISION_ID variable in the env.py file to the given revision id."""
84
+ """Updates the CURRENT_REVISION_ID variable in the env.py file to the given revision id. REMOVE WITH v4 release."""
82
85
  env_file_content = env_file_path.read_text().split("\n")
83
86
  for i, line in enumerate(env_file_content):
84
87
  if f"{benchling_env}_CURRENT_REVISION_ID =" in line:
@@ -0,0 +1,5 @@
1
+ from liminal.connection.benchling_connection import (
2
+ BenchlingConnection, # noqa: F401
3
+ TenantConfigFlags, # noqa: F401
4
+ )
5
+ from liminal.connection.benchling_service import BenchlingService # noqa: F401
@@ -16,10 +16,20 @@ from tenacity import (
16
16
  wait_exponential,
17
17
  )
18
18
 
19
+ from liminal.base.properties.base_field_properties import BaseFieldProperties
20
+ from liminal.base.properties.base_schema_properties import BaseSchemaProperties
19
21
  from liminal.connection.benchling_connection import BenchlingConnection
22
+ from liminal.enums import (
23
+ BenchlingEntityType,
24
+ BenchlingFieldType,
25
+ BenchlingNamingStrategy,
26
+ )
20
27
 
21
28
  logger = logging.getLogger(__name__)
22
29
 
30
+ REMOTE_LIMINAL_SCHEMA_NAME = "_liminal_remote"
31
+ REMOTE_REVISION_ID_FIELD_WH_NAME = "revision_id"
32
+
23
33
 
24
34
  class BenchlingService(Benchling):
25
35
  """
@@ -138,6 +148,99 @@ class BenchlingService(Benchling):
138
148
  """Closes all sessions and cleans up engine"""
139
149
  self.engine.dispose()
140
150
 
151
+ def get_remote_revision_id(self) -> str:
152
+ """
153
+ Uses internal API to to search for the _liminal_remote schema, where the revision_id is stored.
154
+ This schema contains the remote revision_id in the name of the revision_id field.
155
+
156
+ Returns the remote revision_id stored on the entity.
157
+ """
158
+ from liminal.entity_schemas.tag_schema_models import TagSchemaModel
159
+
160
+ try:
161
+ liminal_schema = TagSchemaModel.get_one(self, REMOTE_LIMINAL_SCHEMA_NAME)
162
+ except Exception:
163
+ raise ValueError(
164
+ f"Did not find any schema name '{REMOTE_LIMINAL_SCHEMA_NAME}'. Run a liminal migration to populate your registry with the Liminal entity that stores the remote revision_id."
165
+ )
166
+ revision_id_fields = [
167
+ f
168
+ for f in liminal_schema.fields
169
+ if f.systemName == REMOTE_REVISION_ID_FIELD_WH_NAME
170
+ ]
171
+ if len(revision_id_fields) == 1:
172
+ revision_id = revision_id_fields[0].name
173
+ assert revision_id is not None, "No revision_id set in field name."
174
+ return revision_id
175
+ else:
176
+ raise ValueError(
177
+ f"Error finding field on {REMOTE_LIMINAL_SCHEMA_NAME} schema with warehouse_name {REMOTE_REVISION_ID_FIELD_WH_NAME}. Check schema fields to ensure this field exists and is defined according to documentation."
178
+ )
179
+
180
+ def upsert_remote_revision_id(self, revision_id: str) -> None:
181
+ """Updates or inserts a remote Liminal schema into your tenant with the given revision_id stored in the name of a field.
182
+ If the '_liminal_remote' schema is found, check and make sure a field with warehouse_name 'revision_id' is present. If both are present, update the revision_id stored within the name.
183
+ If no schema is found, create the _liminal_remote entity schema.
184
+ Upsert is needed to migrate users from using the CURRENT_REVISION_ID stored in the env.py file smoothly to storing in Benchling itself.
185
+
186
+ Parameters
187
+ ----------
188
+ revision_id : str
189
+ revision_id of migration file to set in Benchling on remote liminal entity.
190
+
191
+ Returns
192
+ -------
193
+ CustomEntity
194
+ remote liminal entity with updated revision_id field.
195
+ """
196
+ from liminal.entity_schemas.tag_schema_models import TagSchemaModel
197
+
198
+ try:
199
+ liminal_schema = TagSchemaModel.get_one(self, REMOTE_LIMINAL_SCHEMA_NAME)
200
+ except Exception:
201
+ # No _liminal_remote schema found. Create schema.
202
+ from liminal.entity_schemas.operations import CreateEntitySchema
203
+
204
+ CreateEntitySchema(
205
+ schema_properties=BaseSchemaProperties(
206
+ name={REMOTE_LIMINAL_SCHEMA_NAME},
207
+ warehouse_name={REMOTE_LIMINAL_SCHEMA_NAME},
208
+ prefix={REMOTE_LIMINAL_SCHEMA_NAME},
209
+ entity_type=BenchlingEntityType.CUSTOM_ENTITY,
210
+ naming_strategies={BenchlingNamingStrategy.NEW_IDS},
211
+ ),
212
+ fields=[
213
+ BaseFieldProperties(
214
+ name={revision_id},
215
+ warehouse_name=REMOTE_REVISION_ID_FIELD_WH_NAME,
216
+ type=BenchlingFieldType.TEXT,
217
+ parent_link=False,
218
+ is_multi=False,
219
+ required=True,
220
+ )
221
+ ],
222
+ ).execute(self)
223
+ # _liminal_remote schema found. Check if revision_id field exists on it.
224
+ revision_id_fields = [
225
+ f
226
+ for f in liminal_schema.fields
227
+ if f.systemName == REMOTE_REVISION_ID_FIELD_WH_NAME
228
+ ]
229
+ if len(revision_id_fields) == 1:
230
+ # _liminal_remote schema found, revision_id field found. Update revision_id field on it with given revision_id.
231
+ from liminal.entity_schemas.operations import UpdateEntitySchemaField
232
+
233
+ revision_id_field = revision_id_fields[0]
234
+ UpdateEntitySchemaField(
235
+ liminal_schema.sqlIdentifier,
236
+ revision_id_field.systemName,
237
+ BaseFieldProperties(name=revision_id),
238
+ ).execute(self)
239
+ else:
240
+ raise ValueError(
241
+ f"Error finding field on {REMOTE_LIMINAL_SCHEMA_NAME} schema with warehouse_name {REMOTE_REVISION_ID_FIELD_WH_NAME}. Check schema fields to ensure this field exists and is defined according to documentation."
242
+ )
243
+
141
244
  @classmethod
142
245
  @retry(
143
246
  stop=stop_after_attempt(3),