albert 1.9.1__tar.gz → 1.9.3__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.
- {albert-1.9.1 → albert-1.9.3}/PKG-INFO +1 -1
- {albert-1.9.1 → albert-1.9.3}/src/albert/__init__.py +1 -1
- {albert-1.9.1 → albert-1.9.3}/src/albert/collections/lots.py +118 -3
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/lots.py +17 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/sheets.py +194 -115
- {albert-1.9.1 → albert-1.9.3}/tests/collections/test_inventory.py +0 -1
- {albert-1.9.1 → albert-1.9.3}/tests/resources/test_sheets.py +142 -0
- {albert-1.9.1 → albert-1.9.3}/.circleci/config.yml +0 -0
- {albert-1.9.1 → albert-1.9.3}/.gitignore +0 -0
- {albert-1.9.1 → albert-1.9.3}/.pre-commit-config.yaml +0 -0
- {albert-1.9.1 → albert-1.9.3}/.vscode/settings.json +0 -0
- {albert-1.9.1 → albert-1.9.3}/CHANGELOG.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/CONTRIBUTING.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/LICENSE +0 -0
- {albert-1.9.1 → albert-1.9.3}/README.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/CONTRIBUTING.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/albert.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/assets/Vector_Favicon_Blue.svg +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/assets/Wordmark_Black.png +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/assets/Wordmark_White.png +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/authentication.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/collections/activities.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/collections/attachments.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/collections/base.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/collections/batch_data.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/collections/btdataset.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/collections/btinsight.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/collections/btmodel.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/collections/cas.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/collections/companies.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/collections/custom_fields.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/collections/custom_templates.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/collections/data_columns.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/collections/data_templates.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/collections/entity_types.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/collections/files.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/collections/hazards.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/collections/inventory.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/collections/links.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/collections/lists.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/collections/locations.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/collections/lots.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/collections/notebooks.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/collections/notes.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/collections/parameter_groups.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/collections/parameters.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/collections/pricings.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/collections/product_design.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/collections/projects.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/collections/property_data.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/collections/reports.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/collections/roles.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/collections/storage_classes.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/collections/storage_locations.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/collections/substances.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/collections/tags.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/collections/tasks.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/collections/un_numbers.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/collections/units.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/collections/users.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/collections/workflows.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/collections/worksheets.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/concepts.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/configuration.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/credentials.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/index.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/installation.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/migration.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/resources/activities.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/resources/attachments.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/resources/batch_data.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/resources/btdataset.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/resources/btinsight.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/resources/btmodel.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/resources/cas.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/resources/companies.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/resources/custom_fields.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/resources/custom_templates.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/resources/data_columns.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/resources/data_templates.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/resources/entity_types.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/resources/files.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/resources/hazards.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/resources/identifiers.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/resources/inventory.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/resources/links.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/resources/lists.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/resources/locations.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/resources/lots.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/resources/notebooks.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/resources/notes.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/resources/parameter_groups.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/resources/parameters.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/resources/pricings.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/resources/product_design.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/resources/projects.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/resources/property_data.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/resources/reports.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/resources/roles.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/resources/sheets.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/resources/storage_classes.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/resources/storage_locations.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/resources/substances.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/resources/tags.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/resources/tasks.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/resources/un_numbers.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/resources/units.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/resources/users.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/resources/workflows.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/resources/worksheets.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/sso.md +0 -0
- {albert-1.9.1 → albert-1.9.3}/docs/styles/extra.css +0 -0
- {albert-1.9.1 → albert-1.9.3}/mkdocs.yml +0 -0
- {albert-1.9.1 → albert-1.9.3}/pyproject.toml +0 -0
- {albert-1.9.1 → albert-1.9.3}/scripts/validate_release_tag.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/scripts/validate_version_bump.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/setup.sh +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/client.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/collections/__init__.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/collections/activities.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/collections/attachments.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/collections/base.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/collections/batch_data.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/collections/btdataset.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/collections/btinsight.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/collections/btmodel.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/collections/cas.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/collections/companies.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/collections/custom_fields.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/collections/custom_templates.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/collections/data_columns.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/collections/data_templates.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/collections/entity_types.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/collections/files.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/collections/hazards.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/collections/inventory.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/collections/links.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/collections/lists.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/collections/locations.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/collections/notebooks.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/collections/notes.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/collections/parameter_groups.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/collections/parameters.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/collections/pricings.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/collections/product_design.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/collections/projects.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/collections/property_data.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/collections/report_templates.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/collections/reports.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/collections/roles.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/collections/storage_classes.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/collections/storage_locations.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/collections/substance.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/collections/tags.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/collections/tasks.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/collections/un_numbers.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/collections/units.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/collections/users.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/collections/workflows.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/collections/worksheets.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/core/__init__.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/core/auth/__init__.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/core/auth/_listener.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/core/auth/_manager.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/core/auth/credentials.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/core/auth/sso.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/core/base.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/core/logging.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/core/pagination.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/core/session.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/core/shared/__init__.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/core/shared/enums.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/core/shared/identifiers.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/core/shared/models/base.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/core/shared/models/patch.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/core/shared/types.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/exceptions.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/__init__.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/_mixins.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/acls.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/activities.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/attachments.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/batch_data.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/btdataset.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/btinsight.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/btmodel.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/cas.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/companies.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/custom_fields.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/custom_templates.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/data_columns.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/data_templates.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/entity_types.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/facet.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/files.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/hazards.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/inventory.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/links.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/lists.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/locations.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/notebooks.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/notes.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/parameter_groups.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/parameters.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/pricings.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/product_design.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/projects.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/property_data.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/report_templates.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/reports.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/roles.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/storage_classes.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/storage_locations.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/substance.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/tagged_base.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/tags.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/tasks.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/un_numbers.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/units.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/users.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/workflows.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/resources/worksheets.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/utils/__init__.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/utils/_auth.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/utils/_patch.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/src/albert/utils/inventory.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/__init__.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/collections/__init__.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/collections/test_activities.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/collections/test_attachments.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/collections/test_batch_data.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/collections/test_btdataset.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/collections/test_btinsight.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/collections/test_btmodel.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/collections/test_cas.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/collections/test_company.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/collections/test_custom_fields.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/collections/test_custom_templates.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/collections/test_data_columns.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/collections/test_data_templates.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/collections/test_entity_types.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/collections/test_files.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/collections/test_hazards.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/collections/test_links.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/collections/test_lists.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/collections/test_locations.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/collections/test_lots.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/collections/test_notebooks.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/collections/test_notes.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/collections/test_parameter_groups.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/collections/test_parameters.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/collections/test_pricings.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/collections/test_product_design.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/collections/test_projects.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/collections/test_property_data.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/collections/test_report_templates.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/collections/test_reports.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/collections/test_roles.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/collections/test_storage_classes.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/collections/test_storage_locations.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/collections/test_substance.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/collections/test_tags.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/collections/test_tasks.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/collections/test_un_number.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/collections/test_units.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/collections/test_users.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/collections/test_workflows.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/collections/test_worksheet.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/conftest.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/core/shared/__init__.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/core/shared/test_identifiers.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/core/shared/test_types.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/core/test_auth.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/data/SDS_HCL.pdf +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/data/dontpanic.jpg +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/resources/test_inventory.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/resources/test_lots.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/resources/test_notebooks.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/seeding.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/utils/fake_session.py +0 -0
- {albert-1.9.1 → albert-1.9.3}/tests/utils/test_patches.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: albert
|
|
3
|
-
Version: 1.9.
|
|
3
|
+
Version: 1.9.3
|
|
4
4
|
Summary: The official Python SDK for the Albert Invent platform.
|
|
5
5
|
Project-URL: Homepage, https://www.albertinvent.com/
|
|
6
6
|
Project-URL: Documentation, https://docs.developer.albertinvent.com/albert-python
|
|
@@ -7,10 +7,11 @@ from albert.collections.base import BaseCollection
|
|
|
7
7
|
from albert.core.logging import logger
|
|
8
8
|
from albert.core.pagination import AlbertPaginator
|
|
9
9
|
from albert.core.session import AlbertSession
|
|
10
|
-
from albert.core.shared.enums import PaginationMode
|
|
11
|
-
from albert.core.shared.identifiers import InventoryId, LotId
|
|
10
|
+
from albert.core.shared.enums import OrderBy, PaginationMode
|
|
11
|
+
from albert.core.shared.identifiers import InventoryId, LotId, TaskId
|
|
12
12
|
from albert.core.shared.models.patch import PatchDatum, PatchOperation, PatchPayload
|
|
13
|
-
from albert.resources.
|
|
13
|
+
from albert.resources.inventory import InventoryCategory
|
|
14
|
+
from albert.resources.lots import Lot, LotSearchItem
|
|
14
15
|
|
|
15
16
|
# 14 decimal places for inventory on hand delta calculations
|
|
16
17
|
DECIMAL_DELTA_QUANTIZE = Decimal("0.00000000000000")
|
|
@@ -120,6 +121,120 @@ class LotCollection(BaseCollection):
|
|
|
120
121
|
url = f"{self.base_path}?id={id}"
|
|
121
122
|
self.session.delete(url)
|
|
122
123
|
|
|
124
|
+
@validate_call
|
|
125
|
+
def search(
|
|
126
|
+
self,
|
|
127
|
+
*,
|
|
128
|
+
text: str | None = None,
|
|
129
|
+
inventory_id: InventoryId | list[InventoryId] | None = None,
|
|
130
|
+
location_id: str | list[str] | None = None,
|
|
131
|
+
storage_location_id: str | list[str] | None = None,
|
|
132
|
+
task_id: TaskId | list[TaskId] | None = None,
|
|
133
|
+
category: InventoryCategory | str | list[InventoryCategory | str] | None = None,
|
|
134
|
+
external_barcode_id: str | list[str] | None = None,
|
|
135
|
+
search_field: str | list[str] | None = None,
|
|
136
|
+
source_field: str | list[str] | None = None,
|
|
137
|
+
additional_field: str | list[str] | None = None,
|
|
138
|
+
is_drop_down: bool | None = None,
|
|
139
|
+
order_by: OrderBy = OrderBy.DESCENDING,
|
|
140
|
+
sort_by: str | None = None,
|
|
141
|
+
offset: int | None = None,
|
|
142
|
+
max_items: int | None = None,
|
|
143
|
+
) -> Iterator[LotSearchItem]:
|
|
144
|
+
"""
|
|
145
|
+
Search for Lot records matching the provided filters.
|
|
146
|
+
|
|
147
|
+
⚠️ This method returns partial (unhydrated) entities to optimize performance.
|
|
148
|
+
To retrieve fully detailed entities, use :meth:`get_all` instead.
|
|
149
|
+
|
|
150
|
+
Parameters
|
|
151
|
+
----------
|
|
152
|
+
text : str, optional
|
|
153
|
+
Free-text query matched against lot fields.
|
|
154
|
+
inventory_id : InventoryId or list[InventoryId], optional
|
|
155
|
+
Filter by parent inventory IDs.
|
|
156
|
+
location_id : str or list[str], optional
|
|
157
|
+
Filter by specific location IDs.
|
|
158
|
+
storage_location_id : str or list[str], optional
|
|
159
|
+
Filter by storage location IDs.
|
|
160
|
+
task_id : TaskId or list[TaskId], optional
|
|
161
|
+
Filter by source task IDs.
|
|
162
|
+
category : InventoryCategory or list[str], optional
|
|
163
|
+
Filter by parent inventory categories.
|
|
164
|
+
external_barcode_id : str or list[str], optional
|
|
165
|
+
Filter by external barcode IDs.
|
|
166
|
+
search_field : str or list[str], optional
|
|
167
|
+
Restrict the fields the `text` query searches.
|
|
168
|
+
source_field : str or list[str], optional
|
|
169
|
+
Restrict which fields are returned in the response.
|
|
170
|
+
additional_field : str or list[str], optional
|
|
171
|
+
Request additional columns from the search index.
|
|
172
|
+
is_drop_down : bool, optional
|
|
173
|
+
Use dropdown sanitization for the search text when True.
|
|
174
|
+
order_by : OrderBy, optional
|
|
175
|
+
Sort order for the results, default DESCENDING.
|
|
176
|
+
sort_by : str, optional
|
|
177
|
+
Attribute to sort by.
|
|
178
|
+
offset : int, optional
|
|
179
|
+
Pagination offset to start from.
|
|
180
|
+
max_items : int, optional
|
|
181
|
+
Maximum number of items to return in total. If None, fetches all available items.
|
|
182
|
+
|
|
183
|
+
Returns
|
|
184
|
+
-------
|
|
185
|
+
Iterator[LotSearchItem]
|
|
186
|
+
An iterator of matching partial (unhydrated) lot entities.
|
|
187
|
+
"""
|
|
188
|
+
|
|
189
|
+
search_text = text if (text is None or len(text) < 50) else text[:50]
|
|
190
|
+
|
|
191
|
+
def _ensure_list(value):
|
|
192
|
+
if value is None:
|
|
193
|
+
return None
|
|
194
|
+
if isinstance(value, list | tuple | set):
|
|
195
|
+
return list(value)
|
|
196
|
+
return [value]
|
|
197
|
+
|
|
198
|
+
def _format_categories(value):
|
|
199
|
+
raw = _ensure_list(value)
|
|
200
|
+
if raw is None:
|
|
201
|
+
return None
|
|
202
|
+
formatted: list[str] = []
|
|
203
|
+
for category in raw:
|
|
204
|
+
formatted.append(
|
|
205
|
+
category.value if isinstance(category, InventoryCategory) else category
|
|
206
|
+
)
|
|
207
|
+
return formatted
|
|
208
|
+
|
|
209
|
+
params = {
|
|
210
|
+
"offset": offset,
|
|
211
|
+
"order": order_by.value,
|
|
212
|
+
"text": search_text,
|
|
213
|
+
"sortBy": sort_by,
|
|
214
|
+
"isDropDown": is_drop_down,
|
|
215
|
+
"inventoryId": _ensure_list(inventory_id),
|
|
216
|
+
"locationId": _ensure_list(location_id),
|
|
217
|
+
"storageLocationId": _ensure_list(storage_location_id),
|
|
218
|
+
"taskId": _ensure_list(task_id),
|
|
219
|
+
"category": _format_categories(category),
|
|
220
|
+
"externalBarcodeId": _ensure_list(external_barcode_id),
|
|
221
|
+
"searchField": _ensure_list(search_field),
|
|
222
|
+
"sourceField": _ensure_list(source_field),
|
|
223
|
+
"additionalField": _ensure_list(additional_field),
|
|
224
|
+
}
|
|
225
|
+
params = {key: value for key, value in params.items() if value is not None}
|
|
226
|
+
|
|
227
|
+
return AlbertPaginator(
|
|
228
|
+
mode=PaginationMode.OFFSET,
|
|
229
|
+
path=f"{self.base_path}/search",
|
|
230
|
+
session=self.session,
|
|
231
|
+
params=params,
|
|
232
|
+
max_items=max_items,
|
|
233
|
+
deserialize=lambda items: [
|
|
234
|
+
LotSearchItem(**item)._bind_collection(self) for item in items
|
|
235
|
+
],
|
|
236
|
+
)
|
|
237
|
+
|
|
123
238
|
@validate_call
|
|
124
239
|
def get_all(
|
|
125
240
|
self,
|
|
@@ -3,9 +3,11 @@ from typing import Any
|
|
|
3
3
|
|
|
4
4
|
from pydantic import Field, NonNegativeFloat, field_serializer, field_validator
|
|
5
5
|
|
|
6
|
+
from albert.core.base import BaseAlbertModel
|
|
6
7
|
from albert.core.shared.identifiers import InventoryId, LotId
|
|
7
8
|
from albert.core.shared.models.base import BaseResource
|
|
8
9
|
from albert.core.shared.types import MetadataItem, SerializeAsEntityLink
|
|
10
|
+
from albert.resources._mixins import HydrationMixin
|
|
9
11
|
from albert.resources.inventory import InventoryCategory
|
|
10
12
|
from albert.resources.locations import Location
|
|
11
13
|
from albert.resources.storage_locations import StorageLocation
|
|
@@ -143,3 +145,18 @@ class Lot(BaseResource):
|
|
|
143
145
|
@field_serializer("inventory_on_hand", return_type=str)
|
|
144
146
|
def serialize_inventory_on_hand(self, inventory_on_hand: NonNegativeFloat):
|
|
145
147
|
return self._format_decimal(inventory_on_hand)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class LotSearchItem(BaseAlbertModel, HydrationMixin[Lot]):
|
|
151
|
+
"""Lightweight representation of a Lot returned from search()."""
|
|
152
|
+
|
|
153
|
+
id: LotId = Field(alias="albertId")
|
|
154
|
+
inventory_id: InventoryId | None = Field(default=None, alias="parentId")
|
|
155
|
+
parent_name: str | None = Field(default=None, alias="parentName")
|
|
156
|
+
parent_unit: str | None = Field(default=None, alias="parentUnit")
|
|
157
|
+
parent_category: InventoryCategory | None = Field(default=None, alias="parentIdCategory")
|
|
158
|
+
task_id: str | None = Field(default=None, alias="taskId")
|
|
159
|
+
barcode_id: str | None = Field(default=None, alias="barcodeId")
|
|
160
|
+
expiration_date: str | None = Field(default=None, alias="expirationDate")
|
|
161
|
+
manufacturer_lot_number: str | None = Field(default=None, alias="manufacturerLotNumber")
|
|
162
|
+
lot_number: str | None = Field(default=None, alias="number")
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from enum import Enum
|
|
2
|
-
from typing import Any, ForwardRef, Union
|
|
2
|
+
from typing import Any, ForwardRef, TypedDict, Union
|
|
3
3
|
|
|
4
4
|
import pandas as pd
|
|
5
5
|
from pydantic import Field, PrivateAttr, field_validator, model_validator, validate_call
|
|
@@ -7,6 +7,7 @@ from pydantic import Field, PrivateAttr, field_validator, model_validator, valid
|
|
|
7
7
|
from albert.core.base import BaseAlbertModel
|
|
8
8
|
from albert.core.shared.identifiers import InventoryId
|
|
9
9
|
from albert.core.shared.models.base import BaseResource, BaseSessionResource
|
|
10
|
+
from albert.core.shared.models.patch import PatchDatum
|
|
10
11
|
from albert.exceptions import AlbertException
|
|
11
12
|
from albert.resources.inventory import InventoryItem
|
|
12
13
|
|
|
@@ -15,6 +16,18 @@ Row = ForwardRef("Row")
|
|
|
15
16
|
Column = ForwardRef("Column")
|
|
16
17
|
Sheet = ForwardRef("Sheet")
|
|
17
18
|
|
|
19
|
+
CellAttributeValue = str | float | int | dict[str, Any] | list[Any] | None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class CellChangeId(TypedDict):
|
|
23
|
+
rowId: str
|
|
24
|
+
colId: str
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class CellChangePayload(TypedDict):
|
|
28
|
+
Id: CellChangeId
|
|
29
|
+
data: list[PatchDatum]
|
|
30
|
+
|
|
18
31
|
|
|
19
32
|
class CellColor(str, Enum):
|
|
20
33
|
"""The allowed colors for a cell"""
|
|
@@ -611,6 +624,7 @@ class Sheet(BaseSessionResource): # noqa:F811
|
|
|
611
624
|
enforce_order: bool = False,
|
|
612
625
|
clear: bool = True,
|
|
613
626
|
) -> Column:
|
|
627
|
+
all_cells: list[Cell] = []
|
|
614
628
|
existing_formulation_names = [x.name for x in self.columns]
|
|
615
629
|
if clear and formulation_name in existing_formulation_names:
|
|
616
630
|
# get the existing column and clear it out to put the new formulation in
|
|
@@ -618,10 +632,10 @@ class Sheet(BaseSessionResource): # noqa:F811
|
|
|
618
632
|
self._clear_formulation_from_column(column=col)
|
|
619
633
|
else:
|
|
620
634
|
col = self.add_formulation_columns(formulation_names=[formulation_name])[0]
|
|
621
|
-
|
|
635
|
+
column_id = col.column_id
|
|
622
636
|
|
|
623
|
-
all_cells = []
|
|
624
637
|
self.grid = None # reset the grid for saftey
|
|
638
|
+
product_rows = list(self.product_design.rows)
|
|
625
639
|
|
|
626
640
|
for component in components:
|
|
627
641
|
component_inventory_id = component.inventory_item_id
|
|
@@ -629,6 +643,7 @@ class Sheet(BaseSessionResource): # noqa:F811
|
|
|
629
643
|
inventory_id=component_inventory_id,
|
|
630
644
|
existing_cells=all_cells,
|
|
631
645
|
enforce_order=enforce_order,
|
|
646
|
+
product_rows=product_rows,
|
|
632
647
|
)
|
|
633
648
|
if row_id is None:
|
|
634
649
|
raise AlbertException(f"No Component with id {component_inventory_id}")
|
|
@@ -637,7 +652,7 @@ class Sheet(BaseSessionResource): # noqa:F811
|
|
|
637
652
|
min_value = str(component.min_value) if component.min_value is not None else None
|
|
638
653
|
max_value = str(component.max_value) if component.max_value is not None else None
|
|
639
654
|
this_cell = Cell(
|
|
640
|
-
column_id=
|
|
655
|
+
column_id=column_id,
|
|
641
656
|
row_id=row_id,
|
|
642
657
|
value=value,
|
|
643
658
|
calculation="",
|
|
@@ -651,48 +666,70 @@ class Sheet(BaseSessionResource): # noqa:F811
|
|
|
651
666
|
all_cells.append(this_cell)
|
|
652
667
|
|
|
653
668
|
self.update_cells(cells=all_cells)
|
|
654
|
-
return self.get_column(column_id=
|
|
669
|
+
return self.get_column(column_id=column_id)
|
|
655
670
|
|
|
656
671
|
def _get_row_id_for_component(
|
|
657
|
-
self,
|
|
672
|
+
self,
|
|
673
|
+
*,
|
|
674
|
+
inventory_id: InventoryId,
|
|
675
|
+
existing_cells,
|
|
676
|
+
enforce_order,
|
|
677
|
+
product_rows: list["Row"],
|
|
658
678
|
):
|
|
659
|
-
self.grid = None
|
|
660
|
-
|
|
661
|
-
# within a sheet, the "INV" prefix is dropped
|
|
662
679
|
sheet_inv_id = inventory_id
|
|
663
|
-
matching_rows = [
|
|
680
|
+
matching_rows = [row for row in product_rows if row.inventory_id == sheet_inv_id]
|
|
681
|
+
|
|
682
|
+
used_row_ids = [cell.row_id for cell in existing_cells]
|
|
664
683
|
|
|
665
|
-
|
|
684
|
+
existing_inv_order: list[str] = []
|
|
685
|
+
index_last_row = 0
|
|
666
686
|
if enforce_order:
|
|
667
687
|
existing_inv_order = [
|
|
668
|
-
|
|
688
|
+
row.row_id for row in product_rows if row.inventory_id is not None
|
|
669
689
|
]
|
|
670
|
-
index_last_row = 0
|
|
671
690
|
for row_id in used_row_ids:
|
|
672
691
|
if row_id in existing_inv_order:
|
|
673
692
|
this_row_index = existing_inv_order.index(row_id)
|
|
674
693
|
if this_row_index > index_last_row:
|
|
675
694
|
index_last_row = this_row_index
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
695
|
+
|
|
696
|
+
for row in matching_rows:
|
|
697
|
+
if row.row_id in used_row_ids:
|
|
698
|
+
continue
|
|
699
|
+
if not enforce_order:
|
|
700
|
+
return row.row_id
|
|
701
|
+
|
|
702
|
+
if row.row_id in existing_inv_order:
|
|
703
|
+
if existing_inv_order.index(row.row_id) >= index_last_row:
|
|
704
|
+
return row.row_id
|
|
705
|
+
continue
|
|
706
|
+
|
|
686
707
|
if enforce_order:
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
"position": "below",
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
708
|
+
if existing_inv_order:
|
|
709
|
+
reference_row_id = existing_inv_order[index_last_row]
|
|
710
|
+
new_row = self.add_inventory_row(
|
|
711
|
+
inventory_id=inventory_id,
|
|
712
|
+
position={"reference_id": reference_row_id, "position": "below"},
|
|
713
|
+
)
|
|
714
|
+
|
|
715
|
+
insert_position = None
|
|
716
|
+
for idx, row in enumerate(product_rows):
|
|
717
|
+
if row.row_id == reference_row_id:
|
|
718
|
+
insert_position = idx + 1
|
|
719
|
+
break
|
|
720
|
+
if insert_position is None:
|
|
721
|
+
product_rows.append(new_row)
|
|
722
|
+
else:
|
|
723
|
+
product_rows.insert(insert_position, new_row)
|
|
724
|
+
return new_row.row_id
|
|
725
|
+
|
|
726
|
+
new_row = self.add_inventory_row(inventory_id=inventory_id)
|
|
727
|
+
product_rows.append(new_row)
|
|
728
|
+
return new_row.row_id
|
|
729
|
+
|
|
730
|
+
new_row = self.add_inventory_row(inventory_id=inventory_id)
|
|
731
|
+
product_rows.append(new_row)
|
|
732
|
+
return new_row.row_id
|
|
696
733
|
|
|
697
734
|
def add_formulation_columns(
|
|
698
735
|
self,
|
|
@@ -816,69 +853,79 @@ class Sheet(BaseSessionResource): # noqa:F811
|
|
|
816
853
|
return (updated, failed)
|
|
817
854
|
|
|
818
855
|
def _get_current_cell(self, *, cell: Cell) -> Cell:
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
856
|
+
def _matches_column(column_label: str) -> bool:
|
|
857
|
+
col_parts = column_label.split("#", 1)
|
|
858
|
+
return col_parts[0] == cell.column_id
|
|
859
|
+
|
|
860
|
+
def _matches_row(index_label: str) -> bool:
|
|
861
|
+
row_parts = index_label.split("#", 2)
|
|
862
|
+
if len(row_parts) < 2:
|
|
863
|
+
return False
|
|
864
|
+
return row_parts[0] == cell.design_id and row_parts[1] == cell.row_id
|
|
865
|
+
|
|
866
|
+
filtered_columns = [col for col in self.grid.columns if _matches_column(col)]
|
|
867
|
+
filtered_rows = [idx for idx in self.grid.index if _matches_row(idx)]
|
|
825
868
|
|
|
826
|
-
first_value = None
|
|
827
869
|
for row in filtered_rows:
|
|
828
870
|
for col in filtered_columns:
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
return first_value
|
|
871
|
+
return self.grid.loc[row, col]
|
|
872
|
+
return None
|
|
832
873
|
|
|
833
|
-
def _generate_attribute_change(
|
|
874
|
+
def _generate_attribute_change(
|
|
875
|
+
self,
|
|
876
|
+
*,
|
|
877
|
+
new_value: CellAttributeValue,
|
|
878
|
+
old_value: CellAttributeValue,
|
|
879
|
+
api_attribute_name: str,
|
|
880
|
+
) -> PatchDatum | None:
|
|
834
881
|
"""Generates a change dictionary for a single attribute."""
|
|
835
882
|
if new_value == old_value:
|
|
836
883
|
return None
|
|
837
884
|
|
|
838
885
|
if new_value is None or new_value in ("", {}):
|
|
839
|
-
return
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
886
|
+
return PatchDatum(
|
|
887
|
+
operation="delete",
|
|
888
|
+
attribute=api_attribute_name,
|
|
889
|
+
old_value=old_value,
|
|
890
|
+
)
|
|
844
891
|
if old_value is None or old_value in ("", {}):
|
|
845
|
-
return
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
return
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
892
|
+
return PatchDatum(
|
|
893
|
+
operation="add",
|
|
894
|
+
attribute=api_attribute_name,
|
|
895
|
+
new_value=new_value,
|
|
896
|
+
)
|
|
897
|
+
return PatchDatum(
|
|
898
|
+
operation="update",
|
|
899
|
+
attribute=api_attribute_name,
|
|
900
|
+
old_value=old_value,
|
|
901
|
+
new_value=new_value,
|
|
902
|
+
)
|
|
856
903
|
|
|
857
|
-
def _get_cell_changes(self, *, cell: Cell) ->
|
|
904
|
+
def _get_cell_changes(self, *, cell: Cell) -> CellChangePayload | None:
|
|
858
905
|
current_cell = self._get_current_cell(cell=cell)
|
|
859
906
|
if current_cell is None:
|
|
860
907
|
return None
|
|
861
908
|
|
|
862
|
-
data = []
|
|
909
|
+
data: list[PatchDatum] = []
|
|
863
910
|
|
|
864
911
|
# Handle format change
|
|
865
912
|
if cell.format != current_cell.format:
|
|
866
913
|
if cell.format is None or cell.format == {}:
|
|
867
914
|
data.append(
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
915
|
+
PatchDatum(
|
|
916
|
+
operation="delete",
|
|
917
|
+
attribute="cellFormat",
|
|
918
|
+
old_value=current_cell.format,
|
|
919
|
+
)
|
|
873
920
|
)
|
|
874
921
|
else:
|
|
875
922
|
data.append(
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
923
|
+
PatchDatum(
|
|
924
|
+
operation="update",
|
|
925
|
+
attribute="cellFormat",
|
|
926
|
+
old_value=current_cell.format,
|
|
927
|
+
new_value=cell.format,
|
|
928
|
+
)
|
|
882
929
|
)
|
|
883
930
|
|
|
884
931
|
# Handle calculation change
|
|
@@ -937,9 +984,9 @@ class Sheet(BaseSessionResource): # noqa:F811
|
|
|
937
984
|
return False
|
|
938
985
|
|
|
939
986
|
def update_cells(self, *, cells: list[Cell]):
|
|
940
|
-
request_path_dict = {}
|
|
941
|
-
updated = []
|
|
942
|
-
failed = []
|
|
987
|
+
request_path_dict: dict[str, list[Cell]] = {}
|
|
988
|
+
updated: list[Cell] = []
|
|
989
|
+
failed: list[Cell] = []
|
|
943
990
|
# sort by design ID
|
|
944
991
|
for c in cells:
|
|
945
992
|
if c.design_id not in request_path_dict:
|
|
@@ -948,57 +995,89 @@ class Sheet(BaseSessionResource): # noqa:F811
|
|
|
948
995
|
request_path_dict[c.design_id].append(c)
|
|
949
996
|
|
|
950
997
|
for design_id, cell_list in request_path_dict.items():
|
|
951
|
-
|
|
998
|
+
payload_entries: list[tuple[CellChangePayload, Cell]] = []
|
|
952
999
|
for cell in cell_list:
|
|
953
1000
|
change_dict = self._get_cell_changes(cell=cell)
|
|
954
|
-
if change_dict is
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
if not
|
|
1001
|
+
if change_dict is None:
|
|
1002
|
+
continue
|
|
1003
|
+
|
|
1004
|
+
is_calculation_cell = cell.calculation is not None and cell.calculation != ""
|
|
1005
|
+
max_items = 2 if is_calculation_cell else 1
|
|
1006
|
+
|
|
1007
|
+
if len(change_dict["data"]) > max_items:
|
|
1008
|
+
for item in change_dict["data"]:
|
|
1009
|
+
single_change: CellChangePayload = {
|
|
1010
|
+
"Id": change_dict["Id"],
|
|
1011
|
+
"data": [item],
|
|
1012
|
+
}
|
|
1013
|
+
payload_entries.append((single_change, cell))
|
|
1014
|
+
else:
|
|
1015
|
+
payload_entries.append((change_dict, cell))
|
|
1016
|
+
|
|
1017
|
+
if not payload_entries:
|
|
971
1018
|
continue
|
|
972
1019
|
|
|
973
1020
|
this_url = f"/api/v3/worksheet/{design_id}/values"
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1021
|
+
pending_by_cell: dict[tuple[str, str], list[tuple[CellChangePayload, Cell]]] = {}
|
|
1022
|
+
for payload, cell in payload_entries:
|
|
1023
|
+
key = (payload["Id"]["rowId"], payload["Id"]["colId"])
|
|
1024
|
+
pending_by_cell.setdefault(key, []).append((payload, cell))
|
|
1025
|
+
|
|
1026
|
+
ordered_keys = list(pending_by_cell.keys())
|
|
1027
|
+
|
|
1028
|
+
def _unique_cells(cells: list[Cell]) -> list[Cell]:
|
|
1029
|
+
seen: set[tuple[str, str, str]] = set()
|
|
1030
|
+
result: list[Cell] = []
|
|
1031
|
+
for c in cells:
|
|
1032
|
+
key = (c.design_id, c.row_id, c.column_id)
|
|
1033
|
+
if key not in seen:
|
|
1034
|
+
seen.add(key)
|
|
1035
|
+
result.append(c)
|
|
1036
|
+
return result
|
|
1037
|
+
|
|
1038
|
+
batch_index = 0
|
|
1039
|
+
while True:
|
|
1040
|
+
batch_payloads: list[CellChangePayload] = []
|
|
1041
|
+
batch_cells: list[Cell] = []
|
|
1042
|
+
for key in ordered_keys:
|
|
1043
|
+
queue = pending_by_cell.get(key)
|
|
1044
|
+
if queue:
|
|
1045
|
+
payload, cell = queue.pop(0)
|
|
1046
|
+
batch_payloads.append(payload)
|
|
1047
|
+
batch_cells.append(cell)
|
|
1048
|
+
if not batch_payloads:
|
|
1049
|
+
break
|
|
1050
|
+
|
|
1051
|
+
payload_body = [
|
|
1052
|
+
{
|
|
1053
|
+
"Id": payload["Id"],
|
|
1054
|
+
"data": [datum.model_dump(by_alias=True) for datum in payload["data"]],
|
|
1055
|
+
}
|
|
1056
|
+
for payload in batch_payloads
|
|
1057
|
+
]
|
|
1058
|
+
response = self.session.patch(this_url, json=payload_body)
|
|
1059
|
+
target_cells = _unique_cells(batch_cells)
|
|
989
1060
|
|
|
990
1061
|
if response.status_code == 204:
|
|
991
|
-
|
|
992
|
-
updated
|
|
1062
|
+
for c in target_cells:
|
|
1063
|
+
if c not in updated:
|
|
1064
|
+
updated.append(c)
|
|
993
1065
|
elif response.status_code == 206:
|
|
994
1066
|
cell_results = self._filter_cells(
|
|
995
|
-
cells=
|
|
1067
|
+
cells=target_cells, response_dict=response.json()
|
|
996
1068
|
)
|
|
997
|
-
|
|
998
|
-
|
|
1069
|
+
for c in cell_results[0]:
|
|
1070
|
+
if c not in updated:
|
|
1071
|
+
updated.append(c)
|
|
1072
|
+
for c in cell_results[1]:
|
|
1073
|
+
if c not in failed:
|
|
1074
|
+
failed.append(c)
|
|
999
1075
|
else:
|
|
1000
|
-
|
|
1001
|
-
failed
|
|
1076
|
+
for c in target_cells:
|
|
1077
|
+
if c not in failed:
|
|
1078
|
+
failed.append(c)
|
|
1079
|
+
|
|
1080
|
+
batch_index += 1
|
|
1002
1081
|
|
|
1003
1082
|
# reset the in-memory grid after updates
|
|
1004
1083
|
self.grid = None
|
|
@@ -144,7 +144,6 @@ def test_inventory_update(client: Albert, seed_prefix: str):
|
|
|
144
144
|
company="",
|
|
145
145
|
)
|
|
146
146
|
created = client.inventory.create(inventory_item=ii)
|
|
147
|
-
|
|
148
147
|
# Give time for the DB to sync - somewhere between 1 and 4 seconds is needed
|
|
149
148
|
# for this test to work
|
|
150
149
|
time.sleep(4)
|