OperonDBS 0.8.3__tar.gz → 0.8.4__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.
- {operondbs-0.8.3 → operondbs-0.8.4}/.github/workflows/publish.yml +42 -1
- {operondbs-0.8.3 → operondbs-0.8.4}/AGENTS.md +21 -9
- {operondbs-0.8.3 → operondbs-0.8.4}/OperonDBS.egg-info/PKG-INFO +1 -1
- {operondbs-0.8.3 → operondbs-0.8.4}/PKG-INFO +1 -1
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/contributor/development-testing.md +22 -9
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/contributor/pypi-release.md +21 -3
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/contributor/development-testing.md +19 -9
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/contributor/pypi-release.md +17 -3
- {operondbs-0.8.3 → operondbs-0.8.4}/pyproject.toml +1 -1
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_docs_projects.py +38 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_tui_config.py +54 -26
- {operondbs-0.8.3 → operondbs-0.8.4}/.github/workflows/test.yml +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/.gitignore +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/LICENSE +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/MANIFEST.in +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/OperonDBS.egg-info/SOURCES.txt +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/OperonDBS.egg-info/dependency_links.txt +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/OperonDBS.egg-info/entry_points.txt +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/OperonDBS.egg-info/requires.txt +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/OperonDBS.egg-info/top_level.txt +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/README.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/README_ZH.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/benchmarks/qc_representative_entities.tsv +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/conf_common.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/.readthedocs.yaml +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/architecture/extensibility.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/architecture/external-analysis.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/architecture/files-and-storage.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/architecture/index.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/architecture/metadata-and-data-model.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/architecture/overview.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/architecture/qc-and-rules.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/architecture/release-lifecycle.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/architecture/taxonomy-coverage.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/conf.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/contributor/defect-tracking.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/contributor/documentation-deployment.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/contributor/index.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/contributor/repository-guide.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/getting-started/daily-workflow.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/getting-started/first-project.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/getting-started/index.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/getting-started/installation.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/getting-started/quickstart.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/guides/backup-migration.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/guides/curation-lifecycle.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/guides/external-analysis.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/guides/file-archiving.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/guides/index.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/guides/metadata-import.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/guides/ncbi-datasets.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/guides/phylogenetics-workflow.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/guides/qc-profiles.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/guides/remote-execution.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/guides/remote-first.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/guides/remote-storage.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/guides/taxonomy-coverage.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/guides/troubleshooting.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/index.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/operations/database-compatibility.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/operations/index.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/operations/ncbi-recovery-migration.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/operations/qc-performance.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/overview.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/reference/behaviors-and-limitations.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/reference/cli-analysis.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/reference/cli-decisions-reports.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/reference/cli-files-qc.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/reference/cli-project-metadata.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/reference/cli-remote.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/reference/cli-taxonomy-lifecycle-admin.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/reference/cli-tui.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/reference/cli-workflow.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/reference/data-model.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/reference/index.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/reference/recipe-fields.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/reference/recipe-overview.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/reference/recipe-parsers-examples.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/en/reference/resolved-issues.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/locales/README.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/requirements.txt +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/.readthedocs.yaml +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/architecture/extensibility.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/architecture/external-analysis.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/architecture/files-and-storage.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/architecture/index.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/architecture/metadata-and-data-model.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/architecture/overview.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/architecture/qc-and-rules.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/architecture/release-lifecycle.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/architecture/taxonomy-coverage.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/conf.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/contributor/defect-tracking.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/contributor/documentation-deployment.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/contributor/index.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/contributor/repository-guide.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/getting-started/daily-workflow.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/getting-started/first-project.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/getting-started/index.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/getting-started/installation.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/getting-started/quickstart.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/guides/backup-migration.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/guides/curation-lifecycle.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/guides/external-analysis.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/guides/file-archiving.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/guides/index.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/guides/metadata-import.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/guides/ncbi-datasets.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/guides/phylogenetics-workflow.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/guides/qc-profiles.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/guides/remote-execution.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/guides/remote-first.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/guides/remote-storage.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/guides/taxonomy-coverage.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/guides/troubleshooting.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/index.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/operations/database-compatibility.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/operations/index.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/operations/ncbi-recovery-migration.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/operations/qc-performance.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/overview.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/reference/behaviors-and-limitations.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/reference/cli-analysis.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/reference/cli-decisions-reports.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/reference/cli-files-qc.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/reference/cli-project-metadata.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/reference/cli-remote.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/reference/cli-taxonomy-lifecycle-admin.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/reference/cli-tui.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/reference/cli-workflow.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/reference/data-model.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/reference/index.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/reference/recipe-fields.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/reference/recipe-overview.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/reference/recipe-parsers-examples.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/docs/zh/reference/resolved-issues.md +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/__init__.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/__main__.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/adapters/__init__.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/adapters/ncbi_datasets.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/adapters/timetree.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/backup.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/classify.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/cli.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/config.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/coverage.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/database.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/demo.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/entity_view.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/environment.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/environment_capture.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/errors.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/execution.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/export.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/fanout.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/files.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/import_wizard.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/lifecycle.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/lineage.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/metadata_files.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/ncbi_reconcile.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/profiles.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/qc/__init__.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/qc/_alignment.pyx +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/qc/_parsers.pyx +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/qc/alignment.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/qc/measure.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/qc/parsers.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/release.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/remotes.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/reports.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/rules.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/schema.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/sequence_tools.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/shutdown.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/sql.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/table_import.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/taxonomy.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/timetree.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/tools.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/tui/__init__.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/tui/actions.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/tui/app.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/tui/app.tcss +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/tui/assets/splash.png +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/tui/assets/splash.rgb.z +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/tui/data.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/tui/parity.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/tui/screens/__init__.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/tui/screens/analyze.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/tui/screens/classify.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/tui/screens/common.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/tui/screens/config.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/tui/screens/config_classification.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/tui/screens/coverage.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/tui/screens/decisions.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/tui/screens/derived_ops.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/tui/screens/entities.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/tui/screens/environments.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/tui/screens/files.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/tui/screens/files_ops.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/tui/screens/hits.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/tui/screens/home.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/tui/screens/import_wizard.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/tui/screens/labels.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/tui/screens/publish.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/tui/screens/run_external.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/tui/screens/runs.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/tui/splash.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/tui/splash_terminal.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/utils.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/operon/workflow.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/setup.cfg +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/setup.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/__init__.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/compatibility/__init__.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/compatibility/test_python_support.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/helpers.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/integration/__init__.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/integration/test_analysis_array.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/integration/test_analysis_resume.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/integration/test_analysis_shutdown.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/integration/test_analysis_tools.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/integration/test_classify_sequences.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/integration/test_execution_backends.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/integration/test_fanout_analyze.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/integration/test_lineage_cascade.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/integration/test_ncbi_datasets_adapter.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/integration/test_pipeline_and_release.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/integration/test_python_packaging.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/integration/test_qc_remote_measure.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/integration/test_taxonomy_coverage.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/regression/__init__.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/regression/test_correctness.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/regression/test_cython_alignment_parity.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/regression/test_cython_parser_parity.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/regression/test_parser_semantics.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/regression/test_sql_identifier_security.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/__init__.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_alignment_qc.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_classify.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_cli_edges.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_config_workflow_edges.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_core_coverage_more.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_coverage_edges.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_database_edges_more.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_defect_registry.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_defects_cli.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_docs_versions.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_environment.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_environment_capture.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_execution.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_execution_edges.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_export.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_fanout.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_files_edges.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_follow.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_import_backup_show.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_import_wizard_edges.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_lifecycle.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_lineage.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_misc_coverage_more.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_ncbi_coverage_gaps.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_ncbi_edge_cases.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_ncbi_reconcile_edges.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_parser_edge_paths.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_qc_and_rules.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_qc_edges_more.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_qc_measure.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_qc_remote_only.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_recipe_history.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_remote_taxonomy_more.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_remote_transport_edges.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_remotes.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_remotes_edges.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_rules_schema_edges.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_schema_2_10.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_schema_2_11.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_schema_2_9.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_schema_and_metadata.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_sequence_tools.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_sequences.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_shutdown.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_support_edges.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_table_import_edges.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_taxonomy_edges.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_timetree.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_timetree_snapshots.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_tools_edges.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_tools_execution_more.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_tui.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_tui_analyze.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_tui_cli_parity.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_tui_derived_ops.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_tui_flows_more.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_tui_m3_loop.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_tui_publish.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_tui_screens_more.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_tui_splash_terminal.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_tui_writes.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_views_release_reports_edges.py +0 -0
- {operondbs-0.8.3 → operondbs-0.8.4}/tests/unit/test_workflow_cli.py +0 -0
|
@@ -8,7 +8,46 @@ permissions:
|
|
|
8
8
|
contents: read
|
|
9
9
|
|
|
10
10
|
jobs:
|
|
11
|
+
verify-release:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
permissions:
|
|
14
|
+
contents: read
|
|
15
|
+
actions: read
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v6
|
|
18
|
+
with:
|
|
19
|
+
ref: ${{ github.event.release.tag_name }}
|
|
20
|
+
fetch-depth: 0
|
|
21
|
+
persist-credentials: false
|
|
22
|
+
- uses: actions/setup-python@v7
|
|
23
|
+
with:
|
|
24
|
+
python-version: "3.12"
|
|
25
|
+
- run: python -m pip install --upgrade pip pyyaml
|
|
26
|
+
- name: The tagged commit passed its own CI run
|
|
27
|
+
env:
|
|
28
|
+
GH_TOKEN: ${{ github.token }}
|
|
29
|
+
RELEASE_TAG: ${{ github.event.release.tag_name }}
|
|
30
|
+
run: |
|
|
31
|
+
sha=$(git rev-parse HEAD)
|
|
32
|
+
conclusion=$(gh api \
|
|
33
|
+
"repos/${{ github.repository }}/actions/workflows/test.yml/runs?head_sha=$sha&per_page=1" \
|
|
34
|
+
--jq '.workflow_runs[0].conclusion // "no run"')
|
|
35
|
+
echo "$RELEASE_TAG -> $sha: test.yml concluded '$conclusion'"
|
|
36
|
+
if [ "$conclusion" != "success" ]; then
|
|
37
|
+
echo "::error::test.yml on $sha concluded '$conclusion'; rerun it (gh run rerun) before releasing"
|
|
38
|
+
exit 1
|
|
39
|
+
fi
|
|
40
|
+
- name: Import the maintainer's published GPG key
|
|
41
|
+
run: |
|
|
42
|
+
curl -sSL "https://github.com/${{ github.repository_owner }}.gpg" | gpg --import
|
|
43
|
+
gpg --list-keys --with-colons | grep '^pub' | wc -l
|
|
44
|
+
- name: Release preflight
|
|
45
|
+
env:
|
|
46
|
+
RELEASE_TAG: ${{ github.event.release.tag_name }}
|
|
47
|
+
run: scripts/release-preflight.sh --ci --tag "$RELEASE_TAG"
|
|
48
|
+
|
|
11
49
|
source-distribution:
|
|
50
|
+
needs: verify-release
|
|
12
51
|
runs-on: ubuntu-latest
|
|
13
52
|
steps:
|
|
14
53
|
- uses: actions/checkout@v6
|
|
@@ -35,6 +74,7 @@ jobs:
|
|
|
35
74
|
if-no-files-found: error
|
|
36
75
|
|
|
37
76
|
documentation:
|
|
77
|
+
needs: verify-release
|
|
38
78
|
runs-on: ubuntu-latest
|
|
39
79
|
steps:
|
|
40
80
|
- uses: actions/checkout@v6
|
|
@@ -48,6 +88,7 @@ jobs:
|
|
|
48
88
|
sphinx-build -W --keep-going -b html docs/zh docs/_build/zh/html
|
|
49
89
|
|
|
50
90
|
wheels:
|
|
91
|
+
needs: verify-release
|
|
51
92
|
strategy:
|
|
52
93
|
fail-fast: false
|
|
53
94
|
matrix:
|
|
@@ -86,7 +127,7 @@ jobs:
|
|
|
86
127
|
if-no-files-found: error
|
|
87
128
|
|
|
88
129
|
pypi-publish:
|
|
89
|
-
needs: [source-distribution, wheels]
|
|
130
|
+
needs: [verify-release, source-distribution, wheels]
|
|
90
131
|
runs-on: ubuntu-latest
|
|
91
132
|
environment:
|
|
92
133
|
name: pypi
|
|
@@ -26,7 +26,7 @@ principle-to-implementation mapping.
|
|
|
26
26
|
|
|
27
27
|
Current version markers (must stay consistent across code and docs):
|
|
28
28
|
|
|
29
|
-
- `operon` 0.8.
|
|
29
|
+
- `operon` 0.8.4 (`pyproject.toml`)
|
|
30
30
|
- database schema 2.11 (`operon/database.py`, `SCHEMA_VERSION`)
|
|
31
31
|
- metadata schema 1.4 (`operon/schema.py`, `METADATA_SCHEMA_VERSION`)
|
|
32
32
|
|
|
@@ -171,10 +171,11 @@ The project is licensed AGPL-3.0-or-later (`LICENSE` at the repo root).
|
|
|
171
171
|
- `benchmarks/` — representative entity sets for QC performance diagnostics
|
|
172
172
|
(see `docs/*/operations/qc-performance.md`).
|
|
173
173
|
- `scripts/` — local developer tooling; `setup-test-matrix.sh` and
|
|
174
|
-
`run-test-matrix.sh` build and drive the uv-managed Python 3.10–3.
|
|
174
|
+
`run-test-matrix.sh` build and drive the uv-managed Python 3.10–3.15
|
|
175
175
|
matrix under .matrix/ (see `docs/*/contributor/development-testing.md`);
|
|
176
|
-
`
|
|
177
|
-
|
|
176
|
+
`release-preflight.sh` is the release gate and the script the publish
|
|
177
|
+
workflow runs on the tag; `defects.sh` appends to and queries the defect
|
|
178
|
+
registry (`defects.yml`, see "Defect reports" below).
|
|
178
179
|
|
|
179
180
|
## Setup, test, and build
|
|
180
181
|
|
|
@@ -193,7 +194,13 @@ python -m pytest tests/unit # by category: unit / integration /
|
|
|
193
194
|
|
|
194
195
|
python -m pytest --lf -q --no-cov # iterate: last failures, no coverage
|
|
195
196
|
python -m pytest -n 4 --dist loadfile # parallel full suite (xdist, in the dev/test extras)
|
|
196
|
-
scripts/
|
|
197
|
+
scripts/setup-test-matrix.sh # once per machine: uv-managed 3.10-3.15 venvs
|
|
198
|
+
scripts/run-test-matrix.sh # whole suite on 3.10-3.15, in waves
|
|
199
|
+
# (MATRIX_CONCURRENCY / MATRIX_JOBS override the split)
|
|
200
|
+
scripts/release-preflight.sh # the release gate: version, full suite, both doc
|
|
201
|
+
# trees, defect registry, matrix evidence for HEAD;
|
|
202
|
+
# --run-matrix runs the matrix inside the gate, and
|
|
203
|
+
# --tag vX.Y.Z asserts the tag's name/signature/target
|
|
197
204
|
|
|
198
205
|
python setup.py build_ext --inplace # rebuild only the Cython extension
|
|
199
206
|
|
|
@@ -205,14 +212,19 @@ Run the relevant test category after any change; run the full suite before
|
|
|
205
212
|
considering work done.
|
|
206
213
|
|
|
207
214
|
CI (`.github/workflows/test.yml`) runs pytest on Python 3.10–3.15 and the strict
|
|
208
|
-
Sphinx build. Releases are published exclusively to PyPI
|
|
215
|
+
Sphinx build. Releases are published exclusively to PyPI, and
|
|
216
|
+
`.github/workflows/publish.yml` builds nothing and uploads nothing until its
|
|
217
|
+
`verify-release` job has seen a `success` conclusion for the tagged commit's `test`
|
|
218
|
+
run *and* a passing `scripts/release-preflight.sh --ci --tag <tag>`; see
|
|
219
|
+
`docs/*/contributor/pypi-release.md`.
|
|
209
220
|
|
|
210
221
|
The project uses `pytest-xdist` to parallel testing. Avoid sharing state between tests
|
|
211
222
|
to prevent unexpected or random test results.
|
|
212
223
|
|
|
213
224
|
> Running `python -m pytest` directly takes approx 35 seconds
|
|
214
|
-
> (auto: 24 workers). Running the local test matrix takes
|
|
215
|
-
> (6
|
|
225
|
+
> (auto: 24 workers). Running the local test matrix takes about 5 minutes
|
|
226
|
+
> (6 versions, 3 at a time × 7 workers by default; `MATRIX_CONCURRENCY` /
|
|
227
|
+
> `MATRIX_JOBS` override the split).
|
|
216
228
|
>
|
|
217
229
|
> Measured on Intel Core i7-13700HX (16c24t).
|
|
218
230
|
|
|
@@ -303,7 +315,7 @@ change:
|
|
|
303
315
|
`docs/<language>/.readthedocs.yaml`, and the registry in
|
|
304
316
|
`tests/unit/test_docs_projects.py`
|
|
305
317
|
|
|
306
|
-
Version markers in docs (`operon` 0.8.
|
|
318
|
+
Version markers in docs (`operon` 0.8.4, database schema 2.11, metadata
|
|
307
319
|
schema 1.4) must match `pyproject.toml` and the code. Do not write the
|
|
308
320
|
current values literally in Markdown sources: use the `myst_substitutions`
|
|
309
321
|
references `{{ operon_version }}`, `{{ db_schema }}`, and
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: OperonDBS
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.4
|
|
4
4
|
Summary: Operon the Database System: Archive, Quality-Control, Organize, Analyze and Release Your Bio-Data
|
|
5
5
|
Author-email: hyli360 <lihuanyu2003@gmail.com>
|
|
6
6
|
License-Expression: AGPL-3.0-or-later
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: OperonDBS
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.4
|
|
4
4
|
Summary: Operon the Database System: Archive, Quality-Control, Organize, Analyze and Release Your Bio-Data
|
|
5
5
|
Author-email: hyli360 <lihuanyu2003@gmail.com>
|
|
6
6
|
License-Expression: AGPL-3.0-or-later
|
|
@@ -14,7 +14,8 @@ python -m pytest tests/integration
|
|
|
14
14
|
python -m pytest tests/regression tests/compatibility
|
|
15
15
|
|
|
16
16
|
# build the Sphinx documentation strictly
|
|
17
|
-
sphinx-build -W --keep-going -b html docs docs/_build/html
|
|
17
|
+
sphinx-build -W --keep-going -b html docs/en docs/_build/en/html
|
|
18
|
+
sphinx-build -W --keep-going -b html docs/zh docs/_build/zh/html
|
|
18
19
|
```
|
|
19
20
|
|
|
20
21
|
The pytest suite is organized into four categories — `unit`, `integration`, `regression`, `compatibility` — covering:
|
|
@@ -67,18 +68,30 @@ The full suite takes about seven minutes serially; the loop below aims to run it
|
|
|
67
68
|
is assigned to only one worker, which is required for Textual UI testing.
|
|
68
69
|
Coverages are the most resource-intensive: use `--no-cov` during iterations and run the test only once at the end.
|
|
69
70
|
4. **Cross-version matrix in one pass.** `scripts/setup-test-matrix.sh` creates uv-managed CPython 3.10-3.15
|
|
70
|
-
interpreters plus one venv per version inside `.matrix/`. `scripts/run-test-matrix.sh` runs
|
|
71
|
+
interpreters plus one venv per version inside `.matrix/`. `scripts/run-test-matrix.sh` runs them
|
|
72
|
+
`MATRIX_CONCURRENCY` at a time (three by default, so six versions go out as two waves) with
|
|
73
|
+
`MATRIX_JOBS` xdist workers each; the defaults split the machine's cores over the concurrent
|
|
74
|
+
versions and leave one core free:
|
|
71
75
|
|
|
72
76
|
```bash
|
|
73
|
-
scripts/setup-test-matrix.sh
|
|
74
|
-
scripts/run-test-matrix.sh
|
|
75
|
-
scripts/run-test-matrix.sh tests/unit -x
|
|
76
|
-
MATRIX_JOBS=4 scripts/run-test-matrix.sh
|
|
77
|
+
scripts/setup-test-matrix.sh # once per machine
|
|
78
|
+
scripts/run-test-matrix.sh # whole suite, six versions, ~5 min on 24 threads
|
|
79
|
+
scripts/run-test-matrix.sh tests/unit -x # extra pytest arguments are forwarded
|
|
80
|
+
MATRIX_JOBS=4 scripts/run-test-matrix.sh # xdist workers per version
|
|
81
|
+
MATRIX_CONCURRENCY=2 scripts/run-test-matrix.sh # versions running at once
|
|
77
82
|
```
|
|
78
83
|
|
|
84
|
+
The matrix is six times one suite, so waves trade peak load and nothing else: the wall
|
|
85
|
+
clock follows the cores, not the grouping. What a Linux box cannot run is still CI's
|
|
86
|
+
half — `.github/workflows/test.yml` runs the same versions on macOS.
|
|
87
|
+
|
|
79
88
|
Each version logs to `.matrix/logs/<version>.log`, so a failure there can be re-run
|
|
80
|
-
directly with that interpreter
|
|
81
|
-
|
|
89
|
+
directly with that interpreter; the directory is wiped before a run starts, so the
|
|
90
|
+
leftovers of an earlier one never pass for the current run, and a second run refuses
|
|
91
|
+
to start (exit 2) while one is in flight rather than wipe the files it is still
|
|
92
|
+
reporting from. The run also records the commit it tested in `.matrix/logs/head`,
|
|
93
|
+
which is how `scripts/release-preflight.sh` refuses matrix evidence that belongs to
|
|
94
|
+
another commit. The scripts work on Linux and macOS. `.matrix/` is git-ignored.
|
|
82
95
|
5. **Platform-specific code cannot be verified by one matrix job alone.** Linux and macOS
|
|
83
96
|
differ in the system utilities the environment probe calls and in `pathlib`/`resource`
|
|
84
97
|
behaviour. Code guarded by `sys.platform`/`os.name`, or shelling out to system tools,
|
|
@@ -175,4 +188,4 @@ Software versions, database schema versions, and metadata schema versions stated
|
|
|
175
188
|
{{ operon_version }} {{ db_schema }} {{ metadata_schema }}
|
|
176
189
|
```
|
|
177
190
|
|
|
178
|
-
`docs/
|
|
191
|
+
`docs/conf_common.py` resolves them from `pyproject.toml` and the code constants at build time. Substitutions expand in paragraph text only, not inside code spans or fenced code blocks; examples there use `<version>` placeholders instead. Intentional historical pins stay literal: they either live on the allowlisted era-pinned pages under `docs/*/operations/` or carry an inline `<!-- version-pin -->` marker. `tests/unit/test_docs_versions.py` enforces the rule.
|
|
@@ -16,6 +16,14 @@ It validates that the release tag is exactly `v<project.version>`, then builds:
|
|
|
16
16
|
- CPython 3.10-3.14 macOS Intel wheels; and
|
|
17
17
|
- CPython 3.10-3.14 macOS Apple Silicon wheels.
|
|
18
18
|
|
|
19
|
+
A `verify-release` job runs before every other job, and they all need it: it
|
|
20
|
+
checks out the release tag and refuses to continue unless the `test` workflow's run
|
|
21
|
+
for that exact commit concluded `success` and `scripts/release-preflight.sh --ci
|
|
22
|
+
--tag <tag>` passes. It imports the maintainer's published GPG key first, so the
|
|
23
|
+
tag's signature is verified rather than merely present; without that key the gate
|
|
24
|
+
reports the check as skipped instead of failed. A tag on a commit whose CI is red — or cancelled before it ran
|
|
25
|
+
— therefore builds and uploads nothing.
|
|
26
|
+
|
|
19
27
|
Each wheel is installed in an isolated test environment before upload. The
|
|
20
28
|
test imports the compiled parser and invokes the CLI. The source distribution
|
|
21
29
|
is checked with Twine. The final publish job cannot start until all artifacts
|
|
@@ -31,13 +39,23 @@ manual approval before the final upload.
|
|
|
31
39
|
|
|
32
40
|
1. Update `[project].version`, plus `SCHEMA_VERSION` / `METADATA_SCHEMA_VERSION`
|
|
33
41
|
in the code when they change. Documentation version markers render from
|
|
34
|
-
these single sources through `myst_substitutions` in `docs/
|
|
42
|
+
these single sources through `myst_substitutions` in `docs/conf_common.py`, so no
|
|
35
43
|
manual sweep is needed; `tests/unit/test_docs_versions.py` rejects
|
|
36
44
|
hardcoded current versions in the Markdown sources.
|
|
37
|
-
2. Run the
|
|
45
|
+
2. Run `scripts/release-preflight.sh` — the release gate in one command, one exit
|
|
46
|
+
code: the full pytest suite, the strict documentation build for both language
|
|
47
|
+
trees, the version in `pyproject.toml` against the installed metadata, the defect
|
|
48
|
+
registry (every record shipping in this version is `verified`, names a
|
|
49
|
+
`fix_commit`, and that commit is in the tree), and the cross-version matrix
|
|
50
|
+
evidence for the commit you are about to tag (`--run-matrix` runs the matrix as
|
|
51
|
+
part of the gate; `--tag vX.Y.Z` additionally asserts the created tag is
|
|
52
|
+
annotated, GPG-signed, named `v<version>`, and pointing at `HEAD`). Nothing about
|
|
53
|
+
a release depends on remembering the individual commands.
|
|
38
54
|
3. Commit the release state and create tag `v<project.version>` on that exact
|
|
39
55
|
commit. Never reuse a tag that points to older package metadata.
|
|
40
|
-
4. Wait for the `
|
|
56
|
+
4. Wait for the `test` workflow on the tagged commit to pass. The publish
|
|
57
|
+
workflow refuses to build or upload while that run is missing, cancelled, or red;
|
|
58
|
+
give such a commit its own run with `gh run rerun <run-id>` and publish again.
|
|
41
59
|
5. Create a GitHub Release from that tag, initially as a draft if release notes
|
|
42
60
|
still need review, then publish it.
|
|
43
61
|
6. Verify the files and metadata on the `OperonDBS` PyPI project page.
|
|
@@ -13,7 +13,8 @@ python -m pytest tests/integration
|
|
|
13
13
|
python -m pytest tests/regression tests/compatibility
|
|
14
14
|
|
|
15
15
|
# 严格构建 Sphinx 文档
|
|
16
|
-
sphinx-build -W --keep-going -b html docs docs/_build/html
|
|
16
|
+
sphinx-build -W --keep-going -b html docs/en docs/_build/en/html
|
|
17
|
+
sphinx-build -W --keep-going -b html docs/zh docs/_build/zh/html
|
|
17
18
|
```
|
|
18
19
|
|
|
19
20
|
pytest 测试按 `unit`、`integration`、`regression`、`compatibility` 四类组织,覆盖:
|
|
@@ -61,17 +62,26 @@ F821 检查仍然启用。Markdown 制表符检查允许围栏代码块中的 TS
|
|
|
61
62
|
可将耗时压到 30~40 秒。`--dist loadfile` 保证同一个测试文件只落在一个
|
|
62
63
|
worker 上,这是 Textual UI 测试所需要的。覆盖率统计最贵:迭代时用 `--no-cov`,最后只测一次。
|
|
63
64
|
4. **一次跑完跨版本矩阵。** `scripts/setup-test-matrix.sh` 用 uv 安装 CPython 3.10–3.15 并在
|
|
64
|
-
`.matrix/` 下为每个版本建 venv。`scripts/run-test-matrix.sh`
|
|
65
|
+
`.matrix/` 下为每个版本建 venv。`scripts/run-test-matrix.sh` 每次并发 `MATRIX_CONCURRENCY`
|
|
66
|
+
个版本(默认 3 个,六个版本分两波),每个版本给 `MATRIX_JOBS` 个 xdist worker;默认值把机器
|
|
67
|
+
核心数摊到并发版本上,并留出一个核心:
|
|
65
68
|
|
|
66
69
|
```bash
|
|
67
|
-
scripts/setup-test-matrix.sh
|
|
68
|
-
scripts/run-test-matrix.sh
|
|
69
|
-
scripts/run-test-matrix.sh tests/unit -x
|
|
70
|
-
MATRIX_JOBS=4 scripts/run-test-matrix.sh
|
|
70
|
+
scripts/setup-test-matrix.sh # 每台机器执行一次
|
|
71
|
+
scripts/run-test-matrix.sh # 全量 × 六个版本,24 线程约 5 分钟
|
|
72
|
+
scripts/run-test-matrix.sh tests/unit -x # 其余 pytest 参数原样透传
|
|
73
|
+
MATRIX_JOBS=4 scripts/run-test-matrix.sh # 每个版本的 worker 数
|
|
74
|
+
MATRIX_CONCURRENCY=2 scripts/run-test-matrix.sh # 同时运行的版本数
|
|
71
75
|
```
|
|
72
76
|
|
|
73
|
-
|
|
74
|
-
|
|
77
|
+
矩阵相当于把全量测试跑六遍,所以分波只影响峰值占用,不改变总耗时——总耗时由核心数决定。
|
|
78
|
+
Linux 机器跑不到的那一半仍由 CI 覆盖(`.github/workflows/test.yml` 在 macOS 上跑同样版本)。
|
|
79
|
+
|
|
80
|
+
每个版本的日志在 `.matrix/logs/<version>.log`,失败后可直接用对应解释器重跑;日志目录在每次
|
|
81
|
+
运行前清空,因此不会把上一轮的残留误当成本轮结果,且同一时间只允许一个运行——第二个会以退出码 2
|
|
82
|
+
拒绝启动,而不是清空正在汇报的那一轮的日志。运行还会把被测提交写入
|
|
83
|
+
`.matrix/logs/head`,`scripts/release-preflight.sh` 据此拒绝把别的提交的矩阵结果当作
|
|
84
|
+
本次发布的证据。脚本在 Linux 与 macOS 上均可运行,`.matrix/` 已被 git 忽略。
|
|
75
85
|
5. **平台相关代码不能只靠单个矩阵任务验证。** Linux 与 macOS 在环境探测所调用的系统工具、以及
|
|
76
86
|
`pathlib`/`resource` 行为上都不同。凡是用 `sys.platform`/`os.name` 分支或调用系统工具的代码,
|
|
77
87
|
都应带一个能在 Linux 上复现对端平台条件的测试(例如不含 GNU `timeout` 的空 `PATH`),而不是
|
|
@@ -157,7 +167,7 @@ CLI 是规范面:新能力先落在 CLI 与核心函数,TUI 只调用同一
|
|
|
157
167
|
{{ operon_version }} {{ db_schema }} {{ metadata_schema }}
|
|
158
168
|
```
|
|
159
169
|
|
|
160
|
-
由 `docs/
|
|
170
|
+
由 `docs/conf_common.py` 在构建时从 `pyproject.toml` 与代码常量解析。替换只在正文段落中展开,行内代码与代码块内不生效,
|
|
161
171
|
这些位置的示例改用 `<version>` 占位符。有意保留的历史版本号保持字面量:要么位于 `docs/*/operations/`
|
|
162
172
|
下 allowlist 中的时代绑定页面,要么在行内附带 `<!-- version-pin -->` 标记。`tests/unit/test_docs_versions.py`
|
|
163
173
|
负责强制此规则。
|
|
@@ -16,6 +16,13 @@ tag 必须严格等于 `v<project.version>`,再构建:
|
|
|
16
16
|
- CPython 3.10-3.14 的 macOS Intel wheel;
|
|
17
17
|
- CPython 3.10-3.14 的 macOS Apple Silicon wheel。
|
|
18
18
|
|
|
19
|
+
`verify-release` job 会先于其他所有 job 运行,且它们都依赖它:它检出 release tag,
|
|
20
|
+
并断言"该提交的 `test` workflow 结论为 `success`"以及
|
|
21
|
+
`scripts/release-preflight.sh --ci --tag <tag>` 通过。它会先导入维护者公开的 GPG 公钥,
|
|
22
|
+
因此 tag 的签名是被**真正验证**过,而不只是"存在签名";若该公钥不可用,这一项报 `skip`
|
|
23
|
+
而不是 `fail`。因此,tag 落在 CI 为红(或压根
|
|
24
|
+
被取消、没跑完)的提交上时,什么都不会被构建,更不会被上传。
|
|
25
|
+
|
|
19
26
|
每个 wheel 都会先安装到隔离测试环境,导入编译后的 parser 并调用 CLI;源码分发包
|
|
20
27
|
通过 Twine 检查。最终发布 job 必须等待全部构建成功,并通过 PyPI Trusted
|
|
21
28
|
Publishing 认证,不保存长期 API token。
|
|
@@ -27,13 +34,20 @@ workflow 填 `publish.yml`,environment 填 `pypi`。GitHub environment 名称
|
|
|
27
34
|
## 发布步骤
|
|
28
35
|
|
|
29
36
|
1. 更新 `[project].version`;如有变化,同时更新代码中的 `SCHEMA_VERSION` /
|
|
30
|
-
`METADATA_SCHEMA_VERSION`。文档版本标记通过 `docs/
|
|
37
|
+
`METADATA_SCHEMA_VERSION`。文档版本标记通过 `docs/conf_common.py` 的
|
|
31
38
|
`myst_substitutions` 从这些唯一来源渲染,无需手工批量替换;
|
|
32
39
|
`tests/unit/test_docs_versions.py` 会拒绝 Markdown 源文件中硬编码的当前版本。
|
|
33
|
-
2.
|
|
40
|
+
2. 运行 `scripts/release-preflight.sh` —— 一条命令、一个退出码,就是发布闸门:
|
|
41
|
+
全量 pytest 套件、两棵树语言的严格文档构建、`pyproject.toml` 版本与已安装元数据的
|
|
42
|
+
一致性、缺陷登记库(凡是 `fixed_in` 为本版本的记录都必须 `verified`、带
|
|
43
|
+
`fix_commit`,且该提交包含在当前树中),以及"你即将打 tag 的那个提交"的跨版本矩阵
|
|
44
|
+
证据(`--run-matrix` 会把矩阵跑在闸门里;`--tag vX.Y.Z` 还会断言 create 出来的 tag
|
|
45
|
+
是附注、已 GPG 签名、名为 `v<version>` 且指向 `HEAD`)。发布不再依赖谁记得住这些
|
|
46
|
+
单个命令。
|
|
34
47
|
3. 提交发布状态,并让 `v<project.version>` tag 指向该提交;不要复用仍指向旧软件包
|
|
35
48
|
元数据的 tag。
|
|
36
|
-
4. 等待该 tag 对应提交的 `
|
|
49
|
+
4. 等待该 tag 对应提交的 `test` workflow 全部通过。若该 run 缺失、被取消或为红,
|
|
50
|
+
发布链路会拒绝构建与上传;先用 `gh run rerun <run-id>` 给它补一轮,再发布。
|
|
37
51
|
5. 从该 tag 创建 GitHub Release;如仍需检查发布说明可先保存为 draft,确认后发布。
|
|
38
52
|
6. 在 PyPI 的 `OperonDBS` 项目页核对文件与元数据。
|
|
39
53
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "OperonDBS"
|
|
7
|
-
version = "0.8.
|
|
7
|
+
version = "0.8.4"
|
|
8
8
|
description = "Operon the Database System: Archive, Quality-Control, Organize, Analyze and Release Your Bio-Data"
|
|
9
9
|
authors = [
|
|
10
10
|
{ name = "hyli360", email = "lihuanyu2003@gmail.com" }
|
|
@@ -16,6 +16,7 @@ extra installs no Sphinx, so the documentation must stay verifiable without it.
|
|
|
16
16
|
from __future__ import annotations
|
|
17
17
|
|
|
18
18
|
import ast
|
|
19
|
+
import os
|
|
19
20
|
from pathlib import Path
|
|
20
21
|
|
|
21
22
|
import pytest
|
|
@@ -186,3 +187,40 @@ def test_every_workflow_builds_both_language_trees():
|
|
|
186
187
|
f"{directories or ['docs']} — the pre-split source directory no "
|
|
187
188
|
"longer holds a conf.py"
|
|
188
189
|
)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def test_the_publish_workflow_gates_on_the_release_preflight() -> None:
|
|
193
|
+
"""A release builds and uploads only after the tagged commit proved itself.
|
|
194
|
+
|
|
195
|
+
The publish workflow used to build whatever the tag pointed at, so a tag on a
|
|
196
|
+
commit whose CI was red — or cancelled before it ever ran — reached PyPI anyway.
|
|
197
|
+
The gate is the ``verify-release`` job: it asserts the ``test`` workflow's
|
|
198
|
+
conclusion for that exact commit and then runs
|
|
199
|
+
``scripts/release-preflight.sh``, and every job that builds or uploads waits for
|
|
200
|
+
it. A release cannot exercise that path in a test, so this keeps it in place.
|
|
201
|
+
"""
|
|
202
|
+
|
|
203
|
+
preflight = REPO_ROOT / "scripts" / "release-preflight.sh"
|
|
204
|
+
assert preflight.is_file(), "the release preflight script is missing"
|
|
205
|
+
assert os.access(preflight, os.X_OK), f"{preflight} is not executable"
|
|
206
|
+
|
|
207
|
+
publish = yaml.safe_load(
|
|
208
|
+
(REPO_ROOT / ".github" / "workflows" / "publish.yml").read_text(encoding="utf-8")
|
|
209
|
+
)
|
|
210
|
+
jobs = publish["jobs"]
|
|
211
|
+
verify = jobs["verify-release"]
|
|
212
|
+
commands = "\n".join(
|
|
213
|
+
step.get("run", "") for step in verify["steps"] if isinstance(step, dict)
|
|
214
|
+
)
|
|
215
|
+
assert "release-preflight.sh" in commands, "verify-release does not run the preflight"
|
|
216
|
+
assert "test.yml" in commands and "conclusion" in commands, (
|
|
217
|
+
"verify-release does not assert the test workflow's conclusion for the tagged commit"
|
|
218
|
+
)
|
|
219
|
+
assert verify.get("permissions", {}).get("actions") == "read", (
|
|
220
|
+
"verify-release reads workflow runs, so it needs actions: read"
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
for job in ("source-distribution", "documentation", "wheels", "pypi-publish"):
|
|
224
|
+
needs = jobs[job]["needs"]
|
|
225
|
+
needs = [needs] if isinstance(needs, str) else list(needs)
|
|
226
|
+
assert "verify-release" in needs, f"{job} does not wait for verify-release"
|
|
@@ -137,7 +137,9 @@ async def _click(pilot, selector: str) -> None:
|
|
|
137
137
|
if isinstance(widget, Button) and widget.has_class("-active"):
|
|
138
138
|
await _wait_until(lambda: not widget.has_class("-active"),
|
|
139
139
|
f"{selector} to settle")
|
|
140
|
-
|
|
140
|
+
loop = asyncio.get_running_loop()
|
|
141
|
+
deadline = loop.time() + SETTLE_TIMEOUT
|
|
142
|
+
while True:
|
|
141
143
|
try:
|
|
142
144
|
landed = await pilot.click(selector)
|
|
143
145
|
except OutOfBounds:
|
|
@@ -148,9 +150,10 @@ async def _click(pilot, selector: str) -> None:
|
|
|
148
150
|
widget.press()
|
|
149
151
|
await pilot.pause()
|
|
150
152
|
return
|
|
153
|
+
if loop.time() > deadline:
|
|
154
|
+
raise AssertionError(f"click did not land on {selector}")
|
|
151
155
|
await pilot.pause()
|
|
152
156
|
await asyncio.sleep(0.02)
|
|
153
|
-
raise AssertionError(f"click did not land on {selector}")
|
|
154
157
|
|
|
155
158
|
|
|
156
159
|
# ---------------------------------------------------------------------------
|
|
@@ -572,6 +575,7 @@ def test_config_screen_profile_save_end_to_end(project: Project) -> None:
|
|
|
572
575
|
assert sorted(row["profile_version"] for row in rows) == [1, 2]
|
|
573
576
|
|
|
574
577
|
|
|
578
|
+
@pytest.mark.bug("ODR-0027")
|
|
575
579
|
def test_profile_editor_scrolls_to_all_rules(project: Project) -> None:
|
|
576
580
|
"""The rules editor must scroll: rule containers use height 1fr by default
|
|
577
581
|
(plain Vertical), which clipped the rules to a fixed non-scrolling window."""
|
|
@@ -581,8 +585,15 @@ def test_profile_editor_scrolls_to_all_rules(project: Project) -> None:
|
|
|
581
585
|
async with app.run_test(size=(160, 50)) as pilot:
|
|
582
586
|
panel = await _open_config(app, pilot)
|
|
583
587
|
_select_profile(panel, "assembly_production_v1")
|
|
584
|
-
await pilot
|
|
588
|
+
await _await_form_ready(pilot, panel)
|
|
585
589
|
editor = panel.query_one("#profile-editor", VerticalScroll)
|
|
590
|
+
# The rows are in the tree before the layout gives the editor its
|
|
591
|
+
# content height, so wait for the growth the test is about instead
|
|
592
|
+
# of measuring in the mounting turn (ODR-0027).
|
|
593
|
+
await _wait_until(
|
|
594
|
+
lambda: editor.virtual_size.height > editor.scrollable_content_region.height,
|
|
595
|
+
"the rules editor to grow past its viewport",
|
|
596
|
+
)
|
|
586
597
|
assert editor.virtual_size.height > editor.scrollable_content_region.height
|
|
587
598
|
assert editor.max_scroll_y > 0
|
|
588
599
|
editor.scroll_end(animate=False)
|
|
@@ -705,6 +716,20 @@ def test_config_screen_recipe_save_end_to_end(project: Project) -> None:
|
|
|
705
716
|
# ---------------------------------------------------------------------------
|
|
706
717
|
|
|
707
718
|
|
|
719
|
+
async def _await_notification(pilot, app, needle: str) -> None:
|
|
720
|
+
"""Wait until some raised notification's message contains ``needle``.
|
|
721
|
+
|
|
722
|
+
A save runs on a worker and raises its notification a message-loop turn
|
|
723
|
+
after the modal closes, so reading ``_notifications(app)`` right after a
|
|
724
|
+
single ``pause()`` sees the list before the text is in it — on a slow
|
|
725
|
+
runner that turns a correct save into a failure (ODR-0027).
|
|
726
|
+
"""
|
|
727
|
+
await _wait_until(
|
|
728
|
+
lambda: any(needle in message for _, message in _notifications(app)),
|
|
729
|
+
f"the notification {needle!r}",
|
|
730
|
+
)
|
|
731
|
+
|
|
732
|
+
|
|
708
733
|
def _notifications(app) -> list[tuple[str, str]]:
|
|
709
734
|
"""(severity, message) of every notification raised on the app so far.
|
|
710
735
|
|
|
@@ -893,8 +918,7 @@ def test_config_screen_profile_unmodeled_keys_survive_save(project: Project) ->
|
|
|
893
918
|
await _settled(app)
|
|
894
919
|
await pilot.pause()
|
|
895
920
|
assert not isinstance(app.screen, ProfileSaveModal)
|
|
896
|
-
|
|
897
|
-
for _, message in _notifications(app))
|
|
921
|
+
await _await_notification(pilot, app, "saved probe_extras_v1 version 2")
|
|
898
922
|
|
|
899
923
|
_run(scenario())
|
|
900
924
|
loaded = load_profile(project.profiles_dir, "probe_extras_v1", expected_kind="qc")
|
|
@@ -1013,10 +1037,8 @@ def test_config_screen_unchanged_profile_save_notifies(project: Project) -> None
|
|
|
1013
1037
|
await _settled(app)
|
|
1014
1038
|
await pilot.pause()
|
|
1015
1039
|
assert not isinstance(app.screen, ProfileSaveModal)
|
|
1016
|
-
|
|
1017
|
-
"assembly_production_v1: unchanged — version 1 kept"
|
|
1018
|
-
for _, message in _notifications(app)
|
|
1019
|
-
)
|
|
1040
|
+
await _await_notification(
|
|
1041
|
+
pilot, app, "assembly_production_v1: unchanged — version 1 kept")
|
|
1020
1042
|
|
|
1021
1043
|
_run(scenario())
|
|
1022
1044
|
assert load_profile(
|
|
@@ -1103,8 +1125,7 @@ def test_config_screen_new_profile_is_created_as_version_1(project: Project) ->
|
|
|
1103
1125
|
await _settled(app)
|
|
1104
1126
|
await pilot.pause()
|
|
1105
1127
|
assert not isinstance(app.screen, ProfileSaveModal)
|
|
1106
|
-
|
|
1107
|
-
for _, message in _notifications(app))
|
|
1128
|
+
await _await_notification(pilot, app, "saved assembly_strict_v1 version 1")
|
|
1108
1129
|
|
|
1109
1130
|
_run(scenario())
|
|
1110
1131
|
loaded = load_profile(project.profiles_dir, "assembly_strict_v1", expected_kind="qc")
|
|
@@ -1136,10 +1157,8 @@ def test_config_screen_new_profile_with_existing_name_opens_it(project: Project)
|
|
|
1136
1157
|
assert "reads_qc_v1" in _static_text(panel.query_one("#profile-heading", Static))
|
|
1137
1158
|
assert panel.query_one("#profile-description", Input).value.startswith("Raw read QC")
|
|
1138
1159
|
assert len(panel._rule_rows("required")) == 4
|
|
1139
|
-
|
|
1140
|
-
"profile 'reads_qc_v1' already exists — opening it instead"
|
|
1141
|
-
for _, message in _notifications(app)
|
|
1142
|
-
)
|
|
1160
|
+
await _await_notification(
|
|
1161
|
+
pilot, app, "profile 'reads_qc_v1' already exists — opening it instead")
|
|
1143
1162
|
|
|
1144
1163
|
_run(scenario())
|
|
1145
1164
|
|
|
@@ -1310,8 +1329,7 @@ def test_config_screen_recipe_unchanged_max_hits_and_cancel(project: Project) ->
|
|
|
1310
1329
|
await _settled(app)
|
|
1311
1330
|
await pilot.pause()
|
|
1312
1331
|
assert not isinstance(app.screen, RecipeSaveModal)
|
|
1313
|
-
|
|
1314
|
-
for _, message in _notifications(app))
|
|
1332
|
+
await _await_notification(pilot, app, "saved blastn_nt version 2")
|
|
1315
1333
|
|
|
1316
1334
|
# A recipe without max_hits_per_query also saves as a no-op.
|
|
1317
1335
|
panel._load_recipe("busco_autolineage")
|
|
@@ -1980,15 +1998,23 @@ async def _await_rows(pilot, root, selector: str, count: int, child: str) -> lis
|
|
|
1980
1998
|
"""Wait until ``root`` holds ``count`` ``selector`` rows whose ``child`` exists.
|
|
1981
1999
|
|
|
1982
2000
|
Mounting a row subtree takes more than one message-loop turn, so a single
|
|
1983
|
-
``pilot.pause()`` can observe a row that has not composed yet.
|
|
2001
|
+
``pilot.pause()`` can observe a row that has not composed yet. The budget is
|
|
2002
|
+
this file's wall-clock settle timeout rather than a fixed number of cycles: a
|
|
2003
|
+
loaded CI runner needs seconds to deliver the click and mount the row it
|
|
2004
|
+
produces, and a cycle count that is generous on a fast machine runs out there
|
|
2005
|
+
(ODR-0027).
|
|
1984
2006
|
"""
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
await
|
|
1990
|
-
|
|
1991
|
-
|
|
2007
|
+
def composed() -> list:
|
|
2008
|
+
return [row for row in root.query(selector) if len(list(row.query(child))) > 0]
|
|
2009
|
+
|
|
2010
|
+
try:
|
|
2011
|
+
await _wait_until(lambda: len(composed()) >= count,
|
|
2012
|
+
f"{count} {selector} rows with {child}")
|
|
2013
|
+
except TimeoutError as error:
|
|
2014
|
+
raise AssertionError(
|
|
2015
|
+
f"{selector} rows with {child}: saw {len(composed())}, wanted {count}"
|
|
2016
|
+
) from error
|
|
2017
|
+
return composed()
|
|
1992
2018
|
|
|
1993
2019
|
|
|
1994
2020
|
async def _await_form_ready(pilot, panel) -> None:
|
|
@@ -2079,6 +2105,7 @@ def test_save_classification_profile_versions_and_validation(project: Project) -
|
|
|
2079
2105
|
assert loaded["rules"][0]["when"][0]["value"] == 1e-05
|
|
2080
2106
|
|
|
2081
2107
|
|
|
2108
|
+
@pytest.mark.bug("ODR-0027")
|
|
2082
2109
|
def test_config_classification_editor_end_to_end(project: Project) -> None:
|
|
2083
2110
|
_write_classification_profile(project, "bhlh_tiers", BHLH_PROFILE)
|
|
2084
2111
|
|
|
@@ -2160,7 +2187,7 @@ def test_config_classification_editor_end_to_end(project: Project) -> None:
|
|
|
2160
2187
|
await _settled(app)
|
|
2161
2188
|
await pilot.pause()
|
|
2162
2189
|
assert not isinstance(app.screen, ClassificationSaveModal)
|
|
2163
|
-
|
|
2190
|
+
await _await_notification(pilot, app, "saved bhlh_tiers version 2")
|
|
2164
2191
|
# The editor reloaded from disk: the freshly saved structure renders.
|
|
2165
2192
|
assert len(list(panel.query(".source-row"))) == 2
|
|
2166
2193
|
assert len(list(panel.query(".classrule-row"))) == 4
|
|
@@ -2183,6 +2210,7 @@ def test_config_classification_editor_end_to_end(project: Project) -> None:
|
|
|
2183
2210
|
"WHERE profile_name='bhlh_tiers'")[-1]["profile_version"] == 2
|
|
2184
2211
|
|
|
2185
2212
|
|
|
2213
|
+
@pytest.mark.bug("ODR-0027")
|
|
2186
2214
|
def test_config_classification_editor_guards_and_readonly(project: Project) -> None:
|
|
2187
2215
|
nested = json.loads(json.dumps(BHLH_PROFILE))
|
|
2188
2216
|
nested["rules"][0]["when"] = [{"any": [{"not": {"field": "x", "operator": "exists"}}]}]
|
|
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
|
|
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
|