liminal-orm 5.0.0__tar.gz → 5.1.0__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.
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.gitignore +2 -1
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/PKG-INFO +1 -1
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/cli/cli.py +69 -1
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/cli/controller.py +66 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/pyproject.toml +1 -1
- liminal_orm-5.1.0/skills/liminal/SKILL.md +411 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/__init__.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/base/base_dropdown.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/base/base_operation.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/base/base_validation_filters.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/base/compare_operation.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/base/name_template_parts.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/base/properties/base_field_properties.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/base/properties/base_name_template.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/base/properties/base_schema_properties.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/base/str_enum.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/cli/cli.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/cli/controller.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/cli/live_test_dropdown_migration.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/cli/live_test_entity_schema_migration.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/cli/utils.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/connection/__init__.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/connection/benchling_connection.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/connection/benchling_service.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/dropdowns/api.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/dropdowns/compare.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/dropdowns/generate_files.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/dropdowns/operations.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/dropdowns/utils.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/entity_schemas/api.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/entity_schemas/compare.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/entity_schemas/entity_schema_models.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/entity_schemas/generate_files.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/entity_schemas/operations.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/entity_schemas/tag_schema_models.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/entity_schemas/utils.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/enums/__init__.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/enums/benchling_api_field_type.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/enums/benchling_entity_type.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/enums/benchling_field_type.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/enums/benchling_folder_item_type.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/enums/benchling_naming_strategy.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/enums/benchling_sequence_type.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/enums/name_template_part_type.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/enums/sequence_constraint.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/external/__init__.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/mappers.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/migrate/components.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/migrate/revision.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/migrate/revisions_timeline.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/orm/base.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/orm/base_model.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/orm/base_results_model.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/orm/base_tables/registry_entity.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/orm/base_tables/schema.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/orm/base_tables/user.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/orm/column.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/orm/mixins.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/orm/name_template.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/orm/name_template_parts.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/orm/relationship.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/orm/results_schema_properties.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/orm/schema_properties.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/py.typed +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/results_schemas/generate_files.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/results_schemas/models/results_schema_model.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/results_schemas/utils.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/tests/__init__.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/tests/conftest.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/tests/from benchling_sdk.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/tests/test_benchling_service.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/tests/test_dataframe_compatibility.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/tests/test_dropdown_compare.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/tests/test_entity_schema_compare.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/unit_dictionary/utils.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/utils.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/validation/__init__.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/validation/validation_severity.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/__init__.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/base/base_dropdown.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/base/base_operation.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/base/base_validation_filters.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/base/compare_operation.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/base/name_template_parts.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/base/properties/base_field_properties.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/base/properties/base_name_template.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/base/properties/base_schema_properties.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/base/str_enum.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/cli/cli.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/cli/controller.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/cli/live_test_dropdown_migration.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/cli/live_test_entity_schema_migration.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/cli/utils.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/connection/__init__.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/connection/benchling_connection.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/connection/benchling_service.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/dropdowns/api.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/dropdowns/compare.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/dropdowns/generate_files.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/dropdowns/operations.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/dropdowns/utils.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/entity_schemas/api.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/entity_schemas/compare.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/entity_schemas/entity_schema_models.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/entity_schemas/generate_files.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/entity_schemas/operations.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/entity_schemas/tag_schema_models.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/entity_schemas/utils.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/enums/__init__.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/enums/benchling_api_field_type.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/enums/benchling_entity_type.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/enums/benchling_field_type.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/enums/benchling_folder_item_type.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/enums/benchling_naming_strategy.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/enums/benchling_sequence_type.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/enums/name_template_part_type.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/enums/sequence_constraint.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/external/__init__.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/mappers.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/migrate/components.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/migrate/revision.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/migrate/revisions_timeline.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/orm/base.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/orm/base_model.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/orm/base_results_model.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/orm/base_tables/registry_entity.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/orm/base_tables/schema.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/orm/base_tables/user.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/orm/column.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/orm/mixins.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/orm/name_template.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/orm/name_template_parts.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/orm/relationship.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/orm/results_schema_properties.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/orm/schema_properties.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/py.typed +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/results_schemas/generate_files.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/results_schemas/models/results_schema_model.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/results_schemas/utils.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/tests/__init__.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/tests/conftest.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/tests/from benchling_sdk.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/tests/test_benchling_service.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/tests/test_dataframe_compatibility.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/tests/test_dropdown_compare.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/tests/test_entity_schema_compare.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/unit_dictionary/utils.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/utils.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/validation/__init__.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/arvZabAJhner6mVH/liminal/validation/validation_severity.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/__init__.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/base/base_dropdown.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/base/base_operation.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/base/base_validation_filters.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/base/compare_operation.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/base/name_template_parts.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/base/properties/base_field_properties.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/base/properties/base_name_template.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/base/properties/base_schema_properties.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/base/str_enum.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/cli/cli.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/cli/controller.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/cli/live_test_dropdown_migration.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/cli/live_test_entity_schema_migration.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/cli/utils.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/connection/__init__.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/connection/benchling_connection.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/connection/benchling_service.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/dropdowns/api.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/dropdowns/compare.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/dropdowns/generate_files.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/dropdowns/operations.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/dropdowns/utils.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/entity_schemas/api.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/entity_schemas/compare.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/entity_schemas/entity_schema_models.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/entity_schemas/generate_files.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/entity_schemas/operations.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/entity_schemas/tag_schema_models.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/entity_schemas/utils.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/enums/__init__.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/enums/benchling_api_field_type.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/enums/benchling_entity_type.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/enums/benchling_field_type.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/enums/benchling_folder_item_type.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/enums/benchling_naming_strategy.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/enums/benchling_sequence_type.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/enums/name_template_part_type.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/enums/sequence_constraint.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/external/__init__.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/mappers.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/migrate/components.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/migrate/revision.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/migrate/revisions_timeline.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/orm/base.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/orm/base_model.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/orm/base_results_model.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/orm/base_tables/registry_entity.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/orm/base_tables/schema.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/orm/base_tables/user.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/orm/column.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/orm/mixins.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/orm/name_template.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/orm/name_template_parts.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/orm/relationship.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/orm/results_schema_properties.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/orm/schema_properties.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/py.typed +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/results_schemas/generate_files.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/results_schemas/models/results_schema_model.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/results_schemas/utils.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/tests/__init__.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/tests/conftest.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/tests/from benchling_sdk.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/tests/test_benchling_service.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/tests/test_dataframe_compatibility.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/tests/test_dropdown_compare.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/tests/test_entity_schema_compare.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/unit_dictionary/utils.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/utils.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/validation/__init__.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/.uv-cache/archive-v0/eXa8KUkHhZHOAuoK/liminal/validation/validation_severity.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/LICENSE.md +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/README.md +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/__init__.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/base/base_dropdown.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/base/base_operation.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/base/base_validation_filters.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/base/compare_operation.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/base/name_template_parts.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/base/properties/base_field_properties.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/base/properties/base_name_template.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/base/properties/base_schema_properties.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/base/str_enum.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/cli/live_test_dropdown_migration.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/cli/live_test_entity_schema_migration.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/cli/utils.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/connection/__init__.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/connection/benchling_connection.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/connection/benchling_service.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/dropdowns/api.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/dropdowns/compare.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/dropdowns/generate_files.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/dropdowns/operations.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/dropdowns/utils.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/entity_schemas/api.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/entity_schemas/compare.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/entity_schemas/entity_schema_models.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/entity_schemas/generate_files.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/entity_schemas/operations.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/entity_schemas/test_entity_dataframe_compatibility.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/entity_schemas/utils.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/enums/__init__.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/enums/benchling_entity_schema_endpoint_type.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/enums/benchling_entity_type.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/enums/benchling_entity_type_name.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/enums/benchling_field_definition_input_type.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/enums/benchling_field_definition_type.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/enums/benchling_field_type.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/enums/benchling_link_definition_type.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/enums/benchling_naming_strategy.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/enums/name_template_part_type.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/enums/sequence_constraint.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/external/__init__.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/mappers.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/migrate/components.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/migrate/revision.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/migrate/revisions_timeline.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/orm/base.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/orm/base_model.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/orm/base_results_model.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/orm/base_tables/registry_entity.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/orm/base_tables/schema.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/orm/base_tables/user.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/orm/column.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/orm/mixins.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/orm/name_template.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/orm/name_template_parts.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/orm/relationship.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/orm/results_schema_properties.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/orm/schema_properties.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/py.typed +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/results_schemas/api.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/results_schemas/generate_files.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/results_schemas/models/results_schema_model.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/results_schemas/test_results_dataframe_compatibility.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/results_schemas/utils.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/tests/__init__.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/tests/conftest.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/tests/from benchling_sdk.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/tests/test_benchling_service.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/tests/test_dropdown_compare.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/tests/test_entity_schema_compare.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/unit_dictionary/utils.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/utils.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/validation/__init__.py +0 -0
- {liminal_orm-5.0.0 → liminal_orm-5.1.0}/liminal/validation/validation_severity.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: liminal-orm
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.1.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
|
Project-URL: Homepage, https://github.com/dynotx/liminal-orm
|
|
6
6
|
Project-URL: Repository, https://github.com/dynotx/liminal-orm
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
|
+
import json
|
|
2
3
|
from pathlib import Path
|
|
4
|
+
import sys
|
|
3
5
|
from typing import Any
|
|
4
6
|
|
|
5
7
|
import typer
|
|
@@ -8,6 +10,7 @@ from typer.core import TyperGroup
|
|
|
8
10
|
|
|
9
11
|
from liminal.cli.controller import (
|
|
10
12
|
autogenerate_revision_file,
|
|
13
|
+
diff_benchling_tenant,
|
|
11
14
|
downgrade_benchling_tenant,
|
|
12
15
|
generate_all_files,
|
|
13
16
|
upgrade_benchling_tenant,
|
|
@@ -176,6 +179,71 @@ def head(
|
|
|
176
179
|
print(f"[blue]{revision_timeline.get_latest_revision().id}: (head)[/blue]")
|
|
177
180
|
|
|
178
181
|
|
|
182
|
+
@app.command(
|
|
183
|
+
name="diff",
|
|
184
|
+
help="Compares the locally defined dropdowns and entity schemas to the given Benchling tenant. Exit codes: 0=in sync, 1=error, 2=differences found.",
|
|
185
|
+
)
|
|
186
|
+
def diff(
|
|
187
|
+
benchling_tenant: str = typer.Argument(
|
|
188
|
+
..., help="Benchling tenant (or alias) to connect to."
|
|
189
|
+
),
|
|
190
|
+
dropdowns_flag: bool = typer.Option(
|
|
191
|
+
False,
|
|
192
|
+
"-d",
|
|
193
|
+
"--dropdowns",
|
|
194
|
+
help="Only compare dropdowns.",
|
|
195
|
+
),
|
|
196
|
+
entity_schemas_flag: bool = typer.Option(
|
|
197
|
+
False,
|
|
198
|
+
"-es",
|
|
199
|
+
"--entity-schemas",
|
|
200
|
+
help="Only compare entity schemas.",
|
|
201
|
+
),
|
|
202
|
+
json_output: bool = typer.Option(
|
|
203
|
+
False,
|
|
204
|
+
"--json",
|
|
205
|
+
help="Write a machine-readable comparison report to stdout.",
|
|
206
|
+
),
|
|
207
|
+
) -> None:
|
|
208
|
+
benchling_connection = read_local_liminal_dir(LIMINAL_DIR_PATH, benchling_tenant)
|
|
209
|
+
benchling_service = BenchlingService(benchling_connection)
|
|
210
|
+
if not dropdowns_flag and not entity_schemas_flag:
|
|
211
|
+
dropdowns_flag = True
|
|
212
|
+
entity_schemas_flag = True
|
|
213
|
+
report = diff_benchling_tenant(
|
|
214
|
+
benchling_service, dropdowns_flag, entity_schemas_flag
|
|
215
|
+
)
|
|
216
|
+
if json_output:
|
|
217
|
+
typer.echo(json.dumps(report, sort_keys=True))
|
|
218
|
+
else:
|
|
219
|
+
for key, label in (
|
|
220
|
+
("dropdowns", "Dropdowns"),
|
|
221
|
+
("entity_schemas", "Entity schemas"),
|
|
222
|
+
):
|
|
223
|
+
section = report[key]
|
|
224
|
+
if not section["compared"]:
|
|
225
|
+
continue
|
|
226
|
+
if section["in_sync"]:
|
|
227
|
+
print(f"[bold green]{label} are in sync for {report['tenant']} tenant.")
|
|
228
|
+
continue
|
|
229
|
+
print(
|
|
230
|
+
f"[bold yellow]{label}: {len(section['differences'])} difference(s) for {report['tenant']} tenant."
|
|
231
|
+
)
|
|
232
|
+
for index, difference in enumerate(section["differences"], start=1):
|
|
233
|
+
print(f" {index}. {difference['description']}")
|
|
234
|
+
|
|
235
|
+
if report["in_sync"]:
|
|
236
|
+
print(
|
|
237
|
+
f"[bold green]No differences found. {report['tenant']} is in sync with code."
|
|
238
|
+
)
|
|
239
|
+
else:
|
|
240
|
+
print(
|
|
241
|
+
f"[bold yellow]{report['total_differences']} total difference(s) found between code and {report['tenant']}. Run `liminal revision {report['tenant']} '<description>'` to generate a revision file that resolves them."
|
|
242
|
+
)
|
|
243
|
+
if not report["in_sync"]:
|
|
244
|
+
raise typer.Exit(code=2)
|
|
245
|
+
|
|
246
|
+
|
|
179
247
|
@app.command(
|
|
180
248
|
name="revision",
|
|
181
249
|
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/.",
|
|
@@ -327,5 +395,5 @@ def live_test(
|
|
|
327
395
|
)
|
|
328
396
|
|
|
329
397
|
|
|
330
|
-
print("Starting Liminal CLI...")
|
|
398
|
+
print("Starting Liminal CLI...", file=sys.stderr)
|
|
331
399
|
app()
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
|
+
from typing import Any
|
|
2
3
|
|
|
3
4
|
from rich import print
|
|
4
5
|
|
|
6
|
+
from liminal.base.compare_operation import CompareOperation
|
|
5
7
|
from liminal.connection.benchling_service import BenchlingService
|
|
8
|
+
from liminal.dropdowns.compare import compare_dropdowns
|
|
6
9
|
from liminal.dropdowns.generate_files import generate_all_dropdown_files
|
|
10
|
+
from liminal.entity_schemas.compare import compare_entity_schemas
|
|
7
11
|
from liminal.entity_schemas.generate_files import generate_all_entity_schema_files
|
|
8
12
|
from liminal.migrate.components import execute_operations, get_full_migration_operations
|
|
9
13
|
from liminal.migrate.revisions_timeline import RevisionsTimeline
|
|
@@ -45,6 +49,68 @@ def generate_all_files(
|
|
|
45
49
|
generate_all_results_schema_files(benchling_service, write_path, overwrite)
|
|
46
50
|
|
|
47
51
|
|
|
52
|
+
def diff_benchling_tenant(
|
|
53
|
+
benchling_service: BenchlingService,
|
|
54
|
+
dropdowns_flag: bool = True,
|
|
55
|
+
entity_schemas_flag: bool = True,
|
|
56
|
+
) -> dict[str, Any]:
|
|
57
|
+
"""Compares locally defined schemas to a Benchling tenant.
|
|
58
|
+
|
|
59
|
+
This is read-only and does not make any changes to the Benchling tenant or
|
|
60
|
+
write any files.
|
|
61
|
+
|
|
62
|
+
Parameters
|
|
63
|
+
----------
|
|
64
|
+
benchling_service : BenchlingService
|
|
65
|
+
The Benchling service object that is connected to a specified Benchling tenant.
|
|
66
|
+
dropdowns_flag : bool
|
|
67
|
+
Whether to compare dropdowns.
|
|
68
|
+
entity_schemas_flag : bool
|
|
69
|
+
Whether to compare entity schemas.
|
|
70
|
+
|
|
71
|
+
Returns
|
|
72
|
+
-------
|
|
73
|
+
dict[str, Any]
|
|
74
|
+
A machine-readable report of the comparison.
|
|
75
|
+
"""
|
|
76
|
+
tenant = benchling_service.benchling_tenant
|
|
77
|
+
sections: dict[str, dict[str, list[CompareOperation]] | None] = {
|
|
78
|
+
"dropdowns": None,
|
|
79
|
+
"entity_schemas": None,
|
|
80
|
+
}
|
|
81
|
+
if dropdowns_flag:
|
|
82
|
+
sections["dropdowns"] = compare_dropdowns(benchling_service)
|
|
83
|
+
if entity_schemas_flag:
|
|
84
|
+
sections["entity_schemas"] = compare_entity_schemas(benchling_service)
|
|
85
|
+
|
|
86
|
+
report: dict[str, Any] = {"tenant": tenant, "total_differences": 0}
|
|
87
|
+
for section_name, comparison in sections.items():
|
|
88
|
+
compared = comparison is not None
|
|
89
|
+
differences = [
|
|
90
|
+
{
|
|
91
|
+
"name": name,
|
|
92
|
+
"operation": compare_op.op.__class__.__name__,
|
|
93
|
+
"description": compare_op.op.describe(),
|
|
94
|
+
}
|
|
95
|
+
for name, compare_op in sorted(
|
|
96
|
+
(
|
|
97
|
+
(name, compare_op)
|
|
98
|
+
for name, compare_ops in (comparison or {}).items()
|
|
99
|
+
for compare_op in compare_ops
|
|
100
|
+
),
|
|
101
|
+
key=lambda difference: difference[1],
|
|
102
|
+
)
|
|
103
|
+
]
|
|
104
|
+
report[section_name] = {
|
|
105
|
+
"compared": compared,
|
|
106
|
+
"in_sync": len(differences) == 0 if compared else None,
|
|
107
|
+
"differences": differences,
|
|
108
|
+
}
|
|
109
|
+
report["total_differences"] += len(differences)
|
|
110
|
+
report["in_sync"] = report["total_differences"] == 0
|
|
111
|
+
return report
|
|
112
|
+
|
|
113
|
+
|
|
48
114
|
def autogenerate_revision_file(
|
|
49
115
|
benchling_service: BenchlingService,
|
|
50
116
|
revisions_timeline: RevisionsTimeline,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "liminal-orm"
|
|
3
|
-
version = "5.
|
|
3
|
+
version = "5.1.0"
|
|
4
4
|
description = "An ORM and toolkit that builds on top of Benchling's platform to keep your schemas and downstream code dependencies in sync."
|
|
5
5
|
authors = [{ name = "DynoTx Open Source", email = "opensource@dynotx.com" }]
|
|
6
6
|
requires-python = ">=3.10,<3.14"
|
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: liminal
|
|
3
|
+
description: Operate the Liminal ORM CLI to manage Benchling schemas as code. Covers every `liminal` CLI command, two-step Benchling migrations (generate a revision file, get human verification, then upgrade), downgrades, generating schema files from a tenant, and propagating entity schema / dropdown changes safely across a codebase. Use when the user mentions Liminal, liminal-orm, Benchling schemas, Benchling migrations, revision files, dropdowns, entity schemas, results schemas, or runs any `liminal` command.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Liminal ORM CLI
|
|
7
|
+
|
|
8
|
+
Liminal defines Benchling dropdowns, entity schemas, and results schemas as Python classes and migrates Benchling tenants to match them. Migrations follow an Alembic-style linear revision history stored in `liminal/versions/`. Full docs: https://dynotx.github.io/liminal-orm/
|
|
9
|
+
|
|
10
|
+
## Non-negotiable rules
|
|
11
|
+
|
|
12
|
+
1. **Every write to Benchling requires explicit human approval first.** Follow the [Benchling write approval gate](#benchling-write-approval-gate) before each one. No exceptions, including test and dev tenants.
|
|
13
|
+
2. **Migrations are always two steps with a human gate in between, and that gate is a single approval.** Run `liminal revision ...`, review the file, then present one combined [review and upgrade approval request](#review-and-approval-one-approval-per-tenant) (review results, confidence score, and the exact `liminal upgrade` command) and **stop**. Only run `liminal upgrade ...` after the user explicitly approves that request. Never chain `revision` and `upgrade` in one command, script, or turn, even if the user asked to "just migrate". Don't ask the user to approve the revision file and then separately approve the upgrade.
|
|
14
|
+
3. **Never answer interactive prompts on the user's behalf** (e.g. piping `yes` into `liminal head --reset`). Ask the user and let them confirm.
|
|
15
|
+
4. **Never re-run a failed `upgrade` unchanged.** See [If an upgrade fails](#if-an-upgrade-fails).
|
|
16
|
+
5. **Never write secrets into `liminal/env.py`.** Read credentials from environment variables or a secrets manager.
|
|
17
|
+
6. **Run every command from the Liminal root**, the directory that contains `liminal/env.py`. The CLI resolves `liminal/` relative to the current working directory.
|
|
18
|
+
|
|
19
|
+
## Benchling write approval gate
|
|
20
|
+
|
|
21
|
+
### What counts as a Benchling write
|
|
22
|
+
|
|
23
|
+
- `liminal upgrade` and `liminal downgrade`
|
|
24
|
+
- `liminal revision` when the timeline contains only the initial revision (`liminal/versions/` has a single file). That run writes the initial revision_id to the tenant's `liminal_remote` schema. Later `revision` runs are read-only against Benchling.
|
|
25
|
+
- `liminal live-test ... -e/--execute`
|
|
26
|
+
- Any Python that changes Benchling: calling `.execute()` on a Liminal operation, `execute_operations(...)`, `upsert_remote_revision_id(...)`, or Benchling SDK create/update/archive/delete calls through `BenchlingService`. Don't use these to get around the CLI gate. Prefer the CLI for all schema changes.
|
|
27
|
+
|
|
28
|
+
Read-only commands (`current`, `head` without `--reset`, `diff`, `generate-files`, and `revision` after the first) don't need this gate. Their local side effects still follow the rules elsewhere in this file.
|
|
29
|
+
|
|
30
|
+
### Protocol
|
|
31
|
+
|
|
32
|
+
Before every Benchling write:
|
|
33
|
+
|
|
34
|
+
1. **Present an approval request** and nothing else actionable in the same message:
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
Approval needed: Benchling write
|
|
38
|
+
Tenant: <tenant_name / alias> (<prod | test | dev>)
|
|
39
|
+
Command: <exact command to run>
|
|
40
|
+
Revision: <revision_id(s) and file path(s)>, if applicable
|
|
41
|
+
Changes: <numbered plain-language list of every operation that will run>
|
|
42
|
+
Risks: <data-bearing archives, required-field changes, type changes, downstream impact; "none identified" if none>
|
|
43
|
+
Rollback: <downgrade command, or "manual" if not reversible>
|
|
44
|
+
Reply "approve" to run this exact command, or tell me what to change.
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
2. **Stop and end your turn.** Don't run the command, start it in the background, or move on to other steps while waiting.
|
|
48
|
+
3. **Run it only on an explicit affirmative reply** to that request, such as "approve", "yes, run it", or "go ahead". Treat silence, questions, "looks good so far", partial answers, and approvals with conditions you haven't addressed as **not approved**. Answer or adjust, then ask again.
|
|
49
|
+
4. **Report the outcome** after running: the CLI output summary, success or failure, and `liminal current <tenant>`. Then stop again before the next write.
|
|
50
|
+
|
|
51
|
+
### Scope of an approval
|
|
52
|
+
|
|
53
|
+
- An approval covers **one command, against one tenant, for the exact operations shown**. It doesn't carry over to another tenant (approving `test` doesn't approve `prod`), another revision, or a retry.
|
|
54
|
+
- For a migration, the combined review and upgrade request is the approval for that upgrade. One "approve" covers reviewing the revision file and running the exact `liminal upgrade` command shown. Don't add a second confirmation.
|
|
55
|
+
- **Ask again** if anything changes after approval: the revision file is edited, the descriptor changes (e.g. `head` now resolves to a newer revision), or a previous attempt failed.
|
|
56
|
+
- Instructions given ahead of time ("migrate everything", "you have permission to upgrade prod", "don't ask me again") are **not** a substitute for the per-write approval request. Acknowledge them, then still ask at each write.
|
|
57
|
+
- Never run Benchling writes unattended: no `--yes`-style piping, no background jobs, no CI changes, and no scripts that bundle several writes behind a single approval.
|
|
58
|
+
|
|
59
|
+
## Project layout
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
<root>/
|
|
63
|
+
liminal/
|
|
64
|
+
env.py # BenchlingConnection(s) + imports of all schema classes
|
|
65
|
+
versions/ # revision files, one linear chain
|
|
66
|
+
dropdowns/ # BaseDropdown subclasses
|
|
67
|
+
entity_schemas/ # BaseModel subclasses, grouped by entity type
|
|
68
|
+
custom_entities/ dna_sequences/ aa_sequences/ ...
|
|
69
|
+
results_schemas/ # BaseResultsModel subclasses (no migrations)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`env.py` must import every dropdown and entity schema class, e.g. `from pizzahouse.dropdowns import *`. Liminal only sees classes that are imported. **Any Benchling schema or dropdown not imported is treated as deleted from code, and `revision` will generate `ArchiveEntitySchema` / `ArchiveDropdown` operations for it.**
|
|
73
|
+
|
|
74
|
+
Example `env.py`:
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
import os
|
|
78
|
+
|
|
79
|
+
from pizzahouse.dropdowns import * # noqa: F401,F403
|
|
80
|
+
from pizzahouse.entity_schemas import * # noqa: F401,F403
|
|
81
|
+
|
|
82
|
+
from liminal.connection import BenchlingConnection, TenantConfigFlags
|
|
83
|
+
|
|
84
|
+
prod = BenchlingConnection(
|
|
85
|
+
tenant_name="pizzahouse-prod", # {tenant_name}.benchling.com
|
|
86
|
+
tenant_alias="prod", # usable in place of tenant_name in the CLI
|
|
87
|
+
api_client_id=os.environ["BENCHLING_PROD_CLIENT_ID"],
|
|
88
|
+
api_client_secret=os.environ["BENCHLING_PROD_CLIENT_SECRET"],
|
|
89
|
+
warehouse_connection_string=os.environ.get("BENCHLING_PROD_WAREHOUSE_URL"), # optional
|
|
90
|
+
config_flags=TenantConfigFlags(schemas_enable_change_warehouse_name=False),
|
|
91
|
+
)
|
|
92
|
+
test = BenchlingConnection(tenant_name="pizzahouse-test", tenant_alias="test", ...)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`api_client_id` and `api_client_secret` are required for every CLI command that connects to Benchling. `<tenant>` in the commands below is either a `tenant_name` or a `tenant_alias` from `env.py`.
|
|
96
|
+
|
|
97
|
+
## CLI reference
|
|
98
|
+
|
|
99
|
+
| Command | Connects to Benchling | Writes to Benchling (approval gate) | Writes local files |
|
|
100
|
+
|---|---|---|---|
|
|
101
|
+
| `liminal init` | no | no | yes |
|
|
102
|
+
| `liminal generate-files <tenant>` | yes | no | yes |
|
|
103
|
+
| `liminal current <tenant>` | yes | no | no |
|
|
104
|
+
| `liminal head [--reset]` | no | no | `--reset` deletes a file |
|
|
105
|
+
| `liminal diff <tenant>` | yes | no | no |
|
|
106
|
+
| `liminal revision <tenant> "<desc>"` | yes | **first run on a fresh timeline only** | yes |
|
|
107
|
+
| `liminal upgrade <tenant> <descriptor>` | yes | **yes** | no |
|
|
108
|
+
| `liminal downgrade <tenant> <descriptor>` | yes | **yes** | no |
|
|
109
|
+
| `liminal live-test <tenant> -e` | yes | **yes** | no |
|
|
110
|
+
|
|
111
|
+
### `liminal init`
|
|
112
|
+
|
|
113
|
+
Creates `liminal/`, `liminal/versions/` with an empty initial revision file, and a template `liminal/env.py`. It fails if `liminal/` already exists. Afterwards, fill in `env.py` and add the schema imports.
|
|
114
|
+
|
|
115
|
+
### `liminal generate-files <tenant> [-p PATH] [-d] [-es] [-rs] [-o]`
|
|
116
|
+
|
|
117
|
+
Reads the tenant's schemas and writes Python classes into `PATH/dropdowns/`, `PATH/entity_schemas/`, and `PATH/results_schemas/`, regenerating each `__init__.py`.
|
|
118
|
+
|
|
119
|
+
- `-p, --write-path PATH`: output directory (default `.`). It is created if missing.
|
|
120
|
+
- `-d, --dropdowns`, `-es, --entity-schemas`, `-rs, --results-schemas`: limit output to those types. With none of these flags, all three are generated.
|
|
121
|
+
- `-o, --overwrite`: **deletes the entire target subdirectory** (e.g. all of `entity_schemas/`) and regenerates it. This wipes hand-written code such as validators, relationships, and custom methods. Without `-o`, existing files are left alone and only new files are written.
|
|
122
|
+
|
|
123
|
+
Before using `-o`, check that `git status` is clean, warn the user that custom code will be lost, and review the diff afterwards. Generated files use relative imports (`from ...dropdowns import X`), so keep `dropdowns/` and `entity_schemas/` as siblings in the same package. Generate from the production tenant, which is the source of truth.
|
|
124
|
+
|
|
125
|
+
### `liminal current <tenant>`
|
|
126
|
+
|
|
127
|
+
Prints the revision_id the tenant is on. It is stored remotely in Benchling in the `liminal_remote` schema. This command is read-only and also a quick way to test connection setup.
|
|
128
|
+
|
|
129
|
+
### `liminal head [-r | --reset]`
|
|
130
|
+
|
|
131
|
+
Prints the latest local revision_id. It takes **no tenant argument**.
|
|
132
|
+
|
|
133
|
+
`--reset` deletes the head revision file, making its `down_revision` the new head, and asks for interactive confirmation. Only use it for a revision that has **not been applied to any tenant**, e.g. a revision the user rejected during review. The initial revision cannot be deleted.
|
|
134
|
+
|
|
135
|
+
### `liminal diff <tenant> [-d] [-es] [--json]`
|
|
136
|
+
|
|
137
|
+
Compares the dropdowns and entity schemas imported by `env.py` with the target Benchling tenant. This is read-only: it does not write to Benchling or create a revision file. Use it as the default preflight after changing schema code and before running `revision`.
|
|
138
|
+
|
|
139
|
+
- With no type flags, compares both dropdowns and entity schemas.
|
|
140
|
+
- `-d, --dropdowns`: compare only dropdowns.
|
|
141
|
+
- `-es, --entity-schemas`: compare only entity schemas.
|
|
142
|
+
- `--json`: writes a machine-readable report to stdout. The report contains the tenant, total difference count, per-type sync state, and each difference's model name, operation class, and description. Use this for CI or when a structured review is useful.
|
|
143
|
+
- Exit code `0`: the selected schema types are in sync.
|
|
144
|
+
- Exit code `2`: differences were found. This is an expected result, not a command failure; review the printed differences and generate a revision only when the user wants to migrate them.
|
|
145
|
+
- Exit code `1`: an error occurred, such as a configuration, connection, or import error.
|
|
146
|
+
|
|
147
|
+
`diff` uses the same comparison logic as `revision`, so it may identify archive operations when classes are missing from `env.py`. Investigate unexpected differences before creating a revision.
|
|
148
|
+
|
|
149
|
+
### `liminal revision <tenant> "<description>"`
|
|
150
|
+
|
|
151
|
+
Compares the classes imported in `env.py` against the tenant and writes `liminal/versions/<id>_<description>.py` with `upgrade()` and `downgrade()` operation lists. Covers dropdowns and entity schemas, not results schemas.
|
|
152
|
+
|
|
153
|
+
- Preconditions: the tenant's remote revision_id must equal the local head. Otherwise the command errors; upgrade that tenant first, or `head --reset` an unapplied head.
|
|
154
|
+
- On a timeline that only has the initial revision, this command writes that initial revision_id to the tenant's `liminal_remote` schema. That makes it a Benchling write, so go through the approval gate first.
|
|
155
|
+
- `--autogenerate` is on by default and cannot be disabled from the CLI. To hand-write operations, edit the generated file.
|
|
156
|
+
- Multiple tenants share one timeline: generate against production (or whichever tenant acts as prod), then apply to every tenant with `upgrade`.
|
|
157
|
+
|
|
158
|
+
### `liminal upgrade <tenant> <descriptor>`
|
|
159
|
+
|
|
160
|
+
Validates and then executes the `upgrade()` operations of each revision from the tenant's current revision up to the target, then updates the remote revision_id. Descriptor values:
|
|
161
|
+
|
|
162
|
+
- `head`: upgrade to the latest local revision
|
|
163
|
+
- `<revision_id>`: e.g. `c3a9cd009713`
|
|
164
|
+
- `+N`: upgrade N revisions forward, e.g. `+1`
|
|
165
|
+
|
|
166
|
+
### `liminal downgrade <tenant> <descriptor>`
|
|
167
|
+
|
|
168
|
+
Executes the `downgrade()` operations of each revision, newest first, back to the target. Descriptor values:
|
|
169
|
+
|
|
170
|
+
- `<revision_id>`: the revision to land on
|
|
171
|
+
- `-N`: go back N revisions, e.g. `-1`
|
|
172
|
+
|
|
173
|
+
This is a Benchling write. Use the approval gate and list every `downgrade()` operation that will run.
|
|
174
|
+
|
|
175
|
+
### Hidden and deprecated commands
|
|
176
|
+
|
|
177
|
+
- `liminal autogenerate ...`: deprecated and always raises an error. Use `liminal revision`.
|
|
178
|
+
- `liminal live-test <tenant> [-es | -d] [-e]`: maintainer smoke test that creates, updates, and archives throwaway `test_dont_touch_*` schemas or dropdowns. It is a dry run unless `-e/--execute` is passed. Do not run it unless the user explicitly asks. `-e` is a Benchling write that needs the approval gate, and never run it against production.
|
|
179
|
+
|
|
180
|
+
Benchling's v3 API is rate limited, so `generate-files`, `revision`, and `upgrade` can take several minutes on large tenants. Let them finish; don't kill and retry them.
|
|
181
|
+
|
|
182
|
+
## Groundwork: making schema changes in code
|
|
183
|
+
|
|
184
|
+
Do this before generating a revision. A Benchling schema change is also a code change: the Python attribute name of a column **is** its warehouse column name, and other code depends on it.
|
|
185
|
+
|
|
186
|
+
### 1. Pin down the exact change
|
|
187
|
+
|
|
188
|
+
Restate the requested change as concrete edits and confirm with the user if anything is ambiguous. For example: "Archive the `dough` field on `pizza`; add an optional integer field `Slice Count` (`slice_count`)." If the change is non-trivial, suggest the user prototype it in a dev tenant through the Benchling UI first, then encode the final version in code.
|
|
189
|
+
|
|
190
|
+
### 2. Edit the schema classes
|
|
191
|
+
|
|
192
|
+
| Change | How to express it in code |
|
|
193
|
+
|---|---|
|
|
194
|
+
| Add field | Add a `Column(...)` attribute. Unless `schemas_enable_change_warehouse_name=True`, the attribute name must equal `to_snake_case(name)`. Position in the class sets the field order. |
|
|
195
|
+
| Archive field | Delete the attribute, or keep it with `_archived=True` if code still needs to read historical warehouse data. |
|
|
196
|
+
| Change display name only | Edit `name=` and keep the attribute name, which keeps the existing warehouse name. |
|
|
197
|
+
| Rename warehouse name | Requires `schemas_enable_change_warehouse_name=True` on the connection. Rename the attribute / `warehouse_name`, then **hand-edit the revision file** (see review checklist). |
|
|
198
|
+
| Change type, `is_multi`, `required`, `tooltip`, `decimal_places` | Edit the `Column` arguments. |
|
|
199
|
+
| Change `unit_name` | **Not possible** once set. Archive the field and add a new one. |
|
|
200
|
+
| Reorder fields | Reorder the attribute lines. |
|
|
201
|
+
| Add / archive schema | Add or delete the class, and make sure it is (or stops being) imported by `env.py`. `_archived=True` on `SchemaProperties` keeps an archived schema queryable. |
|
|
202
|
+
| Dropdown option add / archive / reorder | Edit `__allowed_values__`. Order matters. |
|
|
203
|
+
| Rename dropdown option | Edit the value in `__allowed_values__`, then **hand-edit the revision file**. Autogenerate produces an archive + create pair. |
|
|
204
|
+
| Rename dropdown | Edit `__benchling_name__`, then **hand-edit the revision file**. Autogenerate produces `CreateDropdown` + `ArchiveDropdown`. |
|
|
205
|
+
| Entity link target | `entity_link=` is the target schema's **warehouse_name** string. |
|
|
206
|
+
| Name template | Edit `__name_template__`. `FieldPart(wh_field_name=...)` references a warehouse field name. |
|
|
207
|
+
|
|
208
|
+
### 3. Propagate the change across the codebase
|
|
209
|
+
|
|
210
|
+
Search the whole repository, not just the schema package, for every identifier the change touches: the class name, the attribute/warehouse name, the old display name, the dropdown class, and each affected dropdown option string. Typical places that need updating:
|
|
211
|
+
|
|
212
|
+
- **The model's own `__init__`**: generated classes define an `__init__` that takes the fields as parameters. Add, remove, or rename parameters to match, and keep `SomeDropdown.validate(value)` calls in sync.
|
|
213
|
+
- **Relationships**: `single_relationship("Target", column)` / `multi_relationship(...)` take the column object and the target class name.
|
|
214
|
+
- **Validators**: `@liminal_validator` methods that read `self.<field>`.
|
|
215
|
+
- **Other schemas**: `entity_link="<warehouse_name>"`, `dropdown=<DropdownClass>`, `constraint_fields={...}`, and name templates in other classes, including results schemas.
|
|
216
|
+
- **Warehouse queries**: SQLAlchemy queries (`Model.field`), `.df()` column names, and any raw SQL, dbt models, or dashboards that reference warehouse table/column names.
|
|
217
|
+
- **Benchling SDK / app code**: payloads that use field **display names** or literal dropdown option values (e.g. `fields={"Cook Time": ...}`), and Benchling apps, automations, or integrations that read those fields.
|
|
218
|
+
- **Tests and fixtures** that construct models or assert on field names.
|
|
219
|
+
- **Package exports**: `__init__.py` files and `env.py` imports for new or deleted modules.
|
|
220
|
+
|
|
221
|
+
Only search within the repository. Don't check or report on anything outside it.
|
|
222
|
+
|
|
223
|
+
### 4. Verify the code before generating the revision
|
|
224
|
+
|
|
225
|
+
- Run the project's tests, type checker, and linter if they exist.
|
|
226
|
+
- Confirm the schema package still imports cleanly; any import error in `env.py` breaks every `liminal` command.
|
|
227
|
+
- Show the user the diff of the schema and dependent code changes.
|
|
228
|
+
|
|
229
|
+
## Two-step migration workflow
|
|
230
|
+
|
|
231
|
+
Copy this checklist and track progress:
|
|
232
|
+
|
|
233
|
+
```
|
|
234
|
+
- [ ] Groundwork done: schema edits + codebase propagation + tests pass
|
|
235
|
+
- [ ] Preflight: `liminal current <tenant>` == `liminal head`
|
|
236
|
+
- [ ] Inspect `liminal diff <tenant>` output; investigate every unexpected difference
|
|
237
|
+
- [ ] Step 1: `liminal revision <tenant> "<description>"` (approval gate first if this is a fresh timeline)
|
|
238
|
+
- [ ] Review: revision file checked against the review checklist, coherence check done (code and other schemas), confidence score computed
|
|
239
|
+
- [ ] GATE (single approval): combined review + `liminal upgrade <test_tenant> <revision_id>` request approved
|
|
240
|
+
- [ ] Step 2: upgrade test tenant → user verifies in Benchling
|
|
241
|
+
- [ ] GATE (single approval): combined review + `liminal upgrade <prod_tenant> <revision_id>` request approved
|
|
242
|
+
- [ ] Step 2: upgrade prod tenant → user verifies in Benchling
|
|
243
|
+
- [ ] `liminal current <tenant>` shows the new revision on every tenant
|
|
244
|
+
- [ ] Schema changes + code changes + revision file committed together in one PR
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Preflight
|
|
248
|
+
|
|
249
|
+
Run `liminal current <tenant>` and `liminal head`. They must match before generating a revision. If they don't, there are pending revisions; apply those first (with their own review) or stop and ask the user. Then run `liminal diff <tenant>` and review every reported difference. A `diff` exit code of `2` means differences were found and is expected when code has changed; it is not an error.
|
|
250
|
+
|
|
251
|
+
### Step 1: generate the revision file
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
liminal revision prod "archive dough field on pizza"
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Open the generated file in `liminal/versions/`. It looks like:
|
|
258
|
+
|
|
259
|
+
```python
|
|
260
|
+
import liminal.external as b
|
|
261
|
+
|
|
262
|
+
revision = "c3a9cd009713"
|
|
263
|
+
down_revision = "d28335bffaba"
|
|
264
|
+
|
|
265
|
+
def upgrade() -> list[b.BaseOperation]:
|
|
266
|
+
return [b.ArchiveEntitySchemaField('pizza', 'dough')]
|
|
267
|
+
|
|
268
|
+
def downgrade() -> list[b.BaseOperation]:
|
|
269
|
+
return [b.UnarchiveEntitySchemaField('pizza', 'dough')]
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
If the output says dropdowns and entity schemas are in sync and `upgrade()` is empty, there is nothing to migrate. Tell the user and offer to remove the empty revision with `liminal head --reset`.
|
|
273
|
+
|
|
274
|
+
### Review checklist
|
|
275
|
+
|
|
276
|
+
Go through every operation. Report only failures, fixes, and risks; don't list passing items:
|
|
277
|
+
|
|
278
|
+
- [ ] Every operation in `upgrade()` maps to a change the user asked for, and nothing they asked for is missing.
|
|
279
|
+
- [ ] **No unexpected `ArchiveEntitySchema` / `ArchiveDropdown` / `ArchiveEntitySchemaField` / `ArchiveDropdownOption` operations.** These usually mean a class isn't imported in `env.py`, or the code is stale compared with what someone changed in the Benchling UI. If you find any, stop and investigate. Don't edit them away without understanding why.
|
|
280
|
+
- [ ] **Renames:** autogenerate cannot detect renames of warehouse names, dropdown names, or dropdown options. Each one appears as a create + archive pair, which would lose the link to existing data. Replace the pair with an update operation in `upgrade()`, and its inverse (old and new swapped) in `downgrade()`:
|
|
281
|
+
- schema: `b.UpdateEntitySchema('old_wh', b.BaseSchemaProperties(warehouse_name='new_wh'))`
|
|
282
|
+
- field: `b.UpdateEntitySchemaField('schema_wh', 'old_field_wh', b.BaseFieldProperties(warehouse_name='new_field_wh'))`
|
|
283
|
+
- dropdown: `b.UpdateDropdownName('Old Name', 'New Name')`
|
|
284
|
+
- dropdown option: `b.UpdateDropdownOption('Dropdown Name', 'Old Option', 'New Option')`
|
|
285
|
+
- [ ] `downgrade()` is the exact inverse of `upgrade()`, in reverse order. Fix it if you hand-edited `upgrade()`.
|
|
286
|
+
- [ ] `down_revision` equals the previous head and the tenant's current revision.
|
|
287
|
+
- [ ] Flag risky changes to the user: archiving fields or options that hold data, making fields required, changing field types, and anything that affects downstream consumers identified in groundwork step 3.
|
|
288
|
+
- [ ] **Coherence with the codebase and the rest of the Benchling schemas.** Run the [coherence check](#coherence-check) below. A migration that is valid on its own can still break something else.
|
|
289
|
+
|
|
290
|
+
### Coherence check
|
|
291
|
+
|
|
292
|
+
Do this for every revision, before scoring. Search the whole repo (not just the schema package) and the full set of schema and dropdown classes imported by `env.py`, using the identifiers each operation touches: class name, warehouse name, display name, dropdown name, and option strings.
|
|
293
|
+
|
|
294
|
+
- [ ] **No dangling references to removed things.** For each archived schema, field, dropdown, or option, nothing still references it: no `entity_link=` / `dropdown=` / `parent_link=` in other schemas (including results schemas), no `constraint_fields`, name-template `FieldPart`, relationship, validator, `__init__` parameter, query, test, fixture, or app payload.
|
|
295
|
+
- [ ] **No broken targets for new things.** Every new `entity_link`, `dropdown`, or `parent_link` points to a schema or dropdown that exists (in code and on the tenant) and is not archived. Each new schema or dropdown is imported by `env.py`.
|
|
296
|
+
- [ ] **Code and tenant models agree.** The model class, its `__init__`, validators, and exports match the revision (same fields, names, types, required flags, `is_multi`). Run `liminal diff <tenant>`; after the upgrade it should report in sync, and no unrelated differences should remain.
|
|
297
|
+
- [ ] **No contradictory data models.** Check the new state against the other schemas: no duplicate or conflicting field names or warehouse names for the same concept across related schemas, no `required=True` field that existing creators (code, apps, other schemas' links) can't populate, no type mismatch between a field and the field it links to or is queried alongside, and a name template that only uses fields that still exist.
|
|
298
|
+
- [ ] **Downstream consumers in the repo are updated.** Warehouse queries, dbt models, apps, and integrations in this repo that read the affected names or values are updated in the same change. Only check within the repo; don't flag or score anything outside it.
|
|
299
|
+
|
|
300
|
+
For each failed item, say what you found (file and line) and fix it or ask the user. Don't mark an item as passed unless you searched for it; if you couldn't search within the repo, report it as **unverified**. Don't report passing items.
|
|
301
|
+
|
|
302
|
+
### Confidence score
|
|
303
|
+
|
|
304
|
+
After the checklist, give a confidence score from 0 to 100 for whether the user should run the upgrade, and a verdict: **Upgrade** (90-100), **Upgrade with caution** (70-89), or **Do not upgrade yet** (below 70). Start at 100 and subtract:
|
|
305
|
+
|
|
306
|
+
| Finding | Deduction |
|
|
307
|
+
|---|---|
|
|
308
|
+
| Unexpected archive operation, or any operation not tied to the requested change | -40 (stop and investigate; verdict is **Do not upgrade yet** until resolved) |
|
|
309
|
+
| Autogenerate rename not yet converted to an update operation | -40 |
|
|
310
|
+
| `downgrade()` is not the exact inverse of `upgrade()` | -25 |
|
|
311
|
+
| `down_revision` doesn't match the tenant's current revision | -50 |
|
|
312
|
+
| Requested change missing from `upgrade()` | -30 |
|
|
313
|
+
| Archives a field, option, or schema that may hold data | -20 |
|
|
314
|
+
| Makes a field required, or changes a field type, `is_multi`, or `entity_link` | -15 each |
|
|
315
|
+
| Dangling reference to something the revision archives or renames (in code or another schema) | -40 each (verdict is **Do not upgrade yet** until fixed) |
|
|
316
|
+
| New `entity_link` / `dropdown` / `parent_link` target missing, archived, or not imported by `env.py` | -40 |
|
|
317
|
+
| Model code (class, `__init__`, validators) doesn't match the revision | -30 |
|
|
318
|
+
| Contradictory data model found (conflicting names, unpopulatable required field, type mismatch, name template using a removed field) | -25 each |
|
|
319
|
+
| Coherence check item left unverified inside the repo (didn't search) | -15 each |
|
|
320
|
+
| Tests, type checker, or linter not run or failing | -10 |
|
|
321
|
+
| `liminal diff` showed unexplained differences | -15 |
|
|
322
|
+
| Target is prod and the revision hasn't been applied to a test tenant first | -10 |
|
|
323
|
+
|
|
324
|
+
Show the score with the deductions that produced it, e.g. `Confidence: 100/100 (Upgrade): additive optional field, clean inverse, no archives`. The score is advice only. It never replaces the approval request, and a high score doesn't let you skip it.
|
|
325
|
+
|
|
326
|
+
### Review and approval: one approval per tenant
|
|
327
|
+
|
|
328
|
+
Don't ask the user to approve the revision file and then ask again to approve the upgrade. Send **one** message containing the review results and the upgrade approval request, then **stop**:
|
|
329
|
+
|
|
330
|
+
```
|
|
331
|
+
Revision review: <revision_id> (<file path>)
|
|
332
|
+
Issues: <only failed or unverified checklist/coherence items with file and line, or "none">
|
|
333
|
+
Operations: <numbered plain-language list of upgrade() operations>
|
|
334
|
+
Confidence: <score>/100 (<verdict>): <deductions or "no deductions">
|
|
335
|
+
|
|
336
|
+
Approval needed: Benchling write
|
|
337
|
+
Tenant: <tenant> (<prod | test | dev>)
|
|
338
|
+
Command: liminal upgrade <tenant> <revision_id>
|
|
339
|
+
Revision: <revision_id>, <file path>
|
|
340
|
+
Changes: <same numbered list as Operations>
|
|
341
|
+
Risks: <flagged risks, or "none identified">
|
|
342
|
+
Rollback: liminal downgrade <tenant> <down_revision>
|
|
343
|
+
Reply "approve" to run this exact command, or tell me what to change.
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
A single "approve" covers both the file review and running that upgrade. Run it right away; don't ask again. If the verdict is **Do not upgrade yet**, say so, explain why, and offer fixes rather than asking for approval. If the user rejects the revision, edit the code or the revision file and review again, or delete the unapplied revision with `liminal head --reset` (the user confirms the prompt).
|
|
347
|
+
|
|
348
|
+
### Step 2: apply the revision (one approval per tenant)
|
|
349
|
+
|
|
350
|
+
Apply to the test tenant first and target the specific reviewed revision_id rather than `head`, so nothing unreviewed sneaks in. The combined review and approval request above is the only approval needed for that tenant. Run this only after explicit approval:
|
|
351
|
+
|
|
352
|
+
```bash
|
|
353
|
+
liminal upgrade test c3a9cd009713
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
Report the result and have the user verify the change in the Benchling UI. Then send a **new** combined request for production (a different tenant needs its own approval), stop, and run this only after explicit approval:
|
|
357
|
+
|
|
358
|
+
```bash
|
|
359
|
+
liminal upgrade prod c3a9cd009713
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
Then run `liminal current <tenant>` on each tenant to confirm it is on the new revision. Remind the user to commit the schema changes, dependent code changes, and the revision file together in one PR, and to attach evidence of the successful migration.
|
|
363
|
+
|
|
364
|
+
If several revisions are pending on a tenant, prefer applying them one at a time with `+1`. See the next section for why.
|
|
365
|
+
|
|
366
|
+
### If an upgrade fails
|
|
367
|
+
|
|
368
|
+
The remote revision_id is updated only after **every** targeted revision succeeds. After a failure, the tenant's recorded revision is unchanged, even though some operations, or even whole earlier revisions in a multi-revision upgrade, may already have been applied.
|
|
369
|
+
|
|
370
|
+
1. Do not re-run the same command. Re-executing operations that already succeeded is unsafe.
|
|
371
|
+
2. Show the user the error output. The CLI prints which numbered operation failed.
|
|
372
|
+
3. Propose commenting out the operations in `upgrade()` that already succeeded. After making the edit, send a new approval request listing only the operations that will now run, and stop. Run `liminal upgrade` again for that revision only after explicit approval.
|
|
373
|
+
4. Afterwards, restore the commented-out operations in the file so the revision is complete for other tenants, and confirm with `liminal current`.
|
|
374
|
+
|
|
375
|
+
## Downgrade workflow
|
|
376
|
+
|
|
377
|
+
1. Run `liminal current <tenant>` and `liminal head`, and identify the target revision.
|
|
378
|
+
2. Send an approval request listing the `downgrade()` operations of every revision that will be reverted, newest first, and stop.
|
|
379
|
+
3. After explicit approval, run `liminal downgrade <tenant> -1` (or `<revision_id>`). Do the test tenant first, and get a separate approval for each further tenant.
|
|
380
|
+
4. If the code should match the downgraded state, revert the code changes too. Otherwise the next `revision` will try to re-apply them.
|
|
381
|
+
|
|
382
|
+
## Resyncing a dev or test tenant to code
|
|
383
|
+
|
|
384
|
+
To bring a scratch tenant back in line with the code after experimenting in the UI:
|
|
385
|
+
|
|
386
|
+
1. Make sure the tenant is at the local head (`liminal current` == `liminal head`).
|
|
387
|
+
2. `liminal revision <dev_tenant> "resync dev"`, then review the file and get approval as usual.
|
|
388
|
+
3. Send an approval request, stop, and after explicit approval run `liminal upgrade <dev_tenant> <revision_id>`.
|
|
389
|
+
4. This revision is not part of the shared history. With the user's approval, delete it with `liminal head --reset` and don't commit it.
|
|
390
|
+
|
|
391
|
+
## Results schemas
|
|
392
|
+
|
|
393
|
+
Liminal does not migrate results schemas. Edit them in the Benchling UI, then regenerate the classes:
|
|
394
|
+
|
|
395
|
+
```bash
|
|
396
|
+
liminal generate-files <tenant> -rs # only writes new files
|
|
397
|
+
liminal generate-files <tenant> -rs -o # deletes and rebuilds results_schemas/
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
## Troubleshooting
|
|
401
|
+
|
|
402
|
+
| Error | Fix |
|
|
403
|
+
|---|---|
|
|
404
|
+
| `/liminal directory not found at current working directory` | `cd` to the Liminal root, or run `liminal init`. |
|
|
405
|
+
| `BenchlingConnection with tenant name or alias X not found` | Use a `tenant_name`/`tenant_alias` defined in `env.py`. |
|
|
406
|
+
| `api_client_id and api_client_secret must be provided` | Set both on that `BenchlingConnection`. |
|
|
407
|
+
| `...not up to date with the local head revision` (on `revision`) | Upgrade that tenant first, or `liminal head --reset` an unapplied head. |
|
|
408
|
+
| `revision_id ... does not exist within your revision timeline` | The tenant was migrated from another branch or a deleted revision file. Restore the missing file before doing anything else. |
|
|
409
|
+
| `Multiple revisions found that include X as their down_revision_id` | Two branches each added a revision. Point one file's `down_revision` at the other's `revision`, but only if neither has been applied, and confirm with the user. |
|
|
410
|
+
| Warehouse name validation error | The change needs `schemas_enable_change_warehouse_name=True`, a tenant flag set by Benchling support. |
|
|
411
|
+
| HTTP 429 / slow commands | Benchling v3 API rate limits. Wait and let the command finish. |
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|