From 12ac764d01352769ae120d81e1fac83379fa2265 Mon Sep 17 00:00:00 2001 From: Juan Sugg Date: Thu, 9 Jul 2026 21:38:17 -0300 Subject: [PATCH 01/12] docs(api): classify phase 2 public modules --- .local/public-api-surface-improvements.md | 148 +++++++++++++++++++++- 1 file changed, 146 insertions(+), 2 deletions(-) diff --git a/.local/public-api-surface-improvements.md b/.local/public-api-surface-improvements.md index adae2c9..98e09ab 100644 --- a/.local/public-api-surface-improvements.md +++ b/.local/public-api-surface-improvements.md @@ -191,7 +191,7 @@ and observed green via `verifier-ci`. | ID | Task | Status | Depends on | |----|------|--------|------------| -| P2-01 | Inventory & classification appendix | TODO | P1-01 | +| P2-01 | Inventory & classification appendix | DONE | P1-01 | | P2-02 | `ser/models` → internal (keep facades) | TODO | P2-01 | | P2-03 | `ser/runtime` → internal (keep contracts/pipeline/registry facades) | TODO | P2-01 | | P2-04 | `ser/data` → internal (keep `application.py` + curated `__init__`) | TODO | P2-01 | @@ -309,6 +309,16 @@ Template: - Deviations / follow-ups: … ``` +### 2026-07-09 21:34 — P2-01 done +- What: Classified every Python file under the public non-tier-1 Phase 2 package set as `keep-as-facade` or `move-internal`, including destinations for all moved modules. +- Evidence: `python` classification generator covered 133 files; `rg -n '\| `[^`]+` \| unclassified' .local/public-api-surface-improvements.md` → no matches; Appendix A now contains no empty placeholder. +- Deviations / follow-ups: `ser.diagnostics.domain` remains a public contract facade because the P1 tier-1 snapshot points `ser.api` diagnostic signature types at that module. + +### 2026-07-09 21:32 — P2-01 started +- What: Build the Phase 2 inventory/review artifact before any moves, preserving P1 snapshot targets for tier-1 signatures. +- Evidence: `rg --files ser/data ser/models ser/runtime ser/transcript ser/features ser/heads ser/repr ser/diagnostics ser/utils` listed the current public non-tier-1 candidates. +- Deviations / follow-ups: none. + ### 2026-07-09 21:00 — P1-05 done - What: Wired `make type-completeness` into CI code-quality, added `workflow_dispatch` to CI so the validation workflow can be dispatched for this @@ -523,4 +533,138 @@ Template: ## Appendix A — Phase 2 classification (populated by P2-01) -*(empty until P2-01)* +| Path | Classification | Destination | Reason | +|---|---|---|---| +| `ser/data/__init__.py` | keep-as-facade | — | Public data package facade; curated training data exports only. | +| `ser/data/adapters/__init__.py` | move-internal | `ser/_internal/data/adapters/__init__.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/data/adapters/biic_podcast.py` | move-internal | `ser/_internal/data/adapters/biic_podcast.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/data/adapters/crema_d.py` | move-internal | `ser/_internal/data/adapters/crema_d.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/data/adapters/csv_manifest_builder.py` | move-internal | `ser/_internal/data/adapters/csv_manifest_builder.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/data/adapters/msp_podcast.py` | move-internal | `ser/_internal/data/adapters/msp_podcast.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/data/adapters/public_csv_datasets.py` | move-internal | `ser/_internal/data/adapters/public_csv_datasets.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/data/adapters/ravdess.py` | move-internal | `ser/_internal/data/adapters/ravdess.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/data/application.py` | keep-as-facade | — | Public data application facade over internal dataset workflows. | +| `ser/data/archive_extraction.py` | move-internal | `ser/_internal/data/archive_extraction.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/data/catalog/__init__.py` | move-internal | `ser/_internal/data/catalog/__init__.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/data/catalog/public_datasets.py` | move-internal | `ser/_internal/data/catalog/public_datasets.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/data/cli.py` | move-internal | `ser/_internal/data/cli.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/data/data_loader.py` | move-internal | `ser/_internal/data/data_loader.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/data/dataset_capabilities.py` | move-internal | `ser/_internal/data/dataset_capabilities.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/data/dataset_consents.py` | move-internal | `ser/_internal/data/dataset_consents.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/data/dataset_prepare.py` | move-internal | `ser/_internal/data/dataset_prepare.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/data/dataset_registry.py` | move-internal | `ser/_internal/data/dataset_registry.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/data/embedding_cache.py` | move-internal | `ser/_internal/data/embedding_cache.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/data/jl_corpus_downloads.py` | move-internal | `ser/_internal/data/jl_corpus_downloads.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/data/label_ontology.py` | move-internal | `ser/_internal/data/label_ontology.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/data/manifest.py` | move-internal | `ser/_internal/data/manifest.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/data/manifest_jsonl.py` | move-internal | `ser/_internal/data/manifest_jsonl.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/data/mendeley_dataset_preparation.py` | move-internal | `ser/_internal/data/mendeley_dataset_preparation.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/data/mendeley_downloads.py` | move-internal | `ser/_internal/data/mendeley_downloads.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/data/msp_podcast_mirror.py` | move-internal | `ser/_internal/data/msp_podcast_mirror.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/data/ontology.py` | move-internal | `ser/_internal/data/ontology.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/data/openslr_dataset_preparation.py` | move-internal | `ser/_internal/data/openslr_dataset_preparation.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/data/openslr_downloads.py` | move-internal | `ser/_internal/data/openslr_downloads.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/data/openslr_resolution.py` | move-internal | `ser/_internal/data/openslr_resolution.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/data/provider_dataset_preparation.py` | move-internal | `ser/_internal/data/provider_dataset_preparation.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/data/provider_downloads.py` | move-internal | `ser/_internal/data/provider_downloads.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/data/public_dataset_downloads.py` | move-internal | `ser/_internal/data/public_dataset_downloads.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/data/public_dataset_label_inference.py` | move-internal | `ser/_internal/data/public_dataset_label_inference.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/data/strategies/__init__.py` | move-internal | `ser/_internal/data/strategies/__init__.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/data/strategies/auto_csv.py` | move-internal | `ser/_internal/data/strategies/auto_csv.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/data/strategies/base.py` | move-internal | `ser/_internal/data/strategies/base.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/data/strategies/default.py` | move-internal | `ser/_internal/data/strategies/default.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/data/zenodo_downloads.py` | move-internal | `ser/_internal/data/zenodo_downloads.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/models/__init__.py` | keep-as-facade | — | Lazy package facade exposing intended model facade modules. | +| `ser/models/accurate_feature_dataset.py` | move-internal | `ser/_internal/models/accurate_feature_dataset.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/models/accurate_training_execution.py` | move-internal | `ser/_internal/models/accurate_training_execution.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/models/accurate_training_preparation.py` | move-internal | `ser/_internal/models/accurate_training_preparation.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/models/artifact_envelope.py` | move-internal | `ser/_internal/models/artifact_envelope.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/models/artifact_loading.py` | move-internal | `ser/_internal/models/artifact_loading.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/models/artifact_metadata.py` | move-internal | `ser/_internal/models/artifact_metadata.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/models/artifact_persistence.py` | move-internal | `ser/_internal/models/artifact_persistence.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/models/dataset_controls.py` | move-internal | `ser/_internal/models/dataset_controls.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/models/dataset_splitting.py` | move-internal | `ser/_internal/models/dataset_splitting.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/models/dataset_training_consents.py` | move-internal | `ser/_internal/models/dataset_training_consents.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/models/emotion_model.py` | keep-as-facade | — | Public model convenience facade over internal loading/training/prediction owners. | +| `ser/models/fast_path.py` | move-internal | `ser/_internal/models/fast_path.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/models/fast_training.py` | move-internal | `ser/_internal/models/fast_training.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/models/feature_runtime_encoding.py` | move-internal | `ser/_internal/models/feature_runtime_encoding.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/models/medium_feature_dataset.py` | move-internal | `ser/_internal/models/medium_feature_dataset.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/models/medium_noise_controls.py` | move-internal | `ser/_internal/models/medium_noise_controls.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/models/medium_training_preparation.py` | move-internal | `ser/_internal/models/medium_training_preparation.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/models/profile_runtime.py` | keep-as-facade | — | Public profile-runtime selector facade over internal backend resolution. | +| `ser/models/profile_training_preparation.py` | move-internal | `ser/_internal/models/profile_training_preparation.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/models/training_entrypoints.py` | keep-as-facade | — | Public training entrypoint facade over internal training workflow owners. | +| `ser/models/training_execution.py` | move-internal | `ser/_internal/models/training_execution.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/models/training_preparation.py` | move-internal | `ser/_internal/models/training_preparation.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/models/training_reporting.py` | move-internal | `ser/_internal/models/training_reporting.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/models/training_support.py` | move-internal | `ser/_internal/models/training_support.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/models/training_types.py` | move-internal | `ser/_internal/models/training_types.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/runtime/__init__.py` | keep-as-facade | — | Runtime package facade exporting public contracts, pipeline, and registry symbols. | +| `ser/runtime/accurate_execution.py` | move-internal | `ser/_internal/runtime/accurate_execution.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/runtime/accurate_inference.py` | move-internal | `ser/_internal/runtime/accurate_inference.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/runtime/accurate_model_contract.py` | move-internal | `ser/_internal/runtime/accurate_model_contract.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/runtime/accurate_prediction.py` | move-internal | `ser/_internal/runtime/accurate_prediction.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/runtime/accurate_research_inference.py` | move-internal | `ser/_internal/runtime/accurate_research_inference.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/runtime/benchmarks.py` | move-internal | `ser/_internal/runtime/benchmarks.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/runtime/contracts.py` | keep-as-facade | — | Public runtime contract dataclasses used by tier-1 signatures. | +| `ser/runtime/fast_inference.py` | move-internal | `ser/_internal/runtime/fast_inference.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/runtime/medium_execution.py` | move-internal | `ser/_internal/runtime/medium_execution.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/runtime/medium_inference.py` | move-internal | `ser/_internal/runtime/medium_inference.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/runtime/medium_prediction.py` | move-internal | `ser/_internal/runtime/medium_prediction.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/runtime/mps_oom.py` | move-internal | `ser/_internal/runtime/mps_oom.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/runtime/phase_contract.py` | move-internal | `ser/_internal/runtime/phase_contract.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/runtime/phase_timing.py` | move-internal | `ser/_internal/runtime/phase_timing.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/runtime/pipeline.py` | keep-as-facade | — | Public runtime pipeline factory facade over internal orchestration. | +| `ser/runtime/postprocessing.py` | move-internal | `ser/_internal/runtime/postprocessing.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/runtime/profile_quality_gate.py` | move-internal | `ser/_internal/runtime/profile_quality_gate.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/runtime/quality_gate_cli.py` | move-internal | `ser/_internal/runtime/quality_gate_cli.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/runtime/quality_gate_evaluation.py` | move-internal | `ser/_internal/runtime/quality_gate_evaluation.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/runtime/quality_gate_policy.py` | move-internal | `ser/_internal/runtime/quality_gate_policy.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/runtime/quality_gate_reporting.py` | move-internal | `ser/_internal/runtime/quality_gate_reporting.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/runtime/registry.py` | keep-as-facade | — | Public runtime capability registry contract facade. | +| `ser/runtime/schema.py` | move-internal | `ser/_internal/runtime/schema.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/transcript/__init__.py` | keep-as-facade | — | Transcript package facade exporting supported transcript boundary APIs. | +| `ser/transcript/backends/__init__.py` | keep-as-facade | — | Backend package marker for the supported stable-whisper facade path. | +| `ser/transcript/backends/base.py` | move-internal | `ser/_internal/transcript/backends/base.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/transcript/backends/factory.py` | move-internal | `ser/_internal/transcript/backends/factory.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/transcript/backends/faster_whisper.py` | move-internal | `ser/_internal/transcript/backends/faster_whisper.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/transcript/backends/stable_whisper.py` | keep-as-facade | — | Public stable-whisper adapter facade over internal transcript backend owners. | +| `ser/transcript/backends/stable_whisper_admission_runtime.py` | move-internal | `ser/_internal/transcript/backends/stable_whisper_admission_runtime.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/transcript/backends/stable_whisper_mps_compat.py` | move-internal | `ser/_internal/transcript/backends/stable_whisper_mps_compat.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/transcript/backends/stable_whisper_torio_probe.py` | move-internal | `ser/_internal/transcript/backends/stable_whisper_torio_probe.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/transcript/backends/stable_whisper_transcribe_admission.py` | move-internal | `ser/_internal/transcript/backends/stable_whisper_transcribe_admission.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/transcript/backends/stable_whisper_transcribe_execution.py` | move-internal | `ser/_internal/transcript/backends/stable_whisper_transcribe_execution.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/transcript/backends/stable_whisper_transcribe_kwargs.py` | move-internal | `ser/_internal/transcript/backends/stable_whisper_transcribe_kwargs.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/transcript/backends/stable_whisper_transcribe_runtime.py` | move-internal | `ser/_internal/transcript/backends/stable_whisper_transcribe_runtime.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/transcript/mps_admission.py` | move-internal | `ser/_internal/transcript/mps_admission.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/transcript/mps_admission_overrides.py` | move-internal | `ser/_internal/transcript/mps_admission_overrides.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/transcript/profiling.py` | keep-as-facade | — | Public transcription profiling facade over internal profiling/calibration workflows. | +| `ser/transcript/runtime_failures.py` | move-internal | `ser/_internal/transcript/runtime_failures.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/transcript/runtime_policy.py` | move-internal | `ser/_internal/transcript/runtime_policy.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/transcript/transcript_extractor.py` | keep-as-facade | — | Public transcript extraction facade used by transcript package exports. | +| `ser/features/__init__.py` | keep-as-facade | — | Feature package facade exposing curated extraction helpers. | +| `ser/features/feature_extractor.py` | move-internal | `ser/_internal/features/feature_extractor.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/heads/__init__.py` | keep-as-facade | — | Classifier-head package facade exposing curated torch-head helpers. | +| `ser/heads/torch_head.py` | move-internal | `ser/_internal/heads/torch_head.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/repr/__init__.py` | keep-as-facade | — | Representation package facade exposing curated backend contracts/classes. | +| `ser/repr/backend.py` | move-internal | `ser/_internal/repr/backend.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/repr/emotion2vec.py` | move-internal | `ser/_internal/repr/emotion2vec.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/repr/handcrafted.py` | move-internal | `ser/_internal/repr/handcrafted.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/repr/hf_whisper.py` | move-internal | `ser/_internal/repr/hf_whisper.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/repr/hf_xlsr.py` | move-internal | `ser/_internal/repr/hf_xlsr.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/repr/runtime_policy.py` | move-internal | `ser/_internal/repr/runtime_policy.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/diagnostics/__init__.py` | keep-as-facade | — | Diagnostics package facade for diagnostic contracts only. | +| `ser/diagnostics/command.py` | move-internal | `ser/_internal/diagnostics/command.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/diagnostics/domain.py` | keep-as-facade | — | Diagnostic domain contracts remain public because tier-1 `ser.api` signatures target this module. | +| `ser/diagnostics/service.py` | move-internal | `ser/_internal/diagnostics/service.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/utils/__init__.py` | keep-as-facade | — | Tier-1 curated utility facade; helper modules move internal. | +| `ser/utils/audio_utils.py` | move-internal | `ser/_internal/utils/audio_utils.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/utils/common_utils.py` | move-internal | `ser/_internal/utils/common_utils.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/utils/dsp.py` | move-internal | `ser/_internal/utils/dsp.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/utils/logger.py` | move-internal | `ser/_internal/utils/logger.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/utils/segment_canonicalization.py` | move-internal | `ser/_internal/utils/segment_canonicalization.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/utils/subtitles.py` | move-internal | `ser/_internal/utils/subtitles.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/utils/timeline_utils.py` | move-internal | `ser/_internal/utils/timeline_utils.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/utils/torch_inference.py` | move-internal | `ser/_internal/utils/torch_inference.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/utils/transcription_compat.py` | move-internal | `ser/_internal/utils/transcription_compat.py` | Implementation/helper module; first-party callers will use the internal owner path. | From 1c8bab3ba4909d81c26db2be6907243737c5dc80 Mon Sep 17 00:00:00 2001 From: Juan Sugg Date: Thu, 9 Jul 2026 22:30:09 -0300 Subject: [PATCH 02/12] refactor(data): move implementation internal --- ser/_internal/api/data.py | 16 +++---- ser/_internal/data/__init__.py | 21 ++++++++ ser/{ => _internal}/data/adapters/__init__.py | 0 .../data/adapters/biic_podcast.py | 6 +-- ser/{ => _internal}/data/adapters/crema_d.py | 6 +-- .../data/adapters/csv_manifest_builder.py | 8 ++-- .../data/adapters/msp_podcast.py | 6 +-- .../data/adapters/public_csv_datasets.py | 8 ++-- ser/{ => _internal}/data/adapters/ravdess.py | 6 +-- .../data/application/capability_snapshot.py | 4 +- ser/_internal/data/application/models.py | 2 +- ser/_internal/data/application/prepare.py | 8 ++-- .../data/application/registry_snapshot.py | 6 +-- ser/_internal/data/application/uninstall.py | 6 +-- .../data/archive_extraction.py | 0 ser/{ => _internal}/data/catalog/__init__.py | 0 .../data/catalog/public_datasets.py | 0 ser/{ => _internal}/data/cli.py | 12 ++--- ser/{ => _internal}/data/data_loader.py | 14 +++--- .../data/dataset_capabilities.py | 0 ser/{ => _internal}/data/dataset_consents.py | 2 +- ser/{ => _internal}/data/dataset_prepare.py | 10 ++-- ser/{ => _internal}/data/dataset_registry.py | 0 ser/{ => _internal}/data/embedding_cache.py | 2 +- .../data/jl_corpus_downloads.py | 2 +- ser/{ => _internal}/data/label_ontology.py | 2 +- ser/{ => _internal}/data/manifest.py | 2 +- ser/{ => _internal}/data/manifest_jsonl.py | 4 +- .../data/mendeley_dataset_preparation.py | 2 +- .../data/mendeley_downloads.py | 0 .../data/msp_podcast_mirror.py | 0 ser/{ => _internal}/data/ontology.py | 0 .../data/openslr_dataset_preparation.py | 2 +- ser/{ => _internal}/data/openslr_downloads.py | 2 +- .../data/openslr_resolution.py | 0 .../data/provider_dataset_preparation.py | 0 .../data/provider_downloads.py | 0 .../data/public_dataset_downloads.py | 36 +++++++------- .../data/public_dataset_label_inference.py | 0 .../data/strategies/__init__.py | 0 .../data/strategies/auto_csv.py | 12 ++--- ser/{ => _internal}/data/strategies/base.py | 4 +- .../data/strategies/default.py | 18 +++---- ser/{ => _internal}/data/zenodo_downloads.py | 2 +- ser/{ => _internal}/diagnostics/command.py | 0 ser/{ => _internal}/diagnostics/service.py | 0 .../features/feature_extractor.py | 0 ser/{ => _internal}/heads/torch_head.py | 0 .../models/accurate_feature_dataset.py | 0 .../models/accurate_training_execution.py | 0 .../models/accurate_training_preparation.py | 0 .../models/artifact_envelope.py | 0 .../models/artifact_loading.py | 0 .../models/artifact_metadata.py | 0 .../models/artifact_persistence.py | 0 .../models/dataset_controls.py | 0 .../models/dataset_splitting.py | 0 .../models/dataset_training_consents.py | 0 ser/{ => _internal}/models/emotion_model.py | 0 ser/{ => _internal}/models/fast_path.py | 0 ser/{ => _internal}/models/fast_training.py | 0 .../models/feature_runtime_encoding.py | 0 .../models/medium_feature_dataset.py | 0 .../models/medium_noise_controls.py | 0 .../models/medium_training_preparation.py | 0 ser/{ => _internal}/models/profile_runtime.py | 0 .../models/profile_training_preparation.py | 0 .../models/training_entrypoints.py | 0 .../models/training_execution.py | 0 .../models/training_preparation.py | 0 .../models/training_reporting.py | 0 .../models/training_support.py | 0 ser/{ => _internal}/models/training_types.py | 0 ser/{ => _internal}/repr/backend.py | 0 ser/{ => _internal}/repr/emotion2vec.py | 0 ser/{ => _internal}/repr/handcrafted.py | 0 ser/{ => _internal}/repr/hf_whisper.py | 0 ser/{ => _internal}/repr/hf_xlsr.py | 0 ser/{ => _internal}/repr/runtime_policy.py | 0 .../runtime/accurate_execution.py | 0 .../runtime/accurate_inference.py | 0 .../runtime/accurate_model_contract.py | 0 .../runtime/accurate_prediction.py | 0 .../runtime/accurate_research_inference.py | 0 ser/{ => _internal}/runtime/benchmarks.py | 0 ser/{ => _internal}/runtime/fast_inference.py | 0 .../runtime/medium_execution.py | 0 .../runtime/medium_inference.py | 0 .../runtime/medium_prediction.py | 0 ser/{ => _internal}/runtime/mps_oom.py | 0 ser/{ => _internal}/runtime/phase_contract.py | 0 ser/{ => _internal}/runtime/phase_timing.py | 0 ser/{ => _internal}/runtime/pipeline.py | 0 ser/{ => _internal}/runtime/postprocessing.py | 0 .../runtime/profile_quality_gate.py | 0 .../runtime/quality_gate_cli.py | 0 .../runtime/quality_gate_evaluation.py | 0 .../runtime/quality_gate_policy.py | 0 .../runtime/quality_gate_reporting.py | 0 .../transcript/backends/factory.py | 0 .../transcript/backends/faster_whisper.py | 0 .../transcript/backends/stable_whisper.py | 0 .../stable_whisper_admission_runtime.py | 0 .../backends/stable_whisper_mps_compat.py | 0 .../backends/stable_whisper_torio_probe.py | 0 .../stable_whisper_transcribe_admission.py | 0 .../stable_whisper_transcribe_execution.py | 0 .../stable_whisper_transcribe_kwargs.py | 0 .../stable_whisper_transcribe_runtime.py | 0 .../transcript/mps_admission.py | 0 .../transcript/mps_admission_overrides.py | 0 ser/{ => _internal}/transcript/profiling.py | 0 .../transcript/runtime_failures.py | 0 .../transcript/runtime_policy.py | 0 .../transcript/transcript_extractor.py | 0 ser/{ => _internal}/utils/audio_utils.py | 0 ser/{ => _internal}/utils/dsp.py | 0 .../utils/segment_canonicalization.py | 0 ser/{ => _internal}/utils/subtitles.py | 0 ser/{ => _internal}/utils/timeline_utils.py | 0 ser/{ => _internal}/utils/torch_inference.py | 0 .../utils/transcription_compat.py | 0 ser/data/__init__.py | 48 +++++++++++-------- ser/data/application.py | 6 +-- tests/suites/integration/api/test_api.py | 16 +++---- tests/suites/integration/cli/test_data_cli.py | 6 +-- .../integration/data/test_data_application.py | 8 ++-- .../integration/data/test_dataset_prepare.py | 20 ++++---- .../unit/data/test_archive_extraction.py | 2 +- tests/suites/unit/data/test_data_loader.py | 4 +- .../test_dataset_adapters_biic_podcast.py | 4 +- .../data/test_dataset_adapters_crema_d.py | 4 +- .../data/test_dataset_adapters_msp_podcast.py | 4 +- ...st_dataset_adapters_public_csv_datasets.py | 4 +- .../data/test_dataset_adapters_ravdess.py | 4 +- .../test_dataset_catalog_public_datasets.py | 2 +- .../suites/unit/data/test_dataset_consents.py | 6 +-- .../suites/unit/data/test_dataset_controls.py | 6 +-- .../suites/unit/data/test_dataset_registry.py | 4 +- .../unit/data/test_dataset_strategies.py | 4 +- .../suites/unit/data/test_embedding_cache.py | 4 +- .../unit/data/test_jl_corpus_downloads.py | 4 +- tests/suites/unit/data/test_manifest_jsonl.py | 6 +-- .../data/test_mendeley_dataset_preparation.py | 2 +- .../unit/data/test_mendeley_downloads.py | 2 +- .../unit/data/test_msp_podcast_mirror.py | 4 +- .../data/test_openslr_dataset_preparation.py | 2 +- .../unit/data/test_openslr_downloads.py | 4 +- .../data/test_provider_dataset_preparation.py | 2 +- .../unit/data/test_provider_downloads.py | 2 +- .../data/test_public_dataset_downloads.py | 2 +- .../suites/unit/data/test_zenodo_downloads.py | 2 +- 152 files changed, 227 insertions(+), 192 deletions(-) rename ser/{ => _internal}/data/adapters/__init__.py (100%) rename ser/{ => _internal}/data/adapters/biic_podcast.py (97%) rename ser/{ => _internal}/data/adapters/crema_d.py (96%) rename ser/{ => _internal}/data/adapters/csv_manifest_builder.py (96%) rename ser/{ => _internal}/data/adapters/msp_podcast.py (97%) rename ser/{ => _internal}/data/adapters/public_csv_datasets.py (97%) rename ser/{ => _internal}/data/adapters/ravdess.py (96%) rename ser/{ => _internal}/data/archive_extraction.py (100%) rename ser/{ => _internal}/data/catalog/__init__.py (100%) rename ser/{ => _internal}/data/catalog/public_datasets.py (100%) rename ser/{ => _internal}/data/cli.py (99%) rename ser/{ => _internal}/data/data_loader.py (96%) rename ser/{ => _internal}/data/dataset_capabilities.py (100%) rename ser/{ => _internal}/data/dataset_consents.py (99%) rename ser/{ => _internal}/data/dataset_prepare.py (99%) rename ser/{ => _internal}/data/dataset_registry.py (100%) rename ser/{ => _internal}/data/embedding_cache.py (99%) rename ser/{ => _internal}/data/jl_corpus_downloads.py (99%) rename ser/{ => _internal}/data/label_ontology.py (94%) rename ser/{ => _internal}/data/manifest.py (98%) rename ser/{ => _internal}/data/manifest_jsonl.py (95%) rename ser/{ => _internal}/data/mendeley_dataset_preparation.py (98%) rename ser/{ => _internal}/data/mendeley_downloads.py (100%) rename ser/{ => _internal}/data/msp_podcast_mirror.py (100%) rename ser/{ => _internal}/data/ontology.py (100%) rename ser/{ => _internal}/data/openslr_dataset_preparation.py (98%) rename ser/{ => _internal}/data/openslr_downloads.py (99%) rename ser/{ => _internal}/data/openslr_resolution.py (100%) rename ser/{ => _internal}/data/provider_dataset_preparation.py (100%) rename ser/{ => _internal}/data/provider_downloads.py (100%) rename ser/{ => _internal}/data/public_dataset_downloads.py (95%) rename ser/{ => _internal}/data/public_dataset_label_inference.py (100%) rename ser/{ => _internal}/data/strategies/__init__.py (100%) rename ser/{ => _internal}/data/strategies/auto_csv.py (96%) rename ser/{ => _internal}/data/strategies/base.py (95%) rename ser/{ => _internal}/data/strategies/default.py (95%) rename ser/{ => _internal}/data/zenodo_downloads.py (99%) rename ser/{ => _internal}/diagnostics/command.py (100%) rename ser/{ => _internal}/diagnostics/service.py (100%) rename ser/{ => _internal}/features/feature_extractor.py (100%) rename ser/{ => _internal}/heads/torch_head.py (100%) rename ser/{ => _internal}/models/accurate_feature_dataset.py (100%) rename ser/{ => _internal}/models/accurate_training_execution.py (100%) rename ser/{ => _internal}/models/accurate_training_preparation.py (100%) rename ser/{ => _internal}/models/artifact_envelope.py (100%) rename ser/{ => _internal}/models/artifact_loading.py (100%) rename ser/{ => _internal}/models/artifact_metadata.py (100%) rename ser/{ => _internal}/models/artifact_persistence.py (100%) rename ser/{ => _internal}/models/dataset_controls.py (100%) rename ser/{ => _internal}/models/dataset_splitting.py (100%) rename ser/{ => _internal}/models/dataset_training_consents.py (100%) rename ser/{ => _internal}/models/emotion_model.py (100%) rename ser/{ => _internal}/models/fast_path.py (100%) rename ser/{ => _internal}/models/fast_training.py (100%) rename ser/{ => _internal}/models/feature_runtime_encoding.py (100%) rename ser/{ => _internal}/models/medium_feature_dataset.py (100%) rename ser/{ => _internal}/models/medium_noise_controls.py (100%) rename ser/{ => _internal}/models/medium_training_preparation.py (100%) rename ser/{ => _internal}/models/profile_runtime.py (100%) rename ser/{ => _internal}/models/profile_training_preparation.py (100%) rename ser/{ => _internal}/models/training_entrypoints.py (100%) rename ser/{ => _internal}/models/training_execution.py (100%) rename ser/{ => _internal}/models/training_preparation.py (100%) rename ser/{ => _internal}/models/training_reporting.py (100%) rename ser/{ => _internal}/models/training_support.py (100%) rename ser/{ => _internal}/models/training_types.py (100%) rename ser/{ => _internal}/repr/backend.py (100%) rename ser/{ => _internal}/repr/emotion2vec.py (100%) rename ser/{ => _internal}/repr/handcrafted.py (100%) rename ser/{ => _internal}/repr/hf_whisper.py (100%) rename ser/{ => _internal}/repr/hf_xlsr.py (100%) rename ser/{ => _internal}/repr/runtime_policy.py (100%) rename ser/{ => _internal}/runtime/accurate_execution.py (100%) rename ser/{ => _internal}/runtime/accurate_inference.py (100%) rename ser/{ => _internal}/runtime/accurate_model_contract.py (100%) rename ser/{ => _internal}/runtime/accurate_prediction.py (100%) rename ser/{ => _internal}/runtime/accurate_research_inference.py (100%) rename ser/{ => _internal}/runtime/benchmarks.py (100%) rename ser/{ => _internal}/runtime/fast_inference.py (100%) rename ser/{ => _internal}/runtime/medium_execution.py (100%) rename ser/{ => _internal}/runtime/medium_inference.py (100%) rename ser/{ => _internal}/runtime/medium_prediction.py (100%) rename ser/{ => _internal}/runtime/mps_oom.py (100%) rename ser/{ => _internal}/runtime/phase_contract.py (100%) rename ser/{ => _internal}/runtime/phase_timing.py (100%) rename ser/{ => _internal}/runtime/pipeline.py (100%) rename ser/{ => _internal}/runtime/postprocessing.py (100%) rename ser/{ => _internal}/runtime/profile_quality_gate.py (100%) rename ser/{ => _internal}/runtime/quality_gate_cli.py (100%) rename ser/{ => _internal}/runtime/quality_gate_evaluation.py (100%) rename ser/{ => _internal}/runtime/quality_gate_policy.py (100%) rename ser/{ => _internal}/runtime/quality_gate_reporting.py (100%) rename ser/{ => _internal}/transcript/backends/factory.py (100%) rename ser/{ => _internal}/transcript/backends/faster_whisper.py (100%) rename ser/{ => _internal}/transcript/backends/stable_whisper.py (100%) rename ser/{ => _internal}/transcript/backends/stable_whisper_admission_runtime.py (100%) rename ser/{ => _internal}/transcript/backends/stable_whisper_mps_compat.py (100%) rename ser/{ => _internal}/transcript/backends/stable_whisper_torio_probe.py (100%) rename ser/{ => _internal}/transcript/backends/stable_whisper_transcribe_admission.py (100%) rename ser/{ => _internal}/transcript/backends/stable_whisper_transcribe_execution.py (100%) rename ser/{ => _internal}/transcript/backends/stable_whisper_transcribe_kwargs.py (100%) rename ser/{ => _internal}/transcript/backends/stable_whisper_transcribe_runtime.py (100%) rename ser/{ => _internal}/transcript/mps_admission.py (100%) rename ser/{ => _internal}/transcript/mps_admission_overrides.py (100%) rename ser/{ => _internal}/transcript/profiling.py (100%) rename ser/{ => _internal}/transcript/runtime_failures.py (100%) rename ser/{ => _internal}/transcript/runtime_policy.py (100%) rename ser/{ => _internal}/transcript/transcript_extractor.py (100%) rename ser/{ => _internal}/utils/audio_utils.py (100%) rename ser/{ => _internal}/utils/dsp.py (100%) rename ser/{ => _internal}/utils/segment_canonicalization.py (100%) rename ser/{ => _internal}/utils/subtitles.py (100%) rename ser/{ => _internal}/utils/timeline_utils.py (100%) rename ser/{ => _internal}/utils/torch_inference.py (100%) rename ser/{ => _internal}/utils/transcription_compat.py (100%) diff --git a/ser/_internal/api/data.py b/ser/_internal/api/data.py index 8a12e24..75a8d01 100644 --- a/ser/_internal/api/data.py +++ b/ser/_internal/api/data.py @@ -15,7 +15,7 @@ type ComplianceMode = Literal["advisory", "strict"] if TYPE_CHECKING: - from ser.data.msp_podcast_mirror import MspPodcastMirrorArtifacts + from ser._internal.data.msp_podcast_mirror import MspPodcastMirrorArtifacts @dataclass(frozen=True, slots=True) @@ -54,21 +54,21 @@ class DatasetRegistryHealthIssueRecord: def run_configure_command(argv: list[str], *, settings: AppConfig) -> int: """Runs `ser configure ...` command argv via the public API boundary.""" - from ser.data.cli import run_configure_command as _run_configure_command + from ser._internal.data.cli import run_configure_command as _run_configure_command return _run_configure_command(argv, settings=settings) def run_data_command(argv: list[str], *, settings: AppConfig) -> int: """Runs `ser data ...` command argv via the public API boundary.""" - from ser.data.cli import run_data_command as _run_data_command + from ser._internal.data.cli import run_data_command as _run_data_command return _run_data_command(argv, settings=settings) def list_datasets() -> tuple[str, ...]: """Returns sorted supported dataset identifiers.""" - from ser.data.dataset_prepare import SUPPORTED_DATASETS + from ser._internal.data.dataset_prepare import SUPPORTED_DATASETS return tuple(sorted(SUPPORTED_DATASETS)) @@ -120,7 +120,7 @@ def show_dataset_consents( settings: AppConfig, ) -> DatasetConsents: """Returns persisted dataset consent IDs as `(policy_ids, license_ids)`.""" - from ser.data.dataset_consents import load_persisted_dataset_consents + from ser._internal.data.dataset_consents import load_persisted_dataset_consents consents = load_persisted_dataset_consents(settings=settings) return DatasetConsents( @@ -137,7 +137,7 @@ def configure_dataset_consents( source: str = "ser.api.configure_dataset_consents", ) -> DatasetConsents: """Persists dataset consents and returns updated `(policy_ids, license_ids)`.""" - from ser.data.dataset_consents import ( + from ser._internal.data.dataset_consents import ( load_persisted_dataset_consents, persist_dataset_consents, ) @@ -171,12 +171,12 @@ def prepare_dataset( settings: AppConfig, ) -> DatasetPrepareResult: """Programmatic dataset acquisition + manifest preparation.""" + from ser._internal.data.dataset_consents import DatasetConsentError from ser.data.application import ( compute_dataset_descriptor_missing_consents, persist_missing_dataset_descriptor_consents, run_dataset_prepare_workflow, ) - from ser.data.dataset_consents import DatasetConsentError if compliance_mode not in {"advisory", "strict"}: raise ValueError( @@ -253,7 +253,7 @@ def prepare_msp_podcast_mirror( token: str | None = None, ) -> MspPodcastMirrorArtifacts: """Prepares MSP-Podcast artifacts from the configured Hugging Face mirror.""" - from ser.data.msp_podcast_mirror import prepare_msp_podcast_from_hf_mirror + from ser._internal.data.msp_podcast_mirror import prepare_msp_podcast_from_hf_mirror return prepare_msp_podcast_from_hf_mirror( dataset_root=dataset_root, diff --git a/ser/_internal/data/__init__.py b/ser/_internal/data/__init__.py index f5dca85..c35809e 100644 --- a/ser/_internal/data/__init__.py +++ b/ser/_internal/data/__init__.py @@ -1 +1,22 @@ """Internal dataset services and orchestration helpers.""" + +from ser._internal.data.data_loader import ( + DataSplit, + LabeledAudioSample, + load_data, + load_labeled_audio_paths, + load_utterances, +) +from ser._internal.data.embedding_cache import EmbeddingCache, EmbeddingCacheEntry +from ser._internal.data.manifest import Utterance + +__all__ = [ + "DataSplit", + "EmbeddingCache", + "EmbeddingCacheEntry", + "LabeledAudioSample", + "Utterance", + "load_data", + "load_labeled_audio_paths", + "load_utterances", +] diff --git a/ser/data/adapters/__init__.py b/ser/_internal/data/adapters/__init__.py similarity index 100% rename from ser/data/adapters/__init__.py rename to ser/_internal/data/adapters/__init__.py diff --git a/ser/data/adapters/biic_podcast.py b/ser/_internal/data/adapters/biic_podcast.py similarity index 97% rename from ser/data/adapters/biic_podcast.py rename to ser/_internal/data/adapters/biic_podcast.py index 47ccc60..52088be 100644 --- a/ser/data/adapters/biic_podcast.py +++ b/ser/_internal/data/adapters/biic_podcast.py @@ -6,9 +6,9 @@ from pathlib import Path from typing import Any -from ser.data.manifest import SplitName, Utterance -from ser.data.manifest_jsonl import write_manifest_jsonl -from ser.data.ontology import LabelOntology, remap_label +from ser._internal.data.manifest import SplitName, Utterance +from ser._internal.data.manifest_jsonl import write_manifest_jsonl +from ser._internal.data.ontology import LabelOntology, remap_label from ser.utils.logger import get_logger logger = get_logger(__name__) diff --git a/ser/data/adapters/crema_d.py b/ser/_internal/data/adapters/crema_d.py similarity index 96% rename from ser/data/adapters/crema_d.py rename to ser/_internal/data/adapters/crema_d.py index 9e709aa..1e877a5 100644 --- a/ser/data/adapters/crema_d.py +++ b/ser/_internal/data/adapters/crema_d.py @@ -6,9 +6,9 @@ from collections.abc import Mapping from pathlib import Path -from ser.data.manifest import Utterance -from ser.data.manifest_jsonl import write_manifest_jsonl -from ser.data.ontology import LabelOntology, remap_label +from ser._internal.data.manifest import Utterance +from ser._internal.data.manifest_jsonl import write_manifest_jsonl +from ser._internal.data.ontology import LabelOntology, remap_label from ser.utils.logger import get_logger logger = get_logger(__name__) diff --git a/ser/data/adapters/csv_manifest_builder.py b/ser/_internal/data/adapters/csv_manifest_builder.py similarity index 96% rename from ser/data/adapters/csv_manifest_builder.py rename to ser/_internal/data/adapters/csv_manifest_builder.py index 44252b0..965c82e 100644 --- a/ser/data/adapters/csv_manifest_builder.py +++ b/ser/_internal/data/adapters/csv_manifest_builder.py @@ -6,10 +6,10 @@ from pathlib import Path from typing import Any -from ser.data.catalog.public_datasets import CsvManifestSpec -from ser.data.manifest import SplitName, Utterance -from ser.data.manifest_jsonl import write_manifest_jsonl -from ser.data.ontology import LabelOntology, remap_label +from ser._internal.data.catalog.public_datasets import CsvManifestSpec +from ser._internal.data.manifest import SplitName, Utterance +from ser._internal.data.manifest_jsonl import write_manifest_jsonl +from ser._internal.data.ontology import LabelOntology, remap_label from ser.utils.logger import get_logger logger = get_logger(__name__) diff --git a/ser/data/adapters/msp_podcast.py b/ser/_internal/data/adapters/msp_podcast.py similarity index 97% rename from ser/data/adapters/msp_podcast.py rename to ser/_internal/data/adapters/msp_podcast.py index efa3f52..dca6840 100644 --- a/ser/data/adapters/msp_podcast.py +++ b/ser/_internal/data/adapters/msp_podcast.py @@ -6,9 +6,9 @@ from pathlib import Path from typing import Any -from ser.data.manifest import SplitName, Utterance -from ser.data.manifest_jsonl import write_manifest_jsonl -from ser.data.ontology import LabelOntology, remap_label +from ser._internal.data.manifest import SplitName, Utterance +from ser._internal.data.manifest_jsonl import write_manifest_jsonl +from ser._internal.data.ontology import LabelOntology, remap_label from ser.utils.logger import get_logger logger = get_logger(__name__) diff --git a/ser/data/adapters/public_csv_datasets.py b/ser/_internal/data/adapters/public_csv_datasets.py similarity index 97% rename from ser/data/adapters/public_csv_datasets.py rename to ser/_internal/data/adapters/public_csv_datasets.py index 4127cad..2c16033 100644 --- a/ser/data/adapters/public_csv_datasets.py +++ b/ser/_internal/data/adapters/public_csv_datasets.py @@ -4,8 +4,8 @@ from pathlib import Path -from ser.data.adapters.csv_manifest_builder import build_csv_labeled_manifest_jsonl -from ser.data.catalog.public_datasets import ( +from ser._internal.data.adapters.csv_manifest_builder import build_csv_labeled_manifest_jsonl +from ser._internal.data.catalog.public_datasets import ( ASVP_ESD_CORPUS_ID, ASVP_ESD_DATASET_LICENSE_ID, ASVP_ESD_DATASET_POLICY_ID, @@ -56,8 +56,8 @@ SPANISH_MEACORPUS_2023_MANIFEST_SPEC, CsvManifestSpec, ) -from ser.data.manifest import Utterance -from ser.data.ontology import LabelOntology +from ser._internal.data.manifest import Utterance +from ser._internal.data.ontology import LabelOntology __all__ = [ "ASVP_ESD_CORPUS_ID", diff --git a/ser/data/adapters/ravdess.py b/ser/_internal/data/adapters/ravdess.py similarity index 96% rename from ser/data/adapters/ravdess.py rename to ser/_internal/data/adapters/ravdess.py index c1fb189..adbebb7 100644 --- a/ser/data/adapters/ravdess.py +++ b/ser/_internal/data/adapters/ravdess.py @@ -7,9 +7,9 @@ from collections.abc import Mapping from pathlib import Path -from ser.data.manifest import Utterance -from ser.data.manifest_jsonl import write_manifest_jsonl -from ser.data.ontology import LabelOntology, remap_label +from ser._internal.data.manifest import Utterance +from ser._internal.data.manifest_jsonl import write_manifest_jsonl +from ser._internal.data.ontology import LabelOntology, remap_label from ser.utils.logger import get_logger logger = get_logger(__name__) diff --git a/ser/_internal/data/application/capability_snapshot.py b/ser/_internal/data/application/capability_snapshot.py index 28cf6f3..2241407 100644 --- a/ser/_internal/data/application/capability_snapshot.py +++ b/ser/_internal/data/application/capability_snapshot.py @@ -12,9 +12,9 @@ from ser._internal.data.application.registry_snapshot import ( collect_dataset_registry_snapshot, ) +from ser._internal.data.dataset_capabilities import resolve_dataset_capability_profile +from ser._internal.data.dataset_prepare import SUPPORTED_DATASETS from ser.config import AppConfig -from ser.data.dataset_capabilities import resolve_dataset_capability_profile -from ser.data.dataset_prepare import SUPPORTED_DATASETS def _resolve_manifest_audio_path( diff --git a/ser/_internal/data/application/models.py b/ser/_internal/data/application/models.py index 65cb5f6..8e13f7f 100644 --- a/ser/_internal/data/application/models.py +++ b/ser/_internal/data/application/models.py @@ -5,7 +5,7 @@ from dataclasses import dataclass from pathlib import Path -from ser.data.dataset_prepare import DatasetDescriptor +from ser._internal.data.dataset_prepare import DatasetDescriptor @dataclass(frozen=True, slots=True) diff --git a/ser/_internal/data/application/prepare.py b/ser/_internal/data/application/prepare.py index 50c11f6..c202a8c 100644 --- a/ser/_internal/data/application/prepare.py +++ b/ser/_internal/data/application/prepare.py @@ -5,19 +5,19 @@ from pathlib import Path from ser._internal.data.application.models import DatasetPrepareWorkflowResult -from ser.config import AppConfig -from ser.data.dataset_prepare import ( +from ser._internal.data.dataset_prepare import ( default_dataset_root, default_manifest_path, download_dataset, prepare_dataset_manifest, resolve_dataset_descriptor, ) -from ser.data.dataset_registry import ( +from ser._internal.data.dataset_registry import ( load_dataset_registry, parse_dataset_registry_options, ) -from ser.data.label_ontology import resolve_label_ontology +from ser._internal.data.label_ontology import resolve_label_ontology +from ser.config import AppConfig def run_dataset_prepare_workflow( diff --git a/ser/_internal/data/application/registry_snapshot.py b/ser/_internal/data/application/registry_snapshot.py index 00a22f1..6b06117 100644 --- a/ser/_internal/data/application/registry_snapshot.py +++ b/ser/_internal/data/application/registry_snapshot.py @@ -7,12 +7,12 @@ DatasetRegistrySnapshotEntry, DatasetRegistrySnapshotIssue, ) -from ser.config import AppConfig -from ser.data.dataset_prepare import collect_dataset_registry_health_issues -from ser.data.dataset_registry import ( +from ser._internal.data.dataset_prepare import collect_dataset_registry_health_issues +from ser._internal.data.dataset_registry import ( load_dataset_registry, parse_dataset_registry_options, ) +from ser.config import AppConfig def collect_dataset_registry_snapshot( diff --git a/ser/_internal/data/application/uninstall.py b/ser/_internal/data/application/uninstall.py index f174283..745c221 100644 --- a/ser/_internal/data/application/uninstall.py +++ b/ser/_internal/data/application/uninstall.py @@ -6,13 +6,13 @@ from pathlib import Path from ser._internal.data.application.models import DatasetUninstallWorkflowResult -from ser.config import AppConfig -from ser.data.dataset_prepare import ( +from ser._internal.data.dataset_prepare import ( default_dataset_root, default_manifest_path, resolve_dataset_descriptor, ) -from ser.data.dataset_registry import remove_dataset_registry_entry +from ser._internal.data.dataset_registry import remove_dataset_registry_entry +from ser.config import AppConfig def run_dataset_uninstall_workflow( diff --git a/ser/data/archive_extraction.py b/ser/_internal/data/archive_extraction.py similarity index 100% rename from ser/data/archive_extraction.py rename to ser/_internal/data/archive_extraction.py diff --git a/ser/data/catalog/__init__.py b/ser/_internal/data/catalog/__init__.py similarity index 100% rename from ser/data/catalog/__init__.py rename to ser/_internal/data/catalog/__init__.py diff --git a/ser/data/catalog/public_datasets.py b/ser/_internal/data/catalog/public_datasets.py similarity index 100% rename from ser/data/catalog/public_datasets.py rename to ser/_internal/data/catalog/public_datasets.py diff --git a/ser/data/cli.py b/ser/_internal/data/cli.py similarity index 99% rename from ser/data/cli.py rename to ser/_internal/data/cli.py index 41a516f..60b761b 100644 --- a/ser/data/cli.py +++ b/ser/_internal/data/cli.py @@ -7,6 +7,12 @@ import sys from pathlib import Path +from ser._internal.data.dataset_consents import ( + DatasetConsentError, + load_persisted_dataset_consents, + persist_dataset_consents, +) +from ser._internal.data.dataset_prepare import SUPPORTED_DATASETS from ser.config import AppConfig from ser.data.application import ( build_dataset_capability_snapshot_json_payload, @@ -18,12 +24,6 @@ run_dataset_prepare_workflow, run_dataset_uninstall_workflow, ) -from ser.data.dataset_consents import ( - DatasetConsentError, - load_persisted_dataset_consents, - persist_dataset_consents, -) -from ser.data.dataset_prepare import SUPPORTED_DATASETS def _is_interactive() -> bool: diff --git a/ser/data/data_loader.py b/ser/_internal/data/data_loader.py similarity index 96% rename from ser/data/data_loader.py rename to ser/_internal/data/data_loader.py index fc83a43..c7f2e3c 100644 --- a/ser/data/data_loader.py +++ b/ser/_internal/data/data_loader.py @@ -13,14 +13,14 @@ from numpy.typing import NDArray from sklearn.model_selection import train_test_split +from ser._internal.data.adapters.ravdess import build_ravdess_utterances +from ser._internal.data.dataset_prepare import prepare_from_registry_entry +from ser._internal.data.dataset_registry import load_dataset_registry +from ser._internal.data.label_ontology import resolve_label_ontology +from ser._internal.data.manifest import Utterance +from ser._internal.data.manifest_jsonl import load_manifest_jsonl +from ser._internal.features.feature_extractor import _extract_feature_for_settings from ser.config import AppConfig, AudioReadConfig, FeatureFlags, reload_settings -from ser.data.adapters.ravdess import build_ravdess_utterances -from ser.data.dataset_prepare import prepare_from_registry_entry -from ser.data.dataset_registry import load_dataset_registry -from ser.data.label_ontology import resolve_label_ontology -from ser.data.manifest import Utterance -from ser.data.manifest_jsonl import load_manifest_jsonl -from ser.features.feature_extractor import _extract_feature_for_settings from ser.utils.logger import get_logger logger: logging.Logger = get_logger(__name__) diff --git a/ser/data/dataset_capabilities.py b/ser/_internal/data/dataset_capabilities.py similarity index 100% rename from ser/data/dataset_capabilities.py rename to ser/_internal/data/dataset_capabilities.py diff --git a/ser/data/dataset_consents.py b/ser/_internal/data/dataset_consents.py similarity index 99% rename from ser/data/dataset_consents.py rename to ser/_internal/data/dataset_consents.py index c982de3..dd1e5b7 100644 --- a/ser/data/dataset_consents.py +++ b/ser/_internal/data/dataset_consents.py @@ -17,8 +17,8 @@ from dataclasses import dataclass from pathlib import Path +from ser._internal.data.manifest import Utterance from ser.config import AppConfig -from ser.data.manifest import Utterance _CONSENT_STORE_ENV = "SER_DATASET_CONSENTS_FILE" _CONSENT_SCHEMA_VERSION = 1 diff --git a/ser/data/dataset_prepare.py b/ser/_internal/data/dataset_prepare.py similarity index 99% rename from ser/data/dataset_prepare.py rename to ser/_internal/data/dataset_prepare.py index adccabd..f430a90 100644 --- a/ser/data/dataset_prepare.py +++ b/ser/_internal/data/dataset_prepare.py @@ -15,22 +15,22 @@ from pathlib import Path from time import perf_counter -from ser.config import AppConfig -from ser.data.dataset_registry import ( +from ser._internal.data.dataset_registry import ( DatasetRegistryEntry, load_dataset_registry, parse_dataset_registry_options, upsert_dataset_registry_entry, ) -from ser.data.msp_podcast_mirror import ( +from ser._internal.data.msp_podcast_mirror import ( DEFAULT_MSP_MIRROR_MANIFEST_FILE, ) -from ser.data.ontology import LabelOntology -from ser.data.strategies import ( +from ser._internal.data.ontology import LabelOntology +from ser._internal.data.strategies import ( DatasetStrategy, DatasetStrategyRegistry, build_default_dataset_strategies, ) +from ser.config import AppConfig from ser.utils.logger import get_logger logger = get_logger(__name__) diff --git a/ser/data/dataset_registry.py b/ser/_internal/data/dataset_registry.py similarity index 100% rename from ser/data/dataset_registry.py rename to ser/_internal/data/dataset_registry.py diff --git a/ser/data/embedding_cache.py b/ser/_internal/data/embedding_cache.py similarity index 99% rename from ser/data/embedding_cache.py rename to ser/_internal/data/embedding_cache.py index 0f12bda..023a34e 100644 --- a/ser/data/embedding_cache.py +++ b/ser/_internal/data/embedding_cache.py @@ -14,7 +14,7 @@ import numpy as np from numpy.typing import NDArray -from ser.repr import EncodedSequence +from ser._internal.repr import EncodedSequence from ser.utils.logger import get_logger logger: Logger = get_logger(__name__) diff --git a/ser/data/jl_corpus_downloads.py b/ser/_internal/data/jl_corpus_downloads.py similarity index 99% rename from ser/data/jl_corpus_downloads.py rename to ser/_internal/data/jl_corpus_downloads.py index 96c3200..be40da3 100644 --- a/ser/data/jl_corpus_downloads.py +++ b/ser/_internal/data/jl_corpus_downloads.py @@ -8,7 +8,7 @@ from typing import Protocol from urllib import parse -from ser.data.provider_dataset_preparation import ( +from ser._internal.data.provider_dataset_preparation import ( AutoDownloadArtifacts, GeneratedLabelsStats, GeneratedLabelsStatsLike, diff --git a/ser/data/label_ontology.py b/ser/_internal/data/label_ontology.py similarity index 94% rename from ser/data/label_ontology.py rename to ser/_internal/data/label_ontology.py index 3fadae5..7dd6a0c 100644 --- a/ser/data/label_ontology.py +++ b/ser/_internal/data/label_ontology.py @@ -5,8 +5,8 @@ import os from typing import cast +from ser._internal.data.ontology import LabelOntology, UnknownLabelPolicy, normalize_label from ser.config import AppConfig -from ser.data.ontology import LabelOntology, UnknownLabelPolicy, normalize_label def _read_unknown_label_policy_env() -> UnknownLabelPolicy: diff --git a/ser/data/manifest.py b/ser/_internal/data/manifest.py similarity index 98% rename from ser/data/manifest.py rename to ser/_internal/data/manifest.py index 0471ecd..31841fb 100644 --- a/ser/data/manifest.py +++ b/ser/_internal/data/manifest.py @@ -7,7 +7,7 @@ from pathlib import Path from typing import Literal, cast -from ser.data.ontology import LabelOntology, ensure_label_allowed, normalize_label +from ser._internal.data.ontology import LabelOntology, ensure_label_allowed, normalize_label MANIFEST_SCHEMA_VERSION = 1 diff --git a/ser/data/manifest_jsonl.py b/ser/_internal/data/manifest_jsonl.py similarity index 95% rename from ser/data/manifest_jsonl.py rename to ser/_internal/data/manifest_jsonl.py index 32489ea..351a4e6 100644 --- a/ser/data/manifest_jsonl.py +++ b/ser/_internal/data/manifest_jsonl.py @@ -7,8 +7,8 @@ from pathlib import Path from typing import cast -from ser.data.manifest import Utterance -from ser.data.ontology import LabelOntology +from ser._internal.data.manifest import Utterance +from ser._internal.data.ontology import LabelOntology def load_manifest_jsonl( diff --git a/ser/data/mendeley_dataset_preparation.py b/ser/_internal/data/mendeley_dataset_preparation.py similarity index 98% rename from ser/data/mendeley_dataset_preparation.py rename to ser/_internal/data/mendeley_dataset_preparation.py index f4a7356..fdb9d2b 100644 --- a/ser/data/mendeley_dataset_preparation.py +++ b/ser/_internal/data/mendeley_dataset_preparation.py @@ -6,7 +6,7 @@ from pathlib import Path from typing import Protocol -from ser.data.provider_dataset_preparation import ( +from ser._internal.data.provider_dataset_preparation import ( AutoDownloadArtifacts, GeneratedLabelsStatsLike, ) diff --git a/ser/data/mendeley_downloads.py b/ser/_internal/data/mendeley_downloads.py similarity index 100% rename from ser/data/mendeley_downloads.py rename to ser/_internal/data/mendeley_downloads.py diff --git a/ser/data/msp_podcast_mirror.py b/ser/_internal/data/msp_podcast_mirror.py similarity index 100% rename from ser/data/msp_podcast_mirror.py rename to ser/_internal/data/msp_podcast_mirror.py diff --git a/ser/data/ontology.py b/ser/_internal/data/ontology.py similarity index 100% rename from ser/data/ontology.py rename to ser/_internal/data/ontology.py diff --git a/ser/data/openslr_dataset_preparation.py b/ser/_internal/data/openslr_dataset_preparation.py similarity index 98% rename from ser/data/openslr_dataset_preparation.py rename to ser/_internal/data/openslr_dataset_preparation.py index da0e66c..720f915 100644 --- a/ser/data/openslr_dataset_preparation.py +++ b/ser/_internal/data/openslr_dataset_preparation.py @@ -6,7 +6,7 @@ from pathlib import Path from typing import Protocol, TypeVar -from ser.data.provider_dataset_preparation import ( +from ser._internal.data.provider_dataset_preparation import ( AutoDownloadArtifacts, GeneratedLabelsStatsLike, ) diff --git a/ser/data/openslr_downloads.py b/ser/_internal/data/openslr_downloads.py similarity index 99% rename from ser/data/openslr_downloads.py rename to ser/_internal/data/openslr_downloads.py index 9726cd6..109dd8b 100644 --- a/ser/data/openslr_downloads.py +++ b/ser/_internal/data/openslr_downloads.py @@ -7,7 +7,7 @@ from typing import Protocol from urllib import parse, request -from ser.data.openslr_resolution import OpenSlrPinnedArtifact +from ser._internal.data.openslr_resolution import OpenSlrPinnedArtifact class _DownloadFile(Protocol): diff --git a/ser/data/openslr_resolution.py b/ser/_internal/data/openslr_resolution.py similarity index 100% rename from ser/data/openslr_resolution.py rename to ser/_internal/data/openslr_resolution.py diff --git a/ser/data/provider_dataset_preparation.py b/ser/_internal/data/provider_dataset_preparation.py similarity index 100% rename from ser/data/provider_dataset_preparation.py rename to ser/_internal/data/provider_dataset_preparation.py diff --git a/ser/data/provider_downloads.py b/ser/_internal/data/provider_downloads.py similarity index 100% rename from ser/data/provider_downloads.py rename to ser/_internal/data/provider_downloads.py diff --git a/ser/data/public_dataset_downloads.py b/ser/_internal/data/public_dataset_downloads.py similarity index 95% rename from ser/data/public_dataset_downloads.py rename to ser/_internal/data/public_dataset_downloads.py index c75b0f3..92e58ea 100644 --- a/ser/data/public_dataset_downloads.py +++ b/ser/_internal/data/public_dataset_downloads.py @@ -15,29 +15,33 @@ from collections.abc import Callable from pathlib import Path -from ser.data import archive_extraction as archive_extraction_helpers -from ser.data import jl_corpus_downloads as jl_corpus_download_helpers -from ser.data import mendeley_dataset_preparation as mendeley_dataset_preparation_helpers -from ser.data import mendeley_downloads as mendeley_download_helpers -from ser.data import openslr_dataset_preparation as openslr_dataset_preparation_helpers -from ser.data import openslr_downloads as openslr_download_helpers -from ser.data import openslr_resolution as openslr_resolution_helpers -from ser.data import provider_dataset_preparation as provider_dataset_preparation_helpers -from ser.data import provider_downloads as provider_download_helpers -from ser.data import zenodo_downloads as zenodo_download_helpers -from ser.data.provider_dataset_preparation import ( +from ser._internal.data import archive_extraction as archive_extraction_helpers +from ser._internal.data import jl_corpus_downloads as jl_corpus_download_helpers +from ser._internal.data import mendeley_dataset_preparation as mendeley_dataset_preparation_helpers +from ser._internal.data import mendeley_downloads as mendeley_download_helpers +from ser._internal.data import openslr_dataset_preparation as openslr_dataset_preparation_helpers +from ser._internal.data import openslr_downloads as openslr_download_helpers +from ser._internal.data import openslr_resolution as openslr_resolution_helpers +from ser._internal.data import provider_dataset_preparation as provider_dataset_preparation_helpers +from ser._internal.data import provider_downloads as provider_download_helpers +from ser._internal.data import zenodo_downloads as zenodo_download_helpers +from ser._internal.data.provider_dataset_preparation import ( AutoDownloadArtifacts, GeneratedLabelsStats, ) -from ser.data.public_dataset_label_inference import infer_att_hack_label as _infer_att_hack_label -from ser.data.public_dataset_label_inference import infer_coraa_ser_label as _infer_coraa_ser_label -from ser.data.public_dataset_label_inference import ( +from ser._internal.data.public_dataset_label_inference import ( + infer_att_hack_label as _infer_att_hack_label, +) +from ser._internal.data.public_dataset_label_inference import ( + infer_coraa_ser_label as _infer_coraa_ser_label, +) +from ser._internal.data.public_dataset_label_inference import ( infer_escorpus_pe_label as _infer_escorpus_pe_label, ) -from ser.data.public_dataset_label_inference import ( +from ser._internal.data.public_dataset_label_inference import ( infer_label_from_path_tokens as _infer_label_from_path_tokens, ) -from ser.data.public_dataset_label_inference import infer_mesd_label as _infer_mesd_label +from ser._internal.data.public_dataset_label_inference import infer_mesd_label as _infer_mesd_label from ser.utils.logger import get_logger logger = get_logger(__name__) diff --git a/ser/data/public_dataset_label_inference.py b/ser/_internal/data/public_dataset_label_inference.py similarity index 100% rename from ser/data/public_dataset_label_inference.py rename to ser/_internal/data/public_dataset_label_inference.py diff --git a/ser/data/strategies/__init__.py b/ser/_internal/data/strategies/__init__.py similarity index 100% rename from ser/data/strategies/__init__.py rename to ser/_internal/data/strategies/__init__.py diff --git a/ser/data/strategies/auto_csv.py b/ser/_internal/data/strategies/auto_csv.py similarity index 96% rename from ser/data/strategies/auto_csv.py rename to ser/_internal/data/strategies/auto_csv.py index 1f3ce47..3fc6978 100644 --- a/ser/data/strategies/auto_csv.py +++ b/ser/_internal/data/strategies/auto_csv.py @@ -6,8 +6,7 @@ from pathlib import Path from typing import TYPE_CHECKING -from ser.config import AppConfig -from ser.data.adapters.public_csv_datasets import ( +from ser._internal.data.adapters.public_csv_datasets import ( build_asvp_esd_manifest_jsonl, build_att_hack_manifest_jsonl, build_cafe_manifest_jsonl, @@ -21,8 +20,8 @@ build_pavoque_manifest_jsonl, build_spanish_meacorpus_2023_manifest_jsonl, ) -from ser.data.ontology import LabelOntology -from ser.data.public_dataset_downloads import ( +from ser._internal.data.ontology import LabelOntology +from ser._internal.data.public_dataset_downloads import ( DEFAULT_LABELS_FILE_NAME, AutoDownloadArtifacts, prepare_asvp_esd_from_zenodo, @@ -38,13 +37,14 @@ prepare_pavoque_from_github_release, prepare_spanish_meacorpus_2023_from_zenodo, ) +from ser.config import AppConfig from ser.utils.logger import get_logger from .base import PreparedManifestResult if TYPE_CHECKING: - from ser.data.dataset_prepare import DatasetDescriptor - from ser.data.manifest import Utterance + from ser._internal.data.dataset_prepare import DatasetDescriptor + from ser._internal.data.manifest import Utterance logger = get_logger(__name__) diff --git a/ser/data/strategies/base.py b/ser/_internal/data/strategies/base.py similarity index 95% rename from ser/data/strategies/base.py rename to ser/_internal/data/strategies/base.py index 4aafdb3..dac6f79 100644 --- a/ser/data/strategies/base.py +++ b/ser/_internal/data/strategies/base.py @@ -8,11 +8,11 @@ from types import MappingProxyType from typing import TYPE_CHECKING, Protocol +from ser._internal.data.ontology import LabelOntology from ser.config import AppConfig -from ser.data.ontology import LabelOntology if TYPE_CHECKING: - from ser.data.dataset_prepare import DatasetDescriptor + from ser._internal.data.dataset_prepare import DatasetDescriptor @dataclass(frozen=True, slots=True) diff --git a/ser/data/strategies/default.py b/ser/_internal/data/strategies/default.py similarity index 95% rename from ser/data/strategies/default.py rename to ser/_internal/data/strategies/default.py index bad57e7..0fae0db 100644 --- a/ser/data/strategies/default.py +++ b/ser/_internal/data/strategies/default.py @@ -7,22 +7,22 @@ from pathlib import Path from typing import TYPE_CHECKING -from ser.config import AppConfig -from ser.data.adapters.biic_podcast import build_biic_podcast_manifest_jsonl -from ser.data.adapters.crema_d import build_crema_d_manifest_jsonl -from ser.data.adapters.msp_podcast import build_msp_podcast_manifest_jsonl -from ser.data.adapters.ravdess import build_ravdess_manifest_jsonl -from ser.data.msp_podcast_mirror import ( +from ser._internal.data.adapters.biic_podcast import build_biic_podcast_manifest_jsonl +from ser._internal.data.adapters.crema_d import build_crema_d_manifest_jsonl +from ser._internal.data.adapters.msp_podcast import build_msp_podcast_manifest_jsonl +from ser._internal.data.adapters.ravdess import build_ravdess_manifest_jsonl +from ser._internal.data.msp_podcast_mirror import ( DEFAULT_MSP_MIRROR_AUDIO_SUBDIR, DEFAULT_MSP_MIRROR_LABELS_FILE, DEFAULT_MSP_MIRROR_REPO_ID, DEFAULT_MSP_MIRROR_REVISION, prepare_msp_podcast_from_hf_mirror, ) -from ser.data.ontology import LabelOntology -from ser.data.public_dataset_downloads import ( +from ser._internal.data.ontology import LabelOntology +from ser._internal.data.public_dataset_downloads import ( prepare_ravdess_from_zenodo, ) +from ser.config import AppConfig from ser.utils.logger import get_logger from .auto_csv import ( @@ -42,7 +42,7 @@ from .base import DatasetStrategy, PreparedManifestResult if TYPE_CHECKING: - from ser.data.dataset_prepare import DatasetDescriptor + from ser._internal.data.dataset_prepare import DatasetDescriptor logger = get_logger(__name__) diff --git a/ser/data/zenodo_downloads.py b/ser/_internal/data/zenodo_downloads.py similarity index 99% rename from ser/data/zenodo_downloads.py rename to ser/_internal/data/zenodo_downloads.py index 1b3d687..52c2d46 100644 --- a/ser/data/zenodo_downloads.py +++ b/ser/_internal/data/zenodo_downloads.py @@ -8,7 +8,7 @@ from pathlib import Path from typing import Protocol -from ser.data.provider_dataset_preparation import ( +from ser._internal.data.provider_dataset_preparation import ( AutoDownloadArtifacts, GeneratedLabelsStats, GeneratedLabelsStatsLike, diff --git a/ser/diagnostics/command.py b/ser/_internal/diagnostics/command.py similarity index 100% rename from ser/diagnostics/command.py rename to ser/_internal/diagnostics/command.py diff --git a/ser/diagnostics/service.py b/ser/_internal/diagnostics/service.py similarity index 100% rename from ser/diagnostics/service.py rename to ser/_internal/diagnostics/service.py diff --git a/ser/features/feature_extractor.py b/ser/_internal/features/feature_extractor.py similarity index 100% rename from ser/features/feature_extractor.py rename to ser/_internal/features/feature_extractor.py diff --git a/ser/heads/torch_head.py b/ser/_internal/heads/torch_head.py similarity index 100% rename from ser/heads/torch_head.py rename to ser/_internal/heads/torch_head.py diff --git a/ser/models/accurate_feature_dataset.py b/ser/_internal/models/accurate_feature_dataset.py similarity index 100% rename from ser/models/accurate_feature_dataset.py rename to ser/_internal/models/accurate_feature_dataset.py diff --git a/ser/models/accurate_training_execution.py b/ser/_internal/models/accurate_training_execution.py similarity index 100% rename from ser/models/accurate_training_execution.py rename to ser/_internal/models/accurate_training_execution.py diff --git a/ser/models/accurate_training_preparation.py b/ser/_internal/models/accurate_training_preparation.py similarity index 100% rename from ser/models/accurate_training_preparation.py rename to ser/_internal/models/accurate_training_preparation.py diff --git a/ser/models/artifact_envelope.py b/ser/_internal/models/artifact_envelope.py similarity index 100% rename from ser/models/artifact_envelope.py rename to ser/_internal/models/artifact_envelope.py diff --git a/ser/models/artifact_loading.py b/ser/_internal/models/artifact_loading.py similarity index 100% rename from ser/models/artifact_loading.py rename to ser/_internal/models/artifact_loading.py diff --git a/ser/models/artifact_metadata.py b/ser/_internal/models/artifact_metadata.py similarity index 100% rename from ser/models/artifact_metadata.py rename to ser/_internal/models/artifact_metadata.py diff --git a/ser/models/artifact_persistence.py b/ser/_internal/models/artifact_persistence.py similarity index 100% rename from ser/models/artifact_persistence.py rename to ser/_internal/models/artifact_persistence.py diff --git a/ser/models/dataset_controls.py b/ser/_internal/models/dataset_controls.py similarity index 100% rename from ser/models/dataset_controls.py rename to ser/_internal/models/dataset_controls.py diff --git a/ser/models/dataset_splitting.py b/ser/_internal/models/dataset_splitting.py similarity index 100% rename from ser/models/dataset_splitting.py rename to ser/_internal/models/dataset_splitting.py diff --git a/ser/models/dataset_training_consents.py b/ser/_internal/models/dataset_training_consents.py similarity index 100% rename from ser/models/dataset_training_consents.py rename to ser/_internal/models/dataset_training_consents.py diff --git a/ser/models/emotion_model.py b/ser/_internal/models/emotion_model.py similarity index 100% rename from ser/models/emotion_model.py rename to ser/_internal/models/emotion_model.py diff --git a/ser/models/fast_path.py b/ser/_internal/models/fast_path.py similarity index 100% rename from ser/models/fast_path.py rename to ser/_internal/models/fast_path.py diff --git a/ser/models/fast_training.py b/ser/_internal/models/fast_training.py similarity index 100% rename from ser/models/fast_training.py rename to ser/_internal/models/fast_training.py diff --git a/ser/models/feature_runtime_encoding.py b/ser/_internal/models/feature_runtime_encoding.py similarity index 100% rename from ser/models/feature_runtime_encoding.py rename to ser/_internal/models/feature_runtime_encoding.py diff --git a/ser/models/medium_feature_dataset.py b/ser/_internal/models/medium_feature_dataset.py similarity index 100% rename from ser/models/medium_feature_dataset.py rename to ser/_internal/models/medium_feature_dataset.py diff --git a/ser/models/medium_noise_controls.py b/ser/_internal/models/medium_noise_controls.py similarity index 100% rename from ser/models/medium_noise_controls.py rename to ser/_internal/models/medium_noise_controls.py diff --git a/ser/models/medium_training_preparation.py b/ser/_internal/models/medium_training_preparation.py similarity index 100% rename from ser/models/medium_training_preparation.py rename to ser/_internal/models/medium_training_preparation.py diff --git a/ser/models/profile_runtime.py b/ser/_internal/models/profile_runtime.py similarity index 100% rename from ser/models/profile_runtime.py rename to ser/_internal/models/profile_runtime.py diff --git a/ser/models/profile_training_preparation.py b/ser/_internal/models/profile_training_preparation.py similarity index 100% rename from ser/models/profile_training_preparation.py rename to ser/_internal/models/profile_training_preparation.py diff --git a/ser/models/training_entrypoints.py b/ser/_internal/models/training_entrypoints.py similarity index 100% rename from ser/models/training_entrypoints.py rename to ser/_internal/models/training_entrypoints.py diff --git a/ser/models/training_execution.py b/ser/_internal/models/training_execution.py similarity index 100% rename from ser/models/training_execution.py rename to ser/_internal/models/training_execution.py diff --git a/ser/models/training_preparation.py b/ser/_internal/models/training_preparation.py similarity index 100% rename from ser/models/training_preparation.py rename to ser/_internal/models/training_preparation.py diff --git a/ser/models/training_reporting.py b/ser/_internal/models/training_reporting.py similarity index 100% rename from ser/models/training_reporting.py rename to ser/_internal/models/training_reporting.py diff --git a/ser/models/training_support.py b/ser/_internal/models/training_support.py similarity index 100% rename from ser/models/training_support.py rename to ser/_internal/models/training_support.py diff --git a/ser/models/training_types.py b/ser/_internal/models/training_types.py similarity index 100% rename from ser/models/training_types.py rename to ser/_internal/models/training_types.py diff --git a/ser/repr/backend.py b/ser/_internal/repr/backend.py similarity index 100% rename from ser/repr/backend.py rename to ser/_internal/repr/backend.py diff --git a/ser/repr/emotion2vec.py b/ser/_internal/repr/emotion2vec.py similarity index 100% rename from ser/repr/emotion2vec.py rename to ser/_internal/repr/emotion2vec.py diff --git a/ser/repr/handcrafted.py b/ser/_internal/repr/handcrafted.py similarity index 100% rename from ser/repr/handcrafted.py rename to ser/_internal/repr/handcrafted.py diff --git a/ser/repr/hf_whisper.py b/ser/_internal/repr/hf_whisper.py similarity index 100% rename from ser/repr/hf_whisper.py rename to ser/_internal/repr/hf_whisper.py diff --git a/ser/repr/hf_xlsr.py b/ser/_internal/repr/hf_xlsr.py similarity index 100% rename from ser/repr/hf_xlsr.py rename to ser/_internal/repr/hf_xlsr.py diff --git a/ser/repr/runtime_policy.py b/ser/_internal/repr/runtime_policy.py similarity index 100% rename from ser/repr/runtime_policy.py rename to ser/_internal/repr/runtime_policy.py diff --git a/ser/runtime/accurate_execution.py b/ser/_internal/runtime/accurate_execution.py similarity index 100% rename from ser/runtime/accurate_execution.py rename to ser/_internal/runtime/accurate_execution.py diff --git a/ser/runtime/accurate_inference.py b/ser/_internal/runtime/accurate_inference.py similarity index 100% rename from ser/runtime/accurate_inference.py rename to ser/_internal/runtime/accurate_inference.py diff --git a/ser/runtime/accurate_model_contract.py b/ser/_internal/runtime/accurate_model_contract.py similarity index 100% rename from ser/runtime/accurate_model_contract.py rename to ser/_internal/runtime/accurate_model_contract.py diff --git a/ser/runtime/accurate_prediction.py b/ser/_internal/runtime/accurate_prediction.py similarity index 100% rename from ser/runtime/accurate_prediction.py rename to ser/_internal/runtime/accurate_prediction.py diff --git a/ser/runtime/accurate_research_inference.py b/ser/_internal/runtime/accurate_research_inference.py similarity index 100% rename from ser/runtime/accurate_research_inference.py rename to ser/_internal/runtime/accurate_research_inference.py diff --git a/ser/runtime/benchmarks.py b/ser/_internal/runtime/benchmarks.py similarity index 100% rename from ser/runtime/benchmarks.py rename to ser/_internal/runtime/benchmarks.py diff --git a/ser/runtime/fast_inference.py b/ser/_internal/runtime/fast_inference.py similarity index 100% rename from ser/runtime/fast_inference.py rename to ser/_internal/runtime/fast_inference.py diff --git a/ser/runtime/medium_execution.py b/ser/_internal/runtime/medium_execution.py similarity index 100% rename from ser/runtime/medium_execution.py rename to ser/_internal/runtime/medium_execution.py diff --git a/ser/runtime/medium_inference.py b/ser/_internal/runtime/medium_inference.py similarity index 100% rename from ser/runtime/medium_inference.py rename to ser/_internal/runtime/medium_inference.py diff --git a/ser/runtime/medium_prediction.py b/ser/_internal/runtime/medium_prediction.py similarity index 100% rename from ser/runtime/medium_prediction.py rename to ser/_internal/runtime/medium_prediction.py diff --git a/ser/runtime/mps_oom.py b/ser/_internal/runtime/mps_oom.py similarity index 100% rename from ser/runtime/mps_oom.py rename to ser/_internal/runtime/mps_oom.py diff --git a/ser/runtime/phase_contract.py b/ser/_internal/runtime/phase_contract.py similarity index 100% rename from ser/runtime/phase_contract.py rename to ser/_internal/runtime/phase_contract.py diff --git a/ser/runtime/phase_timing.py b/ser/_internal/runtime/phase_timing.py similarity index 100% rename from ser/runtime/phase_timing.py rename to ser/_internal/runtime/phase_timing.py diff --git a/ser/runtime/pipeline.py b/ser/_internal/runtime/pipeline.py similarity index 100% rename from ser/runtime/pipeline.py rename to ser/_internal/runtime/pipeline.py diff --git a/ser/runtime/postprocessing.py b/ser/_internal/runtime/postprocessing.py similarity index 100% rename from ser/runtime/postprocessing.py rename to ser/_internal/runtime/postprocessing.py diff --git a/ser/runtime/profile_quality_gate.py b/ser/_internal/runtime/profile_quality_gate.py similarity index 100% rename from ser/runtime/profile_quality_gate.py rename to ser/_internal/runtime/profile_quality_gate.py diff --git a/ser/runtime/quality_gate_cli.py b/ser/_internal/runtime/quality_gate_cli.py similarity index 100% rename from ser/runtime/quality_gate_cli.py rename to ser/_internal/runtime/quality_gate_cli.py diff --git a/ser/runtime/quality_gate_evaluation.py b/ser/_internal/runtime/quality_gate_evaluation.py similarity index 100% rename from ser/runtime/quality_gate_evaluation.py rename to ser/_internal/runtime/quality_gate_evaluation.py diff --git a/ser/runtime/quality_gate_policy.py b/ser/_internal/runtime/quality_gate_policy.py similarity index 100% rename from ser/runtime/quality_gate_policy.py rename to ser/_internal/runtime/quality_gate_policy.py diff --git a/ser/runtime/quality_gate_reporting.py b/ser/_internal/runtime/quality_gate_reporting.py similarity index 100% rename from ser/runtime/quality_gate_reporting.py rename to ser/_internal/runtime/quality_gate_reporting.py diff --git a/ser/transcript/backends/factory.py b/ser/_internal/transcript/backends/factory.py similarity index 100% rename from ser/transcript/backends/factory.py rename to ser/_internal/transcript/backends/factory.py diff --git a/ser/transcript/backends/faster_whisper.py b/ser/_internal/transcript/backends/faster_whisper.py similarity index 100% rename from ser/transcript/backends/faster_whisper.py rename to ser/_internal/transcript/backends/faster_whisper.py diff --git a/ser/transcript/backends/stable_whisper.py b/ser/_internal/transcript/backends/stable_whisper.py similarity index 100% rename from ser/transcript/backends/stable_whisper.py rename to ser/_internal/transcript/backends/stable_whisper.py diff --git a/ser/transcript/backends/stable_whisper_admission_runtime.py b/ser/_internal/transcript/backends/stable_whisper_admission_runtime.py similarity index 100% rename from ser/transcript/backends/stable_whisper_admission_runtime.py rename to ser/_internal/transcript/backends/stable_whisper_admission_runtime.py diff --git a/ser/transcript/backends/stable_whisper_mps_compat.py b/ser/_internal/transcript/backends/stable_whisper_mps_compat.py similarity index 100% rename from ser/transcript/backends/stable_whisper_mps_compat.py rename to ser/_internal/transcript/backends/stable_whisper_mps_compat.py diff --git a/ser/transcript/backends/stable_whisper_torio_probe.py b/ser/_internal/transcript/backends/stable_whisper_torio_probe.py similarity index 100% rename from ser/transcript/backends/stable_whisper_torio_probe.py rename to ser/_internal/transcript/backends/stable_whisper_torio_probe.py diff --git a/ser/transcript/backends/stable_whisper_transcribe_admission.py b/ser/_internal/transcript/backends/stable_whisper_transcribe_admission.py similarity index 100% rename from ser/transcript/backends/stable_whisper_transcribe_admission.py rename to ser/_internal/transcript/backends/stable_whisper_transcribe_admission.py diff --git a/ser/transcript/backends/stable_whisper_transcribe_execution.py b/ser/_internal/transcript/backends/stable_whisper_transcribe_execution.py similarity index 100% rename from ser/transcript/backends/stable_whisper_transcribe_execution.py rename to ser/_internal/transcript/backends/stable_whisper_transcribe_execution.py diff --git a/ser/transcript/backends/stable_whisper_transcribe_kwargs.py b/ser/_internal/transcript/backends/stable_whisper_transcribe_kwargs.py similarity index 100% rename from ser/transcript/backends/stable_whisper_transcribe_kwargs.py rename to ser/_internal/transcript/backends/stable_whisper_transcribe_kwargs.py diff --git a/ser/transcript/backends/stable_whisper_transcribe_runtime.py b/ser/_internal/transcript/backends/stable_whisper_transcribe_runtime.py similarity index 100% rename from ser/transcript/backends/stable_whisper_transcribe_runtime.py rename to ser/_internal/transcript/backends/stable_whisper_transcribe_runtime.py diff --git a/ser/transcript/mps_admission.py b/ser/_internal/transcript/mps_admission.py similarity index 100% rename from ser/transcript/mps_admission.py rename to ser/_internal/transcript/mps_admission.py diff --git a/ser/transcript/mps_admission_overrides.py b/ser/_internal/transcript/mps_admission_overrides.py similarity index 100% rename from ser/transcript/mps_admission_overrides.py rename to ser/_internal/transcript/mps_admission_overrides.py diff --git a/ser/transcript/profiling.py b/ser/_internal/transcript/profiling.py similarity index 100% rename from ser/transcript/profiling.py rename to ser/_internal/transcript/profiling.py diff --git a/ser/transcript/runtime_failures.py b/ser/_internal/transcript/runtime_failures.py similarity index 100% rename from ser/transcript/runtime_failures.py rename to ser/_internal/transcript/runtime_failures.py diff --git a/ser/transcript/runtime_policy.py b/ser/_internal/transcript/runtime_policy.py similarity index 100% rename from ser/transcript/runtime_policy.py rename to ser/_internal/transcript/runtime_policy.py diff --git a/ser/transcript/transcript_extractor.py b/ser/_internal/transcript/transcript_extractor.py similarity index 100% rename from ser/transcript/transcript_extractor.py rename to ser/_internal/transcript/transcript_extractor.py diff --git a/ser/utils/audio_utils.py b/ser/_internal/utils/audio_utils.py similarity index 100% rename from ser/utils/audio_utils.py rename to ser/_internal/utils/audio_utils.py diff --git a/ser/utils/dsp.py b/ser/_internal/utils/dsp.py similarity index 100% rename from ser/utils/dsp.py rename to ser/_internal/utils/dsp.py diff --git a/ser/utils/segment_canonicalization.py b/ser/_internal/utils/segment_canonicalization.py similarity index 100% rename from ser/utils/segment_canonicalization.py rename to ser/_internal/utils/segment_canonicalization.py diff --git a/ser/utils/subtitles.py b/ser/_internal/utils/subtitles.py similarity index 100% rename from ser/utils/subtitles.py rename to ser/_internal/utils/subtitles.py diff --git a/ser/utils/timeline_utils.py b/ser/_internal/utils/timeline_utils.py similarity index 100% rename from ser/utils/timeline_utils.py rename to ser/_internal/utils/timeline_utils.py diff --git a/ser/utils/torch_inference.py b/ser/_internal/utils/torch_inference.py similarity index 100% rename from ser/utils/torch_inference.py rename to ser/_internal/utils/torch_inference.py diff --git a/ser/utils/transcription_compat.py b/ser/_internal/utils/transcription_compat.py similarity index 100% rename from ser/utils/transcription_compat.py rename to ser/_internal/utils/transcription_compat.py diff --git a/ser/data/__init__.py b/ser/data/__init__.py index 0437df1..d93c929 100644 --- a/ser/data/__init__.py +++ b/ser/data/__init__.py @@ -1,20 +1,30 @@ -from .data_loader import ( - DataSplit, - LabeledAudioSample, - load_data, - load_labeled_audio_paths, - load_utterances, -) -from .embedding_cache import EmbeddingCache, EmbeddingCacheEntry -from .manifest import Utterance +# pyright: reportUnsupportedDunderAll=false +"""Public data package facade with curated training data exports.""" -__all__ = [ - "DataSplit", - "EmbeddingCache", - "EmbeddingCacheEntry", - "LabeledAudioSample", - "Utterance", - "load_data", - "load_labeled_audio_paths", - "load_utterances", -] +from __future__ import annotations + +import importlib +from typing import Any + +_EXPORT_MODULES: dict[str, str] = { + "DataSplit": "ser._internal.data.data_loader", + "EmbeddingCache": "ser._internal.data.embedding_cache", + "EmbeddingCacheEntry": "ser._internal.data.embedding_cache", + "LabeledAudioSample": "ser._internal.data.data_loader", + "Utterance": "ser._internal.data.manifest", + "load_data": "ser._internal.data.data_loader", + "load_labeled_audio_paths": "ser._internal.data.data_loader", + "load_utterances": "ser._internal.data.data_loader", +} + +__all__ = sorted(_EXPORT_MODULES) + + +def __getattr__(name: str) -> Any: + """Lazily resolves one curated data facade export.""" + module_name = _EXPORT_MODULES.get(name) + if module_name is None: + raise AttributeError(f"module 'ser.data' has no attribute {name!r}") + value = getattr(importlib.import_module(module_name), name) + globals()[name] = value + return value diff --git a/ser/data/application.py b/ser/data/application.py index a6b079e..e722f54 100644 --- a/ser/data/application.py +++ b/ser/data/application.py @@ -21,13 +21,13 @@ collect_dataset_registry_snapshot, ) from ser._internal.data.application.uninstall import run_dataset_uninstall_workflow -from ser.config import AppConfig -from ser.data.dataset_consents import ( +from ser._internal.data.dataset_consents import ( is_policy_restricted, load_persisted_dataset_consents, persist_dataset_consents, ) -from ser.data.dataset_prepare import resolve_dataset_descriptor +from ser._internal.data.dataset_prepare import resolve_dataset_descriptor +from ser.config import AppConfig __all__ = [ "DatasetCapabilitySnapshotEntry", diff --git a/tests/suites/integration/api/test_api.py b/tests/suites/integration/api/test_api.py index 2522b0a..20f8d26 100644 --- a/tests/suites/integration/api/test_api.py +++ b/tests/suites/integration/api/test_api.py @@ -16,14 +16,14 @@ import ser._internal.runtime.restricted_backends as restricted_backends_module import ser.api as api import ser.config as config_module +from ser._internal.data.dataset_consents import DatasetConsentError +from ser._internal.data.dataset_registry import upsert_dataset_registry_entry from ser.config import AppConfig from ser.data.application import ( DatasetRegistrySnapshot, DatasetRegistrySnapshotEntry, DatasetRegistrySnapshotIssue, ) -from ser.data.dataset_consents import DatasetConsentError -from ser.data.dataset_registry import upsert_dataset_registry_entry from ser.diagnostics.domain import DiagnosticFinding, DiagnosticReport from ser.license_check import BackendLicensePolicyError from ser.runtime import InferenceExecution, InferenceRequest @@ -57,7 +57,7 @@ def _run_data_command(argv: list[str], *, settings: AppConfig) -> int: captured["settings"] = settings return 7 - monkeypatch.setattr("ser.data.cli.run_data_command", _run_data_command) + monkeypatch.setattr("ser._internal.data.cli.run_data_command", _run_data_command) exit_code = api_data_module.run_data_command( ["download", "--dataset", "ravdess"], @@ -82,7 +82,7 @@ def _run_doctor_command(argv: list[str], *, settings: AppConfig | None = None) - captured["settings"] = settings return 3 - monkeypatch.setattr("ser.diagnostics.command.run_doctor_command", _run_doctor_command) + monkeypatch.setattr("ser._internal.diagnostics.command.run_doctor_command", _run_doctor_command) exit_code = api_diagnostics_module.run_doctor_command(["--strict"], settings=settings) @@ -100,14 +100,14 @@ def test_run_transcription_runtime_calibration_workflow_delegates_to_profiling( sentinel = object() monkeypatch.setattr( - "ser.transcript.profiling.parse_calibration_profiles", + "ser._internal.transcript.profiling.parse_calibration_profiles", lambda raw: ( captured.setdefault("profiles_raw", raw), ("medium", "accurate"), )[1], ) monkeypatch.setattr( - "ser.transcript.profiling.run_transcription_runtime_calibration", + "ser._internal.transcript.profiling.run_transcription_runtime_calibration", lambda **kwargs: ( captured.setdefault("kwargs", kwargs), sentinel, @@ -502,7 +502,7 @@ def _prepare_msp_podcast_from_hf_mirror(**kwargs: object) -> object: return sentinel monkeypatch.setattr( - "ser.data.msp_podcast_mirror.prepare_msp_podcast_from_hf_mirror", + "ser._internal.data.msp_podcast_mirror.prepare_msp_podcast_from_hf_mirror", _prepare_msp_podcast_from_hf_mirror, ) @@ -945,7 +945,7 @@ def test_suppress_preflight_transcription_operational_relogs_marks_emitted( lambda _profile: ("faster_whisper", "distil-large-v3", False, True), ) monkeypatch.setattr( - "ser.transcript.transcript_extractor.mark_compatibility_issues_as_emitted", + "ser._internal.transcript.transcript_extractor.mark_compatibility_issues_as_emitted", lambda **kwargs: captured.update(kwargs), ) diff --git a/tests/suites/integration/cli/test_data_cli.py b/tests/suites/integration/cli/test_data_cli.py index 8313dab..8084715 100644 --- a/tests/suites/integration/cli/test_data_cli.py +++ b/tests/suites/integration/cli/test_data_cli.py @@ -8,8 +8,10 @@ import pytest +import ser._internal.data.cli as data_cli +from ser._internal.data.dataset_consents import PersistedDatasetConsents +from ser._internal.data.dataset_prepare import DatasetDescriptor from ser.config import AppConfig -from ser.data import cli as data_cli from ser.data.application import ( DatasetCapabilitySnapshotEntry, DatasetPrepareWorkflowResult, @@ -18,8 +20,6 @@ DatasetRegistrySnapshotIssue, DatasetUninstallWorkflowResult, ) -from ser.data.dataset_consents import PersistedDatasetConsents -from ser.data.dataset_prepare import DatasetDescriptor def _settings(tmp_path: Path) -> AppConfig: diff --git a/tests/suites/integration/data/test_data_application.py b/tests/suites/integration/data/test_data_application.py index 75a52f9..1b3a27f 100644 --- a/tests/suites/integration/data/test_data_application.py +++ b/tests/suites/integration/data/test_data_application.py @@ -8,12 +8,12 @@ import pytest +from ser._internal.data.dataset_capabilities import list_dataset_capability_profiles +from ser._internal.data.dataset_consents import load_persisted_dataset_consents +from ser._internal.data.dataset_prepare import SUPPORTED_DATASETS, default_dataset_root +from ser._internal.data.dataset_registry import upsert_dataset_registry_entry from ser.config import AppConfig from ser.data import application as data_application -from ser.data.dataset_capabilities import list_dataset_capability_profiles -from ser.data.dataset_consents import load_persisted_dataset_consents -from ser.data.dataset_prepare import SUPPORTED_DATASETS, default_dataset_root -from ser.data.dataset_registry import upsert_dataset_registry_entry def _settings(tmp_path: Path) -> AppConfig: diff --git a/tests/suites/integration/data/test_dataset_prepare.py b/tests/suites/integration/data/test_dataset_prepare.py index bf8b162..ba02658 100644 --- a/tests/suites/integration/data/test_dataset_prepare.py +++ b/tests/suites/integration/data/test_dataset_prepare.py @@ -9,12 +9,12 @@ import pytest +import ser._internal.data.dataset_prepare as dp +from ser._internal.data.dataset_registry import DatasetRegistryEntry +from ser._internal.data.manifest import MANIFEST_SCHEMA_VERSION, Utterance +from ser._internal.data.ontology import LabelOntology +from ser._internal.data.strategies.base import DatasetStrategyRegistry, PreparedManifestResult from ser.config import AppConfig -from ser.data import dataset_prepare as dp -from ser.data.dataset_registry import DatasetRegistryEntry -from ser.data.manifest import MANIFEST_SCHEMA_VERSION, Utterance -from ser.data.ontology import LabelOntology -from ser.data.strategies.base import DatasetStrategyRegistry, PreparedManifestResult def _settings(tmp_path: Path) -> AppConfig: @@ -335,7 +335,7 @@ def _build_manifest(**kwargs: object) -> list[Utterance]: ] monkeypatch.setattr( - "ser.data.strategies.default.build_ravdess_manifest_jsonl", + "ser._internal.data.strategies.default.build_ravdess_manifest_jsonl", _build_manifest, ) @@ -466,7 +466,7 @@ def _prepare_msp_podcast_from_hf_mirror( ) monkeypatch.setattr( - "ser.data.strategies.default.prepare_msp_podcast_from_hf_mirror", + "ser._internal.data.strategies.default.prepare_msp_podcast_from_hf_mirror", _prepare_msp_podcast_from_hf_mirror, ) @@ -501,7 +501,7 @@ def _prepare_ravdess_from_zenodo(*, dataset_root: Path) -> object: ) monkeypatch.setattr( - "ser.data.strategies.default.prepare_ravdess_from_zenodo", + "ser._internal.data.strategies.default.prepare_ravdess_from_zenodo", _prepare_ravdess_from_zenodo, ) @@ -823,7 +823,7 @@ def _capture_registry_kwargs(**kwargs: object) -> None: captured["registry_kwargs"] = kwargs monkeypatch.setattr( - "ser.data.strategies.default.build_msp_podcast_manifest_jsonl", + "ser._internal.data.strategies.default.build_msp_podcast_manifest_jsonl", _build_manifest, ) monkeypatch.setattr(dp, "upsert_dataset_registry_entry", _capture_registry_kwargs) @@ -890,7 +890,7 @@ def _capture_registry_kwargs(**kwargs: object) -> None: captured["registry_kwargs"] = kwargs monkeypatch.setattr( - "ser.data.strategies.default.build_msp_podcast_manifest_jsonl", + "ser._internal.data.strategies.default.build_msp_podcast_manifest_jsonl", _build_manifest, ) monkeypatch.setattr(dp, "upsert_dataset_registry_entry", _capture_registry_kwargs) diff --git a/tests/suites/unit/data/test_archive_extraction.py b/tests/suites/unit/data/test_archive_extraction.py index 32729b8..843576a 100644 --- a/tests/suites/unit/data/test_archive_extraction.py +++ b/tests/suites/unit/data/test_archive_extraction.py @@ -7,7 +7,7 @@ import pytest -from ser.data import archive_extraction +from ser._internal.data import archive_extraction def test_extract_archive_supports_tar_gz(tmp_path: Path) -> None: diff --git a/tests/suites/unit/data/test_data_loader.py b/tests/suites/unit/data/test_data_loader.py index c351abe..ec27508 100644 --- a/tests/suites/unit/data/test_data_loader.py +++ b/tests/suites/unit/data/test_data_loader.py @@ -9,10 +9,10 @@ import numpy as np import pytest +import ser._internal.data.data_loader as dl import ser.config as config +from ser._internal.data.manifest import MANIFEST_SCHEMA_VERSION from ser.config import AppConfig -from ser.data import data_loader as dl -from ser.data.manifest import MANIFEST_SCHEMA_VERSION def _build_settings(max_failed_file_ratio: float = 0.5) -> SimpleNamespace: diff --git a/tests/suites/unit/data/test_dataset_adapters_biic_podcast.py b/tests/suites/unit/data/test_dataset_adapters_biic_podcast.py index a98d5ce..f17c32d 100644 --- a/tests/suites/unit/data/test_dataset_adapters_biic_podcast.py +++ b/tests/suites/unit/data/test_dataset_adapters_biic_podcast.py @@ -4,13 +4,13 @@ from pathlib import Path -from ser.data.adapters.biic_podcast import ( +from ser._internal.data.adapters.biic_podcast import ( BIIC_PODCAST_CORPUS_ID, BIIC_PODCAST_DATASET_LICENSE_ID, BIIC_PODCAST_DATASET_POLICY_ID, build_biic_podcast_utterances, ) -from ser.data.ontology import LabelOntology +from ser._internal.data.ontology import LabelOntology def _ontology() -> LabelOntology: diff --git a/tests/suites/unit/data/test_dataset_adapters_crema_d.py b/tests/suites/unit/data/test_dataset_adapters_crema_d.py index 96d190d..e957fc3 100644 --- a/tests/suites/unit/data/test_dataset_adapters_crema_d.py +++ b/tests/suites/unit/data/test_dataset_adapters_crema_d.py @@ -4,13 +4,13 @@ from pathlib import Path -from ser.data.adapters.crema_d import ( +from ser._internal.data.adapters.crema_d import ( CREMA_D_CORPUS_ID, CREMA_D_DATASET_LICENSE_ID, CREMA_D_DATASET_POLICY_ID, build_crema_d_utterances, ) -from ser.data.ontology import LabelOntology +from ser._internal.data.ontology import LabelOntology def _ontology() -> LabelOntology: diff --git a/tests/suites/unit/data/test_dataset_adapters_msp_podcast.py b/tests/suites/unit/data/test_dataset_adapters_msp_podcast.py index 27ebd6c..8563c72 100644 --- a/tests/suites/unit/data/test_dataset_adapters_msp_podcast.py +++ b/tests/suites/unit/data/test_dataset_adapters_msp_podcast.py @@ -4,13 +4,13 @@ from pathlib import Path -from ser.data.adapters.msp_podcast import ( +from ser._internal.data.adapters.msp_podcast import ( MSP_PODCAST_CORPUS_ID, MSP_PODCAST_DATASET_LICENSE_ID, MSP_PODCAST_DATASET_POLICY_ID, build_msp_podcast_utterances, ) -from ser.data.ontology import LabelOntology +from ser._internal.data.ontology import LabelOntology def _ontology() -> LabelOntology: diff --git a/tests/suites/unit/data/test_dataset_adapters_public_csv_datasets.py b/tests/suites/unit/data/test_dataset_adapters_public_csv_datasets.py index f21c5f1..9499738 100644 --- a/tests/suites/unit/data/test_dataset_adapters_public_csv_datasets.py +++ b/tests/suites/unit/data/test_dataset_adapters_public_csv_datasets.py @@ -4,7 +4,7 @@ from pathlib import Path -from ser.data.adapters.public_csv_datasets import ( +from ser._internal.data.adapters.public_csv_datasets import ( ATT_HACK_CORPUS_ID, ATT_HACK_DATASET_LICENSE_ID, ATT_HACK_DATASET_POLICY_ID, @@ -34,7 +34,7 @@ build_pavoque_manifest_jsonl, build_spanish_meacorpus_2023_manifest_jsonl, ) -from ser.data.ontology import LabelOntology +from ser._internal.data.ontology import LabelOntology def _ontology() -> LabelOntology: diff --git a/tests/suites/unit/data/test_dataset_adapters_ravdess.py b/tests/suites/unit/data/test_dataset_adapters_ravdess.py index 9313295..cfbc326 100644 --- a/tests/suites/unit/data/test_dataset_adapters_ravdess.py +++ b/tests/suites/unit/data/test_dataset_adapters_ravdess.py @@ -4,13 +4,13 @@ from pathlib import Path -from ser.data.adapters.ravdess import ( +from ser._internal.data.adapters.ravdess import ( RAVDESS_CORPUS_ID, RAVDESS_DATASET_LICENSE_ID, RAVDESS_DATASET_POLICY_ID, build_ravdess_utterances, ) -from ser.data.ontology import LabelOntology +from ser._internal.data.ontology import LabelOntology def _ontology() -> LabelOntology: diff --git a/tests/suites/unit/data/test_dataset_catalog_public_datasets.py b/tests/suites/unit/data/test_dataset_catalog_public_datasets.py index 17f1a67..08c388c 100644 --- a/tests/suites/unit/data/test_dataset_catalog_public_datasets.py +++ b/tests/suites/unit/data/test_dataset_catalog_public_datasets.py @@ -2,7 +2,7 @@ from __future__ import annotations -from ser.data.catalog.public_datasets import ( +from ser._internal.data.catalog.public_datasets import ( EMODB_2_CORPUS_ID, EMODB_2_DATASET_LICENSE_ID, EMODB_2_DATASET_POLICY_ID, diff --git a/tests/suites/unit/data/test_dataset_consents.py b/tests/suites/unit/data/test_dataset_consents.py index 9805da3..9425be6 100644 --- a/tests/suites/unit/data/test_dataset_consents.py +++ b/tests/suites/unit/data/test_dataset_consents.py @@ -8,8 +8,7 @@ import pytest -from ser.config import AppConfig -from ser.data.dataset_consents import ( +from ser._internal.data.dataset_consents import ( DatasetConsentError, compute_missing_dataset_consents, ensure_dataset_consents, @@ -17,7 +16,8 @@ load_persisted_dataset_consents, persist_dataset_consents, ) -from ser.data.manifest import MANIFEST_SCHEMA_VERSION, Utterance +from ser._internal.data.manifest import MANIFEST_SCHEMA_VERSION, Utterance +from ser.config import AppConfig def _settings(tmp_path: Path) -> AppConfig: diff --git a/tests/suites/unit/data/test_dataset_controls.py b/tests/suites/unit/data/test_dataset_controls.py index 30d75b1..2cfe44b 100644 --- a/tests/suites/unit/data/test_dataset_controls.py +++ b/tests/suites/unit/data/test_dataset_controls.py @@ -7,7 +7,7 @@ import pytest -from ser.models import dataset_controls +from ser._internal.models import dataset_controls @dataclass(frozen=True) @@ -78,7 +78,7 @@ def test_resolve_registry_manifest_paths_returns_sorted_unique_paths( monkeypatch: pytest.MonkeyPatch, ) -> None: """Registry resolver should emit deterministic sorted unique manifest paths.""" - import ser.data.dataset_registry as dataset_registry + import ser._internal.data.dataset_registry as dataset_registry monkeypatch.setattr( dataset_registry, @@ -107,7 +107,7 @@ def test_resolve_registry_manifest_paths_fails_closed_on_lookup_errors( monkeypatch: pytest.MonkeyPatch, ) -> None: """Registry resolver should return empty tuple when lookup raises.""" - import ser.data.dataset_registry as dataset_registry + import ser._internal.data.dataset_registry as dataset_registry def _raise_lookup_error(*, settings: object) -> object: raise RuntimeError("registry unavailable") diff --git a/tests/suites/unit/data/test_dataset_registry.py b/tests/suites/unit/data/test_dataset_registry.py index 5e30085..353bffa 100644 --- a/tests/suites/unit/data/test_dataset_registry.py +++ b/tests/suites/unit/data/test_dataset_registry.py @@ -8,8 +8,7 @@ import pytest -from ser.config import AppConfig -from ser.data.dataset_registry import ( +from ser._internal.data.dataset_registry import ( DatasetRegistryEntry, load_dataset_registry, parse_dataset_registry_options, @@ -18,6 +17,7 @@ save_dataset_registry, upsert_dataset_registry_entry, ) +from ser.config import AppConfig def _settings(tmp_path: Path) -> AppConfig: diff --git a/tests/suites/unit/data/test_dataset_strategies.py b/tests/suites/unit/data/test_dataset_strategies.py index bee2c29..c294dd3 100644 --- a/tests/suites/unit/data/test_dataset_strategies.py +++ b/tests/suites/unit/data/test_dataset_strategies.py @@ -8,8 +8,8 @@ import pytest -from ser.data import dataset_prepare -from ser.data.strategies import ( +import ser._internal.data.dataset_prepare as dataset_prepare +from ser._internal.data.strategies import ( DatasetStrategyRegistry, Emodb2DatasetStrategy, PreparedManifestResult, diff --git a/tests/suites/unit/data/test_embedding_cache.py b/tests/suites/unit/data/test_embedding_cache.py index 27beb46..ceb54c1 100644 --- a/tests/suites/unit/data/test_embedding_cache.py +++ b/tests/suites/unit/data/test_embedding_cache.py @@ -6,8 +6,8 @@ import numpy as np -from ser.data.embedding_cache import EmbeddingCache -from ser.repr import EncodedSequence +from ser._internal.data.embedding_cache import EmbeddingCache +from ser._internal.repr import EncodedSequence def _encoded_sequence(scale: float = 1.0) -> EncodedSequence: diff --git a/tests/suites/unit/data/test_jl_corpus_downloads.py b/tests/suites/unit/data/test_jl_corpus_downloads.py index 4f41fc9..3b175c7 100644 --- a/tests/suites/unit/data/test_jl_corpus_downloads.py +++ b/tests/suites/unit/data/test_jl_corpus_downloads.py @@ -7,8 +7,8 @@ import pytest -import ser.data.jl_corpus_downloads as jl_downloads -from ser.data.jl_corpus_downloads import ( +import ser._internal.data.jl_corpus_downloads as jl_downloads +from ser._internal.data.jl_corpus_downloads import ( download_jl_corpus_via_hf_rows, extract_jl_corpus_audio_src, prepare_jl_corpus_from_hf_rows, diff --git a/tests/suites/unit/data/test_manifest_jsonl.py b/tests/suites/unit/data/test_manifest_jsonl.py index 773088d..2a6776d 100644 --- a/tests/suites/unit/data/test_manifest_jsonl.py +++ b/tests/suites/unit/data/test_manifest_jsonl.py @@ -6,9 +6,9 @@ import pytest -from ser.data.manifest import MANIFEST_SCHEMA_VERSION, Utterance -from ser.data.manifest_jsonl import load_manifest_jsonl, write_manifest_jsonl -from ser.data.ontology import LabelOntology, remap_label +from ser._internal.data.manifest import MANIFEST_SCHEMA_VERSION, Utterance +from ser._internal.data.manifest_jsonl import load_manifest_jsonl, write_manifest_jsonl +from ser._internal.data.ontology import LabelOntology, remap_label def _ontology() -> LabelOntology: diff --git a/tests/suites/unit/data/test_mendeley_dataset_preparation.py b/tests/suites/unit/data/test_mendeley_dataset_preparation.py index 1977535..2c33fc5 100644 --- a/tests/suites/unit/data/test_mendeley_dataset_preparation.py +++ b/tests/suites/unit/data/test_mendeley_dataset_preparation.py @@ -6,7 +6,7 @@ from dataclasses import dataclass from pathlib import Path -from ser.data import mendeley_dataset_preparation as preparation +import ser._internal.data.mendeley_dataset_preparation as preparation @dataclass(frozen=True, slots=True) diff --git a/tests/suites/unit/data/test_mendeley_downloads.py b/tests/suites/unit/data/test_mendeley_downloads.py index f26bab7..c3f23fc 100644 --- a/tests/suites/unit/data/test_mendeley_downloads.py +++ b/tests/suites/unit/data/test_mendeley_downloads.py @@ -6,7 +6,7 @@ import pytest -from ser.data.mendeley_downloads import ( +from ser._internal.data.mendeley_downloads import ( build_mendeley_folder_paths, download_mendeley_dataset_tree, download_mendeley_dataset_tree_from_api, diff --git a/tests/suites/unit/data/test_msp_podcast_mirror.py b/tests/suites/unit/data/test_msp_podcast_mirror.py index 500797f..71af5a3 100644 --- a/tests/suites/unit/data/test_msp_podcast_mirror.py +++ b/tests/suites/unit/data/test_msp_podcast_mirror.py @@ -9,8 +9,8 @@ import pytest -from ser.data import msp_podcast_mirror -from ser.data.msp_podcast_mirror import generate_msp_labels_csv_from_metadata_jsonl +from ser._internal.data import msp_podcast_mirror +from ser._internal.data.msp_podcast_mirror import generate_msp_labels_csv_from_metadata_jsonl def test_generate_msp_labels_csv_from_metadata_jsonl(tmp_path: Path) -> None: diff --git a/tests/suites/unit/data/test_openslr_dataset_preparation.py b/tests/suites/unit/data/test_openslr_dataset_preparation.py index 2426fda..b2965a4 100644 --- a/tests/suites/unit/data/test_openslr_dataset_preparation.py +++ b/tests/suites/unit/data/test_openslr_dataset_preparation.py @@ -6,7 +6,7 @@ from dataclasses import dataclass from pathlib import Path -from ser.data import openslr_dataset_preparation +from ser._internal.data import openslr_dataset_preparation @dataclass(frozen=True) diff --git a/tests/suites/unit/data/test_openslr_downloads.py b/tests/suites/unit/data/test_openslr_downloads.py index 0005852..76a86e6 100644 --- a/tests/suites/unit/data/test_openslr_downloads.py +++ b/tests/suites/unit/data/test_openslr_downloads.py @@ -7,13 +7,13 @@ import pytest -from ser.data.openslr_downloads import ( +from ser._internal.data.openslr_downloads import ( download_openslr_archives, download_openslr_pinned_artifacts, read_openslr_archive_urls, read_openslr_archive_urls_from_hf_script, ) -from ser.data.openslr_resolution import OpenSlrPinnedArtifact +from ser._internal.data.openslr_resolution import OpenSlrPinnedArtifact def test_download_openslr_pinned_artifacts_retries_mirrors( diff --git a/tests/suites/unit/data/test_provider_dataset_preparation.py b/tests/suites/unit/data/test_provider_dataset_preparation.py index 94f7584..2e4c497 100644 --- a/tests/suites/unit/data/test_provider_dataset_preparation.py +++ b/tests/suites/unit/data/test_provider_dataset_preparation.py @@ -8,7 +8,7 @@ import pytest -from ser.data import provider_dataset_preparation as preparation +import ser._internal.data.provider_dataset_preparation as preparation @dataclass(frozen=True, slots=True) diff --git a/tests/suites/unit/data/test_provider_downloads.py b/tests/suites/unit/data/test_provider_downloads.py index e0eaa1b..205b7ee 100644 --- a/tests/suites/unit/data/test_provider_downloads.py +++ b/tests/suites/unit/data/test_provider_downloads.py @@ -10,7 +10,7 @@ import pytest -from ser.data import provider_downloads +from ser._internal.data import provider_downloads class _DiskUsage(NamedTuple): diff --git a/tests/suites/unit/data/test_public_dataset_downloads.py b/tests/suites/unit/data/test_public_dataset_downloads.py index ebb2f40..c50bfd3 100644 --- a/tests/suites/unit/data/test_public_dataset_downloads.py +++ b/tests/suites/unit/data/test_public_dataset_downloads.py @@ -8,7 +8,7 @@ import pytest -from ser.data import public_dataset_downloads as downloads +import ser._internal.data.public_dataset_downloads as downloads def test_write_source_manifest_delegates_to_provider_helper( diff --git a/tests/suites/unit/data/test_zenodo_downloads.py b/tests/suites/unit/data/test_zenodo_downloads.py index aba4e8c..13efd95 100644 --- a/tests/suites/unit/data/test_zenodo_downloads.py +++ b/tests/suites/unit/data/test_zenodo_downloads.py @@ -8,7 +8,7 @@ from pathlib import Path from typing import cast -from ser.data import zenodo_downloads +from ser._internal.data import zenodo_downloads @dataclass(frozen=True) From 9b50fb2653d0a12a4ecb696acf484b05bea9d044 Mon Sep 17 00:00:00 2001 From: Juan Sugg Date: Thu, 9 Jul 2026 22:30:18 -0300 Subject: [PATCH 03/12] refactor(models): move implementation internal --- .../models/accurate_feature_dataset.py | 16 +++---- .../models/accurate_training_execution.py | 20 ++++----- .../models/accurate_training_preparation.py | 14 +++--- ser/_internal/models/artifact_envelope.py | 4 +- ser/_internal/models/dataset_controls.py | 2 +- ser/_internal/models/dataset_splitting.py | 4 +- .../models/dataset_training_consents.py | 6 +-- ser/_internal/models/emotion_model.py | 24 +++++----- ser/_internal/models/fast_path.py | 2 +- ser/_internal/models/fast_training.py | 2 +- .../models/fast_training_entrypoints.py | 2 +- .../models/feature_runtime_encoding.py | 10 ++--- .../models/medium_feature_dataset.py | 18 ++++---- .../models/medium_training_preparation.py | 14 +++--- ser/_internal/models/model_loading.py | 4 +- ser/_internal/models/profile_runtime.py | 6 +-- .../models/profile_training_preparation.py | 2 +- ser/_internal/models/training_entrypoints.py | 26 +++++------ ser/_internal/models/training_execution.py | 2 +- ser/_internal/models/training_preparation.py | 2 +- ser/_internal/models/training_support.py | 30 +++++++------ ser/models/__init__.py | 5 +-- ser/models/emotion_model.py | 3 ++ ser/models/profile_runtime.py | 44 +++++++++++++++++++ ser/models/training_entrypoints.py | 3 ++ .../architecture/test_pipeline_contracts.py | 6 +-- .../models/test_emotion_model_contracts.py | 28 ++++++------ ...test_training_preparation_and_execution.py | 6 +-- .../integration/test_accurate_inference.py | 6 +-- .../suites/integration/test_fast_inference.py | 8 ++-- .../integration/test_medium_inference.py | 8 ++-- .../smoke/test_cli_runtime_workflows.py | 8 ++-- .../models/test_accurate_feature_dataset.py | 6 +-- .../models/test_accurate_training_artifact.py | 22 +++++----- .../test_accurate_training_preparation.py | 16 +++---- .../unit/models/test_artifact_loading.py | 2 +- .../unit/models/test_artifact_persistence.py | 2 +- .../unit/models/test_artifact_upgrade_path.py | 2 +- .../models/test_dataset_training_consents.py | 8 ++-- .../suites/unit/models/test_emotion_model.py | 20 ++++----- .../models/test_medium_feature_dataset.py | 12 ++--- .../unit/models/test_medium_noise_controls.py | 2 +- .../unit/models/test_medium_quality_report.py | 4 +- .../test_medium_training_noise_controls.py | 2 +- .../test_medium_training_preparation.py | 6 +-- .../models/test_medium_training_windowing.py | 6 +-- .../unit/models/test_provenance_metadata.py | 4 +- tests/suites/unit/models/test_torch_head.py | 2 +- .../unit/models/test_training_reporting.py | 2 +- 49 files changed, 251 insertions(+), 202 deletions(-) create mode 100644 ser/models/emotion_model.py create mode 100644 ser/models/profile_runtime.py create mode 100644 ser/models/training_entrypoints.py diff --git a/ser/_internal/models/accurate_feature_dataset.py b/ser/_internal/models/accurate_feature_dataset.py index a2e648a..218e5ab 100644 --- a/ser/_internal/models/accurate_feature_dataset.py +++ b/ser/_internal/models/accurate_feature_dataset.py @@ -9,26 +9,26 @@ import numpy as np -from ser.config import AppConfig -from ser.data import EmbeddingCache -from ser.models.feature_runtime_encoding import ( +from ser._internal.data import EmbeddingCache +from ser._internal.models.feature_runtime_encoding import ( encode_sequence_with_cache, resolve_accurate_runtime_config, resolve_effective_model_id, ) -from ser.models.profile_runtime import ( +from ser._internal.models.profile_runtime import ( resolve_accurate_model_id, resolve_accurate_research_model_id, ) -from ser.models.profile_training_preparation import ( +from ser._internal.models.profile_training_preparation import ( UtteranceLike, ) -from ser.models.profile_training_preparation import ( +from ser._internal.models.profile_training_preparation import ( build_accurate_feature_dataset as _build_prepared_accurate_feature_dataset, ) +from ser._internal.repr import EncodedSequence, FeatureBackend, PoolingWindow +from ser._internal.utils.audio_utils import read_audio_file +from ser.config import AppConfig from ser.pool import mean_std_pool, temporal_pooling_windows -from ser.repr import EncodedSequence, FeatureBackend, PoolingWindow -from ser.utils.audio_utils import read_audio_file _UtteranceT = TypeVar("_UtteranceT", bound=UtteranceLike) _MetaT = TypeVar("_MetaT") diff --git a/ser/_internal/models/accurate_training_execution.py b/ser/_internal/models/accurate_training_execution.py index fd52e4d..9c1c98a 100644 --- a/ser/_internal/models/accurate_training_execution.py +++ b/ser/_internal/models/accurate_training_execution.py @@ -9,24 +9,24 @@ from pathlib import Path from typing import Any, Protocol, TypeVar -from ser.config import AppConfig -from ser.data import Utterance -from ser.license_check import build_provenance_metadata -from ser.models import training_support as _training_support -from ser.models.artifact_envelope import build_model_artifact as _build_model_artifact -from ser.models.artifact_persistence import ( +from ser._internal.data import Utterance +from ser._internal.models import training_support as _training_support +from ser._internal.models.artifact_envelope import build_model_artifact as _build_model_artifact +from ser._internal.models.artifact_persistence import ( persist_pickle_artifact, persist_secure_artifact, persist_training_report, ) -from ser.models.dataset_splitting import MediumSplitMetadata -from ser.models.training_execution import run_accurate_profile_training -from ser.models.training_reporting import build_grouped_evaluation_controls -from ser.models.training_types import ( +from ser._internal.models.dataset_splitting import MediumSplitMetadata +from ser._internal.models.training_execution import run_accurate_profile_training +from ser._internal.models.training_reporting import build_grouped_evaluation_controls +from ser._internal.models.training_types import ( AccurateTrainingPreparation, PersistedArtifactsLike, TrainingEvaluation, ) +from ser.config import AppConfig +from ser.license_check import build_provenance_metadata _ModelT = TypeVar("_ModelT") diff --git a/ser/_internal/models/accurate_training_preparation.py b/ser/_internal/models/accurate_training_preparation.py index fabb4b1..01ae0c6 100644 --- a/ser/_internal/models/accurate_training_preparation.py +++ b/ser/_internal/models/accurate_training_preparation.py @@ -9,20 +9,20 @@ import numpy as np -from ser.config import AppConfig -from ser.data import EmbeddingCache, Utterance -from ser.models.accurate_training_execution import ( +from ser._internal.data import EmbeddingCache, Utterance +from ser._internal.models.accurate_training_execution import ( PreparedAccurateTrainingRunner, ) -from ser.models.dataset_splitting import MediumSplitMetadata -from ser.models.training_preparation import ( +from ser._internal.models.dataset_splitting import MediumSplitMetadata +from ser._internal.models.training_preparation import ( prepare_accurate_training_features, prepare_accurate_training_payload, ) -from ser.models.training_types import ( +from ser._internal.models.training_types import ( AccurateTrainingPreparation, ) -from ser.repr import Emotion2VecBackend, WhisperBackend +from ser._internal.repr import Emotion2VecBackend, WhisperBackend +from ser.config import AppConfig _MetaT = TypeVar("_MetaT") diff --git a/ser/_internal/models/artifact_envelope.py b/ser/_internal/models/artifact_envelope.py index 54f8f9a..7a0d73a 100644 --- a/ser/_internal/models/artifact_envelope.py +++ b/ser/_internal/models/artifact_envelope.py @@ -7,11 +7,11 @@ from sklearn.neural_network import MLPClassifier from sklearn.pipeline import Pipeline -from ser.models.artifact_loading import ( +from ser._internal.models.artifact_loading import ( build_model_artifact_envelope, deserialize_model_artifact_envelope, ) -from ser.models.artifact_metadata import ( +from ser._internal.models.artifact_metadata import ( build_v2_artifact_metadata, normalize_v2_artifact_metadata, read_positive_int, diff --git a/ser/_internal/models/dataset_controls.py b/ser/_internal/models/dataset_controls.py index 9e332d2..250618b 100644 --- a/ser/_internal/models/dataset_controls.py +++ b/ser/_internal/models/dataset_controls.py @@ -35,7 +35,7 @@ def dataset(self) -> _DatasetConfigLike: ... def resolve_registry_manifest_paths(*, settings: object) -> tuple[Path, ...]: """Resolves registry manifest paths when optional registry data is available.""" try: - from ser.data.dataset_registry import ( + from ser._internal.data.dataset_registry import ( load_dataset_registry, registered_manifest_paths, ) diff --git a/ser/_internal/models/dataset_splitting.py b/ser/_internal/models/dataset_splitting.py index a46b001..1a7fbc3 100644 --- a/ser/_internal/models/dataset_splitting.py +++ b/ser/_internal/models/dataset_splitting.py @@ -10,9 +10,9 @@ import numpy as np from sklearn.model_selection import train_test_split +from ser._internal.data import LabeledAudioSample, Utterance +from ser._internal.data.data_loader import extract_ravdess_speaker_id_from_path from ser.config import AppConfig -from ser.data import LabeledAudioSample, Utterance -from ser.data.data_loader import extract_ravdess_speaker_id_from_path from ser.train.eval import grouped_train_test_split diff --git a/ser/_internal/models/dataset_training_consents.py b/ser/_internal/models/dataset_training_consents.py index 4204456..325dde5 100644 --- a/ser/_internal/models/dataset_training_consents.py +++ b/ser/_internal/models/dataset_training_consents.py @@ -6,14 +6,14 @@ from collections.abc import Callable, Sequence from typing import Any -from ser.config import AppConfig -from ser.data import Utterance -from ser.data.dataset_consents import ( +from ser._internal.data import Utterance +from ser._internal.data.dataset_consents import ( DatasetConsentError, compute_missing_dataset_consents, ensure_dataset_consents, persist_dataset_consents, ) +from ser.config import AppConfig def ensure_dataset_training_consents( diff --git a/ser/_internal/models/emotion_model.py b/ser/_internal/models/emotion_model.py index b171315..4e8dcce 100644 --- a/ser/_internal/models/emotion_model.py +++ b/ser/_internal/models/emotion_model.py @@ -5,26 +5,26 @@ import logging from functools import partial +import ser._internal.models.artifact_envelope as _artifact_envelope import ser._internal.models.model_loading as _model_loading_entrypoints -import ser.models.artifact_envelope as _artifact_envelope -import ser.models.profile_runtime as _profile_runtime -import ser.models.training_support as _training_support +import ser._internal.models.profile_runtime as _profile_runtime +import ser._internal.models.training_support as _training_support +from ser._internal.features import extract_feature_frames +from ser._internal.models.artifact_envelope import LoadedModel +from ser._internal.models.artifact_loading import ( + load_model_with_resolution, + resolve_model_for_loading_from_settings, +) +from ser._internal.models.fast_path import ( + predict_emotions_detailed_with_model as _fast_predict_emotions_detailed_with_model, +) from ser._internal.models.model_loading import ResolveModelFn from ser._internal.models.model_loading import load_model as _load_model_entrypoint from ser._internal.runtime.environment_plan import build_runtime_environment_plan from ser._internal.runtime.process_env import temporary_process_env from ser.config import AppConfig, reload_settings from ser.domain import EmotionSegment -from ser.features import extract_feature_frames from ser.models import training_entrypoints as _training_entrypoints -from ser.models.artifact_envelope import LoadedModel -from ser.models.artifact_loading import ( - load_model_with_resolution, - resolve_model_for_loading_from_settings, -) -from ser.models.fast_path import ( - predict_emotions_detailed_with_model as _fast_predict_emotions_detailed_with_model, -) from ser.runtime.schema import OUTPUT_SCHEMA_VERSION, InferenceResult, to_legacy_emotion_segments from ser.utils.logger import get_logger diff --git a/ser/_internal/models/fast_path.py b/ser/_internal/models/fast_path.py index 1f2ba6e..82ea2a5 100644 --- a/ser/_internal/models/fast_path.py +++ b/ser/_internal/models/fast_path.py @@ -10,7 +10,7 @@ from sklearn.neural_network import MLPClassifier from sklearn.pipeline import Pipeline -from ser.features import FeatureFrame +from ser._internal.features import FeatureFrame from ser.runtime.schema import FramePrediction, InferenceResult, SegmentPrediction type EmotionClassifier = MLPClassifier | Pipeline diff --git a/ser/_internal/models/fast_training.py b/ser/_internal/models/fast_training.py index 3b4e48b..d36b719 100644 --- a/ser/_internal/models/fast_training.py +++ b/ser/_internal/models/fast_training.py @@ -12,7 +12,7 @@ from sklearn.neural_network import MLPClassifier from sklearn.pipeline import Pipeline -from ser.data import Utterance +from ser._internal.data import Utterance type EmotionClassifier = MLPClassifier | Pipeline type DatasetSplit = tuple[np.ndarray, np.ndarray, list[str], list[str]] diff --git a/ser/_internal/models/fast_training_entrypoints.py b/ser/_internal/models/fast_training_entrypoints.py index 28db004..02682a3 100644 --- a/ser/_internal/models/fast_training_entrypoints.py +++ b/ser/_internal/models/fast_training_entrypoints.py @@ -6,9 +6,9 @@ from contextlib import AbstractContextManager from typing import Protocol +from ser._internal.models.fast_training import FastTrainingHooks, train_fast_model from ser._internal.runtime.environment_plan import ProcessEnvDelta from ser.config import AppConfig -from ser.models.fast_training import FastTrainingHooks, train_fast_model class RuntimeEnvironmentPlanLike(Protocol): diff --git a/ser/_internal/models/feature_runtime_encoding.py b/ser/_internal/models/feature_runtime_encoding.py index 9db727d..f57fbe8 100644 --- a/ser/_internal/models/feature_runtime_encoding.py +++ b/ser/_internal/models/feature_runtime_encoding.py @@ -9,12 +9,12 @@ import numpy as np from numpy.typing import NDArray +from ser._internal.data import EmbeddingCache +from ser._internal.data.embedding_cache import EmbeddingCacheEntry +from ser._internal.repr import EncodedSequence, FeatureBackend +from ser._internal.repr.runtime_policy import resolve_feature_runtime_policy +from ser._internal.utils.audio_utils import read_audio_file from ser.config import AppConfig, ProfileRuntimeConfig -from ser.data import EmbeddingCache -from ser.data.embedding_cache import EmbeddingCacheEntry -from ser.repr import EncodedSequence, FeatureBackend -from ser.repr.runtime_policy import resolve_feature_runtime_policy -from ser.utils.audio_utils import read_audio_file class ReadAudioFile(Protocol): diff --git a/ser/_internal/models/medium_feature_dataset.py b/ser/_internal/models/medium_feature_dataset.py index 41b445a..7bc3c42 100644 --- a/ser/_internal/models/medium_feature_dataset.py +++ b/ser/_internal/models/medium_feature_dataset.py @@ -9,23 +9,23 @@ import numpy as np -from ser.config import AppConfig -from ser.data import EmbeddingCache -from ser.models.feature_runtime_encoding import ( +from ser._internal.data import EmbeddingCache +from ser._internal.models.feature_runtime_encoding import ( encode_sequence_with_cache, resolve_effective_model_id, ) -from ser.models.medium_noise_controls import MediumNoiseControlStats -from ser.models.profile_runtime import resolve_medium_model_id -from ser.models.profile_training_preparation import ( +from ser._internal.models.medium_noise_controls import MediumNoiseControlStats +from ser._internal.models.profile_runtime import resolve_medium_model_id +from ser._internal.models.profile_training_preparation import ( UtteranceLike, ) -from ser.models.profile_training_preparation import ( +from ser._internal.models.profile_training_preparation import ( build_medium_feature_dataset as _build_prepared_medium_feature_dataset, ) +from ser._internal.repr import EncodedSequence, PoolingWindow, XLSRBackend +from ser._internal.utils.audio_utils import read_audio_file +from ser.config import AppConfig from ser.pool import mean_std_pool, temporal_pooling_windows -from ser.repr import EncodedSequence, PoolingWindow, XLSRBackend -from ser.utils.audio_utils import read_audio_file _UtteranceT = TypeVar("_UtteranceT", bound=UtteranceLike) _MetaT = TypeVar("_MetaT") diff --git a/ser/_internal/models/medium_training_preparation.py b/ser/_internal/models/medium_training_preparation.py index 7c89234..c4639a5 100644 --- a/ser/_internal/models/medium_training_preparation.py +++ b/ser/_internal/models/medium_training_preparation.py @@ -9,20 +9,20 @@ import numpy as np -from ser.config import AppConfig -from ser.data import EmbeddingCache, Utterance -from ser.models.dataset_splitting import MediumSplitMetadata -from ser.models.training_execution import run_medium_profile_training -from ser.models.training_preparation import ( +from ser._internal.data import EmbeddingCache, Utterance +from ser._internal.models.dataset_splitting import MediumSplitMetadata +from ser._internal.models.training_execution import run_medium_profile_training +from ser._internal.models.training_preparation import ( prepare_medium_training_features, prepare_medium_training_payload, ) -from ser.models.training_types import ( +from ser._internal.models.training_types import ( MediumTrainingPreparation, PersistedArtifactsLike, TrainingEvaluation, ) -from ser.repr import XLSRBackend +from ser._internal.repr import XLSRBackend +from ser.config import AppConfig _MetaT = TypeVar("_MetaT") _NoiseStatsT = TypeVar("_NoiseStatsT") diff --git a/ser/_internal/models/model_loading.py b/ser/_internal/models/model_loading.py index d314e88..90a1e73 100644 --- a/ser/_internal/models/model_loading.py +++ b/ser/_internal/models/model_loading.py @@ -8,10 +8,10 @@ from functools import partial from typing import Protocol, cast +from ser._internal.models.artifact_envelope import LoadedModel +from ser._internal.models.training_support import ModelCandidate from ser._internal.runtime.environment_plan import ProcessEnvDelta from ser.config import AppConfig -from ser.models.artifact_envelope import LoadedModel -from ser.models.training_support import ModelCandidate type ResolveModelFn = Callable[..., tuple[object, LoadedModel]] type ResolveModelFromSettingsFn = Callable[..., tuple[ModelCandidate, LoadedModel]] diff --git a/ser/_internal/models/profile_runtime.py b/ser/_internal/models/profile_runtime.py index 572660d..b4f536f 100644 --- a/ser/_internal/models/profile_runtime.py +++ b/ser/_internal/models/profile_runtime.py @@ -7,10 +7,10 @@ from typing import Final, Literal from ser._internal.config.schema import default_profile_model_id +from ser._internal.models.feature_runtime_encoding import resolve_profile_runtime_selectors +from ser._internal.repr import Emotion2VecBackend, WhisperBackend, XLSRBackend +from ser._internal.repr.runtime_policy import resolve_feature_runtime_policy from ser.config import AppConfig, reload_settings -from ser.models.feature_runtime_encoding import resolve_profile_runtime_selectors -from ser.repr import Emotion2VecBackend, WhisperBackend, XLSRBackend -from ser.repr.runtime_policy import resolve_feature_runtime_policy MEDIUM_BACKEND_ID: Final[str] = "hf_xlsr" MEDIUM_PROFILE_ID: Final[Literal["medium"]] = "medium" diff --git a/ser/_internal/models/profile_training_preparation.py b/ser/_internal/models/profile_training_preparation.py index 10e2186..18b8930 100644 --- a/ser/_internal/models/profile_training_preparation.py +++ b/ser/_internal/models/profile_training_preparation.py @@ -8,7 +8,7 @@ import numpy as np -from ser.repr import EncodedSequence, PoolingWindow +from ser._internal.repr import EncodedSequence, PoolingWindow _MetaT = TypeVar("_MetaT") _StatsT = TypeVar("_StatsT") diff --git a/ser/_internal/models/training_entrypoints.py b/ser/_internal/models/training_entrypoints.py index 10f8c48..2e46b52 100644 --- a/ser/_internal/models/training_entrypoints.py +++ b/ser/_internal/models/training_entrypoints.py @@ -5,21 +5,21 @@ import glob from functools import partial +import ser._internal.data.data_loader as _data_loader +import ser._internal.models.accurate_feature_dataset as _accurate_feature_dataset +import ser._internal.models.accurate_training_execution as _accurate_training_execution +import ser._internal.models.accurate_training_preparation as _accurate_training_preparation +import ser._internal.models.artifact_envelope as _artifact_envelope +import ser._internal.models.artifact_persistence as _artifact_persistence +import ser._internal.models.fast_training as _fast_training import ser._internal.models.fast_training_entrypoints as _fast_training_entrypoints -import ser.data.data_loader as _data_loader +import ser._internal.models.medium_feature_dataset as _medium_feature_dataset +import ser._internal.models.medium_noise_controls as _medium_noise_controls +import ser._internal.models.medium_training_preparation as _medium_training_preparation +import ser._internal.models.profile_runtime as _profile_runtime +import ser._internal.models.training_reporting as _training_reporting +import ser._internal.models.training_support as _training_support import ser.license_check as _license_check -import ser.models.accurate_feature_dataset as _accurate_feature_dataset -import ser.models.accurate_training_execution as _accurate_training_execution -import ser.models.accurate_training_preparation as _accurate_training_preparation -import ser.models.artifact_envelope as _artifact_envelope -import ser.models.artifact_persistence as _artifact_persistence -import ser.models.fast_training as _fast_training -import ser.models.medium_feature_dataset as _medium_feature_dataset -import ser.models.medium_noise_controls as _medium_noise_controls -import ser.models.medium_training_preparation as _medium_training_preparation -import ser.models.profile_runtime as _profile_runtime -import ser.models.training_reporting as _training_reporting -import ser.models.training_support as _training_support from ser._internal.runtime.environment_plan import build_runtime_environment_plan from ser._internal.runtime.process_env import temporary_process_env from ser.config import AppConfig diff --git a/ser/_internal/models/training_execution.py b/ser/_internal/models/training_execution.py index b033a47..4564d8e 100644 --- a/ser/_internal/models/training_execution.py +++ b/ser/_internal/models/training_execution.py @@ -9,7 +9,7 @@ import numpy as np -from ser.models.training_types import ( +from ser._internal.models.training_types import ( AccurateTrainingPreparation, MediumTrainingPreparation, MediumTrainingSettingsLike, diff --git a/ser/_internal/models/training_preparation.py b/ser/_internal/models/training_preparation.py index ce39c75..a357590 100644 --- a/ser/_internal/models/training_preparation.py +++ b/ser/_internal/models/training_preparation.py @@ -9,7 +9,7 @@ import numpy as np from sklearn.metrics import accuracy_score, f1_score -from ser.models.training_types import ( +from ser._internal.models.training_types import ( AccurateTrainingPreparation, MediumTrainingPreparation, TrainingEvaluation, diff --git a/ser/_internal/models/training_support.py b/ser/_internal/models/training_support.py index 6a23e96..cfa915a 100644 --- a/ser/_internal/models/training_support.py +++ b/ser/_internal/models/training_support.py @@ -13,45 +13,47 @@ from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler -from ser.config import AppConfig -from ser.data import Utterance -from ser.models.artifact_envelope import ( +from ser._internal.data import Utterance +from ser._internal.models.artifact_envelope import ( MODEL_ARTIFACT_VERSION, LoadedModel, ) -from ser.models.artifact_envelope import deserialize_model_artifact as _deserialize_model_artifact -from ser.models.artifact_envelope import ( +from ser._internal.models.artifact_envelope import ( + deserialize_model_artifact as _deserialize_model_artifact, +) +from ser._internal.models.artifact_envelope import ( normalize_model_artifact_metadata as _normalize_v2_artifact_metadata, ) -from ser.models.artifact_loading import ( +from ser._internal.models.artifact_loading import ( load_pickle_model_artifact, load_secure_model_artifact, ) -from ser.models.artifact_persistence import ( +from ser._internal.models.artifact_persistence import ( persist_model_artifacts_for_settings, read_training_report_feature_size, ) -from ser.models.dataset_controls import ( +from ser._internal.models.dataset_controls import ( build_dataset_controls_for_settings, ) -from ser.models.dataset_controls import ( +from ser._internal.models.dataset_controls import ( resolve_registry_manifest_paths as _resolve_registry_manifest_paths_impl, ) -from ser.models.dataset_splitting import ( +from ser._internal.models.dataset_splitting import ( MediumSplitMetadata, ) -from ser.models.dataset_splitting import split_utterances as _split_utterances_impl -from ser.models.dataset_training_consents import ( +from ser._internal.models.dataset_splitting import split_utterances as _split_utterances_impl +from ser._internal.models.dataset_training_consents import ( ensure_dataset_training_consents as _ensure_dataset_training_consents_impl, ) -from ser.models.training_preparation import ( +from ser._internal.models.training_preparation import ( attach_grouped_metrics, evaluate_training_predictions, extract_normalized_artifact_metadata, ) -from ser.models.training_reporting import ( +from ser._internal.models.training_reporting import ( build_training_report_for_settings, ) +from ser.config import AppConfig from ser.runtime.schema import ARTIFACT_SCHEMA_VERSION type EmotionClassifier = MLPClassifier | Pipeline diff --git a/ser/models/__init__.py b/ser/models/__init__.py index c6703c7..06476c1 100644 --- a/ser/models/__init__.py +++ b/ser/models/__init__.py @@ -1,3 +1,4 @@ +# pyright: reportUnsupportedDunderAll=false """Model facades for artifact loading, profile runtimes, and training entrypoints. Only the modules listed in ``__all__`` are intended facades; every other module @@ -9,10 +10,6 @@ import importlib from types import ModuleType -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from ser.models import emotion_model, profile_runtime, training_entrypoints __all__ = [ "emotion_model", diff --git a/ser/models/emotion_model.py b/ser/models/emotion_model.py new file mode 100644 index 0000000..851eeaf --- /dev/null +++ b/ser/models/emotion_model.py @@ -0,0 +1,3 @@ +"""Public facade for `ser._internal.models.emotion_model`.""" + +from ser._internal.models.emotion_model import * # noqa: F403 diff --git a/ser/models/profile_runtime.py b/ser/models/profile_runtime.py new file mode 100644 index 0000000..e8c8566 --- /dev/null +++ b/ser/models/profile_runtime.py @@ -0,0 +1,44 @@ +# pyright: reportUnsupportedDunderAll=false +"""Public profile-runtime facade.""" + +from __future__ import annotations + +import importlib +from typing import Any + +_IMPL_MODULE = "ser._internal.models.profile_runtime" +_EXPORT_NAMES: tuple[str, ...] = ( + "ACCURATE_BACKEND_ID", + "ACCURATE_MODEL_ID", + "ACCURATE_POOLING_STRATEGY", + "ACCURATE_PROFILE_ID", + "ACCURATE_RESEARCH_BACKEND_ID", + "ACCURATE_RESEARCH_MODEL_ID", + "ACCURATE_RESEARCH_PROFILE_ID", + "MEDIUM_BACKEND_ID", + "MEDIUM_FRAME_SIZE_SECONDS", + "MEDIUM_FRAME_STRIDE_SECONDS", + "MEDIUM_MODEL_ID", + "MEDIUM_POOLING_STRATEGY", + "MEDIUM_PROFILE_ID", + "build_accurate_backend_for_settings", + "build_accurate_research_backend_for_settings", + "build_medium_backend_for_settings", + "resolve_accurate_model_id", + "resolve_accurate_research_model_id", + "resolve_medium_model_id", + "resolve_model_id_from_settings", + "resolve_runtime_selectors_for_backend_id", +) +__all__ = list(_EXPORT_NAMES) + + +def __getattr__(name: str) -> Any: + """Lazily resolves one public profile-runtime export.""" + if name not in _EXPORT_NAMES: + raise AttributeError( + f"module 'ser._internal.models.profile_runtime' has no attribute {name!r}" + ) + value = getattr(importlib.import_module(_IMPL_MODULE), name) + globals()[name] = value + return value diff --git a/ser/models/training_entrypoints.py b/ser/models/training_entrypoints.py new file mode 100644 index 0000000..bec59cc --- /dev/null +++ b/ser/models/training_entrypoints.py @@ -0,0 +1,3 @@ +"""Public facade for `ser._internal.models.training_entrypoints`.""" + +from ser._internal.models.training_entrypoints import * # noqa: F403 diff --git a/tests/suites/integration/architecture/test_pipeline_contracts.py b/tests/suites/integration/architecture/test_pipeline_contracts.py index ff8d8e0..1320faa 100644 --- a/tests/suites/integration/architecture/test_pipeline_contracts.py +++ b/tests/suites/integration/architecture/test_pipeline_contracts.py @@ -6,11 +6,11 @@ import pytest import ser.__main__ as cli +import ser._internal.models.emotion_model as emotion_model +import ser._internal.models.training_support as training_support import ser.config as config_module -import ser.models.emotion_model as emotion_model -import ser.models.training_support as training_support from ser import domain -from ser.models.artifact_loading import model_load_candidates +from ser._internal.models.artifact_loading import model_load_candidates def _patch_cli_prerequisites(monkeypatch: pytest.MonkeyPatch) -> None: diff --git a/tests/suites/integration/models/test_emotion_model_contracts.py b/tests/suites/integration/models/test_emotion_model_contracts.py index 7e657a7..8a99cef 100644 --- a/tests/suites/integration/models/test_emotion_model_contracts.py +++ b/tests/suites/integration/models/test_emotion_model_contracts.py @@ -13,21 +13,21 @@ import pytest from sklearn.neural_network import MLPClassifier -import ser.data.data_loader as data_loader +import ser._internal.data.data_loader as data_loader +import ser._internal.models.accurate_training_execution as accurate_training_execution +import ser._internal.models.accurate_training_preparation as accurate_training_preparation +import ser._internal.models.artifact_envelope as artifact_envelope +import ser._internal.models.artifact_persistence as artifact_persistence +import ser._internal.models.emotion_model as em +import ser._internal.models.medium_feature_dataset as medium_feature_dataset +import ser._internal.models.medium_training_preparation as medium_training_preparation +import ser._internal.models.profile_runtime as profile_runtime +import ser._internal.models.training_entrypoints as training_entrypoints +import ser._internal.models.training_reporting as training_reporting +import ser._internal.models.training_support as training_support import ser.license_check as license_check -import ser.models.accurate_training_execution as accurate_training_execution -import ser.models.accurate_training_preparation as accurate_training_preparation -import ser.models.artifact_envelope as artifact_envelope -import ser.models.artifact_persistence as artifact_persistence -import ser.models.emotion_model as em -import ser.models.medium_feature_dataset as medium_feature_dataset -import ser.models.medium_training_preparation as medium_training_preparation -import ser.models.profile_runtime as profile_runtime -import ser.models.training_entrypoints as training_entrypoints -import ser.models.training_reporting as training_reporting -import ser.models.training_support as training_support -from ser.features import FeatureFrame -from ser.models.fast_training import FastTrainingHooks +from ser._internal.features import FeatureFrame +from ser._internal.models.fast_training import FastTrainingHooks from ser.runtime.schema import InferenceResult diff --git a/tests/suites/integration/models/test_training_preparation_and_execution.py b/tests/suites/integration/models/test_training_preparation_and_execution.py index 1bb96ba..1c95f7b 100644 --- a/tests/suites/integration/models/test_training_preparation_and_execution.py +++ b/tests/suites/integration/models/test_training_preparation_and_execution.py @@ -8,17 +8,17 @@ import numpy as np -from ser.models.training_execution import ( +from ser._internal.models.training_execution import ( execute_default_profile_training, finalize_profile_training_report, run_accurate_profile_training, run_medium_profile_training, ) -from ser.models.training_preparation import ( +from ser._internal.models.training_preparation import ( prepare_accurate_training_payload, prepare_medium_training_payload, ) -from ser.models.training_types import ( +from ser._internal.models.training_types import ( AccurateTrainingPreparation, MediumTrainingPreparation, TrainingEvaluation, diff --git a/tests/suites/integration/test_accurate_inference.py b/tests/suites/integration/test_accurate_inference.py index 671a72d..30dc195 100644 --- a/tests/suites/integration/test_accurate_inference.py +++ b/tests/suites/integration/test_accurate_inference.py @@ -11,10 +11,10 @@ from sklearn.neural_network import MLPClassifier import ser.config as config +from ser._internal.models import emotion_model +from ser._internal.repr import EncodedSequence from ser._internal.runtime import accurate_public_boundary as accurate_boundary -from ser.models import emotion_model -from ser.repr import EncodedSequence -from ser.runtime.accurate_inference import ( +from ser._internal.runtime.accurate_inference import ( AccurateInferenceExecutionError, AccurateInferenceTimeoutError, AccurateModelUnavailableError, diff --git a/tests/suites/integration/test_fast_inference.py b/tests/suites/integration/test_fast_inference.py index 1af43ce..b78a726 100644 --- a/tests/suites/integration/test_fast_inference.py +++ b/tests/suites/integration/test_fast_inference.py @@ -9,16 +9,16 @@ import pytest +import ser._internal.models.training_support as training_support import ser.config as config -import ser.models.training_support as training_support +from ser._internal.models import emotion_model from ser._internal.runtime import fast_public_boundary as fast_boundary -from ser.models import emotion_model -from ser.runtime.contracts import InferenceRequest -from ser.runtime.fast_inference import ( +from ser._internal.runtime.fast_inference import ( FastInferenceTimeoutError, FastModelUnavailableError, run_fast_inference, ) +from ser.runtime.contracts import InferenceRequest from ser.runtime.schema import OUTPUT_SCHEMA_VERSION, InferenceResult pytestmark = [pytest.mark.integration, pytest.mark.usefixtures("reset_ambient_settings")] diff --git a/tests/suites/integration/test_medium_inference.py b/tests/suites/integration/test_medium_inference.py index f28670d..fcbc7b1 100644 --- a/tests/suites/integration/test_medium_inference.py +++ b/tests/suites/integration/test_medium_inference.py @@ -12,11 +12,10 @@ from sklearn.neural_network import MLPClassifier import ser.config as config +from ser._internal.models import emotion_model +from ser._internal.repr import EncodedSequence from ser._internal.runtime import medium_public_boundary as medium_boundary -from ser.models import emotion_model -from ser.repr import EncodedSequence -from ser.runtime.contracts import InferenceRequest -from ser.runtime.medium_inference import ( +from ser._internal.runtime.medium_inference import ( MediumInferenceExecutionError, MediumInferenceTimeoutError, MediumModelUnavailableError, @@ -24,6 +23,7 @@ MediumTransientBackendError, run_medium_inference, ) +from ser.runtime.contracts import InferenceRequest from ser.runtime.schema import OUTPUT_SCHEMA_VERSION pytestmark = [pytest.mark.integration, pytest.mark.usefixtures("reset_ambient_settings")] diff --git a/tests/suites/smoke/test_cli_runtime_workflows.py b/tests/suites/smoke/test_cli_runtime_workflows.py index 2912a4f..9a5c4f2 100644 --- a/tests/suites/smoke/test_cli_runtime_workflows.py +++ b/tests/suites/smoke/test_cli_runtime_workflows.py @@ -5,10 +5,10 @@ import pytest import ser.__main__ as cli +import ser._internal.models.emotion_model as emotion_model +import ser._internal.runtime.fast_inference as fast_inference +import ser._internal.utils.timeline_utils as timeline_utils import ser.config as config_module -import ser.models.emotion_model as emotion_model -import ser.runtime.fast_inference as fast_inference -import ser.utils.timeline_utils as timeline_utils from ser.runtime.schema import OUTPUT_SCHEMA_VERSION, InferenceResult, SegmentPrediction pytestmark = [pytest.mark.smoke, pytest.mark.usefixtures("reset_ambient_settings")] @@ -71,7 +71,7 @@ def _fake_build_timeline(_transcript: object, _emotions: object) -> list[object] _fake_run_fast_inference, ) monkeypatch.setattr( - "ser.transcript.extract_transcript", + "ser._internal.transcript.transcript_extractor.extract_transcript", lambda *_args, **_kwargs: (_ for _ in ()).throw( AssertionError("Transcription should not run for --no-transcript.") ), diff --git a/tests/suites/unit/models/test_accurate_feature_dataset.py b/tests/suites/unit/models/test_accurate_feature_dataset.py index dd24f00..3dfc919 100644 --- a/tests/suites/unit/models/test_accurate_feature_dataset.py +++ b/tests/suites/unit/models/test_accurate_feature_dataset.py @@ -11,10 +11,10 @@ import numpy as np import pytest -import ser.models.accurate_feature_dataset as afd +import ser._internal.models.accurate_feature_dataset as afd +from ser._internal.data import EmbeddingCache +from ser._internal.repr import EncodedSequence, FeatureBackend from ser.config import AppConfig, AudioReadConfig -from ser.data import EmbeddingCache -from ser.repr import EncodedSequence, FeatureBackend @dataclass(frozen=True) diff --git a/tests/suites/unit/models/test_accurate_training_artifact.py b/tests/suites/unit/models/test_accurate_training_artifact.py index 0b4f8b7..3a1895f 100644 --- a/tests/suites/unit/models/test_accurate_training_artifact.py +++ b/tests/suites/unit/models/test_accurate_training_artifact.py @@ -12,18 +12,18 @@ from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler -import ser.models.accurate_feature_dataset as accurate_feature_dataset -import ser.models.accurate_training_execution as accurate_training_execution -import ser.models.profile_runtime as profile_runtime -import ser.models.training_entrypoints as training_entrypoints -import ser.models.training_support as training_support +import ser._internal.models.accurate_feature_dataset as accurate_feature_dataset +import ser._internal.models.accurate_training_execution as accurate_training_execution +import ser._internal.models.profile_runtime as profile_runtime +import ser._internal.models.training_entrypoints as training_entrypoints +import ser._internal.models.training_support as training_support +from ser._internal.data import EmbeddingCache +from ser._internal.data.manifest import MANIFEST_SCHEMA_VERSION, Utterance +from ser._internal.models import emotion_model as em +from ser._internal.models.dataset_splitting import MediumSplitMetadata +from ser._internal.models.profile_runtime import resolve_accurate_research_model_id +from ser._internal.repr.runtime_policy import resolve_feature_runtime_policy from ser.config import AppConfig -from ser.data import EmbeddingCache -from ser.data.manifest import MANIFEST_SCHEMA_VERSION, Utterance -from ser.models import emotion_model as em -from ser.models.dataset_splitting import MediumSplitMetadata -from ser.models.profile_runtime import resolve_accurate_research_model_id -from ser.repr.runtime_policy import resolve_feature_runtime_policy def _settings_stub(tmp_path: Path) -> SimpleNamespace: diff --git a/tests/suites/unit/models/test_accurate_training_preparation.py b/tests/suites/unit/models/test_accurate_training_preparation.py index 498ce4c..665b631 100644 --- a/tests/suites/unit/models/test_accurate_training_preparation.py +++ b/tests/suites/unit/models/test_accurate_training_preparation.py @@ -11,16 +11,16 @@ import numpy as np import pytest +import ser._internal.models.accurate_training_execution as ate +import ser._internal.models.accurate_training_preparation as atp +import ser._internal.models.artifact_envelope as artifact_envelope +import ser._internal.models.artifact_persistence as artifact_persistence +import ser._internal.models.training_reporting as training_reporting +import ser._internal.models.training_support as training_support import ser.license_check as license_check -import ser.models.accurate_training_execution as ate -import ser.models.accurate_training_preparation as atp -import ser.models.artifact_envelope as artifact_envelope -import ser.models.artifact_persistence as artifact_persistence -import ser.models.training_reporting as training_reporting -import ser.models.training_support as training_support +from ser._internal.data.manifest import MANIFEST_SCHEMA_VERSION, Utterance +from ser._internal.models.dataset_splitting import MediumSplitMetadata from ser.config import AppConfig -from ser.data.manifest import MANIFEST_SCHEMA_VERSION, Utterance -from ser.models.dataset_splitting import MediumSplitMetadata def _settings_stub(tmp_path: Path) -> AppConfig: diff --git a/tests/suites/unit/models/test_artifact_loading.py b/tests/suites/unit/models/test_artifact_loading.py index cddd91b..28ade1e 100644 --- a/tests/suites/unit/models/test_artifact_loading.py +++ b/tests/suites/unit/models/test_artifact_loading.py @@ -10,7 +10,7 @@ import pytest -from ser.models.artifact_loading import ( +from ser._internal.models.artifact_loading import ( load_model_with_resolution, load_pickle_model_artifact, load_secure_model_artifact, diff --git a/tests/suites/unit/models/test_artifact_persistence.py b/tests/suites/unit/models/test_artifact_persistence.py index 402efe7..1d37f6f 100644 --- a/tests/suites/unit/models/test_artifact_persistence.py +++ b/tests/suites/unit/models/test_artifact_persistence.py @@ -7,7 +7,7 @@ from sklearn.neural_network import MLPClassifier -from ser.models import artifact_persistence +from ser._internal.models import artifact_persistence @dataclass(frozen=True) diff --git a/tests/suites/unit/models/test_artifact_upgrade_path.py b/tests/suites/unit/models/test_artifact_upgrade_path.py index f3551bc..2973855 100644 --- a/tests/suites/unit/models/test_artifact_upgrade_path.py +++ b/tests/suites/unit/models/test_artifact_upgrade_path.py @@ -5,7 +5,7 @@ import pytest from sklearn.neural_network import MLPClassifier -import ser.models.artifact_envelope as artifact_envelope +import ser._internal.models.artifact_envelope as artifact_envelope def _build_classifier() -> MLPClassifier: diff --git a/tests/suites/unit/models/test_dataset_training_consents.py b/tests/suites/unit/models/test_dataset_training_consents.py index f6b9fcb..161756c 100644 --- a/tests/suites/unit/models/test_dataset_training_consents.py +++ b/tests/suites/unit/models/test_dataset_training_consents.py @@ -8,11 +8,11 @@ import pytest -import ser.models.dataset_training_consents as training_consents +import ser._internal.models.dataset_training_consents as training_consents +from ser._internal.data import Utterance +from ser._internal.data.dataset_consents import DatasetConsentError +from ser._internal.data.manifest import MANIFEST_SCHEMA_VERSION from ser.config import AppConfig -from ser.data import Utterance -from ser.data.dataset_consents import DatasetConsentError -from ser.data.manifest import MANIFEST_SCHEMA_VERSION def _settings_stub(tmp_path: Path) -> AppConfig: diff --git a/tests/suites/unit/models/test_emotion_model.py b/tests/suites/unit/models/test_emotion_model.py index a42496b..d79d636 100644 --- a/tests/suites/unit/models/test_emotion_model.py +++ b/tests/suites/unit/models/test_emotion_model.py @@ -10,14 +10,14 @@ import pytest from sklearn.neural_network import MLPClassifier -import ser.models.artifact_persistence as artifact_persistence -import ser.models.training_reporting as training_reporting -import ser.models.training_support as training_support -from ser.data.manifest import MANIFEST_SCHEMA_VERSION, Utterance -from ser.features import FeatureFrame -from ser.models import emotion_model as em -from ser.models.dataset_splitting import MediumSplitMetadata -from ser.models.medium_noise_controls import MediumNoiseControlStats +import ser._internal.models.artifact_persistence as artifact_persistence +import ser._internal.models.training_reporting as training_reporting +import ser._internal.models.training_support as training_support +from ser._internal.data.manifest import MANIFEST_SCHEMA_VERSION, Utterance +from ser._internal.features import FeatureFrame +from ser._internal.models import emotion_model as em +from ser._internal.models.dataset_splitting import MediumSplitMetadata +from ser._internal.models.medium_noise_controls import MediumNoiseControlStats from ser.runtime.schema import OUTPUT_SCHEMA_VERSION, SegmentPrediction @@ -672,11 +672,11 @@ def test_build_dataset_controls_reports_registry_mode( lambda: (_ for _ in ()).throw(AssertionError("helper must use explicit settings")), ) monkeypatch.setattr( - "ser.data.dataset_registry.load_dataset_registry", + "ser._internal.data.dataset_registry.load_dataset_registry", lambda settings: {"ravdess": object()}, ) monkeypatch.setattr( - "ser.data.dataset_registry.registered_manifest_paths", + "ser._internal.data.dataset_registry.registered_manifest_paths", lambda settings: (Path("manifests/ravdess.jsonl"),), ) diff --git a/tests/suites/unit/models/test_medium_feature_dataset.py b/tests/suites/unit/models/test_medium_feature_dataset.py index 1292032..19f9fa8 100644 --- a/tests/suites/unit/models/test_medium_feature_dataset.py +++ b/tests/suites/unit/models/test_medium_feature_dataset.py @@ -10,14 +10,14 @@ import numpy as np import pytest -import ser.models.emotion_model as emotion_model -import ser.models.medium_feature_dataset as mfd -from ser.config import AppConfig, AudioReadConfig -from ser.data import EmbeddingCache -from ser.models.medium_noise_controls import ( +import ser._internal.models.emotion_model as emotion_model +import ser._internal.models.medium_feature_dataset as mfd +from ser._internal.data import EmbeddingCache +from ser._internal.models.medium_noise_controls import ( MediumNoiseControlStats, ) -from ser.repr import EncodedSequence, XLSRBackend +from ser._internal.repr import EncodedSequence, XLSRBackend +from ser.config import AppConfig, AudioReadConfig @dataclass(frozen=True) diff --git a/tests/suites/unit/models/test_medium_noise_controls.py b/tests/suites/unit/models/test_medium_noise_controls.py index 99bcce7..5e7f2a8 100644 --- a/tests/suites/unit/models/test_medium_noise_controls.py +++ b/tests/suites/unit/models/test_medium_noise_controls.py @@ -5,7 +5,7 @@ import numpy as np import pytest -from ser.models.medium_noise_controls import ( +from ser._internal.models.medium_noise_controls import ( MediumNoiseControlStats, apply_medium_noise_controls, merge_medium_noise_stats, diff --git a/tests/suites/unit/models/test_medium_quality_report.py b/tests/suites/unit/models/test_medium_quality_report.py index 54184fe..5711266 100644 --- a/tests/suites/unit/models/test_medium_quality_report.py +++ b/tests/suites/unit/models/test_medium_quality_report.py @@ -9,9 +9,9 @@ import pytest -import ser.models.training_support as training_support +import ser._internal.models.training_support as training_support +from ser._internal.data.manifest import MANIFEST_SCHEMA_VERSION, Utterance from ser.config import AppConfig -from ser.data.manifest import MANIFEST_SCHEMA_VERSION, Utterance def _sample_utterance( diff --git a/tests/suites/unit/models/test_medium_training_noise_controls.py b/tests/suites/unit/models/test_medium_training_noise_controls.py index f9dda0f..5cd3eb5 100644 --- a/tests/suites/unit/models/test_medium_training_noise_controls.py +++ b/tests/suites/unit/models/test_medium_training_noise_controls.py @@ -8,7 +8,7 @@ import pytest import ser.config as config -from ser.models.medium_noise_controls import ( +from ser._internal.models.medium_noise_controls import ( MediumNoiseControlStats, apply_medium_noise_controls, ) diff --git a/tests/suites/unit/models/test_medium_training_preparation.py b/tests/suites/unit/models/test_medium_training_preparation.py index e2d498f..8ebace6 100644 --- a/tests/suites/unit/models/test_medium_training_preparation.py +++ b/tests/suites/unit/models/test_medium_training_preparation.py @@ -9,10 +9,10 @@ import numpy as np import pytest -import ser.models.medium_training_preparation as mtp +import ser._internal.models.medium_training_preparation as mtp +from ser._internal.data.manifest import MANIFEST_SCHEMA_VERSION, Utterance +from ser._internal.models.dataset_splitting import MediumSplitMetadata from ser.config import AppConfig -from ser.data.manifest import MANIFEST_SCHEMA_VERSION, Utterance -from ser.models.dataset_splitting import MediumSplitMetadata def _settings_stub(tmp_path: Path) -> AppConfig: diff --git a/tests/suites/unit/models/test_medium_training_windowing.py b/tests/suites/unit/models/test_medium_training_windowing.py index ab14f02..3db513d 100644 --- a/tests/suites/unit/models/test_medium_training_windowing.py +++ b/tests/suites/unit/models/test_medium_training_windowing.py @@ -8,9 +8,9 @@ import pytest import ser.config as config -from ser.models import medium_feature_dataset -from ser.repr import EncodedSequence -from ser.runtime import medium_execution +from ser._internal.models import medium_feature_dataset +from ser._internal.repr import EncodedSequence +from ser._internal.runtime import medium_execution @pytest.fixture(autouse=True) diff --git a/tests/suites/unit/models/test_provenance_metadata.py b/tests/suites/unit/models/test_provenance_metadata.py index 6845214..7574c9a 100644 --- a/tests/suites/unit/models/test_provenance_metadata.py +++ b/tests/suites/unit/models/test_provenance_metadata.py @@ -9,8 +9,8 @@ import pytest from sklearn.neural_network import MLPClassifier -import ser.models.artifact_envelope as artifact_envelope -import ser.models.training_support as training_support +import ser._internal.models.artifact_envelope as artifact_envelope +import ser._internal.models.training_support as training_support from ser import config from ser.license_check import build_provenance_metadata, persist_backend_consent diff --git a/tests/suites/unit/models/test_torch_head.py b/tests/suites/unit/models/test_torch_head.py index f0af261..b7bfd86 100644 --- a/tests/suites/unit/models/test_torch_head.py +++ b/tests/suites/unit/models/test_torch_head.py @@ -5,7 +5,7 @@ import numpy as np import pytest -from ser.heads.torch_head import build_torch_mlp_head, forward_torch_head +from ser._internal.heads.torch_head import build_torch_mlp_head, forward_torch_head def test_build_torch_mlp_head_rejects_invalid_dimensions() -> None: diff --git a/tests/suites/unit/models/test_training_reporting.py b/tests/suites/unit/models/test_training_reporting.py index 9658d10..a71d91e 100644 --- a/tests/suites/unit/models/test_training_reporting.py +++ b/tests/suites/unit/models/test_training_reporting.py @@ -5,7 +5,7 @@ from dataclasses import dataclass from pathlib import Path -from ser.models import training_reporting +from ser._internal.models import training_reporting class _Artifacts: From e952e419f3ba43167664005ffe4df88f2085d7b0 Mon Sep 17 00:00:00 2001 From: Juan Sugg Date: Thu, 9 Jul 2026 22:30:34 -0300 Subject: [PATCH 04/12] refactor(runtime): move implementation internal --- scripts/run_full_dataset_quality_gate.sh | 2 +- ser/__main__.py | 4 +- ser/_internal/api/runtime.py | 4 +- .../runtime/accurate_backend_runtime.py | 2 +- ser/_internal/runtime/accurate_execution.py | 8 +- ser/_internal/runtime/accurate_inference.py | 4 +- .../runtime/accurate_model_contract.py | 2 +- .../runtime/accurate_public_boundary.py | 60 +++++++------- .../runtime/accurate_research_inference.py | 10 +-- .../runtime/accurate_runtime_support.py | 2 +- ser/_internal/runtime/backend_hooks.py | 8 +- ser/_internal/runtime/benchmarks.py | 2 +- ser/_internal/runtime/commands.py | 16 ++-- ser/_internal/runtime/fast_inference.py | 2 +- ser/_internal/runtime/fast_public_boundary.py | 14 ++-- .../runtime/medium_backend_runtime.py | 4 +- ser/_internal/runtime/medium_execution.py | 8 +- .../runtime/medium_execution_context.py | 2 +- ser/_internal/runtime/medium_inference.py | 4 +- .../runtime/medium_public_boundary.py | 40 +++++----- .../runtime/medium_runtime_support.py | 2 +- ser/_internal/runtime/phase_timing.py | 2 +- ser/_internal/runtime/pipeline.py | 37 +++++---- ser/_internal/runtime/profile_quality_gate.py | 56 +++++++------ ser/_internal/runtime/quality_gate_cli.py | 8 +- ser/runtime/__init__.py | 26 ++++++- ser/runtime/pipeline.py | 3 + tests/suites/integration/cli/test_cli.py | 18 ++--- .../test_accurate_research_inference.py | 12 +-- .../runtime/test_profile_quality_gate.py | 22 +++--- .../integration/test_runtime_pipeline.py | 78 ++++++++++--------- .../runtime/test_accurate_model_contract.py | 4 +- .../unit/runtime/test_accurate_prediction.py | 2 +- tests/suites/unit/runtime/test_benchmarks.py | 2 +- .../unit/runtime/test_dual_schema_compat.py | 2 +- .../unit/runtime/test_medium_prediction.py | 2 +- tests/suites/unit/runtime/test_mps_oom.py | 2 +- .../unit/runtime/test_postprocessing.py | 2 +- .../unit/runtime/test_timestamp_semantics.py | 4 +- ...st_run_full_dataset_quality_gate_script.py | 3 +- tests/suites/unit/test_accurate_execution.py | 8 +- .../unit/test_accurate_execution_flow.py | 2 +- .../unit/test_accurate_runtime_support.py | 2 +- tests/suites/unit/test_medium_execution.py | 8 +- .../unit/test_medium_execution_context.py | 2 +- .../suites/unit/test_medium_execution_flow.py | 2 +- .../unit/test_medium_runtime_support.py | 2 +- 47 files changed, 277 insertions(+), 234 deletions(-) create mode 100644 ser/runtime/pipeline.py diff --git a/scripts/run_full_dataset_quality_gate.sh b/scripts/run_full_dataset_quality_gate.sh index 311b0c3..dea0f8a 100755 --- a/scripts/run_full_dataset_quality_gate.sh +++ b/scripts/run_full_dataset_quality_gate.sh @@ -79,7 +79,7 @@ if [[ "$run_training" == "true" ]]; then fi gate_cmd=( - uv run --frozen --extra medium python -m ser.runtime.profile_quality_gate + uv run --frozen --extra medium python -m ser._internal.runtime.profile_quality_gate --dataset-glob "$dataset_glob" --random-state 42 --test-size 0.25 diff --git a/ser/__main__.py b/ser/__main__.py index 7b9a254..af49522 100644 --- a/ser/__main__.py +++ b/ser/__main__.py @@ -29,13 +29,13 @@ run_training_command, run_transcription_runtime_calibration_command, ) +from ser._internal.runtime.phase_timing import format_duration +from ser._internal.utils.subtitles import SUPPORTED_SUBTITLE_FORMATS from ser.config import AppConfig, reload_settings, settings_override from ser.diagnostics.domain import PreflightMode from ser.profiles import ProfileName from ser.runtime.contracts import SubtitleFormat -from ser.runtime.phase_timing import format_duration from ser.utils.logger import configure_logging, get_logger -from ser.utils.subtitles import SUPPORTED_SUBTITLE_FORMATS logger: logging.Logger = get_logger("ser") type CliProfileName = ProfileName diff --git a/ser/_internal/api/runtime.py b/ser/_internal/api/runtime.py index aa2fce1..ed8ffa8 100644 --- a/ser/_internal/api/runtime.py +++ b/ser/_internal/api/runtime.py @@ -48,8 +48,8 @@ logger = get_logger(__name__) if TYPE_CHECKING: + from ser._internal.transcript.profiling import RuntimeCalibrationResult from ser.runtime.contracts import InferenceExecution, InferenceRequest, SubtitleFormat - from ser.transcript.profiling import RuntimeCalibrationResult class _RuntimePipeline(Protocol): @@ -201,7 +201,7 @@ def resolve_cli_workflow_profile(settings: AppConfig) -> ProfileName: def _build_runtime_pipeline(settings: AppConfig) -> _RuntimePipeline: """Builds one runtime pipeline for API-managed train/inference workflows.""" - from ser.runtime.pipeline import create_runtime_pipeline + from ser._internal.runtime.pipeline import create_runtime_pipeline return create_runtime_pipeline(settings) diff --git a/ser/_internal/runtime/accurate_backend_runtime.py b/ser/_internal/runtime/accurate_backend_runtime.py index c309eaf..1c28685 100644 --- a/ser/_internal/runtime/accurate_backend_runtime.py +++ b/ser/_internal/runtime/accurate_backend_runtime.py @@ -6,8 +6,8 @@ from dataclasses import replace from typing import Protocol +from ser._internal.repr import FeatureBackend from ser.config import AppConfig, ProfileRuntimeConfig -from ser.repr import FeatureBackend class RuntimePolicyLike(Protocol): diff --git a/ser/_internal/runtime/accurate_execution.py b/ser/_internal/runtime/accurate_execution.py index 3b13d6c..a6d6bd7 100644 --- a/ser/_internal/runtime/accurate_execution.py +++ b/ser/_internal/runtime/accurate_execution.py @@ -8,13 +8,13 @@ import numpy as np from numpy.typing import NDArray -from ser.config import ProfileRuntimeConfig -from ser.pool import mean_std_pool, temporal_pooling_windows -from ser.repr import EncodedSequence, PoolingWindow -from ser.runtime.postprocessing import ( +from ser._internal.repr import EncodedSequence, PoolingWindow +from ser._internal.runtime.postprocessing import ( build_segment_postprocessing_config, postprocess_frame_predictions, ) +from ser.config import ProfileRuntimeConfig +from ser.pool import mean_std_pool, temporal_pooling_windows from ser.runtime.schema import ( OUTPUT_SCHEMA_VERSION, FramePrediction, diff --git a/ser/_internal/runtime/accurate_inference.py b/ser/_internal/runtime/accurate_inference.py index 49c7c7a..0d76514 100644 --- a/ser/_internal/runtime/accurate_inference.py +++ b/ser/_internal/runtime/accurate_inference.py @@ -2,10 +2,10 @@ from __future__ import annotations +from ser._internal.models.emotion_model import LoadedModel +from ser._internal.repr import FeatureBackend from ser._internal.runtime import accurate_public_boundary as _boundary_support from ser.config import AppConfig -from ser.models.emotion_model import LoadedModel -from ser.repr import FeatureBackend from ser.runtime.contracts import InferenceRequest from ser.runtime.schema import InferenceResult from ser.utils.logger import get_logger diff --git a/ser/_internal/runtime/accurate_model_contract.py b/ser/_internal/runtime/accurate_model_contract.py index 074ea70..7744a6c 100644 --- a/ser/_internal/runtime/accurate_model_contract.py +++ b/ser/_internal/runtime/accurate_model_contract.py @@ -6,8 +6,8 @@ from collections.abc import Callable from typing import Protocol +from ser._internal.repr.runtime_policy import resolve_feature_runtime_policy from ser.config import AppConfig -from ser.repr.runtime_policy import resolve_feature_runtime_policy class ArtifactMetadataCarrier(Protocol): diff --git a/ser/_internal/runtime/accurate_public_boundary.py b/ser/_internal/runtime/accurate_public_boundary.py index 458876d..f5305ac 100644 --- a/ser/_internal/runtime/accurate_public_boundary.py +++ b/ser/_internal/runtime/accurate_public_boundary.py @@ -14,18 +14,36 @@ import numpy as np from numpy.typing import NDArray +from ser._internal.models.emotion_model import load_model +from ser._internal.models.profile_runtime import ( + resolve_accurate_model_id, + resolve_accurate_research_model_id, +) +from ser._internal.repr import Emotion2VecBackend, FeatureBackend, WhisperBackend +from ser._internal.repr.runtime_policy import resolve_feature_runtime_policy +from ser._internal.runtime import mps_oom as mps_oom_helpers from ser._internal.runtime.accurate_backend_runtime import ( build_backend_for_profile as _build_backend_for_profile_impl, ) from ser._internal.runtime.accurate_backend_runtime import ( runtime_config_for_profile as _runtime_config_for_profile_impl, ) +from ser._internal.runtime.accurate_execution import LoadedModelLike as _AccurateLoadedModelLike +from ser._internal.runtime.accurate_execution import ( + run_accurate_inference_once as _run_accurate_inference_once_impl, +) from ser._internal.runtime.accurate_execution_flow import ( execute_accurate_inference_with_retry as _execute_accurate_inference_with_retry_impl, ) from ser._internal.runtime.accurate_execution_flow import ( run_accurate_retryable_operation as _run_accurate_retryable_operation_impl, ) +from ser._internal.runtime.accurate_model_contract import ( + ArtifactMetadataCarrier, +) +from ser._internal.runtime.accurate_model_contract import ( + validate_accurate_loaded_model_runtime_contract as _validate_accurate_loaded_model_runtime_contract_impl, +) from ser._internal.runtime.accurate_operation_setup import _PayloadLike as _AccuratePayloadLike from ser._internal.runtime.accurate_operation_setup import _RequestLike as _AccurateRequestLike from ser._internal.runtime.accurate_operation_setup import ( @@ -37,6 +55,10 @@ from ser._internal.runtime.accurate_operation_setup import ( run_process_operation as _run_process_operation_orchestration, ) +from ser._internal.runtime.accurate_prediction import ( + confidence_and_probabilities as _confidence_and_probabilities_impl, +) +from ser._internal.runtime.accurate_prediction import predict_labels as _predict_labels_impl from ser._internal.runtime.accurate_retry_operation import ( run_accurate_inference_with_retry_policy as _run_accurate_retry_policy_impl, ) @@ -66,6 +88,12 @@ from ser._internal.runtime.accurate_worker_operation import ( run_inference_operation as _run_inference_operation_impl, ) +from ser._internal.runtime.phase_contract import PHASE_EMOTION_INFERENCE, PHASE_EMOTION_SETUP +from ser._internal.runtime.phase_timing import ( + log_phase_completed, + log_phase_failed, + log_phase_started, +) from ser._internal.runtime.policy import run_with_retry_policy from ser._internal.runtime.process_timeout import ( run_with_process_timeout as _run_with_process_timeout_impl, @@ -103,38 +131,10 @@ from ser._internal.runtime.worker_lifecycle import ( terminate_worker_process as _terminate_worker_process_impl, ) +from ser._internal.utils.audio_utils import read_audio_file from ser.config import AppConfig, ProfileRuntimeConfig -from ser.models.emotion_model import load_model -from ser.models.profile_runtime import ( - resolve_accurate_model_id, - resolve_accurate_research_model_id, -) -from ser.repr import Emotion2VecBackend, FeatureBackend, WhisperBackend -from ser.repr.runtime_policy import resolve_feature_runtime_policy -from ser.runtime import mps_oom as mps_oom_helpers -from ser.runtime.accurate_execution import LoadedModelLike as _AccurateLoadedModelLike -from ser.runtime.accurate_execution import ( - run_accurate_inference_once as _run_accurate_inference_once_impl, -) -from ser.runtime.accurate_model_contract import ( - ArtifactMetadataCarrier, -) -from ser.runtime.accurate_model_contract import ( - validate_accurate_loaded_model_runtime_contract as _validate_accurate_loaded_model_runtime_contract_impl, -) -from ser.runtime.accurate_prediction import ( - confidence_and_probabilities as _confidence_and_probabilities_impl, -) -from ser.runtime.accurate_prediction import predict_labels as _predict_labels_impl from ser.runtime.contracts import InferenceRequest -from ser.runtime.phase_contract import PHASE_EMOTION_INFERENCE, PHASE_EMOTION_SETUP -from ser.runtime.phase_timing import ( - log_phase_completed, - log_phase_failed, - log_phase_started, -) from ser.runtime.schema import InferenceResult -from ser.utils.audio_utils import read_audio_file class _AccurateLoadedModel(ArtifactMetadataCarrier, _AccurateLoadedModelLike, Protocol): @@ -152,7 +152,7 @@ class _AccurateLoadedModel(ArtifactMetadataCarrier, _AccurateLoadedModelLike, Pr _TERMINATE_GRACE_SECONDS = 0.5 _KILL_GRACE_SECONDS = 0.5 _SINGLE_FLIGHT_REGISTRY = SingleFlightRegistry() -_WORKER_LOGGER = logging.getLogger("ser.runtime.accurate_inference") +_WORKER_LOGGER = logging.getLogger("ser._internal.runtime.accurate_inference") @dataclass(frozen=True) diff --git a/ser/_internal/runtime/accurate_research_inference.py b/ser/_internal/runtime/accurate_research_inference.py index a1b787b..b86879a 100644 --- a/ser/_internal/runtime/accurate_research_inference.py +++ b/ser/_internal/runtime/accurate_research_inference.py @@ -2,12 +2,12 @@ from __future__ import annotations +from ser._internal.models.emotion_model import LoadedModel +from ser._internal.models.profile_runtime import resolve_accurate_research_model_id +from ser._internal.repr import Emotion2VecBackend, FeatureBackend +from ser._internal.repr.runtime_policy import resolve_feature_runtime_policy +from ser._internal.runtime.accurate_inference import run_accurate_inference from ser.config import AppConfig -from ser.models.emotion_model import LoadedModel -from ser.models.profile_runtime import resolve_accurate_research_model_id -from ser.repr import Emotion2VecBackend, FeatureBackend -from ser.repr.runtime_policy import resolve_feature_runtime_policy -from ser.runtime.accurate_inference import run_accurate_inference from ser.runtime.contracts import InferenceRequest from ser.runtime.schema import InferenceResult diff --git a/ser/_internal/runtime/accurate_runtime_support.py b/ser/_internal/runtime/accurate_runtime_support.py index bf7c52e..63e28a4 100644 --- a/ser/_internal/runtime/accurate_runtime_support.py +++ b/ser/_internal/runtime/accurate_runtime_support.py @@ -7,6 +7,7 @@ import numpy as np from numpy.typing import NDArray +from ser._internal.repr import EncodedSequence, FeatureBackend from ser._internal.runtime.accurate_backend_runtime import ( prepare_accurate_backend_runtime as _prepare_accurate_backend_runtime_impl, ) @@ -17,7 +18,6 @@ is_optional_dependency_runtime_error as _is_optional_dependency_runtime_error_impl, ) from ser.config import AppConfig -from ser.repr import EncodedSequence, FeatureBackend def build_process_settings_snapshot(settings: AppConfig) -> AppConfig: diff --git a/ser/_internal/runtime/backend_hooks.py b/ser/_internal/runtime/backend_hooks.py index 574d938..dfa94b8 100644 --- a/ser/_internal/runtime/backend_hooks.py +++ b/ser/_internal/runtime/backend_hooks.py @@ -43,7 +43,7 @@ def _missing_optional_modules(required_modules: tuple[str, ...]) -> tuple[str, . def _load_medium_inference_runner() -> MediumInferenceRunner | None: """Loads the concrete medium inference runner when implementation exists.""" try: - module = importlib.import_module("ser.runtime.medium_inference") + module = importlib.import_module("ser._internal.runtime.medium_inference") except ModuleNotFoundError: return None @@ -56,7 +56,7 @@ def _load_medium_inference_runner() -> MediumInferenceRunner | None: def _load_fast_inference_runner() -> FastInferenceRunner | None: """Loads the concrete fast inference runner when implementation exists.""" try: - module = importlib.import_module("ser.runtime.fast_inference") + module = importlib.import_module("ser._internal.runtime.fast_inference") except ModuleNotFoundError: return None @@ -69,7 +69,7 @@ def _load_fast_inference_runner() -> FastInferenceRunner | None: def _load_accurate_inference_runner() -> AccurateInferenceRunner | None: """Loads the concrete accurate inference runner when implementation exists.""" try: - module = importlib.import_module("ser.runtime.accurate_inference") + module = importlib.import_module("ser._internal.runtime.accurate_inference") except ModuleNotFoundError: return None @@ -82,7 +82,7 @@ def _load_accurate_inference_runner() -> AccurateInferenceRunner | None: def _load_accurate_research_inference_runner() -> AccurateResearchInferenceRunner | None: """Loads the concrete accurate-research inference runner when available.""" try: - module = importlib.import_module("ser.runtime.accurate_research_inference") + module = importlib.import_module("ser._internal.runtime.accurate_research_inference") except ModuleNotFoundError: return None diff --git a/ser/_internal/runtime/benchmarks.py b/ser/_internal/runtime/benchmarks.py index 9873ed4..d6e8b33 100644 --- a/ser/_internal/runtime/benchmarks.py +++ b/ser/_internal/runtime/benchmarks.py @@ -13,7 +13,7 @@ def _predict_emotions(audio_path: str) -> object: """Resolves the public predictor lazily to keep benchmark startup lightweight.""" - from ser.models.emotion_model import predict_emotions + from ser._internal.models.emotion_model import predict_emotions return predict_emotions(audio_path) diff --git a/ser/_internal/runtime/commands.py b/ser/_internal/runtime/commands.py index 66b9aa8..8611bd3 100644 --- a/ser/_internal/runtime/commands.py +++ b/ser/_internal/runtime/commands.py @@ -7,12 +7,12 @@ from pathlib import Path from typing import TYPE_CHECKING +from ser._internal.utils.subtitles import resolve_subtitle_export_request from ser.config import AppConfig -from ser.utils.subtitles import resolve_subtitle_export_request if TYPE_CHECKING: + from ser._internal.transcript.profiling import RuntimeCalibrationResult from ser.runtime.contracts import InferenceExecution, SubtitleFormat - from ser.transcript.profiling import RuntimeCalibrationResult type _TrainingWorkflow = Callable[..., None] type _InferenceWorkflow = Callable[..., InferenceExecution] @@ -44,28 +44,28 @@ def classify_training_exception(err: Exception) -> WorkflowErrorDisposition: def classify_inference_exception(err: Exception) -> WorkflowErrorDisposition: """Classifies one inference exception into CLI logging/exit behavior.""" - from ser.license_check import BackendLicensePolicyError - from ser.runtime import UnsupportedProfileError - from ser.runtime.accurate_inference import ( + from ser._internal.runtime.accurate_inference import ( AccurateInferenceExecutionError, AccurateInferenceTimeoutError, AccurateModelLoadError, AccurateModelUnavailableError, AccurateRuntimeDependencyError, ) - from ser.runtime.fast_inference import ( + from ser._internal.runtime.fast_inference import ( FastInferenceExecutionError, FastInferenceTimeoutError, FastModelLoadError, FastModelUnavailableError, ) - from ser.runtime.medium_inference import ( + from ser._internal.runtime.medium_inference import ( MediumInferenceExecutionError, MediumInferenceTimeoutError, MediumModelLoadError, MediumModelUnavailableError, MediumRuntimeDependencyError, ) + from ser.license_check import BackendLicensePolicyError + from ser.runtime import UnsupportedProfileError from ser.transcript import TranscriptionError if isinstance(err, UnsupportedProfileError): @@ -203,7 +203,7 @@ def run_transcription_runtime_calibration_workflow( calibration_profiles: str, ) -> RuntimeCalibrationResult: """Runs runtime calibration workflow with CLI-equivalent argument handling.""" - from ser.transcript.profiling import ( + from ser._internal.transcript.profiling import ( parse_calibration_profiles, run_transcription_runtime_calibration, ) diff --git a/ser/_internal/runtime/fast_inference.py b/ser/_internal/runtime/fast_inference.py index fe63f8a..9e7ac6f 100644 --- a/ser/_internal/runtime/fast_inference.py +++ b/ser/_internal/runtime/fast_inference.py @@ -2,9 +2,9 @@ from __future__ import annotations +from ser._internal.models.emotion_model import LoadedModel from ser._internal.runtime import fast_public_boundary as _boundary_support from ser.config import AppConfig -from ser.models.emotion_model import LoadedModel from ser.runtime.contracts import InferenceRequest from ser.runtime.schema import InferenceResult from ser.utils.logger import get_logger diff --git a/ser/_internal/runtime/fast_public_boundary.py b/ser/_internal/runtime/fast_public_boundary.py index 5506b59..a9da1da 100644 --- a/ser/_internal/runtime/fast_public_boundary.py +++ b/ser/_internal/runtime/fast_public_boundary.py @@ -10,6 +10,13 @@ from multiprocessing.process import BaseProcess from typing import Literal +from ser._internal.models.emotion_model import LoadedModel, load_model, predict_emotions_detailed +from ser._internal.runtime.phase_contract import PHASE_EMOTION_INFERENCE, PHASE_EMOTION_SETUP +from ser._internal.runtime.phase_timing import ( + log_phase_completed, + log_phase_failed, + log_phase_started, +) from ser._internal.runtime.policy import run_with_retry_policy from ser._internal.runtime.process_timeout import ( run_with_process_timeout as _run_with_process_timeout_impl, @@ -45,14 +52,7 @@ terminate_worker_process as _terminate_worker_process_impl, ) from ser.config import AppConfig -from ser.models.emotion_model import LoadedModel, load_model, predict_emotions_detailed from ser.runtime.contracts import InferenceRequest -from ser.runtime.phase_contract import PHASE_EMOTION_INFERENCE, PHASE_EMOTION_SETUP -from ser.runtime.phase_timing import ( - log_phase_completed, - log_phase_failed, - log_phase_started, -) from ser.runtime.schema import InferenceResult type WorkerPhaseMessage = tuple[Literal["phase"], Literal["setup_complete"]] diff --git a/ser/_internal/runtime/medium_backend_runtime.py b/ser/_internal/runtime/medium_backend_runtime.py index bd1174b..e9e2214 100644 --- a/ser/_internal/runtime/medium_backend_runtime.py +++ b/ser/_internal/runtime/medium_backend_runtime.py @@ -7,9 +7,9 @@ from dataclasses import replace from typing import Protocol +from ser._internal.repr import XLSRBackend +from ser._internal.repr.runtime_policy import FeatureRuntimePolicy, resolve_feature_runtime_policy from ser.config import AppConfig -from ser.repr import XLSRBackend -from ser.repr.runtime_policy import FeatureRuntimePolicy, resolve_feature_runtime_policy class LoadedModelLike(Protocol): diff --git a/ser/_internal/runtime/medium_execution.py b/ser/_internal/runtime/medium_execution.py index 0fbbfcc..f7b5e58 100644 --- a/ser/_internal/runtime/medium_execution.py +++ b/ser/_internal/runtime/medium_execution.py @@ -8,13 +8,13 @@ import numpy as np from numpy.typing import NDArray -from ser.config import MediumRuntimeConfig -from ser.pool import mean_std_pool, temporal_pooling_windows -from ser.repr import EncodedSequence, PoolingWindow -from ser.runtime.postprocessing import ( +from ser._internal.repr import EncodedSequence, PoolingWindow +from ser._internal.runtime.postprocessing import ( build_segment_postprocessing_config, postprocess_frame_predictions, ) +from ser.config import MediumRuntimeConfig +from ser.pool import mean_std_pool, temporal_pooling_windows from ser.runtime.schema import ( OUTPUT_SCHEMA_VERSION, FramePrediction, diff --git a/ser/_internal/runtime/medium_execution_context.py b/ser/_internal/runtime/medium_execution_context.py index 50292f2..41a6de4 100644 --- a/ser/_internal/runtime/medium_execution_context.py +++ b/ser/_internal/runtime/medium_execution_context.py @@ -7,13 +7,13 @@ from dataclasses import dataclass from typing import Generic, TypeVar +from ser._internal.repr.runtime_policy import FeatureRuntimePolicy from ser._internal.runtime.medium_worker_operation import ( MediumRetryOperationState, PreparedMediumOperation, ) from ser._internal.runtime.medium_worker_operation import _PayloadLike as _MediumPayloadLike from ser.config import AppConfig, MediumRuntimeConfig -from ser.repr.runtime_policy import FeatureRuntimePolicy from ser.runtime.contracts import InferenceRequest _PayloadT = TypeVar("_PayloadT", bound=_MediumPayloadLike) diff --git a/ser/_internal/runtime/medium_inference.py b/ser/_internal/runtime/medium_inference.py index 13464fa..64c858f 100644 --- a/ser/_internal/runtime/medium_inference.py +++ b/ser/_internal/runtime/medium_inference.py @@ -2,10 +2,10 @@ from __future__ import annotations +from ser._internal.models.emotion_model import LoadedModel +from ser._internal.repr import XLSRBackend from ser._internal.runtime import medium_public_boundary as _boundary_support from ser.config import AppConfig -from ser.models.emotion_model import LoadedModel -from ser.repr import XLSRBackend from ser.runtime.contracts import InferenceRequest from ser.runtime.schema import InferenceResult from ser.utils.logger import get_logger diff --git a/ser/_internal/runtime/medium_public_boundary.py b/ser/_internal/runtime/medium_public_boundary.py index 74f050a..873fdc3 100644 --- a/ser/_internal/runtime/medium_public_boundary.py +++ b/ser/_internal/runtime/medium_public_boundary.py @@ -14,6 +14,12 @@ import numpy as np from numpy.typing import NDArray +from ser._internal.models.emotion_model import LoadedModel as EmotionLoadedModel +from ser._internal.models.emotion_model import load_model +from ser._internal.models.profile_runtime import resolve_medium_model_id +from ser._internal.repr import XLSRBackend +from ser._internal.repr.runtime_policy import FeatureRuntimePolicy +from ser._internal.runtime import medium_execution as medium_execution_helpers from ser._internal.runtime import medium_retry_policy as medium_retry_policy_helpers from ser._internal.runtime import medium_worker_operation as medium_worker_operation_helpers from ser._internal.runtime.medium_backend_runtime import ( @@ -22,6 +28,9 @@ from ser._internal.runtime.medium_backend_runtime import ( resolve_medium_feature_runtime_policy as _resolve_medium_feature_runtime_policy_impl, ) +from ser._internal.runtime.medium_execution import ( + LoadedModelLike as _MediumExecutionLoadedModelLike, +) from ser._internal.runtime.medium_execution_context import ( MediumExecutionContext, ) @@ -31,6 +40,10 @@ from ser._internal.runtime.medium_execution_flow import ( execute_medium_inference_with_retry as _execute_medium_inference_with_retry_impl, ) +from ser._internal.runtime.medium_prediction import ( + confidence_and_probabilities as _confidence_and_probabilities_impl, +) +from ser._internal.runtime.medium_prediction import predict_labels as _predict_labels_impl from ser._internal.runtime.medium_process_operation import _PayloadLike as _MediumPayloadLike from ser._internal.runtime.medium_process_operation import ( prepare_process_operation as _prepare_process_operation_impl, @@ -62,6 +75,12 @@ from ser._internal.runtime.medium_runtime_support import ( warn_on_medium_runtime_selector_mismatch as _warn_on_medium_runtime_selector_mismatch_impl, ) +from ser._internal.runtime.phase_contract import PHASE_EMOTION_INFERENCE, PHASE_EMOTION_SETUP +from ser._internal.runtime.phase_timing import ( + log_phase_completed, + log_phase_failed, + log_phase_started, +) from ser._internal.runtime.policy import run_with_retry_policy from ser._internal.runtime.process_timeout import ( run_with_process_timeout as _run_with_process_timeout_impl, @@ -96,27 +115,10 @@ from ser._internal.runtime.worker_lifecycle import ( terminate_worker_process as _terminate_worker_process_impl, ) +from ser._internal.utils.audio_utils import read_audio_file from ser.config import AppConfig, MediumRuntimeConfig -from ser.models.emotion_model import LoadedModel as EmotionLoadedModel -from ser.models.emotion_model import load_model -from ser.models.profile_runtime import resolve_medium_model_id -from ser.repr import XLSRBackend -from ser.repr.runtime_policy import FeatureRuntimePolicy -from ser.runtime import medium_execution as medium_execution_helpers from ser.runtime.contracts import InferenceRequest -from ser.runtime.medium_execution import LoadedModelLike as _MediumExecutionLoadedModelLike -from ser.runtime.medium_prediction import ( - confidence_and_probabilities as _confidence_and_probabilities_impl, -) -from ser.runtime.medium_prediction import predict_labels as _predict_labels_impl -from ser.runtime.phase_contract import PHASE_EMOTION_INFERENCE, PHASE_EMOTION_SETUP -from ser.runtime.phase_timing import ( - log_phase_completed, - log_phase_failed, - log_phase_started, -) from ser.runtime.schema import InferenceResult -from ser.utils.audio_utils import read_audio_file class _MediumLoadedModel(_MediumExecutionLoadedModelLike, _MediumRuntimeLoadedModelLike, Protocol): @@ -134,7 +136,7 @@ class _MediumLoadedModel(_MediumExecutionLoadedModelLike, _MediumRuntimeLoadedMo _TERMINATE_GRACE_SECONDS = 0.5 _KILL_GRACE_SECONDS = 0.5 _SINGLE_FLIGHT_REGISTRY = SingleFlightRegistry() -_WORKER_LOGGER = logging.getLogger("ser.runtime.medium_inference") +_WORKER_LOGGER = logging.getLogger("ser._internal.runtime.medium_inference") @dataclass(frozen=True) diff --git a/ser/_internal/runtime/medium_runtime_support.py b/ser/_internal/runtime/medium_runtime_support.py index 579ab67..3fe97fd 100644 --- a/ser/_internal/runtime/medium_runtime_support.py +++ b/ser/_internal/runtime/medium_runtime_support.py @@ -9,6 +9,7 @@ import numpy as np from numpy.typing import NDArray +from ser._internal.repr import EncodedSequence, XLSRBackend from ser._internal.runtime import medium_worker_operation as medium_worker_operation_helpers from ser._internal.runtime.medium_backend_runtime import ( build_medium_backend as _build_medium_backend_impl, @@ -20,7 +21,6 @@ warn_on_runtime_selector_mismatch as _warn_on_runtime_selector_mismatch_impl, ) from ser.config import AppConfig -from ser.repr import EncodedSequence, XLSRBackend class LoadedModelLike(Protocol): diff --git a/ser/_internal/runtime/phase_timing.py b/ser/_internal/runtime/phase_timing.py index fe5df47..47aeee2 100644 --- a/ser/_internal/runtime/phase_timing.py +++ b/ser/_internal/runtime/phase_timing.py @@ -5,7 +5,7 @@ import logging from time import perf_counter -from ser.runtime.phase_contract import phase_label +from ser._internal.runtime.phase_contract import phase_label def format_duration(duration_seconds: float) -> str: diff --git a/ser/_internal/runtime/pipeline.py b/ser/_internal/runtime/pipeline.py index 7ae5632..2a697e2 100644 --- a/ser/_internal/runtime/pipeline.py +++ b/ser/_internal/runtime/pipeline.py @@ -12,7 +12,20 @@ from ser._internal.config.bootstrap import resolve_profile_transcription_config from ser._internal.runtime.backend_hooks import build_backend_hooks from ser._internal.runtime.environment_plan import build_runtime_environment_plan +from ser._internal.runtime.phase_contract import ( + PHASE_TIMELINE_BUILD, + PHASE_TIMELINE_OUTPUT, +) +from ser._internal.runtime.phase_timing import ( + log_phase_completed, + log_phase_failed, + log_phase_started, +) from ser._internal.runtime.process_env import temporary_process_env +from ser._internal.utils.subtitles import resolve_subtitle_export_request +from ser._internal.utils.transcription_compat import ( + has_known_faster_whisper_openmp_runtime_conflict, +) from ser.config import AppConfig, settings_override from ser.domain import EmotionSegment, TimelineEntry, TranscriptWord from ser.profiles import RuntimeProfile, TranscriptionBackendId, resolve_profile @@ -22,15 +35,6 @@ InferenceRequest, SubtitleFormat, ) -from ser.runtime.phase_contract import ( - PHASE_TIMELINE_BUILD, - PHASE_TIMELINE_OUTPUT, -) -from ser.runtime.phase_timing import ( - log_phase_completed, - log_phase_failed, - log_phase_started, -) from ser.runtime.registry import ( RuntimeCapability, ensure_profile_supported, @@ -38,10 +42,6 @@ ) from ser.runtime.schema import InferenceResult, to_legacy_emotion_segments from ser.utils.logger import get_logger -from ser.utils.subtitles import resolve_subtitle_export_request -from ser.utils.transcription_compat import ( - has_known_faster_whisper_openmp_runtime_conflict, -) type TrainModelCallable = Callable[[], None] type PredictEmotionsCallable = Callable[[str], list[EmotionSegment]] @@ -275,7 +275,7 @@ def create_runtime_pipeline(settings: AppConfig) -> RuntimePipeline: settings, available_backend_hooks=implemented_backends, ) - from ser.models.emotion_model import ( + from ser._internal.models.emotion_model import ( predict_emotions, predict_emotions_detailed, train_accurate_model, @@ -283,9 +283,12 @@ def create_runtime_pipeline(settings: AppConfig) -> RuntimePipeline: train_medium_model, train_model, ) - from ser.transcript import TranscriptionProfile, extract_transcript - from ser.utils.subtitles import save_timeline_to_subtitles - from ser.utils.timeline_utils import ( + from ser._internal.transcript.transcript_extractor import ( + TranscriptionProfile, + extract_transcript, + ) + from ser._internal.utils.subtitles import save_timeline_to_subtitles + from ser._internal.utils.timeline_utils import ( build_timeline, print_timeline, save_timeline_to_csv, diff --git a/ser/_internal/runtime/profile_quality_gate.py b/ser/_internal/runtime/profile_quality_gate.py index 1ef3121..197b021 100644 --- a/ser/_internal/runtime/profile_quality_gate.py +++ b/ser/_internal/runtime/profile_quality_gate.py @@ -14,56 +14,64 @@ import numpy as np from numpy.typing import NDArray +from ser._internal.data.data_loader import extract_ravdess_speaker_id_from_path +from ser._internal.models.emotion_model import load_model, predict_emotions +from ser._internal.repr import XLSRBackend from ser._internal.runtime.environment_plan import build_runtime_environment_plan +from ser._internal.runtime.medium_inference import run_medium_inference from ser._internal.runtime.process_env import temporary_process_env -from ser.config import AppConfig, reload_settings -from ser.data.data_loader import extract_ravdess_speaker_id_from_path -from ser.models.emotion_model import load_model, predict_emotions -from ser.repr import XLSRBackend -from ser.runtime.contracts import InferenceRequest -from ser.runtime.medium_inference import run_medium_inference -from ser.runtime.quality_gate_cli import ( +from ser._internal.runtime.quality_gate_cli import ( QualityGateCliDefaults, ) -from ser.runtime.quality_gate_cli import ( +from ser._internal.runtime.quality_gate_cli import ( configure_cli_noise_controls as _gate_configure_cli_noise_controls, ) -from ser.runtime.quality_gate_cli import normalize_progress_every as _gate_normalize_progress_every -from ser.runtime.quality_gate_cli import parse_args as _gate_parse_args -from ser.runtime.quality_gate_evaluation import ( +from ser._internal.runtime.quality_gate_cli import ( + normalize_progress_every as _gate_normalize_progress_every, +) +from ser._internal.runtime.quality_gate_cli import parse_args as _gate_parse_args +from ser._internal.runtime.quality_gate_evaluation import ( NormalizedSegment, ProfileEvaluationResult, ) -from ser.runtime.quality_gate_evaluation import ( +from ser._internal.runtime.quality_gate_evaluation import ( clip_label_from_segments as _gate_clip_label_from_segments, ) -from ser.runtime.quality_gate_evaluation import ( +from ser._internal.runtime.quality_gate_evaluation import ( clip_stability_metrics as _gate_clip_stability_metrics, ) -from ser.runtime.quality_gate_evaluation import evaluate_profile as _gate_evaluate_profile -from ser.runtime.quality_gate_evaluation import normalize_segments as _gate_normalize_segments -from ser.runtime.quality_gate_evaluation import percentile as _gate_percentile -from ser.runtime.quality_gate_evaluation import segment_duration as _gate_segment_duration -from ser.runtime.quality_gate_policy import ( +from ser._internal.runtime.quality_gate_evaluation import evaluate_profile as _gate_evaluate_profile +from ser._internal.runtime.quality_gate_evaluation import ( + normalize_segments as _gate_normalize_segments, +) +from ser._internal.runtime.quality_gate_evaluation import percentile as _gate_percentile +from ser._internal.runtime.quality_gate_evaluation import segment_duration as _gate_segment_duration +from ser._internal.runtime.quality_gate_policy import ( ProfileComparisonResult, compare_profiles, metric_as_float, validate_thresholds, ) -from ser.runtime.quality_gate_reporting import build_report_payload as _gate_build_report_payload -from ser.runtime.quality_gate_reporting import enforce_quality_gate as _gate_enforce_quality_gate -from ser.runtime.quality_gate_reporting import ( +from ser._internal.runtime.quality_gate_reporting import ( + build_report_payload as _gate_build_report_payload, +) +from ser._internal.runtime.quality_gate_reporting import ( + enforce_quality_gate as _gate_enforce_quality_gate, +) +from ser._internal.runtime.quality_gate_reporting import ( resolve_report_output_path as _gate_resolve_report_output_path, ) -from ser.runtime.quality_gate_reporting import ( +from ser._internal.runtime.quality_gate_reporting import ( serialize_report_payload as _gate_serialize_report_payload, ) -from ser.runtime.quality_gate_reporting import ( +from ser._internal.runtime.quality_gate_reporting import ( write_serialized_report as _gate_write_serialized_report, ) +from ser._internal.utils.segment_canonicalization import canonicalize_segments +from ser.config import AppConfig, reload_settings +from ser.runtime.contracts import InferenceRequest from ser.train.eval import grouped_train_test_split, speaker_independent_cv from ser.train.metrics import compute_ser_metrics -from ser.utils.segment_canonicalization import canonicalize_segments type IndexArray = NDArray[np.int64] type FoldIndices = tuple[IndexArray, IndexArray] diff --git a/ser/_internal/runtime/quality_gate_cli.py b/ser/_internal/runtime/quality_gate_cli.py index e6c05ad..8b0dc6d 100644 --- a/ser/_internal/runtime/quality_gate_cli.py +++ b/ser/_internal/runtime/quality_gate_cli.py @@ -7,7 +7,7 @@ from collections.abc import Sequence from dataclasses import dataclass -from ser.utils.dsp import configure_feature_extraction_warning_filters +from ser._internal.utils.dsp import configure_feature_extraction_warning_filters @dataclass(frozen=True, slots=True) @@ -31,9 +31,9 @@ class QualityGateCliDefaults: def configure_cli_noise_controls() -> None: """Suppresses non-actionable warning/log noise for long gate executions.""" configure_feature_extraction_warning_filters() - logging.getLogger("ser.models.emotion_model").setLevel(logging.WARNING) - logging.getLogger("ser.features.feature_extractor").setLevel(logging.ERROR) - logging.getLogger("ser.runtime.medium_inference").setLevel(logging.WARNING) + logging.getLogger("ser._internal.models.emotion_model").setLevel(logging.WARNING) + logging.getLogger("ser._internal.features.feature_extractor").setLevel(logging.ERROR) + logging.getLogger("ser._internal.runtime.medium_inference").setLevel(logging.WARNING) def build_arg_parser(defaults: QualityGateCliDefaults) -> argparse.ArgumentParser: diff --git a/ser/runtime/__init__.py b/ser/runtime/__init__.py index 33ab125..c239aa1 100644 --- a/ser/runtime/__init__.py +++ b/ser/runtime/__init__.py @@ -1,14 +1,24 @@ +# pyright: reportUnsupportedDunderAll=false """Runtime helpers for profiling, orchestration, and benchmarks.""" -from .contracts import InferenceExecution, InferenceRequest -from .pipeline import RuntimePipeline, create_runtime_pipeline -from .registry import ( +from __future__ import annotations + +import importlib +from typing import Any + +from ser.runtime.contracts import InferenceExecution, InferenceRequest +from ser.runtime.registry import ( RuntimeCapability, UnsupportedProfileError, ensure_profile_supported, resolve_runtime_capability, ) +_DYNAMIC_EXPORTS: dict[str, str] = { + "RuntimePipeline": "ser.runtime.pipeline", + "create_runtime_pipeline": "ser.runtime.pipeline", +} + __all__ = [ "InferenceExecution", "InferenceRequest", @@ -19,3 +29,13 @@ "ensure_profile_supported", "resolve_runtime_capability", ] + + +def __getattr__(name: str) -> Any: + """Lazily resolves runtime facade exports that import orchestration code.""" + module_name = _DYNAMIC_EXPORTS.get(name) + if module_name is None: + raise AttributeError(f"module 'ser.runtime' has no attribute {name!r}") + value = getattr(importlib.import_module(module_name), name) + globals()[name] = value + return value diff --git a/ser/runtime/pipeline.py b/ser/runtime/pipeline.py new file mode 100644 index 0000000..2242148 --- /dev/null +++ b/ser/runtime/pipeline.py @@ -0,0 +1,3 @@ +"""Public facade for `ser._internal.runtime.pipeline`.""" + +from ser._internal.runtime.pipeline import * # noqa: F403 diff --git a/tests/suites/integration/cli/test_cli.py b/tests/suites/integration/cli/test_cli.py index 1f27e97..2903407 100644 --- a/tests/suites/integration/cli/test_cli.py +++ b/tests/suites/integration/cli/test_cli.py @@ -8,13 +8,13 @@ import pytest import ser.__main__ as cli +import ser._internal.models.emotion_model as emotion_model import ser.config as config_module -import ser.models.emotion_model as emotion_model from ser._internal.config.schema import profile_artifact_file_names +from ser._internal.runtime.accurate_inference import AccurateRuntimeDependencyError +from ser._internal.runtime.medium_inference import MediumRuntimeDependencyError from ser.license_check import load_persisted_backend_consents from ser.runtime import InferenceRequest -from ser.runtime.accurate_inference import AccurateRuntimeDependencyError -from ser.runtime.medium_inference import MediumRuntimeDependencyError from ser.runtime.registry import UnsupportedProfileError @@ -1129,7 +1129,7 @@ def _run_configure_command(argv: list[str], *, settings: object | None = None) - return 0 monkeypatch.setattr( - "ser.data.cli.run_configure_command", + "ser._internal.data.cli.run_configure_command", _run_configure_command, ) @@ -1159,7 +1159,7 @@ def _run_data_command(argv: list[str], *, settings: object | None = None) -> int return 0 monkeypatch.setattr( - "ser.data.cli.run_data_command", + "ser._internal.data.cli.run_data_command", _run_data_command, ) @@ -1189,7 +1189,7 @@ def _run_doctor_command(argv: list[str], *, settings: object | None = None) -> i return 0 monkeypatch.setattr( - "ser.diagnostics.command.run_doctor_command", + "ser._internal.diagnostics.command.run_doctor_command", _run_doctor_command, ) @@ -1317,9 +1317,9 @@ def test_cli_dataset_workflow_configure_download_and_registry_load( tmp_path: Path, ) -> None: """End-to-end dataset workflow should persist consent and load registry manifests.""" - import ser.data.data_loader as data_loader_module - from ser.data.dataset_consents import load_persisted_dataset_consents - from ser.data.dataset_registry import load_dataset_registry + import ser._internal.data.data_loader as data_loader_module + from ser._internal.data.dataset_consents import load_persisted_dataset_consents + from ser._internal.data.dataset_registry import load_dataset_registry dataset_root = tmp_path / "datasets" / "ravdess" (dataset_root / "Actor_01").mkdir(parents=True, exist_ok=True) diff --git a/tests/suites/integration/runtime/test_accurate_research_inference.py b/tests/suites/integration/runtime/test_accurate_research_inference.py index d6d5de8..3678d48 100644 --- a/tests/suites/integration/runtime/test_accurate_research_inference.py +++ b/tests/suites/integration/runtime/test_accurate_research_inference.py @@ -8,9 +8,9 @@ import pytest +from ser._internal.repr import FeatureBackend +from ser._internal.runtime.accurate_research_inference import run_accurate_research_inference from ser.config import AppConfig -from ser.repr import FeatureBackend -from ser.runtime.accurate_research_inference import run_accurate_research_inference from ser.runtime.contracts import InferenceRequest from ser.runtime.schema import OUTPUT_SCHEMA_VERSION, InferenceResult @@ -45,11 +45,11 @@ def __init__( captured["huggingface_cache_root"] = huggingface_cache_root monkeypatch.setattr( - "ser.runtime.accurate_research_inference.Emotion2VecBackend", + "ser._internal.runtime.accurate_research_inference.Emotion2VecBackend", _BackendStub, ) monkeypatch.setattr( - "ser.runtime.accurate_research_inference.resolve_feature_runtime_policy", + "ser._internal.runtime.accurate_research_inference.resolve_feature_runtime_policy", lambda **_kwargs: SimpleNamespace( device="cpu", dtype="float32", @@ -77,7 +77,7 @@ def _fake_run( return InferenceResult(schema_version=OUTPUT_SCHEMA_VERSION, segments=[], frames=[]) monkeypatch.setattr( - "ser.runtime.accurate_research_inference.run_accurate_inference", + "ser._internal.runtime.accurate_research_inference.run_accurate_inference", _fake_run, ) @@ -128,7 +128,7 @@ def _fake_run( return InferenceResult(schema_version=OUTPUT_SCHEMA_VERSION, segments=[], frames=[]) monkeypatch.setattr( - "ser.runtime.accurate_research_inference.run_accurate_inference", + "ser._internal.runtime.accurate_research_inference.run_accurate_inference", _fake_run, ) diff --git a/tests/suites/integration/runtime/test_profile_quality_gate.py b/tests/suites/integration/runtime/test_profile_quality_gate.py index acb7352..1ddf2f7 100644 --- a/tests/suites/integration/runtime/test_profile_quality_gate.py +++ b/tests/suites/integration/runtime/test_profile_quality_gate.py @@ -10,11 +10,8 @@ import pytest -import ser.runtime.profile_quality_gate as gate_module -from ser.config import AppConfig, reload_settings -from ser.domain import EmotionSegment -from ser.runtime.contracts import InferenceRequest -from ser.runtime.profile_quality_gate import ( +import ser._internal.runtime.profile_quality_gate as gate_module +from ser._internal.runtime.profile_quality_gate import ( QualityGateThresholds, _build_fast_predictor, _build_medium_predictor, @@ -23,11 +20,14 @@ enforce_quality_gate, evaluate_profile_quality_gate, ) -from ser.runtime.quality_gate_reporting import ( +from ser._internal.runtime.quality_gate_reporting import ( resolve_report_output_path, serialize_report_payload, write_serialized_report, ) +from ser.config import AppConfig, reload_settings +from ser.domain import EmotionSegment +from ser.runtime.contracts import InferenceRequest type LabeledAudioSample = tuple[str, str] @@ -321,9 +321,9 @@ def _fake_predict_emotions( predict_calls.append((audio_path, loaded_model)) return _segments("happy") - monkeypatch.setattr("ser.runtime.profile_quality_gate.load_model", _fake_load_model) + monkeypatch.setattr("ser._internal.runtime.profile_quality_gate.load_model", _fake_load_model) monkeypatch.setattr( - "ser.runtime.profile_quality_gate.predict_emotions", + "ser._internal.runtime.profile_quality_gate.predict_emotions", _fake_predict_emotions, ) @@ -386,13 +386,13 @@ def _fake_run_medium_inference( ) return SimpleNamespace(segments=_segments("happy")) - monkeypatch.setattr("ser.runtime.profile_quality_gate.load_model", _fake_load_model) + monkeypatch.setattr("ser._internal.runtime.profile_quality_gate.load_model", _fake_load_model) monkeypatch.setattr( - "ser.runtime.profile_quality_gate.XLSRBackend", + "ser._internal.runtime.profile_quality_gate.XLSRBackend", _fake_backend_factory, ) monkeypatch.setattr( - "ser.runtime.profile_quality_gate.run_medium_inference", + "ser._internal.runtime.profile_quality_gate.run_medium_inference", _fake_run_medium_inference, ) base_settings = reload_settings() diff --git a/tests/suites/integration/test_runtime_pipeline.py b/tests/suites/integration/test_runtime_pipeline.py index cec004f..73afb7e 100644 --- a/tests/suites/integration/test_runtime_pipeline.py +++ b/tests/suites/integration/test_runtime_pipeline.py @@ -9,13 +9,13 @@ import pytest +import ser._internal.runtime.pipeline as runtime_pipeline_module import ser.config as config -import ser.runtime.pipeline as runtime_pipeline_module +from ser._internal.runtime.pipeline import RuntimePipeline, create_runtime_pipeline from ser.config import AppConfig, TimelineConfig from ser.domain import EmotionSegment, TimelineEntry, TranscriptWord from ser.profiles import RuntimeProfile from ser.runtime.contracts import InferenceRequest -from ser.runtime.pipeline import RuntimePipeline, create_runtime_pipeline from ser.runtime.registry import RuntimeCapability, UnsupportedProfileError from ser.runtime.schema import ( OUTPUT_SCHEMA_VERSION, @@ -530,7 +530,7 @@ def fake_medium_hook(_request: InferenceRequest) -> InferenceResult: ) monkeypatch.setattr( - "ser.runtime.pipeline.build_backend_hooks", + "ser._internal.runtime.pipeline.build_backend_hooks", lambda _settings: {"hf_xlsr": fake_medium_hook}, ) settings = config.reload_settings() @@ -560,7 +560,7 @@ def fake_accurate_hook(_request: InferenceRequest) -> InferenceResult: ) monkeypatch.setattr( - "ser.runtime.pipeline.build_backend_hooks", + "ser._internal.runtime.pipeline.build_backend_hooks", lambda _settings: {"hf_whisper": fake_accurate_hook}, ) settings = config.reload_settings() @@ -591,7 +591,7 @@ def fake_accurate_research_hook(_request: InferenceRequest) -> InferenceResult: ) monkeypatch.setattr( - "ser.runtime.pipeline.build_backend_hooks", + "ser._internal.runtime.pipeline.build_backend_hooks", lambda _settings: {"emotion2vec": fake_accurate_research_hook}, ) settings = config.reload_settings() @@ -649,7 +649,7 @@ def test_create_runtime_pipeline_uses_profile_specific_transcription_profile( ) -> None: """Factory should bind transcript extraction to selected profile defaults.""" monkeypatch.setattr( - "ser.runtime.pipeline.has_known_faster_whisper_openmp_runtime_conflict", + "ser._internal.runtime.pipeline.has_known_faster_whisper_openmp_runtime_conflict", lambda: False, ) monkeypatch.delenv("WHISPER_MODEL", raising=False) @@ -662,7 +662,7 @@ def test_create_runtime_pipeline_uses_profile_specific_transcription_profile( lambda _required_modules: (), ) monkeypatch.setattr( - "ser.runtime.pipeline.build_backend_hooks", + "ser._internal.runtime.pipeline.build_backend_hooks", lambda _settings: { backend_id: lambda _request: InferenceResult( schema_version=OUTPUT_SCHEMA_VERSION, @@ -684,7 +684,9 @@ def fake_extract( captured["settings"] = settings return [] - monkeypatch.setattr("ser.transcript.extract_transcript", fake_extract) + monkeypatch.setattr( + "ser._internal.transcript.transcript_extractor.extract_transcript", fake_extract + ) settings = config.reload_settings() pipeline = create_runtime_pipeline(settings) @@ -692,7 +694,7 @@ def fake_extract( InferenceRequest(file_path="sample.wav", language="en", save_transcript=False) ) - from ser.transcript import TranscriptionProfile + from ser._internal.transcript.transcript_extractor import TranscriptionProfile profile = captured["profile"] assert isinstance(profile, TranscriptionProfile) @@ -708,28 +710,28 @@ def test_create_runtime_pipeline_uses_settings_timeline_folder_for_default_subti tmp_path: Path, ) -> None: """Factory-backed subtitle export should forward the configured timeline folder.""" - monkeypatch.setattr("ser.runtime.pipeline.build_backend_hooks", lambda _settings: {}) + monkeypatch.setattr("ser._internal.runtime.pipeline.build_backend_hooks", lambda _settings: {}) monkeypatch.setattr( - "ser.models.emotion_model.predict_emotions", + "ser._internal.models.emotion_model.predict_emotions", lambda _file_path, *, settings=None: [EmotionSegment("happy", 0.0, 1.0)], ) monkeypatch.setattr( - "ser.models.emotion_model.predict_emotions_detailed", + "ser._internal.models.emotion_model.predict_emotions_detailed", lambda _file_path, *, settings=None: (_ for _ in ()).throw( AssertionError("Detailed path should not run when schema flag is disabled.") ), ) monkeypatch.setattr( - "ser.transcript.extract_transcript", + "ser._internal.transcript.transcript_extractor.extract_transcript", lambda _file_path, _language, profile=None, *, settings=None: [ TranscriptWord("hello", 0.0, 0.5) ], ) monkeypatch.setattr( - "ser.utils.timeline_utils.build_timeline", + "ser._internal.utils.timeline_utils.build_timeline", lambda _transcript, _emotions: [TimelineEntry(0.0, "happy", "hello")], ) - monkeypatch.setattr("ser.utils.timeline_utils.print_timeline", lambda _timeline: None) + monkeypatch.setattr("ser._internal.utils.timeline_utils.print_timeline", lambda _timeline: None) captured: dict[str, object] = {} def fake_save_timeline_to_subtitles( @@ -744,7 +746,7 @@ def fake_save_timeline_to_subtitles( return "custom/sample.vtt" monkeypatch.setattr( - "ser.utils.subtitles.save_timeline_to_subtitles", + "ser._internal.utils.subtitles.save_timeline_to_subtitles", fake_save_timeline_to_subtitles, ) settings = replace( @@ -776,7 +778,7 @@ def test_create_runtime_pipeline_retains_faster_whisper_on_openmp_conflict( ) -> None: """Fast profile should keep faster-whisper and rely on process isolation.""" monkeypatch.setattr( - "ser.runtime.pipeline.has_known_faster_whisper_openmp_runtime_conflict", + "ser._internal.runtime.pipeline.has_known_faster_whisper_openmp_runtime_conflict", lambda: True, ) monkeypatch.delenv("WHISPER_BACKEND", raising=False) @@ -786,7 +788,7 @@ def test_create_runtime_pipeline_retains_faster_whisper_on_openmp_conflict( lambda _required_modules: (), ) monkeypatch.setattr( - "ser.runtime.pipeline.build_backend_hooks", + "ser._internal.runtime.pipeline.build_backend_hooks", lambda _settings: { "handcrafted": lambda _request: InferenceResult( schema_version=OUTPUT_SCHEMA_VERSION, @@ -808,7 +810,9 @@ def fake_extract( captured["settings"] = settings return [] - monkeypatch.setattr("ser.transcript.extract_transcript", fake_extract) + monkeypatch.setattr( + "ser._internal.transcript.transcript_extractor.extract_transcript", fake_extract + ) settings = config.reload_settings() pipeline = create_runtime_pipeline(settings) @@ -816,7 +820,7 @@ def fake_extract( InferenceRequest(file_path="sample.wav", language="en", save_transcript=False) ) - from ser.transcript import TranscriptionProfile + from ser._internal.transcript.transcript_extractor import TranscriptionProfile profile = captured["profile"] assert isinstance(profile, TranscriptionProfile) @@ -834,7 +838,7 @@ def test_create_runtime_pipeline_respects_explicit_faster_backend_override( monkeypatch.setenv("WHISPER_BACKEND", "faster_whisper") monkeypatch.setenv("WHISPER_MODEL", "distil-large-v3") monkeypatch.setattr( - "ser.runtime.pipeline.has_known_faster_whisper_openmp_runtime_conflict", + "ser._internal.runtime.pipeline.has_known_faster_whisper_openmp_runtime_conflict", lambda: True, ) monkeypatch.setattr( @@ -842,7 +846,7 @@ def test_create_runtime_pipeline_respects_explicit_faster_backend_override( lambda _required_modules: (), ) monkeypatch.setattr( - "ser.runtime.pipeline.build_backend_hooks", + "ser._internal.runtime.pipeline.build_backend_hooks", lambda _settings: { "handcrafted": lambda _request: InferenceResult( schema_version=OUTPUT_SCHEMA_VERSION, @@ -864,7 +868,9 @@ def fake_extract( captured["settings"] = settings return [] - monkeypatch.setattr("ser.transcript.extract_transcript", fake_extract) + monkeypatch.setattr( + "ser._internal.transcript.transcript_extractor.extract_transcript", fake_extract + ) settings = config.reload_settings() pipeline = create_runtime_pipeline(settings) @@ -872,7 +878,7 @@ def fake_extract( InferenceRequest(file_path="sample.wav", language="en", save_transcript=False) ) - from ser.transcript import TranscriptionProfile + from ser._internal.transcript.transcript_extractor import TranscriptionProfile profile = captured["profile"] assert isinstance(profile, TranscriptionProfile) @@ -901,7 +907,7 @@ def fake_medium_hook(_request: InferenceRequest) -> InferenceResult: called = {"fast": False, "medium": False} monkeypatch.setattr( - "ser.runtime.pipeline.build_backend_hooks", + "ser._internal.runtime.pipeline.build_backend_hooks", lambda _settings: {"hf_xlsr": fake_medium_hook}, ) captured_settings: dict[str, object] = {} @@ -914,9 +920,9 @@ def _fake_train_medium_model(*, settings: object | None = None) -> None: captured_settings["medium"] = settings called["medium"] = True - monkeypatch.setattr("ser.models.emotion_model.train_model", _fake_train_model) + monkeypatch.setattr("ser._internal.models.emotion_model.train_model", _fake_train_model) monkeypatch.setattr( - "ser.models.emotion_model.train_medium_model", + "ser._internal.models.emotion_model.train_medium_model", _fake_train_medium_model, ) @@ -943,7 +949,7 @@ def test_create_runtime_pipeline_uses_accurate_training_callable_when_selected( called = {"fast": False, "medium": False, "accurate": False} monkeypatch.setattr( - "ser.runtime.pipeline.build_backend_hooks", + "ser._internal.runtime.pipeline.build_backend_hooks", lambda _settings: { "hf_whisper": lambda _request: InferenceResult( schema_version=OUTPUT_SCHEMA_VERSION, @@ -966,13 +972,13 @@ def _fake_train_accurate_model(*, settings: object | None = None) -> None: captured_settings["accurate"] = settings called["accurate"] = True - monkeypatch.setattr("ser.models.emotion_model.train_model", _fake_train_model) + monkeypatch.setattr("ser._internal.models.emotion_model.train_model", _fake_train_model) monkeypatch.setattr( - "ser.models.emotion_model.train_medium_model", + "ser._internal.models.emotion_model.train_medium_model", _fake_train_medium_model, ) monkeypatch.setattr( - "ser.models.emotion_model.train_accurate_model", + "ser._internal.models.emotion_model.train_accurate_model", _fake_train_accurate_model, ) @@ -1005,7 +1011,7 @@ def test_create_runtime_pipeline_uses_accurate_research_training_callable_when_s } monkeypatch.setattr( - "ser.runtime.pipeline.build_backend_hooks", + "ser._internal.runtime.pipeline.build_backend_hooks", lambda _settings: { "emotion2vec": lambda _request: InferenceResult( schema_version=OUTPUT_SCHEMA_VERSION, @@ -1035,17 +1041,17 @@ def _fake_train_accurate_research_model( captured_settings["accurate_research"] = settings called["accurate_research"] = True - monkeypatch.setattr("ser.models.emotion_model.train_model", _fake_train_model) + monkeypatch.setattr("ser._internal.models.emotion_model.train_model", _fake_train_model) monkeypatch.setattr( - "ser.models.emotion_model.train_medium_model", + "ser._internal.models.emotion_model.train_medium_model", _fake_train_medium_model, ) monkeypatch.setattr( - "ser.models.emotion_model.train_accurate_model", + "ser._internal.models.emotion_model.train_accurate_model", _fake_train_accurate_model, ) monkeypatch.setattr( - "ser.models.emotion_model.train_accurate_research_model", + "ser._internal.models.emotion_model.train_accurate_research_model", _fake_train_accurate_research_model, ) diff --git a/tests/suites/unit/runtime/test_accurate_model_contract.py b/tests/suites/unit/runtime/test_accurate_model_contract.py index de55a3f..60d39f5 100644 --- a/tests/suites/unit/runtime/test_accurate_model_contract.py +++ b/tests/suites/unit/runtime/test_accurate_model_contract.py @@ -9,12 +9,12 @@ import pytest -from ser.config import AppConfig -from ser.runtime.accurate_model_contract import ( +from ser._internal.runtime.accurate_model_contract import ( ensure_accurate_compatible_model, validate_accurate_loaded_model_runtime_contract, warn_on_runtime_selector_mismatch, ) +from ser.config import AppConfig @dataclass(frozen=True) diff --git a/tests/suites/unit/runtime/test_accurate_prediction.py b/tests/suites/unit/runtime/test_accurate_prediction.py index 4f43563..7b298d2 100644 --- a/tests/suites/unit/runtime/test_accurate_prediction.py +++ b/tests/suites/unit/runtime/test_accurate_prediction.py @@ -8,7 +8,7 @@ import numpy as np import pytest -from ser.runtime.accurate_prediction import ( +from ser._internal.runtime.accurate_prediction import ( confidence_and_probabilities, predict_labels, ) diff --git a/tests/suites/unit/runtime/test_benchmarks.py b/tests/suites/unit/runtime/test_benchmarks.py index ef0b9fd..e4eb195 100644 --- a/tests/suites/unit/runtime/test_benchmarks.py +++ b/tests/suites/unit/runtime/test_benchmarks.py @@ -9,7 +9,7 @@ import pytest -from ser.runtime import benchmarks +from ser._internal.runtime import benchmarks pytestmark = pytest.mark.unit diff --git a/tests/suites/unit/runtime/test_dual_schema_compat.py b/tests/suites/unit/runtime/test_dual_schema_compat.py index cac7e41..990ebcc 100644 --- a/tests/suites/unit/runtime/test_dual_schema_compat.py +++ b/tests/suites/unit/runtime/test_dual_schema_compat.py @@ -2,8 +2,8 @@ import pytest +from ser._internal.models import emotion_model as em from ser.domain import EmotionSegment -from ser.models import emotion_model as em from ser.runtime.schema import ( OUTPUT_SCHEMA_VERSION, InferenceResult, diff --git a/tests/suites/unit/runtime/test_medium_prediction.py b/tests/suites/unit/runtime/test_medium_prediction.py index ca78a62..b6d93d9 100644 --- a/tests/suites/unit/runtime/test_medium_prediction.py +++ b/tests/suites/unit/runtime/test_medium_prediction.py @@ -8,7 +8,7 @@ import numpy as np import pytest -from ser.runtime.medium_prediction import ( +from ser._internal.runtime.medium_prediction import ( confidence_and_probabilities, predict_labels, ) diff --git a/tests/suites/unit/runtime/test_mps_oom.py b/tests/suites/unit/runtime/test_mps_oom.py index 68aaabb..e6cfb31 100644 --- a/tests/suites/unit/runtime/test_mps_oom.py +++ b/tests/suites/unit/runtime/test_mps_oom.py @@ -6,7 +6,7 @@ import pytest -from ser.runtime import mps_oom +from ser._internal.runtime import mps_oom def test_is_mps_out_of_memory_error_detects_signature() -> None: diff --git a/tests/suites/unit/runtime/test_postprocessing.py b/tests/suites/unit/runtime/test_postprocessing.py index 1e63128..50298cf 100644 --- a/tests/suites/unit/runtime/test_postprocessing.py +++ b/tests/suites/unit/runtime/test_postprocessing.py @@ -6,7 +6,7 @@ import pytest -from ser.runtime.postprocessing import ( +from ser._internal.runtime.postprocessing import ( SegmentPostprocessingConfig, build_segment_postprocessing_config, postprocess_frame_predictions, diff --git a/tests/suites/unit/runtime/test_timestamp_semantics.py b/tests/suites/unit/runtime/test_timestamp_semantics.py index e5c001e..c07d258 100644 --- a/tests/suites/unit/runtime/test_timestamp_semantics.py +++ b/tests/suites/unit/runtime/test_timestamp_semantics.py @@ -4,9 +4,9 @@ import pytest from sklearn.neural_network import MLPClassifier +from ser._internal.features import FeatureFrame +from ser._internal.models import emotion_model as em from ser.domain import EmotionSegment -from ser.features import FeatureFrame -from ser.models import emotion_model as em class StaticModel(MLPClassifier): diff --git a/tests/suites/unit/scripts/test_run_full_dataset_quality_gate_script.py b/tests/suites/unit/scripts/test_run_full_dataset_quality_gate_script.py index 1ac1628..5e674d4 100644 --- a/tests/suites/unit/scripts/test_run_full_dataset_quality_gate_script.py +++ b/tests/suites/unit/scripts/test_run_full_dataset_quality_gate_script.py @@ -34,5 +34,6 @@ def test_full_dataset_quality_gate_uses_frozen_uv_commands(repo_root: Path) -> N assert "uv run --frozen ser --train" in script_text assert "uv run --frozen --extra medium ser --train" in script_text assert ( - "uv run --frozen --extra medium python -m ser.runtime.profile_quality_gate" in script_text + "uv run --frozen --extra medium python -m ser._internal.runtime.profile_quality_gate" + in script_text ) diff --git a/tests/suites/unit/test_accurate_execution.py b/tests/suites/unit/test_accurate_execution.py index 086eb64..872f7be 100644 --- a/tests/suites/unit/test_accurate_execution.py +++ b/tests/suites/unit/test_accurate_execution.py @@ -9,12 +9,12 @@ import pytest import ser.config as config -from ser.repr import EncodedSequence -from ser.runtime import accurate_execution -from ser.runtime.accurate_prediction import ( +from ser._internal.repr import EncodedSequence +from ser._internal.runtime import accurate_execution +from ser._internal.runtime.accurate_prediction import ( confidence_and_probabilities as _confidence_and_probabilities_impl, ) -from ser.runtime.accurate_prediction import predict_labels as _predict_labels_impl +from ser._internal.runtime.accurate_prediction import predict_labels as _predict_labels_impl pytestmark = [pytest.mark.unit, pytest.mark.usefixtures("reset_ambient_settings")] diff --git a/tests/suites/unit/test_accurate_execution_flow.py b/tests/suites/unit/test_accurate_execution_flow.py index af8356e..181b7b7 100644 --- a/tests/suites/unit/test_accurate_execution_flow.py +++ b/tests/suites/unit/test_accurate_execution_flow.py @@ -11,6 +11,7 @@ from numpy.typing import NDArray import ser.config as config +from ser._internal.repr import EncodedSequence, FeatureBackend, PoolingWindow from ser._internal.runtime.accurate_execution_flow import ( execute_accurate_inference_with_retry, run_accurate_retryable_operation, @@ -19,7 +20,6 @@ AccurateRetryOperationState, PreparedAccurateOperation, ) -from ser.repr import EncodedSequence, FeatureBackend, PoolingWindow from ser.runtime.schema import OUTPUT_SCHEMA_VERSION, InferenceResult pytestmark = pytest.mark.unit diff --git a/tests/suites/unit/test_accurate_runtime_support.py b/tests/suites/unit/test_accurate_runtime_support.py index 79e89b1..3c56288 100644 --- a/tests/suites/unit/test_accurate_runtime_support.py +++ b/tests/suites/unit/test_accurate_runtime_support.py @@ -9,13 +9,13 @@ from numpy.typing import NDArray import ser.config as config +from ser._internal.repr import EncodedSequence, PoolingWindow from ser._internal.runtime.accurate_runtime_support import ( build_cpu_settings_snapshot, build_process_settings_snapshot, encode_accurate_sequence, prepare_accurate_backend_runtime, ) -from ser.repr import EncodedSequence, PoolingWindow pytestmark = pytest.mark.unit diff --git a/tests/suites/unit/test_medium_execution.py b/tests/suites/unit/test_medium_execution.py index ffe9eb6..9c9eb09 100644 --- a/tests/suites/unit/test_medium_execution.py +++ b/tests/suites/unit/test_medium_execution.py @@ -9,12 +9,12 @@ import pytest import ser.config as config -from ser.repr import EncodedSequence -from ser.runtime import medium_execution -from ser.runtime.medium_prediction import ( +from ser._internal.repr import EncodedSequence +from ser._internal.runtime import medium_execution +from ser._internal.runtime.medium_prediction import ( confidence_and_probabilities as _confidence_and_probabilities_impl, ) -from ser.runtime.medium_prediction import predict_labels as _predict_labels_impl +from ser._internal.runtime.medium_prediction import predict_labels as _predict_labels_impl pytestmark = [pytest.mark.unit, pytest.mark.usefixtures("reset_ambient_settings")] diff --git a/tests/suites/unit/test_medium_execution_context.py b/tests/suites/unit/test_medium_execution_context.py index 5ce27df..d54e7a1 100644 --- a/tests/suites/unit/test_medium_execution_context.py +++ b/tests/suites/unit/test_medium_execution_context.py @@ -9,10 +9,10 @@ import pytest +from ser._internal.repr.runtime_policy import FeatureRuntimePolicy from ser._internal.runtime.medium_execution_context import prepare_execution_context from ser._internal.runtime.medium_worker_operation import MediumRetryOperationState from ser.config import AppConfig -from ser.repr.runtime_policy import FeatureRuntimePolicy from ser.runtime.contracts import InferenceRequest pytestmark = pytest.mark.unit diff --git a/tests/suites/unit/test_medium_execution_flow.py b/tests/suites/unit/test_medium_execution_flow.py index 22e0382..950d56f 100644 --- a/tests/suites/unit/test_medium_execution_flow.py +++ b/tests/suites/unit/test_medium_execution_flow.py @@ -9,11 +9,11 @@ import pytest from ser import config +from ser._internal.repr.runtime_policy import FeatureRuntimePolicy from ser._internal.runtime.medium_execution_context import MediumExecutionContext from ser._internal.runtime.medium_execution_flow import execute_medium_inference_with_retry from ser._internal.runtime.medium_worker_operation import MediumRetryOperationState from ser.config import AppConfig -from ser.repr.runtime_policy import FeatureRuntimePolicy from ser.runtime.contracts import InferenceRequest pytestmark = pytest.mark.unit diff --git a/tests/suites/unit/test_medium_runtime_support.py b/tests/suites/unit/test_medium_runtime_support.py index 94a0fd0..1c29a1c 100644 --- a/tests/suites/unit/test_medium_runtime_support.py +++ b/tests/suites/unit/test_medium_runtime_support.py @@ -14,6 +14,7 @@ from numpy.typing import NDArray import ser.config as config +from ser._internal.repr import EncodedSequence, XLSRBackend from ser._internal.runtime.medium_runtime_support import ( build_cpu_medium_backend_for_settings, build_cpu_settings_snapshot, @@ -23,7 +24,6 @@ validate_medium_loaded_model_runtime_contract, ) from ser.config import AppConfig -from ser.repr import EncodedSequence, XLSRBackend pytestmark = pytest.mark.unit From a1bec9f9f3a02d6abcc298f0a3b6480d65ac2617 Mon Sep 17 00:00:00 2001 From: Juan Sugg Date: Thu, 9 Jul 2026 22:30:42 -0300 Subject: [PATCH 05/12] refactor(transcript): move implementation internal --- ser/_internal/api/diagnostics.py | 12 ++-- ser/_internal/transcript/__init__.py | 1 + ser/_internal/transcript/backends/__init__.py | 19 ++++++ ser/_internal/transcript/backends/factory.py | 4 +- .../transcript/backends/faster_whisper.py | 10 +-- .../transcript/backends/stable_whisper.py | 44 ++++++------- .../stable_whisper_admission_runtime.py | 2 +- .../backends/stable_whisper_torio_probe.py | 2 +- .../stable_whisper_transcribe_admission.py | 2 +- .../stable_whisper_transcribe_execution.py | 6 +- .../stable_whisper_transcribe_runtime.py | 4 +- ser/_internal/transcript/mps_admission.py | 2 +- .../transcript/mps_admission_overrides.py | 2 +- ser/_internal/transcript/profiling.py | 10 +-- ser/_internal/transcript/runtime_policy.py | 2 +- .../transcript/transcript_extractor.py | 10 +-- .../transcription/default_profiling.py | 2 +- .../transcription/in_process_orchestration.py | 6 +- .../transcription/process_isolation.py | 8 +-- .../transcription/profiling_reporting.py | 2 +- .../transcription/public_boundary_support.py | 10 +-- .../transcription/runtime_calibration.py | 4 +- ser/transcript/__init__.py | 24 +++++-- ser/transcript/backends/__init__.py | 43 ++++++++----- ser/transcript/backends/stable_whisper.py | 3 + ser/transcript/profiling.py | 3 + ser/transcript/transcript_extractor.py | 3 + ...t_transcription_compatibility_ownership.py | 10 +-- .../integration/test_process_isolation.py | 2 +- .../integration/test_transcript_extractor.py | 2 +- .../test_transcription_default_profiling.py | 2 +- .../test_transcription_profiling.py | 2 +- .../backends/test_faster_whisper_adapter.py | 20 +++--- .../test_stable_whisper_admission_runtime.py | 4 +- .../test_stable_whisper_mps_compat.py | 2 +- ...est_stable_whisper_transcribe_admission.py | 6 +- ...est_stable_whisper_transcribe_execution.py | 8 +-- .../test_stable_whisper_transcribe_kwargs.py | 6 +- .../test_stable_whisper_transcribe_runtime.py | 10 +-- .../test_transcription_admission_overrides.py | 8 +-- .../test_transcription_backends.py | 64 +++++++++++-------- .../test_transcription_factory.py | 16 ++--- .../test_transcription_mps_admission.py | 4 +- .../test_transcription_runtime_failures.py | 2 +- .../test_transcription_runtime_policy.py | 4 +- 45 files changed, 234 insertions(+), 178 deletions(-) create mode 100644 ser/_internal/transcript/__init__.py create mode 100644 ser/_internal/transcript/backends/__init__.py create mode 100644 ser/transcript/backends/stable_whisper.py create mode 100644 ser/transcript/profiling.py create mode 100644 ser/transcript/transcript_extractor.py diff --git a/ser/_internal/api/diagnostics.py b/ser/_internal/api/diagnostics.py index 1d7525d..3546296 100644 --- a/ser/_internal/api/diagnostics.py +++ b/ser/_internal/api/diagnostics.py @@ -56,7 +56,7 @@ def suppress_preflight_transcription_operational_relogs( backend_id, _model_name, _use_demucs, _use_vad = resolve_profile_transcription_config( profile_name ) - from ser.transcript.transcript_extractor import mark_compatibility_issues_as_emitted + from ser._internal.transcript.transcript_extractor import mark_compatibility_issues_as_emitted mark_compatibility_issues_as_emitted( backend_id=backend_id, @@ -67,14 +67,14 @@ def suppress_preflight_transcription_operational_relogs( def run_doctor_command(argv: list[str], *, settings: AppConfig | None = None) -> int: """Runs `ser doctor ...` command argv via the public API boundary.""" - from ser.diagnostics.command import run_doctor_command as _run_doctor_command + from ser._internal.diagnostics.command import run_doctor_command as _run_doctor_command return _run_doctor_command(argv, settings=settings) def parse_preflight_mode(raw_mode: str) -> PreflightMode: """Parses one startup preflight mode string.""" - from ser.diagnostics.service import parse_preflight_mode as _parse_preflight_mode + from ser._internal.diagnostics.service import parse_preflight_mode as _parse_preflight_mode return _parse_preflight_mode(raw_mode) @@ -85,7 +85,7 @@ def run_startup_preflight( include_transcription_checks: bool, ) -> DiagnosticReport: """Runs one startup preflight diagnostics check suite.""" - from ser.diagnostics.service import _run_startup_preflight + from ser._internal.diagnostics.service import _run_startup_preflight return _run_startup_preflight( settings=settings, @@ -95,7 +95,7 @@ def run_startup_preflight( def should_fail_preflight(*, report: DiagnosticReport, mode: PreflightMode) -> bool: """Returns whether one preflight report should block execution.""" - from ser.diagnostics.service import should_fail_preflight as _should_fail_preflight + from ser._internal.diagnostics.service import should_fail_preflight as _should_fail_preflight return _should_fail_preflight(report=report, mode=mode) @@ -106,7 +106,7 @@ def format_startup_preflight_one_liner( doctor_command: str, ) -> str: """Formats one concise one-line startup preflight summary.""" - from ser.diagnostics.service import ( + from ser._internal.diagnostics.service import ( format_startup_preflight_one_liner as _format_startup_preflight_one_liner, ) diff --git a/ser/_internal/transcript/__init__.py b/ser/_internal/transcript/__init__.py new file mode 100644 index 0000000..1a64094 --- /dev/null +++ b/ser/_internal/transcript/__init__.py @@ -0,0 +1 @@ +"""Internal transcription implementation helpers.""" diff --git a/ser/_internal/transcript/backends/__init__.py b/ser/_internal/transcript/backends/__init__.py new file mode 100644 index 0000000..2a0fb2c --- /dev/null +++ b/ser/_internal/transcript/backends/__init__.py @@ -0,0 +1,19 @@ +"""Internal transcription backend implementations.""" + +from ser._internal.transcript.backends.factory import resolve_transcription_backend_adapter +from ser.transcript.backends.base import ( + BackendRuntimeRequest, + CompatibilityIssue, + CompatibilityIssueImpact, + CompatibilityReport, + TranscriptionBackendAdapter, +) + +__all__ = [ + "BackendRuntimeRequest", + "CompatibilityIssue", + "CompatibilityIssueImpact", + "CompatibilityReport", + "TranscriptionBackendAdapter", + "resolve_transcription_backend_adapter", +] diff --git a/ser/_internal/transcript/backends/factory.py b/ser/_internal/transcript/backends/factory.py index 4ed7873..26e7d09 100644 --- a/ser/_internal/transcript/backends/factory.py +++ b/ser/_internal/transcript/backends/factory.py @@ -12,11 +12,11 @@ def _build_adapter(backend_id: TranscriptionBackendId) -> TranscriptionBackendAdapter: """Builds one backend adapter lazily to avoid importing unused heavy stacks.""" if backend_id == "stable_whisper": - from ser.transcript.backends.stable_whisper import StableWhisperAdapter + from ser._internal.transcript.backends.stable_whisper import StableWhisperAdapter return StableWhisperAdapter() if backend_id == "faster_whisper": - from ser.transcript.backends.faster_whisper import FasterWhisperAdapter + from ser._internal.transcript.backends.faster_whisper import FasterWhisperAdapter return FasterWhisperAdapter() raise KeyError(backend_id) diff --git a/ser/_internal/transcript/backends/faster_whisper.py b/ser/_internal/transcript/backends/faster_whisper.py index a0479d5..cbcc765 100644 --- a/ser/_internal/transcript/backends/faster_whisper.py +++ b/ser/_internal/transcript/backends/faster_whisper.py @@ -11,9 +11,13 @@ from pathlib import Path from typing import TYPE_CHECKING, cast +from ser._internal.runtime.phase_contract import PHASE_TRANSCRIPTION +from ser._internal.utils.transcription_compat import ( + FASTER_WHISPER_OPENMP_CONFLICT_ISSUE_CODE, + has_known_faster_whisper_openmp_runtime_conflict, +) from ser.domain import TranscriptWord from ser.profiles import TranscriptionBackendId -from ser.runtime.phase_contract import PHASE_TRANSCRIPTION from ser.transcript.backends.base import ( BackendRuntimeRequest, CompatibilityIssue, @@ -25,10 +29,6 @@ DependencyPolicyContext, scoped_dependency_log_policy, ) -from ser.utils.transcription_compat import ( - FASTER_WHISPER_OPENMP_CONFLICT_ISSUE_CODE, - has_known_faster_whisper_openmp_runtime_conflict, -) if TYPE_CHECKING: from ser.config import AppConfig diff --git a/ser/_internal/transcript/backends/stable_whisper.py b/ser/_internal/transcript/backends/stable_whisper.py index 9db9c4f..d195a8b 100644 --- a/ser/_internal/transcript/backends/stable_whisper.py +++ b/ser/_internal/transcript/backends/stable_whisper.py @@ -13,28 +13,20 @@ from urllib.parse import urlparse from ser._internal.runtime.environment_plan import build_runtime_environment_plan -from ser._internal.runtime.process_env import temporary_process_env -from ser.domain import TranscriptWord -from ser.profiles import TranscriptionBackendId -from ser.runtime.phase_contract import ( +from ser._internal.runtime.phase_contract import ( PHASE_TRANSCRIPTION, PHASE_TRANSCRIPTION_MODEL_LOAD, ) -from ser.transcript.backends import stable_whisper_torio_probe -from ser.transcript.backends.base import ( - BackendRuntimeRequest, - CompatibilityIssue, - CompatibilityReport, - TranscriptionBackendAdapter, -) -from ser.transcript.backends.stable_whisper_admission_runtime import ( +from ser._internal.runtime.process_env import temporary_process_env +from ser._internal.transcript.backends import stable_whisper_torio_probe +from ser._internal.transcript.backends.stable_whisper_admission_runtime import ( is_compatibility_activation_error, is_retryable_precision_failure, mps_compatibility_fallback_reason, resolve_stable_whisper_mps_admission_decision, should_enforce_stable_whisper_transcribe_admission, ) -from ser.transcript.backends.stable_whisper_mps_compat import ( +from ser._internal.transcript.backends.stable_whisper_mps_compat import ( enable_stable_whisper_mps_compatibility, get_stable_whisper_runtime_device, is_stable_whisper_mps_compatibility_enabled, @@ -42,40 +34,48 @@ set_stable_whisper_runtime_device, stable_whisper_mps_timing_compatibility_context, ) -from ser.transcript.backends.stable_whisper_transcribe_admission import ( +from ser._internal.transcript.backends.stable_whisper_transcribe_admission import ( resolve_transcribe_runtime_device as resolve_stable_whisper_transcribe_runtime_device, ) -from ser.transcript.backends.stable_whisper_transcribe_execution import ( +from ser._internal.transcript.backends.stable_whisper_transcribe_execution import ( run_stable_whisper_transcribe_with_retry, ) -from ser.transcript.backends.stable_whisper_transcribe_kwargs import ( +from ser._internal.transcript.backends.stable_whisper_transcribe_kwargs import ( build_stable_whisper_transcribe_kwargs, ) -from ser.transcript.backends.stable_whisper_transcribe_runtime import ( +from ser._internal.transcript.backends.stable_whisper_transcribe_runtime import ( classify_transcription_failure_for_runtime, effective_precision_candidates, release_torch_runtime_memory_for_retry, summarize_runtime_error, ) -from ser.transcript.mps_admission import ( +from ser._internal.transcript.mps_admission import ( MpsAdmissionDecision, decide_mps_admission_for_transcription, log_mps_admission_control_fallback, mps_admission_control_enabled, mps_hard_oom_shortcut_enabled, ) -from ser.transcript.runtime_failures import ( +from ser._internal.transcript.runtime_failures import ( TranscriptionFailureClassification, ) +from ser._internal.utils.transcription_compat import ( + has_known_stable_whisper_sparse_mps_incompatibility, +) +from ser.domain import TranscriptWord +from ser.profiles import TranscriptionBackendId +from ser.transcript.backends.base import ( + BackendRuntimeRequest, + CompatibilityIssue, + CompatibilityReport, + TranscriptionBackendAdapter, +) from ser.utils.logger import ( DependencyLogPolicy, DependencyPolicyContext, WarningPolicy, scoped_dependency_log_policy, ) -from ser.utils.transcription_compat import ( - has_known_stable_whisper_sparse_mps_incompatibility, -) if TYPE_CHECKING: from ser.config import AppConfig diff --git a/ser/_internal/transcript/backends/stable_whisper_admission_runtime.py b/ser/_internal/transcript/backends/stable_whisper_admission_runtime.py index 8eeb54a..d49f1fd 100644 --- a/ser/_internal/transcript/backends/stable_whisper_admission_runtime.py +++ b/ser/_internal/transcript/backends/stable_whisper_admission_runtime.py @@ -6,7 +6,7 @@ from collections.abc import Callable from typing import TYPE_CHECKING, Literal -from ser.transcript.mps_admission import ( +from ser._internal.transcript.mps_admission import ( MpsAdmissionDecision, resolve_mps_admission_decision, should_enforce_transcribe_admission, diff --git a/ser/_internal/transcript/backends/stable_whisper_torio_probe.py b/ser/_internal/transcript/backends/stable_whisper_torio_probe.py index 952782e..8c04181 100644 --- a/ser/_internal/transcript/backends/stable_whisper_torio_probe.py +++ b/ser/_internal/transcript/backends/stable_whisper_torio_probe.py @@ -10,8 +10,8 @@ from pathlib import Path from typing import Protocol +from ser._internal.utils.transcription_compat import format_torio_ffmpeg_remediation from ser.transcript.backends.base import CompatibilityIssue -from ser.utils.transcription_compat import format_torio_ffmpeg_remediation class ModuleAvailabilityProbe(Protocol): diff --git a/ser/_internal/transcript/backends/stable_whisper_transcribe_admission.py b/ser/_internal/transcript/backends/stable_whisper_transcribe_admission.py index deb097b..a82a6d2 100644 --- a/ser/_internal/transcript/backends/stable_whisper_transcribe_admission.py +++ b/ser/_internal/transcript/backends/stable_whisper_transcribe_admission.py @@ -6,7 +6,7 @@ from collections.abc import Callable from typing import TYPE_CHECKING -from ser.transcript.mps_admission import MpsAdmissionDecision +from ser._internal.transcript.mps_admission import MpsAdmissionDecision if TYPE_CHECKING: from ser.transcript.backends.base import BackendRuntimeRequest diff --git a/ser/_internal/transcript/backends/stable_whisper_transcribe_execution.py b/ser/_internal/transcript/backends/stable_whisper_transcribe_execution.py index 4411ccf..16d7dd9 100644 --- a/ser/_internal/transcript/backends/stable_whisper_transcribe_execution.py +++ b/ser/_internal/transcript/backends/stable_whisper_transcribe_execution.py @@ -6,12 +6,12 @@ from collections.abc import Callable from typing import TYPE_CHECKING -from ser.domain import TranscriptWord -from ser.transcript.backends.base import BackendRuntimeRequest -from ser.transcript.runtime_failures import ( +from ser._internal.transcript.runtime_failures import ( FailureDisposition, TranscriptionFailureClassification, ) +from ser.domain import TranscriptWord +from ser.transcript.backends.base import BackendRuntimeRequest if TYPE_CHECKING: from ser.config import AppConfig diff --git a/ser/_internal/transcript/backends/stable_whisper_transcribe_runtime.py b/ser/_internal/transcript/backends/stable_whisper_transcribe_runtime.py index 71d6e55..0e3a9f7 100644 --- a/ser/_internal/transcript/backends/stable_whisper_transcribe_runtime.py +++ b/ser/_internal/transcript/backends/stable_whisper_transcribe_runtime.py @@ -8,12 +8,12 @@ from types import ModuleType from typing import TYPE_CHECKING -from ser.transcript.backends.base import BackendRuntimeRequest -from ser.transcript.runtime_failures import ( +from ser._internal.transcript.runtime_failures import ( FailureDisposition, TranscriptionFailureClassification, classify_stable_whisper_transcription_failure, ) +from ser.transcript.backends.base import BackendRuntimeRequest if TYPE_CHECKING: from ser.config import AppConfig diff --git a/ser/_internal/transcript/mps_admission.py b/ser/_internal/transcript/mps_admission.py index c76e067..d416ab8 100644 --- a/ser/_internal/transcript/mps_admission.py +++ b/ser/_internal/transcript/mps_admission.py @@ -172,7 +172,7 @@ def resolve_mps_admission_decision( safety_margin_mb=float(safety_margin_mb), ) # Local import avoids cyclic dependency with mps_admission_overrides. - from ser.transcript.mps_admission_overrides import ( + from ser._internal.transcript.mps_admission_overrides import ( apply_calibrated_mps_admission_override, ) diff --git a/ser/_internal/transcript/mps_admission_overrides.py b/ser/_internal/transcript/mps_admission_overrides.py index d615acf..0cfd06e 100644 --- a/ser/_internal/transcript/mps_admission_overrides.py +++ b/ser/_internal/transcript/mps_admission_overrides.py @@ -10,8 +10,8 @@ from pathlib import Path from typing import TYPE_CHECKING, Literal, cast +from ser._internal.transcript.mps_admission import MpsAdmissionDecision from ser.transcript.backends.base import BackendRuntimeRequest -from ser.transcript.mps_admission import MpsAdmissionDecision if TYPE_CHECKING: from ser.config import AppConfig diff --git a/ser/_internal/transcript/profiling.py b/ser/_internal/transcript/profiling.py index fafb1f2..98d0f2e 100644 --- a/ser/_internal/transcript/profiling.py +++ b/ser/_internal/transcript/profiling.py @@ -8,6 +8,11 @@ from pathlib import Path from typing import Final, Literal +from ser._internal.transcript.transcript_extractor import ( + TranscriptionProfile, + load_whisper_model, + transcribe_with_model, +) from ser._internal.transcription import default_benchmark as default_benchmark_helpers from ser._internal.transcription import default_profiling as default_profiling_helpers from ser._internal.transcription import default_recommendation as default_recommendation_helpers @@ -40,11 +45,6 @@ ) from ser.domain import TranscriptWord from ser.profiles import TranscriptionBackendId -from ser.transcript.transcript_extractor import ( - TranscriptionProfile, - load_whisper_model, - transcribe_with_model, -) from ser.utils.logger import get_logger logger: logging.Logger = get_logger(__name__) diff --git a/ser/_internal/transcript/runtime_policy.py b/ser/_internal/transcript/runtime_policy.py index 97be858..71dab6b 100644 --- a/ser/_internal/transcript/runtime_policy.py +++ b/ser/_internal/transcript/runtime_policy.py @@ -5,8 +5,8 @@ from dataclasses import dataclass from typing import Literal +from ser._internal.utils.torch_inference import maybe_resolve_torch_runtime from ser.profiles import TranscriptionBackendId -from ser.utils.torch_inference import maybe_resolve_torch_runtime _SUPPORTED_DTYPE_SELECTORS: frozenset[str] = frozenset({"auto", "float16", "float32", "bfloat16"}) DEFAULT_MPS_LOW_MEMORY_THRESHOLD_GB: float = 16.0 diff --git a/ser/_internal/transcript/transcript_extractor.py b/ser/_internal/transcript/transcript_extractor.py index 2b8658d..bd6d384 100644 --- a/ser/_internal/transcript/transcript_extractor.py +++ b/ser/_internal/transcript/transcript_extractor.py @@ -6,6 +6,11 @@ from dataclasses import dataclass, field from typing import TYPE_CHECKING, Literal, Protocol, cast +from ser._internal.runtime.phase_timing import ( + log_phase_completed, + log_phase_failed, + log_phase_started, +) from ser._internal.transcription import public_boundary_support as _boundary_support from ser._internal.transcription.process_worker import ( release_transcription_runtime_memory as _release_transcription_runtime_memory_impl, @@ -17,11 +22,6 @@ TranscriptionBackendId, get_profile_catalog, ) -from ser.runtime.phase_timing import ( - log_phase_completed, - log_phase_failed, - log_phase_started, -) from ser.utils.logger import get_logger if TYPE_CHECKING: diff --git a/ser/_internal/transcription/default_profiling.py b/ser/_internal/transcription/default_profiling.py index 05a07ee..d656e6e 100644 --- a/ser/_internal/transcription/default_profiling.py +++ b/ser/_internal/transcription/default_profiling.py @@ -9,8 +9,8 @@ from dataclasses import dataclass from pathlib import Path +from ser._internal.transcript.transcript_extractor import TranscriptionProfile from ser.domain import TranscriptWord -from ser.transcript.transcript_extractor import TranscriptionProfile type LoadModelFn = Callable[[TranscriptionProfile | None], object] type TranscribeFn = Callable[ diff --git a/ser/_internal/transcription/in_process_orchestration.py b/ser/_internal/transcription/in_process_orchestration.py index 0ce75b9..32c34fd 100644 --- a/ser/_internal/transcription/in_process_orchestration.py +++ b/ser/_internal/transcription/in_process_orchestration.py @@ -6,13 +6,13 @@ from collections.abc import Callable from typing import TYPE_CHECKING, Protocol, TypeVar, cast -from ser.config import AppConfig -from ser.profiles import TranscriptionBackendId -from ser.runtime.phase_contract import ( +from ser._internal.runtime.phase_contract import ( PHASE_TRANSCRIPTION, PHASE_TRANSCRIPTION_MODEL_LOAD, PHASE_TRANSCRIPTION_SETUP, ) +from ser.config import AppConfig +from ser.profiles import TranscriptionBackendId if TYPE_CHECKING: from ser.transcript.backends import BackendRuntimeRequest diff --git a/ser/_internal/transcription/process_isolation.py b/ser/_internal/transcription/process_isolation.py index f98b083..e81c71a 100644 --- a/ser/_internal/transcription/process_isolation.py +++ b/ser/_internal/transcription/process_isolation.py @@ -9,18 +9,18 @@ from types import ModuleType from typing import TYPE_CHECKING, Literal, Never, Protocol, TypeVar, cast -from ser.config import AppConfig -from ser.profiles import TranscriptionBackendId -from ser.runtime.phase_contract import ( +from ser._internal.runtime.phase_contract import ( PHASE_TRANSCRIPTION, PHASE_TRANSCRIPTION_MODEL_LOAD, PHASE_TRANSCRIPTION_SETUP, ) -from ser.runtime.phase_timing import ( +from ser._internal.runtime.phase_timing import ( log_phase_completed, log_phase_failed, log_phase_started, ) +from ser.config import AppConfig +from ser.profiles import TranscriptionBackendId if TYPE_CHECKING: from ser.transcript.backends.base import BackendRuntimeRequest diff --git a/ser/_internal/transcription/profiling_reporting.py b/ser/_internal/transcription/profiling_reporting.py index 044a124..833506d 100644 --- a/ser/_internal/transcription/profiling_reporting.py +++ b/ser/_internal/transcription/profiling_reporting.py @@ -7,7 +7,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from ser.transcript.profiling import ProfilingResult, RuntimeCalibrationResult + from ser._internal.transcript.profiling import ProfilingResult, RuntimeCalibrationResult def persist_profile_report(path: Path, payload: dict[str, object]) -> Path: diff --git a/ser/_internal/transcription/public_boundary_support.py b/ser/_internal/transcription/public_boundary_support.py index dfd2c16..90d08cd 100644 --- a/ser/_internal/transcription/public_boundary_support.py +++ b/ser/_internal/transcription/public_boundary_support.py @@ -7,6 +7,11 @@ from collections.abc import Callable from typing import TYPE_CHECKING, Literal, Never, Protocol, cast +from ser._internal.transcript.backends.factory import resolve_transcription_backend_adapter +from ser._internal.transcript.runtime_policy import ( + DEFAULT_MPS_LOW_MEMORY_THRESHOLD_GB, + resolve_transcription_runtime_policy, +) from ser._internal.transcription.compatibility import ( check_adapter_compatibility as _check_adapter_compatibility_impl, ) @@ -113,11 +118,6 @@ from ser.config import AppConfig from ser.domain import TranscriptWord from ser.profiles import TranscriptionBackendId -from ser.transcript.backends.factory import resolve_transcription_backend_adapter -from ser.transcript.runtime_policy import ( - DEFAULT_MPS_LOW_MEMORY_THRESHOLD_GB, - resolve_transcription_runtime_policy, -) if TYPE_CHECKING: from ser.transcript.backends.base import BackendRuntimeRequest, CompatibilityReport diff --git a/ser/_internal/transcription/runtime_calibration.py b/ser/_internal/transcription/runtime_calibration.py index 22864f8..9328f5f 100644 --- a/ser/_internal/transcription/runtime_calibration.py +++ b/ser/_internal/transcription/runtime_calibration.py @@ -10,9 +10,9 @@ from pathlib import Path from typing import Literal, Protocol, cast +from ser._internal.transcript.transcript_extractor import TranscriptionProfile from ser.config import AppConfig, ArtifactProfileName from ser.profiles import TranscriptionBackendId -from ser.transcript.transcript_extractor import TranscriptionProfile type RecommendationConfidence = Literal["high", "medium", "low"] type RuntimeRecommendation = Literal["prefer_cpu", "prefer_mps", "mps_with_failover"] @@ -114,7 +114,7 @@ def resolve_runtime_device_for_loaded_model( ) -> str: """Resolves active runtime device for one loaded model object.""" if backend_id == "stable_whisper": - from ser.transcript.backends.stable_whisper_mps_compat import ( + from ser._internal.transcript.backends.stable_whisper_mps_compat import ( get_stable_whisper_runtime_device, ) diff --git a/ser/transcript/__init__.py b/ser/transcript/__init__.py index a6b454b..ee9a35e 100644 --- a/ser/transcript/__init__.py +++ b/ser/transcript/__init__.py @@ -1,11 +1,12 @@ -from .transcript_extractor import ( - TranscriptionError, - TranscriptionProfile, - extract_transcript, - load_whisper_model, - transcribe_with_model, -) +# pyright: reportUnsupportedDunderAll=false +"""Public transcript extraction facade.""" +from __future__ import annotations + +import importlib +from typing import Any + +_EXPORT_MODULE = "ser.transcript.transcript_extractor" __all__ = [ "extract_transcript", "TranscriptionError", @@ -13,3 +14,12 @@ "load_whisper_model", "transcribe_with_model", ] + + +def __getattr__(name: str) -> Any: + """Lazily resolves one transcript facade export.""" + if name not in __all__: + raise AttributeError(f"module 'ser.transcript' has no attribute {name!r}") + value = getattr(importlib.import_module(_EXPORT_MODULE), name) + globals()[name] = value + return value diff --git a/ser/transcript/backends/__init__.py b/ser/transcript/backends/__init__.py index ec77eb6..70baa46 100644 --- a/ser/transcript/backends/__init__.py +++ b/ser/transcript/backends/__init__.py @@ -1,19 +1,28 @@ -"""Transcription backend adapters.""" +# pyright: reportUnsupportedDunderAll=false +"""Public transcription backend facade package.""" -from .base import ( - BackendRuntimeRequest, - CompatibilityIssue, - CompatibilityIssueImpact, - CompatibilityReport, - TranscriptionBackendAdapter, -) -from .factory import resolve_transcription_backend_adapter +from __future__ import annotations -__all__ = [ - "BackendRuntimeRequest", - "CompatibilityIssue", - "CompatibilityIssueImpact", - "CompatibilityReport", - "TranscriptionBackendAdapter", - "resolve_transcription_backend_adapter", -] +import importlib +from typing import Any + +_EXPORT_MODULES: dict[str, str] = { + "BackendRuntimeRequest": "ser.transcript.backends.base", + "CompatibilityIssue": "ser.transcript.backends.base", + "CompatibilityIssueImpact": "ser.transcript.backends.base", + "CompatibilityReport": "ser.transcript.backends.base", + "TranscriptionBackendAdapter": "ser.transcript.backends.base", + "resolve_transcription_backend_adapter": "ser._internal.transcript.backends.factory", +} + +__all__ = sorted(_EXPORT_MODULES) + + +def __getattr__(name: str) -> Any: + """Lazily resolves one public backend facade export.""" + module_name = _EXPORT_MODULES.get(name) + if module_name is None: + raise AttributeError(f"module 'ser.transcript.backends' has no attribute {name!r}") + value = getattr(importlib.import_module(module_name), name) + globals()[name] = value + return value diff --git a/ser/transcript/backends/stable_whisper.py b/ser/transcript/backends/stable_whisper.py new file mode 100644 index 0000000..95e0365 --- /dev/null +++ b/ser/transcript/backends/stable_whisper.py @@ -0,0 +1,3 @@ +"""Public facade for `ser._internal.transcript.backends.stable_whisper`.""" + +from ser._internal.transcript.backends.stable_whisper import * # noqa: F403 diff --git a/ser/transcript/profiling.py b/ser/transcript/profiling.py new file mode 100644 index 0000000..e7911c1 --- /dev/null +++ b/ser/transcript/profiling.py @@ -0,0 +1,3 @@ +"""Public facade for `ser._internal.transcript.profiling`.""" + +from ser._internal.transcript.profiling import * # noqa: F403 diff --git a/ser/transcript/transcript_extractor.py b/ser/transcript/transcript_extractor.py new file mode 100644 index 0000000..b15c52f --- /dev/null +++ b/ser/transcript/transcript_extractor.py @@ -0,0 +1,3 @@ +"""Public facade for `ser._internal.transcript.transcript_extractor`.""" + +from ser._internal.transcript.transcript_extractor import * # noqa: F403 diff --git a/tests/suites/integration/architecture/test_transcription_compatibility_ownership.py b/tests/suites/integration/architecture/test_transcription_compatibility_ownership.py index 7f52744..f1d309c 100644 --- a/tests/suites/integration/architecture/test_transcription_compatibility_ownership.py +++ b/tests/suites/integration/architecture/test_transcription_compatibility_ownership.py @@ -6,7 +6,7 @@ _COMPATIBILITY_BOUNDARY_SYMBOLS: tuple[tuple[str, tuple[str, ...]], ...] = ( ( - "ser.transcript.backends.stable_whisper", + "ser._internal.transcript.backends.stable_whisper", ( "StableWhisperAdapter.load_model", "StableWhisperAdapter.transcribe", @@ -17,7 +17,7 @@ ), ), ( - "ser.transcript.backends.stable_whisper_torio_probe", + "ser._internal.transcript.backends.stable_whisper_torio_probe", ( "is_module_available", "detect_torio_ffmpeg_operational_issue", @@ -27,7 +27,7 @@ ), ), ( - "ser.transcript.backends.stable_whisper_mps_compat", + "ser._internal.transcript.backends.stable_whisper_mps_compat", ( "enable_stable_whisper_mps_compatibility", "move_model_to_mps_with_alignment_placeholder", @@ -39,14 +39,14 @@ ), ), ( - "ser.transcript.mps_admission", + "ser._internal.transcript.mps_admission", ( "decide_mps_admission_for_transcription", "capture_mps_pressure_snapshot", ), ), ( - "ser.transcript.mps_admission_overrides", + "ser._internal.transcript.mps_admission_overrides", ("apply_calibrated_mps_admission_override",), ), ) diff --git a/tests/suites/integration/test_process_isolation.py b/tests/suites/integration/test_process_isolation.py index 63143e1..c7267c2 100644 --- a/tests/suites/integration/test_process_isolation.py +++ b/tests/suites/integration/test_process_isolation.py @@ -17,11 +17,11 @@ from ser._internal.runtime import process_timeout as runtime_process_timeout from ser._internal.runtime import worker_lifecycle as runtime_worker_lifecycle +from ser._internal.transcript.transcript_extractor import TranscriptionProfile from ser._internal.transcription import process_isolation as transcription_process_isolation from ser._internal.transcription import public_boundary_support as transcription_boundary_support from ser.config import AppConfig, reload_settings from ser.domain import TranscriptWord -from ser.transcript.transcript_extractor import TranscriptionProfile if TYPE_CHECKING: from ser.transcript.backends.base import BackendRuntimeRequest diff --git a/tests/suites/integration/test_transcript_extractor.py b/tests/suites/integration/test_transcript_extractor.py index c24c9d7..048c5d6 100644 --- a/tests/suites/integration/test_transcript_extractor.py +++ b/tests/suites/integration/test_transcript_extractor.py @@ -8,8 +8,8 @@ import pytest +import ser._internal.transcript.transcript_extractor as te from ser.domain import TranscriptWord -from ser.transcript import transcript_extractor as te pytestmark = pytest.mark.integration diff --git a/tests/suites/unit/internal/transcription/test_transcription_default_profiling.py b/tests/suites/unit/internal/transcription/test_transcription_default_profiling.py index f1cc054..f21b0e6 100644 --- a/tests/suites/unit/internal/transcription/test_transcription_default_profiling.py +++ b/tests/suites/unit/internal/transcription/test_transcription_default_profiling.py @@ -7,9 +7,9 @@ import pytest +from ser._internal.transcript.transcript_extractor import TranscriptionProfile from ser._internal.transcription import default_profiling as default_helpers from ser.domain import TranscriptWord -from ser.transcript.transcript_extractor import TranscriptionProfile def _profile() -> TranscriptionProfile: diff --git a/tests/suites/unit/internal/transcription/test_transcription_profiling.py b/tests/suites/unit/internal/transcription/test_transcription_profiling.py index d897491..9c26c3c 100644 --- a/tests/suites/unit/internal/transcription/test_transcription_profiling.py +++ b/tests/suites/unit/internal/transcription/test_transcription_profiling.py @@ -7,8 +7,8 @@ import pytest +import ser._internal.transcript.profiling as tp from ser._internal.transcription import public_boundary_profiling as public_boundary_helpers -from ser.transcript import profiling as tp def _summary( diff --git a/tests/suites/unit/transcription/backends/test_faster_whisper_adapter.py b/tests/suites/unit/transcription/backends/test_faster_whisper_adapter.py index db10ddd..65f02dc 100644 --- a/tests/suites/unit/transcription/backends/test_faster_whisper_adapter.py +++ b/tests/suites/unit/transcription/backends/test_faster_whisper_adapter.py @@ -10,11 +10,11 @@ import pytest +import ser._internal.transcript.backends.faster_whisper as faster_whisper_backend +from ser._internal.transcript.backends.faster_whisper import FasterWhisperAdapter from ser.config import AppConfig from ser.domain import TranscriptWord -from ser.transcript.backends import faster_whisper as faster_whisper_backend from ser.transcript.backends.base import BackendRuntimeRequest -from ser.transcript.backends.faster_whisper import FasterWhisperAdapter pytestmark = pytest.mark.unit @@ -114,7 +114,7 @@ def _download_model(_model_name: str, *, local_files_only: bool, cache_dir: str) raise RuntimeError("cache miss") monkeypatch.setattr( - "ser.transcript.backends.faster_whisper.importlib.import_module", + "ser._internal.transcript.backends.faster_whisper.importlib.import_module", lambda name: ( SimpleNamespace(download_model=_download_model) if name == "faster_whisper.utils" @@ -212,7 +212,7 @@ def _download_model(model_name: str, *, local_files_only: bool, cache_dir: str) return cache_dir monkeypatch.setattr( - "ser.transcript.backends.faster_whisper.importlib.import_module", + "ser._internal.transcript.backends.faster_whisper.importlib.import_module", lambda name: ( SimpleNamespace(download_model=_download_model) if name == "faster_whisper.utils" @@ -240,7 +240,7 @@ def test_prepare_assets_ignores_missing_dependency( ) -> None: """Missing faster-whisper utilities should become a no-op.""" monkeypatch.setattr( - "ser.transcript.backends.faster_whisper.importlib.import_module", + "ser._internal.transcript.backends.faster_whisper.importlib.import_module", lambda name: (_ for _ in ()).throw(ModuleNotFoundError(name)), ) @@ -259,7 +259,7 @@ def test_load_model_raises_clear_error_when_dependency_is_missing( ) -> None: """Model loading should surface actionable dependency remediation.""" monkeypatch.setattr( - "ser.transcript.backends.faster_whisper.importlib.import_module", + "ser._internal.transcript.backends.faster_whisper.importlib.import_module", lambda name: (_ for _ in ()).throw(ModuleNotFoundError(name)), ) @@ -278,7 +278,7 @@ def test_load_model_requires_whisper_model_symbol( ) -> None: """The adapter should reject packages that do not expose WhisperModel.""" monkeypatch.setattr( - "ser.transcript.backends.faster_whisper.importlib.import_module", + "ser._internal.transcript.backends.faster_whisper.importlib.import_module", lambda name: ( SimpleNamespace() if name == "faster_whisper" @@ -317,7 +317,7 @@ def __init__( captured["download_root"] = download_root monkeypatch.setattr( - "ser.transcript.backends.faster_whisper.importlib.import_module", + "ser._internal.transcript.backends.faster_whisper.importlib.import_module", lambda name: ( SimpleNamespace(WhisperModel=_FakeWhisperModel) if name == "faster_whisper" @@ -366,7 +366,7 @@ def __init__( captured["download_root"] = download_root monkeypatch.setattr( - "ser.transcript.backends.faster_whisper.importlib.import_module", + "ser._internal.transcript.backends.faster_whisper.importlib.import_module", lambda name: ( SimpleNamespace(WhisperModel=_FakeWhisperModel) if name == "faster_whisper" @@ -526,7 +526,7 @@ def test_is_module_available_handles_sys_modules_and_find_spec_errors( sys.modules[module_name] = original monkeypatch.setattr( - "ser.transcript.backends.faster_whisper.importlib.util.find_spec", + "ser._internal.transcript.backends.faster_whisper.importlib.util.find_spec", lambda name: (_ for _ in ()).throw(ValueError(name)), ) assert adapter._is_module_available("missing_dependency") is False diff --git a/tests/suites/unit/transcription/test_stable_whisper_admission_runtime.py b/tests/suites/unit/transcription/test_stable_whisper_admission_runtime.py index 08e5bf8..956f822 100644 --- a/tests/suites/unit/transcription/test_stable_whisper_admission_runtime.py +++ b/tests/suites/unit/transcription/test_stable_whisper_admission_runtime.py @@ -6,9 +6,9 @@ from types import SimpleNamespace from typing import Any, cast -import ser.transcript.backends.stable_whisper_admission_runtime as admission_runtime +import ser._internal.transcript.backends.stable_whisper_admission_runtime as admission_runtime +from ser._internal.transcript.mps_admission import MpsAdmissionDecision from ser.transcript.backends.base import BackendRuntimeRequest -from ser.transcript.mps_admission import MpsAdmissionDecision def test_is_retryable_precision_failure_detects_sparsemps_notimplemented() -> None: diff --git a/tests/suites/unit/transcription/test_stable_whisper_mps_compat.py b/tests/suites/unit/transcription/test_stable_whisper_mps_compat.py index 9a971c8..99c015c 100644 --- a/tests/suites/unit/transcription/test_stable_whisper_mps_compat.py +++ b/tests/suites/unit/transcription/test_stable_whisper_mps_compat.py @@ -9,7 +9,7 @@ import pytest import torch -from ser.transcript.backends import stable_whisper_mps_compat as mps_compat +import ser._internal.transcript.backends.stable_whisper_mps_compat as mps_compat class _FakeModel: diff --git a/tests/suites/unit/transcription/test_stable_whisper_transcribe_admission.py b/tests/suites/unit/transcription/test_stable_whisper_transcribe_admission.py index 5406c06..01f9f52 100644 --- a/tests/suites/unit/transcription/test_stable_whisper_transcribe_admission.py +++ b/tests/suites/unit/transcription/test_stable_whisper_transcribe_admission.py @@ -5,11 +5,11 @@ import logging from typing import Literal -from ser.transcript.backends.base import BackendRuntimeRequest -from ser.transcript.backends.stable_whisper_transcribe_admission import ( +from ser._internal.transcript.backends.stable_whisper_transcribe_admission import ( resolve_transcribe_runtime_device, ) -from ser.transcript.mps_admission import MpsAdmissionDecision +from ser._internal.transcript.mps_admission import MpsAdmissionDecision +from ser.transcript.backends.base import BackendRuntimeRequest def _runtime_request(*, device_type: str = "mps") -> BackendRuntimeRequest: diff --git a/tests/suites/unit/transcription/test_stable_whisper_transcribe_execution.py b/tests/suites/unit/transcription/test_stable_whisper_transcribe_execution.py index 57d1c06..f91c466 100644 --- a/tests/suites/unit/transcription/test_stable_whisper_transcribe_execution.py +++ b/tests/suites/unit/transcription/test_stable_whisper_transcribe_execution.py @@ -8,15 +8,15 @@ import pytest -from ser.config import AppConfig -from ser.transcript.backends.base import BackendRuntimeRequest -from ser.transcript.backends.stable_whisper_transcribe_execution import ( +from ser._internal.transcript.backends.stable_whisper_transcribe_execution import ( run_stable_whisper_transcribe_with_retry, ) -from ser.transcript.runtime_failures import ( +from ser._internal.transcript.runtime_failures import ( FailureDisposition, TranscriptionFailureClassification, ) +from ser.config import AppConfig +from ser.transcript.backends.base import BackendRuntimeRequest def _runtime_request(*, device_type: str, precision: tuple[str, ...]) -> BackendRuntimeRequest: diff --git a/tests/suites/unit/transcription/test_stable_whisper_transcribe_kwargs.py b/tests/suites/unit/transcription/test_stable_whisper_transcribe_kwargs.py index 5c58510..9fe35ac 100644 --- a/tests/suites/unit/transcription/test_stable_whisper_transcribe_kwargs.py +++ b/tests/suites/unit/transcription/test_stable_whisper_transcribe_kwargs.py @@ -6,11 +6,11 @@ import pytest -import ser.transcript.backends.stable_whisper as stable_whisper -from ser.transcript.backends.base import BackendRuntimeRequest -from ser.transcript.backends.stable_whisper_transcribe_kwargs import ( +import ser._internal.transcript.backends.stable_whisper as stable_whisper +from ser._internal.transcript.backends.stable_whisper_transcribe_kwargs import ( build_stable_whisper_transcribe_kwargs, ) +from ser.transcript.backends.base import BackendRuntimeRequest def _runtime_request(*, use_demucs: bool) -> BackendRuntimeRequest: diff --git a/tests/suites/unit/transcription/test_stable_whisper_transcribe_runtime.py b/tests/suites/unit/transcription/test_stable_whisper_transcribe_runtime.py index b79274e..eec25c2 100644 --- a/tests/suites/unit/transcription/test_stable_whisper_transcribe_runtime.py +++ b/tests/suites/unit/transcription/test_stable_whisper_transcribe_runtime.py @@ -7,14 +7,14 @@ import pytest -import ser.transcript.backends.stable_whisper as stable_whisper -import ser.transcript.backends.stable_whisper_transcribe_runtime as runtime_helpers -from ser.config import AppConfig -from ser.transcript.backends.base import BackendRuntimeRequest -from ser.transcript.runtime_failures import ( +import ser._internal.transcript.backends.stable_whisper as stable_whisper +import ser._internal.transcript.backends.stable_whisper_transcribe_runtime as runtime_helpers +from ser._internal.transcript.runtime_failures import ( FailureDisposition, TranscriptionFailureClassification, ) +from ser.config import AppConfig +from ser.transcript.backends.base import BackendRuntimeRequest def test_classify_transcription_failure_for_runtime_downgrades_when_shortcut_disabled( diff --git a/tests/suites/unit/transcription/test_transcription_admission_overrides.py b/tests/suites/unit/transcription/test_transcription_admission_overrides.py index fb59ac2..39546f6 100644 --- a/tests/suites/unit/transcription/test_transcription_admission_overrides.py +++ b/tests/suites/unit/transcription/test_transcription_admission_overrides.py @@ -8,12 +8,12 @@ from types import SimpleNamespace from typing import cast -from ser.config import AppConfig -from ser.transcript.backends.base import BackendRuntimeRequest -from ser.transcript.mps_admission import MpsAdmissionDecision -from ser.transcript.mps_admission_overrides import ( +from ser._internal.transcript.mps_admission import MpsAdmissionDecision +from ser._internal.transcript.mps_admission_overrides import ( apply_calibrated_mps_admission_override, ) +from ser.config import AppConfig +from ser.transcript.backends.base import BackendRuntimeRequest def _settings( diff --git a/tests/suites/unit/transcription/test_transcription_backends.py b/tests/suites/unit/transcription/test_transcription_backends.py index 6612715..5fe0947 100644 --- a/tests/suites/unit/transcription/test_transcription_backends.py +++ b/tests/suites/unit/transcription/test_transcription_backends.py @@ -12,27 +12,27 @@ import pytest +from ser._internal.transcript.backends import stable_whisper_torio_probe +from ser._internal.transcript.backends.faster_whisper import FasterWhisperAdapter +from ser._internal.transcript.backends.stable_whisper import StableWhisperAdapter +from ser._internal.transcript.backends.stable_whisper_torio_probe import ( + detect_default_torio_ffmpeg_operational_issue, + extract_missing_dynamic_library, +) +from ser._internal.utils.transcription_compat import FASTER_WHISPER_OPENMP_CONFLICT_ISSUE_CODE from ser.config import AppConfig -from ser.transcript.backends import stable_whisper_torio_probe from ser.transcript.backends.base import ( BackendRuntimeRequest, CompatibilityIssue, CompatibilityReport, ) -from ser.transcript.backends.faster_whisper import FasterWhisperAdapter -from ser.transcript.backends.stable_whisper import StableWhisperAdapter -from ser.transcript.backends.stable_whisper_torio_probe import ( - detect_default_torio_ffmpeg_operational_issue, - extract_missing_dynamic_library, -) -from ser.utils.transcription_compat import FASTER_WHISPER_OPENMP_CONFLICT_ISSUE_CODE @pytest.fixture(autouse=True) def _disable_openmp_conflict_detection(monkeypatch: pytest.MonkeyPatch) -> None: """Keeps faster-whisper compatibility assertions deterministic in unit tests.""" monkeypatch.setattr( - "ser.transcript.backends.faster_whisper." + "ser._internal.transcript.backends.faster_whisper." "has_known_faster_whisper_openmp_runtime_conflict", lambda: False, ) @@ -44,11 +44,11 @@ def test_stable_whisper_compatibility_report_is_noise_aware( """Stable adapter should expose noise policy metadata without blocking.""" adapter = StableWhisperAdapter() monkeypatch.setattr( - "ser.transcript.backends.stable_whisper_torio_probe.is_module_available", + "ser._internal.transcript.backends.stable_whisper_torio_probe.is_module_available", lambda _name: True, ) monkeypatch.setattr( - "ser.transcript.backends.stable_whisper_torio_probe." + "ser._internal.transcript.backends.stable_whisper_torio_probe." "detect_default_torio_ffmpeg_operational_issue", lambda: None, ) @@ -78,11 +78,11 @@ def test_stable_whisper_compatibility_blocks_on_missing_dependency( """Stable adapter should block when stable_whisper dependency is unavailable.""" adapter = StableWhisperAdapter() monkeypatch.setattr( - "ser.transcript.backends.stable_whisper_torio_probe.is_module_available", + "ser._internal.transcript.backends.stable_whisper_torio_probe.is_module_available", lambda _name: False, ) monkeypatch.setattr( - "ser.transcript.backends.stable_whisper_torio_probe." + "ser._internal.transcript.backends.stable_whisper_torio_probe." "detect_default_torio_ffmpeg_operational_issue", lambda: None, ) @@ -108,11 +108,11 @@ def test_stable_whisper_compatibility_reports_torio_ffmpeg_operational_issue( """Stable adapter should surface torio FFmpeg ABI mismatch as operational.""" adapter = StableWhisperAdapter() monkeypatch.setattr( - "ser.transcript.backends.stable_whisper_torio_probe.is_module_available", + "ser._internal.transcript.backends.stable_whisper_torio_probe.is_module_available", lambda _name: True, ) monkeypatch.setattr( - "ser.transcript.backends.stable_whisper_torio_probe." + "ser._internal.transcript.backends.stable_whisper_torio_probe." "detect_default_torio_ffmpeg_operational_issue", lambda: CompatibilityIssue( code="torio_ffmpeg_abi_mismatch", @@ -252,7 +252,7 @@ def test_faster_whisper_openmp_conflict_is_blocking( ) -> None: """Known OpenMP runtime collisions should block faster-whisper execution.""" monkeypatch.setattr( - "ser.transcript.backends.faster_whisper." + "ser._internal.transcript.backends.faster_whisper." "has_known_faster_whisper_openmp_runtime_conflict", lambda: True, ) @@ -600,7 +600,8 @@ def _fake_load_model( SimpleNamespace(load_model=_fake_load_model), ) monkeypatch.setattr( - "ser.transcript.backends.stable_whisper." "enable_stable_whisper_mps_compatibility", + "ser._internal.transcript.backends.stable_whisper." + "enable_stable_whisper_mps_compatibility", lambda _model: (_ for _ in ()).throw(failure_type(failure_message)), ) @@ -681,7 +682,8 @@ def _raise_compat_activation_error(model: object) -> object: SimpleNamespace(load_model=_fake_load_model), ) monkeypatch.setattr( - "ser.transcript.backends.stable_whisper." "enable_stable_whisper_mps_compatibility", + "ser._internal.transcript.backends.stable_whisper." + "enable_stable_whisper_mps_compatibility", _raise_compat_activation_error, ) @@ -749,7 +751,8 @@ def _fake_load_model( SimpleNamespace(load_model=_fake_load_model), ) monkeypatch.setattr( - "ser.transcript.backends.stable_whisper." "enable_stable_whisper_mps_compatibility", + "ser._internal.transcript.backends.stable_whisper." + "enable_stable_whisper_mps_compatibility", lambda _model: (_ for _ in ()).throw(ValueError("unexpected failure")), ) @@ -814,11 +817,12 @@ def _fake_load_model( SimpleNamespace(load_model=_fake_load_model), ) monkeypatch.setattr( - "ser.transcript.backends.stable_whisper." "enable_stable_whisper_mps_compatibility", + "ser._internal.transcript.backends.stable_whisper." + "enable_stable_whisper_mps_compatibility", lambda _model: moved_model, ) monkeypatch.setattr( - "ser.transcript.backends.stable_whisper." + "ser._internal.transcript.backends.stable_whisper." "has_known_stable_whisper_sparse_mps_incompatibility", lambda: True, ) @@ -889,7 +893,7 @@ def _fake_load_model( SimpleNamespace(load_model=_fake_load_model), ) monkeypatch.setattr( - "ser.transcript.backends.stable_whisper.decide_mps_admission_for_transcription", + "ser._internal.transcript.backends.stable_whisper.decide_mps_admission_for_transcription", lambda **_kwargs: SimpleNamespace( allow_mps=False, reason_code="mps_headroom_below_required_budget", @@ -906,7 +910,8 @@ def _record_enable_call(model: object) -> object: return model monkeypatch.setattr( - "ser.transcript.backends.stable_whisper." "enable_stable_whisper_mps_compatibility", + "ser._internal.transcript.backends.stable_whisper." + "enable_stable_whisper_mps_compatibility", _record_enable_call, ) @@ -951,7 +956,7 @@ def to(self, *, device: str) -> _FakeModel: return self monkeypatch.setattr( - "ser.transcript.backends.stable_whisper.decide_mps_admission_for_transcription", + "ser._internal.transcript.backends.stable_whisper.decide_mps_admission_for_transcription", lambda **_kwargs: SimpleNamespace( allow_mps=False, reason_code="mps_headroom_below_required_budget", @@ -1017,7 +1022,7 @@ def to(self, *, device: str) -> _FakeModel: return self monkeypatch.setattr( - "ser.transcript.backends.stable_whisper.decide_mps_admission_for_transcription", + "ser._internal.transcript.backends.stable_whisper.decide_mps_admission_for_transcription", lambda **_kwargs: SimpleNamespace( allow_mps=False, reason_code="mps_headroom_estimate_below_required_budget", @@ -1077,11 +1082,13 @@ def _fake_context() -> Iterator[None]: context_events.append("exit") monkeypatch.setattr( - "ser.transcript.backends.stable_whisper." "is_stable_whisper_mps_compatibility_enabled", + "ser._internal.transcript.backends.stable_whisper." + "is_stable_whisper_mps_compatibility_enabled", lambda _model: True, ) monkeypatch.setattr( - "ser.transcript.backends.stable_whisper." "stable_whisper_mps_timing_compatibility_context", + "ser._internal.transcript.backends.stable_whisper." + "stable_whisper_mps_timing_compatibility_context", _fake_context, ) @@ -1171,7 +1178,8 @@ def _fake_load_model( SimpleNamespace(load_model=_fake_load_model), ) monkeypatch.setattr( - "ser.transcript.backends.stable_whisper." "enable_stable_whisper_mps_compatibility", + "ser._internal.transcript.backends.stable_whisper." + "enable_stable_whisper_mps_compatibility", lambda _model: (_ for _ in ()).throw(RuntimeError("MPS backend out of memory")), ) monkeypatch.setattr(adapter, "_normalize_result", lambda raw: raw) diff --git a/tests/suites/unit/transcription/test_transcription_factory.py b/tests/suites/unit/transcription/test_transcription_factory.py index ddc0864..c5665cc 100644 --- a/tests/suites/unit/transcription/test_transcription_factory.py +++ b/tests/suites/unit/transcription/test_transcription_factory.py @@ -9,25 +9,25 @@ def test_factory_resolves_faster_adapter_without_importing_stable_backend() -> None: """Resolving faster adapter should not import stable backend or torch stack.""" module_names = ( - "ser.transcript.backends.factory", - "ser.transcript.backends.stable_whisper", - "ser.transcript.backends.stable_whisper_mps_compat", + "ser._internal.transcript.backends.factory", + "ser._internal.transcript.backends.stable_whisper", + "ser._internal.transcript.backends.stable_whisper_mps_compat", ) removed_modules = { module_name: sys.modules.pop(module_name, None) for module_name in module_names } try: - factory = importlib.import_module("ser.transcript.backends.factory") + factory = importlib.import_module("ser._internal.transcript.backends.factory") reloaded_factory = importlib.reload(factory) - assert "ser.transcript.backends.stable_whisper" not in sys.modules - assert "ser.transcript.backends.stable_whisper_mps_compat" not in sys.modules + assert "ser._internal.transcript.backends.stable_whisper" not in sys.modules + assert "ser._internal.transcript.backends.stable_whisper_mps_compat" not in sys.modules adapter = reloaded_factory.resolve_transcription_backend_adapter("faster_whisper") assert adapter.backend_id == "faster_whisper" - assert "ser.transcript.backends.stable_whisper" not in sys.modules - assert "ser.transcript.backends.stable_whisper_mps_compat" not in sys.modules + assert "ser._internal.transcript.backends.stable_whisper" not in sys.modules + assert "ser._internal.transcript.backends.stable_whisper_mps_compat" not in sys.modules finally: for module_name, module in removed_modules.items(): if module is not None: diff --git a/tests/suites/unit/transcription/test_transcription_mps_admission.py b/tests/suites/unit/transcription/test_transcription_mps_admission.py index 8bd6b29..8056fb8 100644 --- a/tests/suites/unit/transcription/test_transcription_mps_admission.py +++ b/tests/suites/unit/transcription/test_transcription_mps_admission.py @@ -8,7 +8,7 @@ import pytest -from ser.transcript import mps_admission +from ser._internal.transcript import mps_admission def test_mps_admission_allows_when_pressure_snapshot_unavailable( @@ -221,7 +221,7 @@ def test_resolve_mps_admission_decision_applies_override_and_logs( lambda **_kwargs: heuristic, ) monkeypatch.setattr( - "ser.transcript.mps_admission_overrides.apply_calibrated_mps_admission_override", + "ser._internal.transcript.mps_admission_overrides.apply_calibrated_mps_admission_override", lambda **_kwargs: overridden, ) diff --git a/tests/suites/unit/transcription/test_transcription_runtime_failures.py b/tests/suites/unit/transcription/test_transcription_runtime_failures.py index 761dcc4..b1afe59 100644 --- a/tests/suites/unit/transcription/test_transcription_runtime_failures.py +++ b/tests/suites/unit/transcription/test_transcription_runtime_failures.py @@ -2,7 +2,7 @@ from __future__ import annotations -from ser.transcript.runtime_failures import ( +from ser._internal.transcript.runtime_failures import ( FailureDisposition, classify_stable_whisper_transcription_failure, ) diff --git a/tests/suites/unit/transcription/test_transcription_runtime_policy.py b/tests/suites/unit/transcription/test_transcription_runtime_policy.py index 306bbaf..a094608 100644 --- a/tests/suites/unit/transcription/test_transcription_runtime_policy.py +++ b/tests/suites/unit/transcription/test_transcription_runtime_policy.py @@ -4,8 +4,8 @@ import pytest -import ser.transcript.runtime_policy as runtime_policy -from ser.utils.torch_inference import TorchRuntime +import ser._internal.transcript.runtime_policy as runtime_policy +from ser._internal.utils.torch_inference import TorchRuntime def _runtime(*, device_spec: str, device_type: str) -> TorchRuntime: From caa71ef88e4d40b23c8935c52e5a2805dbf8e69e Mon Sep 17 00:00:00 2001 From: Juan Sugg Date: Thu, 9 Jul 2026 22:31:08 -0300 Subject: [PATCH 06/12] refactor(api): trim residual public facades --- .local/public-api-surface-improvements.md | 32 +++++++---- boundary_policy.toml | 41 +++----------- docs/codebase-architecture.md | 46 ++++++++-------- pyproject.toml | 7 --- ser/_internal/config/bootstrap.py | 2 +- ser/_internal/diagnostics/__init__.py | 1 + ser/_internal/diagnostics/command.py | 4 +- ser/_internal/diagnostics/service.py | 18 +++---- ser/_internal/features/__init__.py | 17 ++++++ ser/_internal/features/feature_extractor.py | 8 +-- ser/_internal/heads/__init__.py | 5 ++ ser/_internal/repr/__init__.py | 25 +++++++++ ser/_internal/repr/emotion2vec.py | 8 +-- ser/_internal/repr/handcrafted.py | 6 +-- ser/_internal/repr/hf_whisper.py | 6 +-- ser/_internal/repr/hf_xlsr.py | 6 +-- ser/_internal/repr/runtime_policy.py | 2 +- ser/_internal/utils/__init__.py | 1 + ser/_internal/utils/timeline_utils.py | 2 +- ser/diagnostics/__init__.py | 53 +++++++++++-------- ser/features/__init__.py | 24 ++++++--- ser/heads/__init__.py | 18 ++++++- ser/profiles.py | 12 +++-- ser/repr/__init__.py | 52 ++++++++++-------- ser/utils/__init__.py | 42 +++++++-------- .../cli/test_diagnostics_command.py | 10 ++-- .../diagnostics/test_diagnostics_service.py | 8 +-- .../suites/unit/repr/test_backend_contract.py | 2 +- .../unit/repr/test_emotion2vec_backend.py | 6 +-- .../unit/repr/test_feature_extractor.py | 6 +-- .../repr/test_feature_runtime_encoding.py | 6 +-- .../unit/repr/test_feature_runtime_policy.py | 6 +-- .../unit/repr/test_handcrafted_backend.py | 8 +-- .../unit/repr/test_hf_whisper_backend.py | 6 +-- .../suites/unit/repr/test_hf_xlsr_backend.py | 6 +-- tests/suites/unit/repr/test_pooling.py | 2 +- tests/suites/unit/utils/test_audio_utils.py | 2 +- tests/suites/unit/utils/test_dsp.py | 2 +- .../utils/test_segment_canonicalization.py | 2 +- tests/suites/unit/utils/test_subtitles.py | 6 +-- .../suites/unit/utils/test_timeline_utils.py | 2 +- .../suites/unit/utils/test_torch_inference.py | 2 +- .../unit/utils/test_transcription_compat.py | 2 +- 43 files changed, 295 insertions(+), 227 deletions(-) create mode 100644 ser/_internal/diagnostics/__init__.py create mode 100644 ser/_internal/features/__init__.py create mode 100644 ser/_internal/heads/__init__.py create mode 100644 ser/_internal/repr/__init__.py create mode 100644 ser/_internal/utils/__init__.py diff --git a/.local/public-api-surface-improvements.md b/.local/public-api-surface-improvements.md index 98e09ab..1c28985 100644 --- a/.local/public-api-surface-improvements.md +++ b/.local/public-api-surface-improvements.md @@ -192,13 +192,13 @@ and observed green via `verifier-ci`. | ID | Task | Status | Depends on | |----|------|--------|------------| | P2-01 | Inventory & classification appendix | DONE | P1-01 | -| P2-02 | `ser/models` → internal (keep facades) | TODO | P2-01 | -| P2-03 | `ser/runtime` → internal (keep contracts/pipeline/registry facades) | TODO | P2-01 | -| P2-04 | `ser/data` → internal (keep `application.py` + curated `__init__`) | TODO | P2-01 | -| P2-05 | `ser/transcript` → internal (keep policy-listed facades) | TODO | P2-01 | -| P2-06 | `ser/features`, `ser/heads`, `ser/repr`, `ser/diagnostics` residue | TODO | P2-01 | -| P2-07 | Shrink `boundary_policy.toml` to genuine facades; strengthen contract test | TODO | P2-02..06 | -| P2-08 | `ser/utils` trim to curated `__all__` | TODO | P2-01 | +| P2-02 | `ser/models` → internal (keep facades) | DONE | P2-01 | +| P2-03 | `ser/runtime` → internal (keep contracts/pipeline/registry facades) | DONE | P2-01 | +| P2-04 | `ser/data` → internal (keep `application.py` + curated `__init__`) | DONE | P2-01 | +| P2-05 | `ser/transcript` → internal (keep policy-listed facades) | DONE | P2-01 | +| P2-06 | `ser/features`, `ser/heads`, `ser/repr`, `ser/diagnostics` residue | DONE | P2-01 | +| P2-07 | Shrink `boundary_policy.toml` to genuine facades; strengthen contract test | DONE | P2-02..06 | +| P2-08 | `ser/utils` trim to curated `__all__` | DONE | P2-01 | **P2-01** — Generate the full classification: for every `.py` under public non-tier-1 paths, record `keep-as-facade` (with reason) or `move-internal` (with destination). @@ -309,6 +309,16 @@ Template: - Deviations / follow-ups: … ``` +### 2026-07-09 22:27 — P2-02..P2-08 done +- What: Moved Phase 2 implementation modules under `ser/_internal/`, kept only classified public facades/contracts, rewired first-party imports/tests/scripts/docs, and trimmed `boundary_policy.toml` to 10 genuine facade entries. +- Evidence: `rg --files ser/data ser/models ser/runtime ser/transcript ser/features ser/heads ser/repr | sort` → only Appendix-A keep-as-facade files remain public; `uv run --frozen --extra dev pytest -q tests/suites/integration/architecture/test_public_api_snapshot.py tests/suites/integration/architecture/test_public_import_cost.py` → `2 passed`; synthetic unlisted `from ser._internal.config.schema import AppConfig` in `ser/domain.py` made `uv run --frozen --extra dev pytest -q tests/suites/integration/architecture/test_api_import_boundary.py` fail in `test_public_to_internal_imports_match_explicit_allowlist`, then the synthetic edit was removed and the same boundary test → `13 passed`; `bash scripts/run_import_lint.sh` → `17 passed`; `make lint` → Ruff/import-lint/Black/isort passed; `make type` → mypy success and pyright `0 errors, 0 warnings`; `make test` → `1028 passed`; `make test-cov` → `1028 passed`, coverage gate held; `make lock-check` → lock verified. +- Deviations / follow-ups: `ser.runtime.schema`, `ser.transcript.backends.base`, `ser.utils.common_utils`, and `ser.utils.logger` remain public contract/helper leaves to preserve tier-1 snapshot targets and public annotation self-sufficiency; no runtime behavior changes, publish workflows, dependency additions, or torch/torchaudio lock/spec edits. + +### 2026-07-09 21:35 — P2-02..P2-08 started +- What: Execute the classified moves in dependency order, keeping public facades thin and routing implementation imports to internal owners. +- Evidence: P2-01 Appendix A classified all move targets before the first move. +- Deviations / follow-ups: none. + ### 2026-07-09 21:34 — P2-01 done - What: Classified every Python file under the public non-tier-1 Phase 2 package set as `keep-as-facade` or `move-internal`, including destinations for all moved modules. - Evidence: `python` classification generator covered 133 files; `rg -n '\| `[^`]+` \| unclassified' .local/public-api-surface-improvements.md` → no matches; Appendix A now contains no empty placeholder. @@ -623,10 +633,10 @@ Template: | `ser/runtime/quality_gate_policy.py` | move-internal | `ser/_internal/runtime/quality_gate_policy.py` | Implementation/helper module; first-party callers will use the internal owner path. | | `ser/runtime/quality_gate_reporting.py` | move-internal | `ser/_internal/runtime/quality_gate_reporting.py` | Implementation/helper module; first-party callers will use the internal owner path. | | `ser/runtime/registry.py` | keep-as-facade | — | Public runtime capability registry contract facade. | -| `ser/runtime/schema.py` | move-internal | `ser/_internal/runtime/schema.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/runtime/schema.py` | keep-as-facade | — | Public runtime result schema contract referenced by runtime contracts and downstream tests. | | `ser/transcript/__init__.py` | keep-as-facade | — | Transcript package facade exporting supported transcript boundary APIs. | | `ser/transcript/backends/__init__.py` | keep-as-facade | — | Backend package marker for the supported stable-whisper facade path. | -| `ser/transcript/backends/base.py` | move-internal | `ser/_internal/transcript/backends/base.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/transcript/backends/base.py` | keep-as-facade | — | Public backend adapter contract leaf shared by transcript facade consumers. | | `ser/transcript/backends/factory.py` | move-internal | `ser/_internal/transcript/backends/factory.py` | Implementation/helper module; first-party callers will use the internal owner path. | | `ser/transcript/backends/faster_whisper.py` | move-internal | `ser/_internal/transcript/backends/faster_whisper.py` | Implementation/helper module; first-party callers will use the internal owner path. | | `ser/transcript/backends/stable_whisper.py` | keep-as-facade | — | Public stable-whisper adapter facade over internal transcript backend owners. | @@ -660,9 +670,9 @@ Template: | `ser/diagnostics/service.py` | move-internal | `ser/_internal/diagnostics/service.py` | Implementation/helper module; first-party callers will use the internal owner path. | | `ser/utils/__init__.py` | keep-as-facade | — | Tier-1 curated utility facade; helper modules move internal. | | `ser/utils/audio_utils.py` | move-internal | `ser/_internal/utils/audio_utils.py` | Implementation/helper module; first-party callers will use the internal owner path. | -| `ser/utils/common_utils.py` | move-internal | `ser/_internal/utils/common_utils.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/utils/common_utils.py` | keep-as-facade | — | Public utility helper backing the tier-1 `display_elapsed_time` export target. | | `ser/utils/dsp.py` | move-internal | `ser/_internal/utils/dsp.py` | Implementation/helper module; first-party callers will use the internal owner path. | -| `ser/utils/logger.py` | move-internal | `ser/_internal/utils/logger.py` | Implementation/helper module; first-party callers will use the internal owner path. | +| `ser/utils/logger.py` | keep-as-facade | — | Public logger helper backing the tier-1 `get_logger` export target. | | `ser/utils/segment_canonicalization.py` | move-internal | `ser/_internal/utils/segment_canonicalization.py` | Implementation/helper module; first-party callers will use the internal owner path. | | `ser/utils/subtitles.py` | move-internal | `ser/_internal/utils/subtitles.py` | Implementation/helper module; first-party callers will use the internal owner path. | | `ser/utils/timeline_utils.py` | move-internal | `ser/_internal/utils/timeline_utils.py` | Implementation/helper module; first-party callers will use the internal owner path. | diff --git a/boundary_policy.toml b/boundary_policy.toml index d7abe2f..17c8580 100644 --- a/boundary_policy.toml +++ b/boundary_policy.toml @@ -18,56 +18,29 @@ reason = "Public facade over internal config/bootstrap owners." [[public_internal_import]] path = "ser/data/application.py" -reason = "Public data application surface over internal data services." - -[[public_internal_import]] -path = "ser/diagnostics/service.py" -reason = "Public diagnostics surface over internal diagnostics services." +reason = "Public data application facade over internal dataset workflow owners." [[public_internal_import]] path = "ser/models/emotion_model.py" -reason = "Public model facade over internal runtime/model support seams." - -[[public_internal_import]] -path = "ser/models/profile_runtime.py" -reason = "Public profile-runtime facade over internal model/runtime owners." +reason = "Public model facade over internal loading/training/prediction owners." [[public_internal_import]] path = "ser/models/training_entrypoints.py" reason = "Public training facade over internal model workflow owners." -[[public_internal_import]] -path = "ser/repr/emotion2vec.py" -reason = "Public representation adapter requiring internal runtime/config support." - -[[public_internal_import]] -path = "ser/runtime/accurate_inference.py" -reason = "Accurate profile wrapper over internal runtime owners." - -[[public_internal_import]] -path = "ser/runtime/fast_inference.py" -reason = "Fast profile wrapper over internal runtime owner modules." - -[[public_internal_import]] -path = "ser/runtime/medium_inference.py" -reason = "Medium profile wrapper over internal runtime owners." - [[public_internal_import]] path = "ser/runtime/pipeline.py" -reason = "Public runtime orchestration surface over internal workflow owners." - -[[public_internal_import]] -path = "ser/runtime/profile_quality_gate.py" -reason = "Public quality-gate surface over internal runtime/report owners." +reason = "Public runtime pipeline facade over internal orchestration owners." [[public_internal_import]] path = "ser/transcript/backends/stable_whisper.py" -reason = "Public backend adapter over internal transcription owners." +reason = "Public stable-whisper backend facade over internal transcription owners." [[public_internal_import]] path = "ser/transcript/profiling.py" -reason = "Public transcription profiling surface over internal transcription owners." +reason = "Public transcription profiling facade over internal profiling owners." [[public_internal_import]] path = "ser/transcript/transcript_extractor.py" -reason = "Public transcription boundary wrapper over internal transcription owners." +reason = "Public transcription extraction facade over internal transcription owners." + diff --git a/docs/codebase-architecture.md b/docs/codebase-architecture.md index e6f9727..608456a 100644 --- a/docs/codebase-architecture.md +++ b/docs/codebase-architecture.md @@ -120,16 +120,16 @@ Supporting runtime ownership is split between: The runtime design is profile-aware, not fully generic. Each profile still has its own execution path: -- fast: [`ser/runtime/fast_inference.py`](../ser/runtime/fast_inference.py) -- medium: [`ser/runtime/medium_inference.py`](../ser/runtime/medium_inference.py) -- accurate: [`ser/runtime/accurate_inference.py`](../ser/runtime/accurate_inference.py) -- accurate-research: [`ser/runtime/accurate_research_inference.py`](../ser/runtime/accurate_research_inference.py) +- fast: [`ser/runtime/fast_inference.py`](../ser/_internal/runtime/fast_inference.py) +- medium: [`ser/runtime/medium_inference.py`](../ser/_internal/runtime/medium_inference.py) +- accurate: [`ser/runtime/accurate_inference.py`](../ser/_internal/runtime/accurate_inference.py) +- accurate-research: [`ser/runtime/accurate_research_inference.py`](../ser/_internal/runtime/accurate_research_inference.py) The important architectural improvement is that medium and accurate are no longer monolithic runtime modules. They now delegate heavily into owner modules: - public-boundary orchestration: [`ser/_internal/runtime/accurate_public_boundary.py`](../ser/_internal/runtime/accurate_public_boundary.py), [`ser/_internal/runtime/medium_public_boundary.py`](../ser/_internal/runtime/medium_public_boundary.py) -- execution: [`ser/runtime/medium_execution.py`](../ser/runtime/medium_execution.py), [`ser/runtime/accurate_execution.py`](../ser/runtime/accurate_execution.py) +- execution: [`ser/runtime/medium_execution.py`](../ser/_internal/runtime/medium_execution.py), [`ser/runtime/accurate_execution.py`](../ser/_internal/runtime/accurate_execution.py) - setup/context: [`ser/_internal/runtime/medium_execution_context.py`](../ser/_internal/runtime/medium_execution_context.py), [`ser/_internal/runtime/accurate_operation_setup.py`](../ser/_internal/runtime/accurate_operation_setup.py) - retry/execution flow: [`ser/_internal/runtime/medium_execution_flow.py`](../ser/_internal/runtime/medium_execution_flow.py), [`ser/_internal/runtime/accurate_execution_flow.py`](../ser/_internal/runtime/accurate_execution_flow.py) - worker lifecycle: [`ser/_internal/runtime/medium_worker_lifecycle.py`](../ser/_internal/runtime/medium_worker_lifecycle.py), [`ser/_internal/runtime/accurate_worker_lifecycle.py`](../ser/_internal/runtime/accurate_worker_lifecycle.py) @@ -155,14 +155,14 @@ which is now a thin public boundary rather than a primary hotspot. It owns: There has been meaningful decomposition: -- fast training workflow owner: [`ser/models/fast_training.py`](../ser/models/fast_training.py) +- fast training workflow owner: [`ser/models/fast_training.py`](../ser/_internal/models/fast_training.py) - public training-entrypoint wiring: [`ser/models/training_entrypoints.py`](../ser/models/training_entrypoints.py) -- accurate-profile preparation and entrypoint helpers: [`ser/models/accurate_training_preparation.py`](../ser/models/accurate_training_preparation.py) -- accurate-profile prepared-training execution assembly: [`ser/models/accurate_training_execution.py`](../ser/models/accurate_training_execution.py) -- shared training preparation/evaluation: [`ser/models/training_preparation.py`](../ser/models/training_preparation.py) -- shared training execution/reporting: [`ser/models/training_execution.py`](../ser/models/training_execution.py) -- shared boundary helpers: [`ser/models/training_support.py`](../ser/models/training_support.py) -- shared training contracts: [`ser/models/training_types.py`](../ser/models/training_types.py) +- accurate-profile preparation and entrypoint helpers: [`ser/models/accurate_training_preparation.py`](../ser/_internal/models/accurate_training_preparation.py) +- accurate-profile prepared-training execution assembly: [`ser/models/accurate_training_execution.py`](../ser/_internal/models/accurate_training_execution.py) +- shared training preparation/evaluation: [`ser/models/training_preparation.py`](../ser/_internal/models/training_preparation.py) +- shared training execution/reporting: [`ser/models/training_execution.py`](../ser/_internal/models/training_execution.py) +- shared boundary helpers: [`ser/models/training_support.py`](../ser/_internal/models/training_support.py) +- shared training contracts: [`ser/models/training_types.py`](../ser/_internal/models/training_types.py) - fast training entrypoint seam: [`ser/_internal/models/fast_training_entrypoints.py`](../ser/_internal/models/fast_training_entrypoints.py) - shared model loading entrypoint: [`ser/_internal/models/model_loading.py`](../ser/_internal/models/model_loading.py) @@ -172,9 +172,9 @@ boundary-only, while profile-specific entrypoint wiring and shared cross-profile logic live in dedicated `training_*` owner modules, and accurate-profile preparation is now separated from accurate-profile prepared-training execution between -[`ser/models/accurate_training_preparation.py`](../ser/models/accurate_training_preparation.py) +[`ser/models/accurate_training_preparation.py`](../ser/_internal/models/accurate_training_preparation.py) and -[`ser/models/accurate_training_execution.py`](../ser/models/accurate_training_execution.py) +[`ser/models/accurate_training_execution.py`](../ser/_internal/models/accurate_training_execution.py) instead of reconcentrating inside one mixed-responsibility owner. Internal callers and tests now use canonical owner modules directly instead of routing through a compatibility namespace on `emotion_model.py`. The remaining risk is @@ -232,19 +232,19 @@ Public/application layer: Preparation and registry orchestration: -- [`ser/data/dataset_prepare.py`](../ser/data/dataset_prepare.py) -- [`ser/data/dataset_registry.py`](../ser/data/dataset_registry.py) +- [`ser/data/dataset_prepare.py`](../ser/_internal/data/dataset_prepare.py) +- [`ser/data/dataset_registry.py`](../ser/_internal/data/dataset_registry.py) Catalog and strategies: -- declarative catalog: [`ser/data/catalog/public_datasets.py`](../ser/data/catalog/public_datasets.py) -- strategy registry: [`ser/data/strategies/default.py`](../ser/data/strategies/default.py) -- extracted homogeneous CSV strategies: [`ser/data/strategies/auto_csv.py`](../ser/data/strategies/auto_csv.py) +- declarative catalog: [`ser/data/catalog/public_datasets.py`](../ser/_internal/data/catalog/public_datasets.py) +- strategy registry: [`ser/data/strategies/default.py`](../ser/_internal/data/strategies/default.py) +- extracted homogeneous CSV strategies: [`ser/data/strategies/auto_csv.py`](../ser/_internal/data/strategies/auto_csv.py) Adapters: -- [`ser/data/adapters/public_csv_datasets.py`](../ser/data/adapters/public_csv_datasets.py) -- [`ser/data/adapters/csv_manifest_builder.py`](../ser/data/adapters/csv_manifest_builder.py) +- [`ser/data/adapters/public_csv_datasets.py`](../ser/_internal/data/adapters/public_csv_datasets.py) +- [`ser/data/adapters/csv_manifest_builder.py`](../ser/_internal/data/adapters/csv_manifest_builder.py) This subsystem is architecturally strong. Metadata is declarative, behavior is attached to strategies, and application workflows are separated from low-level @@ -254,7 +254,7 @@ registry and manifest concerns. Diagnostics are treated as a first-class subsystem: -- diagnostics service: [`ser/diagnostics/service.py`](../ser/diagnostics/service.py) +- diagnostics service: [`ser/diagnostics/service.py`](../ser/_internal/diagnostics/service.py) - diagnostics API owner: [`ser/_internal/api/diagnostics.py`](../ser/_internal/api/diagnostics.py) - CLI doctor/preflight support: [`ser/_internal/cli/diagnostics.py`](../ser/_internal/cli/diagnostics.py) @@ -307,7 +307,7 @@ discipline more than on absolute architectural isolation. ## Main architectural liabilities -- [`ser/runtime/accurate_inference.py`](../ser/runtime/accurate_inference.py) and [`ser/runtime/medium_inference.py`](../ser/runtime/medium_inference.py) remain large public wrappers because they preserve public/runtime signatures, exception surfaces, and worker-entry orchestration seams, even though most compute, retry, and pooling ownership now lives in [`ser/_internal/runtime/accurate_public_boundary.py`](../ser/_internal/runtime/accurate_public_boundary.py), [`ser/_internal/runtime/medium_public_boundary.py`](../ser/_internal/runtime/medium_public_boundary.py), [`ser/runtime/accurate_execution.py`](../ser/runtime/accurate_execution.py), and [`ser/runtime/medium_execution.py`](../ser/runtime/medium_execution.py) +- [`ser/runtime/accurate_inference.py`](../ser/_internal/runtime/accurate_inference.py) and [`ser/runtime/medium_inference.py`](../ser/_internal/runtime/medium_inference.py) remain large public wrappers because they preserve public/runtime signatures, exception surfaces, and worker-entry orchestration seams, even though most compute, retry, and pooling ownership now lives in [`ser/_internal/runtime/accurate_public_boundary.py`](../ser/_internal/runtime/accurate_public_boundary.py), [`ser/_internal/runtime/medium_public_boundary.py`](../ser/_internal/runtime/medium_public_boundary.py), [`ser/runtime/accurate_execution.py`](../ser/_internal/runtime/accurate_execution.py), and [`ser/runtime/medium_execution.py`](../ser/_internal/runtime/medium_execution.py) - [`ser/transcript/transcript_extractor.py`](../ser/transcript/transcript_extractor.py) remains a large public boundary, but process-isolation, runtime/setup, and wrapper orchestration now route through [`ser/_internal/transcription/public_boundary_process.py`](../ser/_internal/transcription/public_boundary_process.py), [`ser/_internal/transcription/public_boundary_runtime.py`](../ser/_internal/transcription/public_boundary_runtime.py), and [`ser/_internal/transcription/public_boundary_support.py`](../ser/_internal/transcription/public_boundary_support.py) - [`ser/__main__.py`](../ser/__main__.py) is still a long CLI composition function - optional boundary-level `reload_settings()` fallbacks remain, so DI is still diff --git a/pyproject.toml b/pyproject.toml index adc3c18..1378471 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -150,16 +150,9 @@ ignore = ["E501"] "ser/api.py" = ["TID251"] "ser/config.py" = ["TID251"] "ser/data/application.py" = ["TID251"] -"ser/diagnostics/service.py" = ["TID251"] "ser/models/emotion_model.py" = ["TID251"] -"ser/models/profile_runtime.py" = ["TID251"] "ser/models/training_entrypoints.py" = ["TID251"] -"ser/repr/emotion2vec.py" = ["TID251"] -"ser/runtime/accurate_inference.py" = ["TID251"] -"ser/runtime/fast_inference.py" = ["TID251"] -"ser/runtime/medium_inference.py" = ["TID251"] "ser/runtime/pipeline.py" = ["TID251"] -"ser/runtime/profile_quality_gate.py" = ["TID251"] "ser/transcript/backends/stable_whisper.py" = ["TID251"] "ser/transcript/profiling.py" = ["TID251"] "ser/transcript/transcript_extractor.py" = ["TID251"] diff --git a/ser/_internal/config/bootstrap.py b/ser/_internal/config/bootstrap.py index 596f70d..bbc4898 100644 --- a/ser/_internal/config/bootstrap.py +++ b/ser/_internal/config/bootstrap.py @@ -25,12 +25,12 @@ from ser._internal.config.settings_inputs import ( resolve_settings_inputs as _resolve_settings_inputs_from_internal, ) +from ser._internal.utils.torch_inference import normalize_torch_device_selector from ser.profiles import ( ProfileCatalogEntry, TranscriptionBackendId, get_profile_catalog, ) -from ser.utils.torch_inference import normalize_torch_device_selector _SER_TORCH_ENABLE_MPS_FALLBACK_ENV = "SER_TORCH_ENABLE_MPS_FALLBACK" _PYTORCH_ENABLE_MPS_FALLBACK_ENV = "PYTORCH_ENABLE_MPS_FALLBACK" diff --git a/ser/_internal/diagnostics/__init__.py b/ser/_internal/diagnostics/__init__.py new file mode 100644 index 0000000..ea213ad --- /dev/null +++ b/ser/_internal/diagnostics/__init__.py @@ -0,0 +1 @@ +"""Internal diagnostics command and service helpers.""" diff --git a/ser/_internal/diagnostics/command.py b/ser/_internal/diagnostics/command.py index 755d11d..0899533 100644 --- a/ser/_internal/diagnostics/command.py +++ b/ser/_internal/diagnostics/command.py @@ -5,13 +5,13 @@ import argparse from typing import cast -from ser.config import AppConfig, reload_settings -from ser.diagnostics.service import ( +from ser._internal.diagnostics.service import ( apply_profile_override_for_diagnostics, format_report_json, format_report_text, run_doctor_diagnostics, ) +from ser.config import AppConfig, reload_settings from ser.profiles import ProfileName _PROFILE_CHOICES: tuple[ProfileName, ...] = ( diff --git a/ser/_internal/diagnostics/service.py b/ser/_internal/diagnostics/service.py index c31d9dc..8f341f6 100644 --- a/ser/_internal/diagnostics/service.py +++ b/ser/_internal/diagnostics/service.py @@ -8,8 +8,16 @@ from typing import Literal, cast from ser._internal.config.bootstrap import resolve_profile_transcription_config +from ser._internal.data.dataset_prepare import collect_dataset_registry_health_issues +from ser._internal.transcript.runtime_policy import ( + DEFAULT_MPS_LOW_MEMORY_THRESHOLD_GB, + resolve_transcription_runtime_policy, +) +from ser._internal.utils.transcription_compat import ( + FASTER_WHISPER_OPENMP_CONFLICT_ISSUE_CODE, + resolve_transcription_compatibility_lane, +) from ser.config import AppConfig -from ser.data.dataset_prepare import collect_dataset_registry_health_issues from ser.diagnostics.domain import ( DiagnosticFinding, DiagnosticReport, @@ -22,14 +30,6 @@ CompatibilityIssueImpact, resolve_transcription_backend_adapter, ) -from ser.transcript.runtime_policy import ( - DEFAULT_MPS_LOW_MEMORY_THRESHOLD_GB, - resolve_transcription_runtime_policy, -) -from ser.utils.transcription_compat import ( - FASTER_WHISPER_OPENMP_CONFLICT_ISSUE_CODE, - resolve_transcription_compatibility_lane, -) _SUPPORTED_PREFLIGHT_MODES: frozenset[str] = frozenset({"off", "warn", "strict"}) diff --git a/ser/_internal/features/__init__.py b/ser/_internal/features/__init__.py new file mode 100644 index 0000000..f970647 --- /dev/null +++ b/ser/_internal/features/__init__.py @@ -0,0 +1,17 @@ +"""Internal feature extraction helpers.""" + +from ser._internal.features.feature_extractor import ( + FeatureFrame, + extended_extract_feature, + extract_feature, + extract_feature_frames, + extract_feature_from_signal, +) + +__all__ = [ + "FeatureFrame", + "extract_feature", + "extract_feature_frames", + "extract_feature_from_signal", + "extended_extract_feature", +] diff --git a/ser/_internal/features/feature_extractor.py b/ser/_internal/features/feature_extractor.py index 65505ba..ff91055 100644 --- a/ser/_internal/features/feature_extractor.py +++ b/ser/_internal/features/feature_extractor.py @@ -7,11 +7,11 @@ import numpy as np from numpy.typing import NDArray +from ser._internal.repr import HandcraftedBackend +from ser._internal.repr.backend import EncodedSequence +from ser._internal.utils.audio_utils import read_audio_file +from ser._internal.utils.dsp import extract_feature_from_signal as _extract_feature_from_signal from ser.config import AppConfig, AudioReadConfig, FeatureFlags, reload_settings -from ser.repr import HandcraftedBackend -from ser.repr.backend import EncodedSequence -from ser.utils.audio_utils import read_audio_file -from ser.utils.dsp import extract_feature_from_signal as _extract_feature_from_signal from ser.utils.logger import get_logger type FeatureVector = NDArray[np.float64] diff --git a/ser/_internal/heads/__init__.py b/ser/_internal/heads/__init__.py new file mode 100644 index 0000000..cfb5ac8 --- /dev/null +++ b/ser/_internal/heads/__init__.py @@ -0,0 +1,5 @@ +"""Internal classifier head helpers.""" + +from ser._internal.heads.torch_head import build_torch_mlp_head, forward_torch_head + +__all__ = ["build_torch_mlp_head", "forward_torch_head"] diff --git a/ser/_internal/repr/__init__.py b/ser/_internal/repr/__init__.py new file mode 100644 index 0000000..a62edab --- /dev/null +++ b/ser/_internal/repr/__init__.py @@ -0,0 +1,25 @@ +"""Internal representation backend contracts and implementations.""" + +from ser._internal.repr.backend import ( + EncodedSequence, + FeatureBackend, + PoolingWindow, + VectorFeatureBackend, + overlap_frame_mask, +) +from ser._internal.repr.emotion2vec import Emotion2VecBackend +from ser._internal.repr.handcrafted import HandcraftedBackend +from ser._internal.repr.hf_whisper import WhisperBackend +from ser._internal.repr.hf_xlsr import XLSRBackend + +__all__ = [ + "EncodedSequence", + "Emotion2VecBackend", + "FeatureBackend", + "HandcraftedBackend", + "PoolingWindow", + "WhisperBackend", + "XLSRBackend", + "VectorFeatureBackend", + "overlap_frame_mask", +] diff --git a/ser/_internal/repr/emotion2vec.py b/ser/_internal/repr/emotion2vec.py index 0b1f431..9ade09e 100644 --- a/ser/_internal/repr/emotion2vec.py +++ b/ser/_internal/repr/emotion2vec.py @@ -13,21 +13,21 @@ import numpy as np from numpy.typing import NDArray -from ser._internal.runtime.environment_plan import ProcessEnvDelta -from ser._internal.runtime.process_env import temporary_process_env -from ser.repr.backend import ( +from ser._internal.repr.backend import ( EncodedSequence, FeatureMatrix, FeatureVector, PoolingWindow, overlap_frame_mask, ) +from ser._internal.runtime.environment_plan import ProcessEnvDelta +from ser._internal.runtime.process_env import temporary_process_env +from ser._internal.utils.torch_inference import normalize_torch_device_selector from ser.utils.logger import ( DependencyLogPolicy, get_logger, scoped_dependency_log_policy, ) -from ser.utils.torch_inference import normalize_torch_device_selector logger = get_logger(__name__) _NOISY_DEPENDENCY_POLICY: Final[DependencyLogPolicy] = DependencyLogPolicy( diff --git a/ser/_internal/repr/handcrafted.py b/ser/_internal/repr/handcrafted.py index 83f00ca..f0e307e 100644 --- a/ser/_internal/repr/handcrafted.py +++ b/ser/_internal/repr/handcrafted.py @@ -7,9 +7,8 @@ import numpy as np from numpy.typing import NDArray -import ser.utils.dsp as dsp -from ser.config import FeatureFlags -from ser.repr.backend import ( +import ser._internal.utils.dsp as dsp +from ser._internal.repr.backend import ( EncodedSequence, FeatureBackend, FeatureMatrix, @@ -17,6 +16,7 @@ PoolingWindow, overlap_frame_mask, ) +from ser.config import FeatureFlags class HandcraftedBackend(FeatureBackend): diff --git a/ser/_internal/repr/hf_whisper.py b/ser/_internal/repr/hf_whisper.py index 949d401..ff1f7ec 100644 --- a/ser/_internal/repr/hf_whisper.py +++ b/ser/_internal/repr/hf_whisper.py @@ -12,15 +12,14 @@ import numpy as np from numpy.typing import NDArray -from ser.repr.backend import ( +from ser._internal.repr.backend import ( EncodedSequence, FeatureMatrix, FeatureVector, PoolingWindow, overlap_frame_mask, ) -from ser.utils.logger import get_logger -from ser.utils.torch_inference import ( +from ser._internal.utils.torch_inference import ( TorchRuntime, inference_context, maybe_resolve_torch_runtime, @@ -28,6 +27,7 @@ move_model_to_runtime, runtime_with_dtype, ) +from ser.utils.logger import get_logger logger = get_logger(__name__) diff --git a/ser/_internal/repr/hf_xlsr.py b/ser/_internal/repr/hf_xlsr.py index c039fd5..092b209 100644 --- a/ser/_internal/repr/hf_xlsr.py +++ b/ser/_internal/repr/hf_xlsr.py @@ -12,15 +12,14 @@ import numpy as np from numpy.typing import NDArray -from ser.repr.backend import ( +from ser._internal.repr.backend import ( EncodedSequence, FeatureMatrix, FeatureVector, PoolingWindow, overlap_frame_mask, ) -from ser.utils.logger import get_logger -from ser.utils.torch_inference import ( +from ser._internal.utils.torch_inference import ( TorchRuntime, inference_context, maybe_resolve_torch_runtime, @@ -28,6 +27,7 @@ move_model_to_runtime, runtime_with_dtype, ) +from ser.utils.logger import get_logger logger = get_logger(__name__) diff --git a/ser/_internal/repr/runtime_policy.py b/ser/_internal/repr/runtime_policy.py index 0608b77..3ff0718 100644 --- a/ser/_internal/repr/runtime_policy.py +++ b/ser/_internal/repr/runtime_policy.py @@ -5,7 +5,7 @@ from dataclasses import dataclass from typing import Literal -from ser.utils.torch_inference import ( +from ser._internal.utils.torch_inference import ( TorchRuntime, maybe_resolve_torch_runtime, normalize_torch_device_selector, diff --git a/ser/_internal/utils/__init__.py b/ser/_internal/utils/__init__.py new file mode 100644 index 0000000..ac943bf --- /dev/null +++ b/ser/_internal/utils/__init__.py @@ -0,0 +1 @@ +"""Internal utility helper modules.""" diff --git a/ser/_internal/utils/timeline_utils.py b/ser/_internal/utils/timeline_utils.py index c339904..f7dd84c 100644 --- a/ser/_internal/utils/timeline_utils.py +++ b/ser/_internal/utils/timeline_utils.py @@ -6,11 +6,11 @@ from pathlib import Path from typing import Protocol +from ser._internal.utils.segment_canonicalization import canonicalize_segments from ser.config import TimelineConfig from ser.domain import EmotionSegment, TimelineEntry, TranscriptWord from ser.utils.common_utils import display_elapsed_time from ser.utils.logger import get_logger -from ser.utils.segment_canonicalization import canonicalize_segments logger: logging.Logger = get_logger(__name__) diff --git a/ser/diagnostics/__init__.py b/ser/diagnostics/__init__.py index 325f6f0..f8b449d 100644 --- a/ser/diagnostics/__init__.py +++ b/ser/diagnostics/__init__.py @@ -1,35 +1,44 @@ -"""Diagnostics package exports.""" +# pyright: reportUnsupportedDunderAll=false +"""Public diagnostics facade.""" -from .command import run_doctor_command -from .domain import ( +from __future__ import annotations + +import importlib +from typing import Any + +from ser.diagnostics.domain import ( DiagnosticFinding, DiagnosticReport, DiagnosticSeverity, PreflightMode, ) -from .service import ( - apply_profile_override_for_diagnostics, - format_report_brief, - format_report_json, - format_report_text, - format_startup_preflight_one_liner, - parse_preflight_mode, - run_doctor_diagnostics, - should_fail_preflight, -) + +_DYNAMIC_EXPORTS: dict[str, str] = { + "apply_profile_override_for_diagnostics": "ser._internal.diagnostics.service", + "format_report_brief": "ser._internal.diagnostics.service", + "format_report_json": "ser._internal.diagnostics.service", + "format_report_text": "ser._internal.diagnostics.service", + "format_startup_preflight_one_liner": "ser._internal.diagnostics.service", + "parse_preflight_mode": "ser._internal.diagnostics.service", + "run_doctor_command": "ser._internal.diagnostics.command", + "run_doctor_diagnostics": "ser._internal.diagnostics.service", + "should_fail_preflight": "ser._internal.diagnostics.service", +} __all__ = [ "DiagnosticFinding", "DiagnosticReport", "DiagnosticSeverity", "PreflightMode", - "apply_profile_override_for_diagnostics", - "format_report_brief", - "format_report_json", - "format_startup_preflight_one_liner", - "format_report_text", - "parse_preflight_mode", - "run_doctor_command", - "run_doctor_diagnostics", - "should_fail_preflight", + *_DYNAMIC_EXPORTS, ] + + +def __getattr__(name: str) -> Any: + """Lazily resolves one diagnostics facade export.""" + module_name = _DYNAMIC_EXPORTS.get(name) + if module_name is None: + raise AttributeError(f"module 'ser.diagnostics' has no attribute {name!r}") + value = getattr(importlib.import_module(module_name), name) + globals()[name] = value + return value diff --git a/ser/features/__init__.py b/ser/features/__init__.py index 2476ebe..df0ef3b 100644 --- a/ser/features/__init__.py +++ b/ser/features/__init__.py @@ -1,11 +1,12 @@ -from .feature_extractor import ( - FeatureFrame, - extended_extract_feature, - extract_feature, - extract_feature_frames, - extract_feature_from_signal, -) +# pyright: reportUnsupportedDunderAll=false +"""Public feature extraction facade.""" +from __future__ import annotations + +import importlib +from typing import Any + +_EXPORT_MODULE = "ser._internal.features.feature_extractor" __all__ = [ "FeatureFrame", "extract_feature", @@ -13,3 +14,12 @@ "extract_feature_from_signal", "extended_extract_feature", ] + + +def __getattr__(name: str) -> Any: + """Lazily resolves one public feature facade export.""" + if name not in __all__: + raise AttributeError(f"module 'ser.features' has no attribute {name!r}") + value = getattr(importlib.import_module(_EXPORT_MODULE), name) + globals()[name] = value + return value diff --git a/ser/heads/__init__.py b/ser/heads/__init__.py index 4dde23a..b2937da 100644 --- a/ser/heads/__init__.py +++ b/ser/heads/__init__.py @@ -1,5 +1,19 @@ -"""Classifier head helpers for profile-aware training paths.""" +# pyright: reportUnsupportedDunderAll=false +"""Public classifier-head facade.""" -from .torch_head import build_torch_mlp_head, forward_torch_head +from __future__ import annotations +import importlib +from typing import Any + +_EXPORT_MODULE = "ser._internal.heads.torch_head" __all__ = ["build_torch_mlp_head", "forward_torch_head"] + + +def __getattr__(name: str) -> Any: + """Lazily resolves one public classifier-head export.""" + if name not in __all__: + raise AttributeError(f"module 'ser.heads' has no attribute {name!r}") + value = getattr(importlib.import_module(_EXPORT_MODULE), name) + globals()[name] = value + return value diff --git a/ser/profiles.py b/ser/profiles.py index 18f1ce9..fbb8490 100644 --- a/ser/profiles.py +++ b/ser/profiles.py @@ -3,14 +3,12 @@ from __future__ import annotations import importlib -from collections.abc import Mapping +from collections.abc import Callable, Mapping from dataclasses import dataclass from pathlib import Path from types import MappingProxyType from typing import TYPE_CHECKING, Any, Literal, cast -from ser.utils.torch_inference import normalize_torch_device_selector - if TYPE_CHECKING: from ser.config import AppConfig @@ -230,7 +228,13 @@ def _read_required_int( def _normalize_torch_device_selector(value: str) -> str | None: """Normalizes one torch device selector or returns None when invalid.""" - return normalize_torch_device_selector(value) + normalizer = cast( + Callable[[str], str | None], + importlib.import_module( + "ser._internal.utils.torch_inference" + ).normalize_torch_device_selector, + ) + return normalizer(value) def _normalize_torch_dtype_selector(value: str) -> str | None: diff --git a/ser/repr/__init__.py b/ser/repr/__init__.py index 11e3f14..b16f3bb 100644 --- a/ser/repr/__init__.py +++ b/ser/repr/__init__.py @@ -1,25 +1,31 @@ -"""Representation backend contracts and implementations.""" +# pyright: reportUnsupportedDunderAll=false +"""Public representation backend facade.""" -from .backend import ( - EncodedSequence, - FeatureBackend, - PoolingWindow, - VectorFeatureBackend, - overlap_frame_mask, -) -from .emotion2vec import Emotion2VecBackend -from .handcrafted import HandcraftedBackend -from .hf_whisper import WhisperBackend -from .hf_xlsr import XLSRBackend +from __future__ import annotations -__all__ = [ - "EncodedSequence", - "Emotion2VecBackend", - "FeatureBackend", - "HandcraftedBackend", - "PoolingWindow", - "WhisperBackend", - "XLSRBackend", - "VectorFeatureBackend", - "overlap_frame_mask", -] +import importlib +from typing import Any + +_EXPORT_MODULES: dict[str, str] = { + "EncodedSequence": "ser._internal.repr.backend", + "Emotion2VecBackend": "ser._internal.repr.emotion2vec", + "FeatureBackend": "ser._internal.repr.backend", + "HandcraftedBackend": "ser._internal.repr.handcrafted", + "PoolingWindow": "ser._internal.repr.backend", + "WhisperBackend": "ser._internal.repr.hf_whisper", + "XLSRBackend": "ser._internal.repr.hf_xlsr", + "VectorFeatureBackend": "ser._internal.repr.backend", + "overlap_frame_mask": "ser._internal.repr.backend", +} + +__all__ = sorted(_EXPORT_MODULES) + + +def __getattr__(name: str) -> Any: + """Lazily resolves one public representation facade export.""" + module_name = _EXPORT_MODULES.get(name) + if module_name is None: + raise AttributeError(f"module 'ser.repr' has no attribute {name!r}") + value = getattr(importlib.import_module(module_name), name) + globals()[name] = value + return value diff --git a/ser/utils/__init__.py b/ser/utils/__init__.py index 9adcd4b..26cda3b 100644 --- a/ser/utils/__init__.py +++ b/ser/utils/__init__.py @@ -2,7 +2,9 @@ from __future__ import annotations -from typing import TYPE_CHECKING +import importlib +from collections.abc import Callable +from typing import TYPE_CHECKING, cast from .common_utils import display_elapsed_time from .logger import get_logger @@ -27,17 +29,12 @@ def read_audio_file( start_seconds: float | None = None, duration_seconds: float | None = None, ) -> tuple[np.ndarray, int]: - """Reads and normalizes an audio file. - - Args: - file_path: Path to the audio file. - - Returns: - A tuple of `(audio_samples, sample_rate)`. - """ - from .audio_utils import read_audio_file as _read_audio_file - - return _read_audio_file( + """Reads and normalizes an audio file.""" + reader = cast( + Callable[..., tuple[np.ndarray, int]], + importlib.import_module("ser._internal.utils.audio_utils").read_audio_file, + ) + return reader( file_path, start_seconds=start_seconds, duration_seconds=duration_seconds, @@ -50,23 +47,26 @@ def build_timeline( emotion_with_timestamps: list[EmotionSegment], ) -> list[TimelineEntry]: """Merges transcript and emotion segments into timeline rows.""" - from .timeline_utils import build_timeline as _build_timeline - - return _build_timeline(text_with_timestamps, emotion_with_timestamps) + builder = cast( + Callable[[list[TranscriptWord], list[EmotionSegment]], list[TimelineEntry]], + importlib.import_module("ser._internal.utils.timeline_utils").build_timeline, + ) + return builder(text_with_timestamps, emotion_with_timestamps) def print_timeline(timeline: list[TimelineEntry]) -> None: """Prints timeline rows in a terminal-friendly format.""" - from .timeline_utils import print_timeline as _print_timeline - - _print_timeline(timeline) + printer = importlib.import_module("ser._internal.utils.timeline_utils").print_timeline + printer(timeline) def save_timeline_to_csv(timeline: list[TimelineEntry], file_name: str) -> str: """Writes timeline rows to CSV and returns the output path.""" - from .timeline_utils import save_timeline_to_csv as _save_timeline_to_csv - - return _save_timeline_to_csv( + writer = cast( + Callable[..., str], + importlib.import_module("ser._internal.utils.timeline_utils").save_timeline_to_csv, + ) + return writer( timeline, file_name, timeline_config=_resolve_boundary_settings().timeline, diff --git a/tests/suites/integration/cli/test_diagnostics_command.py b/tests/suites/integration/cli/test_diagnostics_command.py index dcef208..a371fb7 100644 --- a/tests/suites/integration/cli/test_diagnostics_command.py +++ b/tests/suites/integration/cli/test_diagnostics_command.py @@ -5,7 +5,7 @@ import pytest import ser.config as config_module -from ser.diagnostics.command import run_doctor_command +from ser._internal.diagnostics.command import run_doctor_command from ser.diagnostics.domain import DiagnosticFinding, DiagnosticReport @@ -25,7 +25,7 @@ def test_run_doctor_command_strict_returns_one_for_warnings( ) monkeypatch.setattr( - "ser.diagnostics.command.run_doctor_diagnostics", + "ser._internal.diagnostics.command.run_doctor_diagnostics", lambda **_kwargs: report, ) @@ -49,7 +49,7 @@ def test_run_doctor_command_strict_returns_zero_for_info( ) monkeypatch.setattr( - "ser.diagnostics.command.run_doctor_diagnostics", + "ser._internal.diagnostics.command.run_doctor_diagnostics", lambda **_kwargs: report, ) @@ -74,7 +74,7 @@ def test_run_doctor_command_returns_two_for_blocking_findings( ) monkeypatch.setattr( - "ser.diagnostics.command.run_doctor_diagnostics", + "ser._internal.diagnostics.command.run_doctor_diagnostics", lambda **_kwargs: report, ) @@ -91,7 +91,7 @@ def test_run_doctor_command_json_output( report = DiagnosticReport(findings=()) monkeypatch.setattr( - "ser.diagnostics.command.run_doctor_diagnostics", + "ser._internal.diagnostics.command.run_doctor_diagnostics", lambda **_kwargs: report, ) diff --git a/tests/suites/integration/diagnostics/test_diagnostics_service.py b/tests/suites/integration/diagnostics/test_diagnostics_service.py index 9f79331..3d2f517 100644 --- a/tests/suites/integration/diagnostics/test_diagnostics_service.py +++ b/tests/suites/integration/diagnostics/test_diagnostics_service.py @@ -8,10 +8,9 @@ import pytest +import ser._internal.diagnostics.service as diagnostics_service import ser.config as config_module -import ser.diagnostics.service as diagnostics_service -from ser.diagnostics.domain import DiagnosticFinding, DiagnosticReport -from ser.diagnostics.service import ( +from ser._internal.diagnostics.service import ( _run_startup_preflight, format_report_brief, format_report_json, @@ -20,8 +19,9 @@ run_doctor_diagnostics, should_fail_preflight, ) +from ser._internal.utils.transcription_compat import FASTER_WHISPER_OPENMP_CONFLICT_ISSUE_CODE +from ser.diagnostics.domain import DiagnosticFinding, DiagnosticReport from ser.transcript.backends import CompatibilityIssue, CompatibilityReport -from ser.utils.transcription_compat import FASTER_WHISPER_OPENMP_CONFLICT_ISSUE_CODE def test_parse_preflight_mode_falls_back_to_warn_for_invalid_values() -> None: diff --git a/tests/suites/unit/repr/test_backend_contract.py b/tests/suites/unit/repr/test_backend_contract.py index 08dd08f..f85984f 100644 --- a/tests/suites/unit/repr/test_backend_contract.py +++ b/tests/suites/unit/repr/test_backend_contract.py @@ -8,7 +8,7 @@ import pytest from numpy.typing import NDArray -from ser.repr import ( +from ser._internal.repr import ( EncodedSequence, FeatureBackend, PoolingWindow, diff --git a/tests/suites/unit/repr/test_emotion2vec_backend.py b/tests/suites/unit/repr/test_emotion2vec_backend.py index 3e0f875..fefdcd8 100644 --- a/tests/suites/unit/repr/test_emotion2vec_backend.py +++ b/tests/suites/unit/repr/test_emotion2vec_backend.py @@ -14,8 +14,8 @@ import pytest from numpy.typing import NDArray +from ser._internal.repr import Emotion2VecBackend from ser._internal.runtime.process_env import temporary_process_env -from ser.repr import Emotion2VecBackend from ser.utils.logger import ( DependencyLogFilter, DependencyLogPolicy, @@ -372,7 +372,7 @@ def test_emotion2vec_backend_suppresses_dependency_noise_outside_debug( ) -> None: """Known dependency INFO lines should be demoted at INFO-level runs.""" backend = Emotion2VecBackend(model_id="iic/emotion2vec_plus_large") - module_logger = logging.getLogger("ser.repr.emotion2vec") + module_logger = logging.getLogger("ser._internal.repr.emotion2vec") original_level = module_logger.level module_logger.setLevel(logging.INFO) try: @@ -401,7 +401,7 @@ def test_emotion2vec_backend_keeps_dependency_noise_in_debug( ) -> None: """DEBUG-level runs should keep third-party diagnostics visible.""" backend = Emotion2VecBackend(model_id="iic/emotion2vec_plus_large") - module_logger = logging.getLogger("ser.repr.emotion2vec") + module_logger = logging.getLogger("ser._internal.repr.emotion2vec") original_level = module_logger.level module_logger.setLevel(logging.DEBUG) try: diff --git a/tests/suites/unit/repr/test_feature_extractor.py b/tests/suites/unit/repr/test_feature_extractor.py index facf8e2..cfe1415 100644 --- a/tests/suites/unit/repr/test_feature_extractor.py +++ b/tests/suites/unit/repr/test_feature_extractor.py @@ -6,10 +6,10 @@ import numpy as np import pytest +import ser._internal.features.feature_extractor as fe +import ser._internal.utils.dsp as dsp +from ser._internal.repr import EncodedSequence from ser.config import AppConfig -from ser.features import feature_extractor as fe -from ser.repr import EncodedSequence -from ser.utils import dsp @pytest.mark.filterwarnings( diff --git a/tests/suites/unit/repr/test_feature_runtime_encoding.py b/tests/suites/unit/repr/test_feature_runtime_encoding.py index 6305937..d181edf 100644 --- a/tests/suites/unit/repr/test_feature_runtime_encoding.py +++ b/tests/suites/unit/repr/test_feature_runtime_encoding.py @@ -10,9 +10,9 @@ import numpy as np import pytest -from ser.data.embedding_cache import EmbeddingCacheEntry -from ser.models import feature_runtime_encoding as helpers -from ser.repr import EncodedSequence, PoolingWindow +import ser._internal.models.feature_runtime_encoding as helpers +from ser._internal.data.embedding_cache import EmbeddingCacheEntry +from ser._internal.repr import EncodedSequence, PoolingWindow class _RuntimePolicyStub: diff --git a/tests/suites/unit/repr/test_feature_runtime_policy.py b/tests/suites/unit/repr/test_feature_runtime_policy.py index ee64ea6..f5f52c4 100644 --- a/tests/suites/unit/repr/test_feature_runtime_policy.py +++ b/tests/suites/unit/repr/test_feature_runtime_policy.py @@ -4,9 +4,9 @@ import pytest -import ser.repr.runtime_policy as runtime_policy -from ser.repr.runtime_policy import resolve_feature_runtime_policy -from ser.utils.torch_inference import TorchRuntime +import ser._internal.repr.runtime_policy as runtime_policy +from ser._internal.repr.runtime_policy import resolve_feature_runtime_policy +from ser._internal.utils.torch_inference import TorchRuntime def _runtime(*, device_spec: str, device_type: str) -> TorchRuntime: diff --git a/tests/suites/unit/repr/test_handcrafted_backend.py b/tests/suites/unit/repr/test_handcrafted_backend.py index ff1f97f..0bcc4f8 100644 --- a/tests/suites/unit/repr/test_handcrafted_backend.py +++ b/tests/suites/unit/repr/test_handcrafted_backend.py @@ -6,8 +6,8 @@ import pytest from numpy.typing import NDArray +from ser._internal.repr import HandcraftedBackend, PoolingWindow from ser.config import FeatureFlags -from ser.repr import HandcraftedBackend, PoolingWindow def test_handcrafted_backend_feature_dim_reflects_feature_flags() -> None: @@ -29,7 +29,7 @@ def test_handcrafted_backend_encode_sequence_uses_frame_boundaries( ) -> None: """Encoding should preserve deterministic frame start/end semantics.""" monkeypatch.setattr( - "ser.utils.dsp.extract_feature_from_signal", + "ser._internal.utils.dsp.extract_feature_from_signal", lambda audio, _sample_rate, *, feature_flags=None: np.asarray( [float(audio[0]), float(audio[-1])], dtype=np.float64, @@ -68,7 +68,7 @@ def test_handcrafted_backend_pool_mean_aggregates_overlapping_frames( ) -> None: """Pooling should average all frames overlapping each window.""" monkeypatch.setattr( - "ser.utils.dsp.extract_feature_from_signal", + "ser._internal.utils.dsp.extract_feature_from_signal", lambda audio, _sample_rate, *, feature_flags=None: np.asarray( [float(audio[0]), float(audio[-1])], dtype=np.float64, @@ -96,7 +96,7 @@ def test_handcrafted_backend_extract_vector_uses_signal_extractor( ) -> None: """extract_vector should preserve legacy whole-signal feature semantics.""" monkeypatch.setattr( - "ser.utils.dsp.extract_feature_from_signal", + "ser._internal.utils.dsp.extract_feature_from_signal", lambda audio, sample_rate, *, feature_flags=None: np.asarray( [float(audio.size), float(sample_rate)], dtype=np.float64, diff --git a/tests/suites/unit/repr/test_hf_whisper_backend.py b/tests/suites/unit/repr/test_hf_whisper_backend.py index 7b4893b..df373cd 100644 --- a/tests/suites/unit/repr/test_hf_whisper_backend.py +++ b/tests/suites/unit/repr/test_hf_whisper_backend.py @@ -9,9 +9,9 @@ import pytest from numpy.typing import NDArray -import ser.repr.hf_whisper as hf_whisper_module -from ser.repr import EncodedSequence, PoolingWindow, WhisperBackend -from ser.utils.torch_inference import TorchRuntime +import ser._internal.repr.hf_whisper as hf_whisper_module +from ser._internal.repr import EncodedSequence, PoolingWindow, WhisperBackend +from ser._internal.utils.torch_inference import TorchRuntime @dataclass(frozen=True) diff --git a/tests/suites/unit/repr/test_hf_xlsr_backend.py b/tests/suites/unit/repr/test_hf_xlsr_backend.py index 86eaddf..d094d22 100644 --- a/tests/suites/unit/repr/test_hf_xlsr_backend.py +++ b/tests/suites/unit/repr/test_hf_xlsr_backend.py @@ -9,9 +9,9 @@ import pytest from numpy.typing import NDArray -import ser.repr.hf_xlsr as hf_xlsr_module -from ser.repr import EncodedSequence, PoolingWindow, XLSRBackend -from ser.utils.torch_inference import TorchRuntime +import ser._internal.repr.hf_xlsr as hf_xlsr_module +from ser._internal.repr import EncodedSequence, PoolingWindow, XLSRBackend +from ser._internal.utils.torch_inference import TorchRuntime @dataclass(frozen=True) diff --git a/tests/suites/unit/repr/test_pooling.py b/tests/suites/unit/repr/test_pooling.py index 1efd577..b980f21 100644 --- a/tests/suites/unit/repr/test_pooling.py +++ b/tests/suites/unit/repr/test_pooling.py @@ -5,8 +5,8 @@ import numpy as np import pytest +from ser._internal.repr import EncodedSequence, PoolingWindow from ser.pool import mean_std_pool, temporal_pooling_windows -from ser.repr import EncodedSequence, PoolingWindow def test_mean_std_pool_returns_expected_stats_per_window() -> None: diff --git a/tests/suites/unit/utils/test_audio_utils.py b/tests/suites/unit/utils/test_audio_utils.py index 5eb12fd..fd02368 100644 --- a/tests/suites/unit/utils/test_audio_utils.py +++ b/tests/suites/unit/utils/test_audio_utils.py @@ -5,8 +5,8 @@ import numpy as np import pytest +import ser._internal.utils.audio_utils as au from ser.config import AudioReadConfig -from ser.utils import audio_utils as au pytestmark = pytest.mark.filterwarnings("ignore:path is deprecated.*:DeprecationWarning") diff --git a/tests/suites/unit/utils/test_dsp.py b/tests/suites/unit/utils/test_dsp.py index f49da99..2f96bf7 100644 --- a/tests/suites/unit/utils/test_dsp.py +++ b/tests/suites/unit/utils/test_dsp.py @@ -7,8 +7,8 @@ import numpy as np import pytest +import ser._internal.utils.dsp as dsp from ser.config import FeatureFlags -from ser.utils import dsp pytestmark = [ pytest.mark.unit, diff --git a/tests/suites/unit/utils/test_segment_canonicalization.py b/tests/suites/unit/utils/test_segment_canonicalization.py index 6a28f35..0e1e8b0 100644 --- a/tests/suites/unit/utils/test_segment_canonicalization.py +++ b/tests/suites/unit/utils/test_segment_canonicalization.py @@ -8,9 +8,9 @@ from hypothesis import settings as hypothesis_settings from hypothesis import strategies as st +from ser._internal.utils.segment_canonicalization import CanonicalSegment, canonicalize_segments from ser.domain import EmotionSegment from ser.runtime.schema import SegmentPrediction -from ser.utils.segment_canonicalization import CanonicalSegment, canonicalize_segments _PROPERTY_TEST_SETTINGS = hypothesis_settings( max_examples=100, diff --git a/tests/suites/unit/utils/test_subtitles.py b/tests/suites/unit/utils/test_subtitles.py index 76d7b0d..e0120f9 100644 --- a/tests/suites/unit/utils/test_subtitles.py +++ b/tests/suites/unit/utils/test_subtitles.py @@ -4,14 +4,14 @@ import pytest -from ser.config import TimelineConfig -from ser.domain import TimelineEntry -from ser.utils.subtitles import ( +from ser._internal.utils.subtitles import ( infer_subtitle_format, resolve_subtitle_export_request, save_timeline_to_subtitles, timeline_to_subtitle_cues, ) +from ser.config import TimelineConfig +from ser.domain import TimelineEntry def test_resolve_subtitle_export_request_infers_format_from_output_path() -> None: diff --git a/tests/suites/unit/utils/test_timeline_utils.py b/tests/suites/unit/utils/test_timeline_utils.py index 5ea8a4a..4ee3243 100644 --- a/tests/suites/unit/utils/test_timeline_utils.py +++ b/tests/suites/unit/utils/test_timeline_utils.py @@ -1,7 +1,7 @@ """Behavior tests for timeline merging utilities.""" +from ser._internal.utils.timeline_utils import build_timeline from ser.domain import EmotionSegment, TimelineEntry, TranscriptWord -from ser.utils.timeline_utils import build_timeline def test_build_timeline_aligns_words_to_emotion_intervals() -> None: diff --git a/tests/suites/unit/utils/test_torch_inference.py b/tests/suites/unit/utils/test_torch_inference.py index 49e0854..2fcb3d1 100644 --- a/tests/suites/unit/utils/test_torch_inference.py +++ b/tests/suites/unit/utils/test_torch_inference.py @@ -6,7 +6,7 @@ import pytest -import ser.utils.torch_inference as torch_inference +import ser._internal.utils.torch_inference as torch_inference class _FakeCuda: diff --git a/tests/suites/unit/utils/test_transcription_compat.py b/tests/suites/unit/utils/test_transcription_compat.py index 0b339e6..69a42c1 100644 --- a/tests/suites/unit/utils/test_transcription_compat.py +++ b/tests/suites/unit/utils/test_transcription_compat.py @@ -8,7 +8,7 @@ import pytest -import ser.utils.transcription_compat as compat +import ser._internal.utils.transcription_compat as compat def test_resolve_stable_whisper_fallback_model_name_maps_distil_to_turbo() -> None: From 7ce7be115849af5731aa99df35e006e92210c466 Mon Sep 17 00:00:00 2001 From: Juan Sugg Date: Fri, 10 Jul 2026 00:16:34 -0300 Subject: [PATCH 07/12] docs(api): add stability policy --- .local/public-api-surface-improvements.md | 12 +++++- README.md | 1 + docs/api-stability.md | 48 +++++++++++++++++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 docs/api-stability.md diff --git a/.local/public-api-surface-improvements.md b/.local/public-api-surface-improvements.md index 1c28985..563a769 100644 --- a/.local/public-api-surface-improvements.md +++ b/.local/public-api-surface-improvements.md @@ -227,7 +227,7 @@ modules not re-exported move internal. | ID | Task | Status | Depends on | |----|------|--------|------------| -| P3-01 | `docs/api-stability.md` | TODO | P2-07 | +| P3-01 | `docs/api-stability.md` | DONE | P2-07 | | P3-02 | README Python API section refresh | TODO | P2-07 | | P3-03 | README examples executed by a contract test | TODO | P3-02 | | P3-04 | `CHANGELOG.md` bootstrap | TODO | — | @@ -309,6 +309,16 @@ Template: - Deviations / follow-ups: … ``` +### 2026-07-10 00:16 — P3-01 done +- What: Added `docs/api-stability.md` with the tier-1 list, first-publish SemVer promise, `_internal` policy, snapshot governance, and a README link. +- Evidence: `make lint` → Ruff/import-lint/Black/isort passed; `rg -n "docs/api-stability\\.md|SemVer|first published|ser\\.api|boundary_policy\\.toml" README.md docs/api-stability.md` → found the README link plus the required policy statements. +- Deviations / follow-ups: none. + +### 2026-07-10 00:15 — P3-01 started +- What: Add the tier-1 API stability policy and link it from README. +- Evidence: P2 ledger is done; `docs/api-stability.md` is absent before this task. +- Deviations / follow-ups: none. + ### 2026-07-09 22:27 — P2-02..P2-08 done - What: Moved Phase 2 implementation modules under `ser/_internal/`, kept only classified public facades/contracts, rewired first-party imports/tests/scripts/docs, and trimmed `boundary_policy.toml` to 10 genuine facade entries. - Evidence: `rg --files ser/data ser/models ser/runtime ser/transcript ser/features ser/heads ser/repr | sort` → only Appendix-A keep-as-facade files remain public; `uv run --frozen --extra dev pytest -q tests/suites/integration/architecture/test_public_api_snapshot.py tests/suites/integration/architecture/test_public_import_cost.py` → `2 passed`; synthetic unlisted `from ser._internal.config.schema import AppConfig` in `ser/domain.py` made `uv run --frozen --extra dev pytest -q tests/suites/integration/architecture/test_api_import_boundary.py` fail in `test_public_to_internal_imports_match_explicit_allowlist`, then the synthetic edit was removed and the same boundary test → `13 passed`; `bash scripts/run_import_lint.sh` → `17 passed`; `make lint` → Ruff/import-lint/Black/isort passed; `make type` → mypy success and pyright `0 errors, 0 warnings`; `make test` → `1028 passed`; `make test-cov` → `1028 passed`, coverage gate held; `make lock-check` → lock verified. diff --git a/README.md b/README.md index 9e2a452..e1f6ba6 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,7 @@ uv run pytest -q \ - Architecture guide: [docs/architecture.md](https://github.com/jsugg/ser/blob/main/docs/architecture.md) - Contributor guide: [CONTRIBUTING.md](https://github.com/jsugg/ser/blob/main/CONTRIBUTING.md) - Compatibility details: [docs/compatibility-matrix.md](https://github.com/jsugg/ser/blob/main/docs/compatibility-matrix.md) +- Public API stability: [docs/api-stability.md](docs/api-stability.md) - Hardware validation workflows: [docs/ci/hardware-validation.md](https://github.com/jsugg/ser/blob/main/docs/ci/hardware-validation.md) - Release and rollback runbook: [docs/release-and-rollback.md](https://github.com/jsugg/ser/blob/main/docs/release-and-rollback.md) - Security policy: [SECURITY.md](https://github.com/jsugg/ser/blob/main/SECURITY.md) diff --git a/docs/api-stability.md b/docs/api-stability.md new file mode 100644 index 0000000..4cb2287 --- /dev/null +++ b/docs/api-stability.md @@ -0,0 +1,48 @@ +# Public API stability + +This project keeps a small tier-1 Python surface. Consumers should build workflows +through these modules only: + +- `ser` +- `ser.api` +- `ser.config` +- `ser.domain` +- `ser.profiles` +- `ser.utils` + +`ser.api` is the supported workflow entry point for inference, training, dataset, +profile, and diagnostics operations. The other tier-1 modules provide package +metadata, domain types, configuration objects, profile discovery, and curated utility +helpers used by that workflow surface. + +## Stability promise + +The SemVer compatibility promise starts with the first published distribution. From +that point, symbols exported by tier-1 `__all__` declarations will not be removed or +changed incompatibly without a major-version release. + +Before first publish, the same surfaces are still governed as if they were stable so +that review catches compatibility drift early. Anything outside tier-1 is not a +compatibility contract. + +## Internal modules + +Modules below `ser._internal` are private implementation details. Public-looking +subpackage paths that are not tier-1 exports are also implementation details unless +they are declared as facade exceptions in [`boundary_policy.toml`](../boundary_policy.toml). + +Facade exceptions may import private owners only when the policy file records a +specific reason. Contributors should move implementation code under `ser._internal` +and keep public facades thin. + +## API change governance + +Tier-1 API drift is machine-reviewed by the checked-in public snapshot at +[`tests/suites/integration/architecture/public_api_snapshot.json`](../tests/suites/integration/architecture/public_api_snapshot.json). +The contract test +[`test_public_api_snapshot.py`](../tests/suites/integration/architecture/test_public_api_snapshot.py) +regenerates that snapshot in memory and fails on unreviewed differences. + +Intentional public API changes must update the snapshot with +[`scripts/dump_public_api.py`](../scripts/dump_public_api.py), review the diff, and keep +the import-boundary policy aligned. From 36778e99f1c23d72f01665496708e892f0681b67 Mon Sep 17 00:00:00 2001 From: Juan Sugg Date: Fri, 10 Jul 2026 00:17:18 -0300 Subject: [PATCH 08/12] docs(api): refresh README API guidance --- .local/public-api-surface-improvements.md | 12 +++++++++++- README.md | 18 ++++++++++++------ 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/.local/public-api-surface-improvements.md b/.local/public-api-surface-improvements.md index 563a769..b6f1e96 100644 --- a/.local/public-api-surface-improvements.md +++ b/.local/public-api-surface-improvements.md @@ -228,7 +228,7 @@ modules not re-exported move internal. | ID | Task | Status | Depends on | |----|------|--------|------------| | P3-01 | `docs/api-stability.md` | DONE | P2-07 | -| P3-02 | README Python API section refresh | TODO | P2-07 | +| P3-02 | README Python API section refresh | DONE | P2-07 | | P3-03 | README examples executed by a contract test | TODO | P3-02 | | P3-04 | `CHANGELOG.md` bootstrap | TODO | — | @@ -309,6 +309,16 @@ Template: - Deviations / follow-ups: … ``` +### 2026-07-10 00:18 — P3-02 done +- What: Updated the README Python API section to make `ser.api` the sole supported workflow entry point, show `ser.__version__`, keep a minimal `infer` example, and link the stability policy. +- Evidence: `rg -n "sole supported Python workflow entry point|__version__|docs/api-stability\\.md|first published" README.md` → found all required README statements; guarded README scan for moved/removed module references → `no moved or removed README references`. +- Deviations / follow-ups: none. + +### 2026-07-10 00:17 — P3-02 started +- What: Refresh the README Python API guidance around `ser.api`, `ser.__version__`, and the stability policy link. +- Evidence: P3-01 added the policy document; README still needs explicit package-version usage and first-publish wording. +- Deviations / follow-ups: none. + ### 2026-07-10 00:16 — P3-01 done - What: Added `docs/api-stability.md` with the tier-1 list, first-publish SemVer promise, `_internal` policy, snapshot governance, and a README link. - Evidence: `make lint` → Ruff/import-lint/Black/isort passed; `rg -n "docs/api-stability\\.md|SemVer|first published|ser\\.api|boundary_policy\\.toml" README.md docs/api-stability.md` → found the README link plus the required policy statements. diff --git a/README.md b/README.md index e1f6ba6..92cad9a 100644 --- a/README.md +++ b/README.md @@ -95,25 +95,31 @@ Profile selection during predict is strict: use an artifact trained for the same When running from a source checkout without activating an environment, prefix commands with `uv run`. ## Python API -`ser.api` is the sole supported Python entry point. Everything else — including -modules under public-looking paths that are not exported from `ser`, `ser.api`, +`ser.api` is the sole supported Python workflow entry point. Import package +metadata from `ser`, but call inference, training, dataset, profile, and +diagnostics operations through `ser.api`. Everything else — including modules +under public-looking paths that are not exported from `ser`, `ser.api`, `ser.domain`, `ser.config`, or `ser.utils` — is implementation detail and may change without notice. Minimal inference example: ```python +import ser import ser.api +print(ser.__version__) + execution = ser.api.infer("sample.wav", profile="fast") for segment in execution.emotions: print(segment.emotion, segment.start_seconds, segment.end_seconds) ``` -Stability promise: symbols exported from `ser.api` (see `ser.api.__all__`), -the domain types re-exported from `ser`/`ser.domain`, the trimmed `ser.config` -surface, and the helpers in `ser.utils` follow semantic versioning from `1.0.0` -onward. Anything not exported there carries no compatibility guarantee. +Stability promise: after the first published distribution, symbols exported +from `ser.api` (see `ser.api.__all__`), the domain types re-exported from +`ser`/`ser.domain`, the trimmed `ser.config` surface, and the helpers in +`ser.utils` follow semantic versioning. Anything not exported there carries no +compatibility guarantee. See [docs/api-stability.md](docs/api-stability.md). ## Boundary Checks (Contributors) If your change touches `ser/api.py`, `ser/_internal/api/*`, or `ser/__main__.py`, run: From c0d032c2c90641c5bc1bff4af2fbf2e3caea6cca Mon Sep 17 00:00:00 2001 From: Juan Sugg Date: Fri, 10 Jul 2026 00:18:55 -0300 Subject: [PATCH 09/12] test(docs): execute README API examples --- .local/public-api-surface-improvements.md | 12 ++- .../architecture/test_readme_examples.py | 80 +++++++++++++++++++ 2 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 tests/suites/integration/architecture/test_readme_examples.py diff --git a/.local/public-api-surface-improvements.md b/.local/public-api-surface-improvements.md index b6f1e96..89898b2 100644 --- a/.local/public-api-surface-improvements.md +++ b/.local/public-api-surface-improvements.md @@ -229,7 +229,7 @@ modules not re-exported move internal. |----|------|--------|------------| | P3-01 | `docs/api-stability.md` | DONE | P2-07 | | P3-02 | README Python API section refresh | DONE | P2-07 | -| P3-03 | README examples executed by a contract test | TODO | P3-02 | +| P3-03 | README examples executed by a contract test | DONE | P3-02 | | P3-04 | `CHANGELOG.md` bootstrap | TODO | — | **P3-01** — Document: tier-1 list (DD-02), the SemVer promise that activates at first @@ -309,6 +309,16 @@ Template: - Deviations / follow-ups: … ``` +### 2026-07-10 00:22 — P3-03 done +- What: Added a subprocess contract test that extracts README fenced `python` blocks and executes them with a lightweight `ser.api.infer` stub. +- Evidence: verifier-lite `019f4a08-3dad-72c1-8e9b-0234161b08bb` ran `rtk uv run --frozen --extra dev pytest -q tests/suites/integration/architecture/test_readme_examples.py` → `1 passed`; parent run of the same command → `1 passed`; synthetic README break from `ser.api.infer` to `ser.api.infer_broken` made the test fail with `AttributeError: module 'ser.api' has no attribute 'infer_broken'`, then the edit was reverted and the same test → `1 passed`. +- Deviations / follow-ups: none. + +### 2026-07-10 00:19 — P3-03 started +- What: Add a README fenced-Python contract test that executes examples in subprocess isolation with a lightweight inference stub. +- Evidence: P3-02 updated the example; no README example contract test exists yet. +- Deviations / follow-ups: none. + ### 2026-07-10 00:18 — P3-02 done - What: Updated the README Python API section to make `ser.api` the sole supported workflow entry point, show `ser.__version__`, keep a minimal `infer` example, and link the stability policy. - Evidence: `rg -n "sole supported Python workflow entry point|__version__|docs/api-stability\\.md|first published" README.md` → found all required README statements; guarded README scan for moved/removed module references → `no moved or removed README references`. diff --git a/tests/suites/integration/architecture/test_readme_examples.py b/tests/suites/integration/architecture/test_readme_examples.py new file mode 100644 index 0000000..150e0f3 --- /dev/null +++ b/tests/suites/integration/architecture/test_readme_examples.py @@ -0,0 +1,80 @@ +"""Contract tests for executable README Python examples.""" + +from __future__ import annotations + +import re +import subprocess +import sys +import textwrap +from pathlib import Path + +import pytest + +pytestmark = pytest.mark.topology_contract + +_README_PYTHON_BLOCK_RE = re.compile(r"```python\n(.*?)\n```", re.DOTALL) + + +def _readme_python_blocks(readme_path: Path) -> tuple[str, ...]: + """Extracts fenced Python examples from README.""" + return tuple( + match.group(1).strip() + for match in _README_PYTHON_BLOCK_RE.finditer(readme_path.read_text(encoding="utf-8")) + ) + + +def _example_runner_source(example_source: str, example_index: int) -> str: + """Builds an isolated runner with a lightweight inference stub.""" + return textwrap.dedent(f""" + from __future__ import annotations + + from types import SimpleNamespace + + import ser.api + + + class _Emotion: + def __init__(self, emotion: str, start_seconds: float, end_seconds: float) -> None: + self.emotion = emotion + self.start_seconds = start_seconds + self.end_seconds = end_seconds + + + def _fake_infer(*_args: object, **_kwargs: object) -> SimpleNamespace: + return SimpleNamespace(emotions=(_Emotion("neutral", 0.0, 1.0),)) + + + ser.api.infer = _fake_infer + _example_source = {example_source!r} + exec( + compile(_example_source, "README.md python block {example_index}", "exec"), + {{"__name__": "__readme_example__"}}, + ) + """) + + +def test_readme_python_examples_execute(repo_root: Path) -> None: + """README Python blocks should stay executable with documented public APIs.""" + examples = _readme_python_blocks(repo_root / "README.md") + assert examples, "README.md should contain at least one fenced Python example." + + failures: list[str] = [] + for example_index, example_source in enumerate(examples, start=1): + completed = subprocess.run( + [sys.executable, "-c", _example_runner_source(example_source, example_index)], + cwd=repo_root, + capture_output=True, + text=True, + ) + if completed.returncode != 0: + failures.append( + "\n".join( + ( + f"README Python example {example_index} failed.", + f"stdout:\n{completed.stdout}", + f"stderr:\n{completed.stderr}", + ) + ) + ) + + assert failures == [] From b9fa4892a9db70741da6b35281e4052e171cf805 Mon Sep 17 00:00:00 2001 From: Juan Sugg Date: Fri, 10 Jul 2026 00:19:58 -0300 Subject: [PATCH 10/12] docs(changelog): bootstrap change log --- .local/public-api-surface-improvements.md | 12 +++++++++++- CHANGELOG.md | 21 +++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 CHANGELOG.md diff --git a/.local/public-api-surface-improvements.md b/.local/public-api-surface-improvements.md index 89898b2..6b1e357 100644 --- a/.local/public-api-surface-improvements.md +++ b/.local/public-api-surface-improvements.md @@ -230,7 +230,7 @@ modules not re-exported move internal. | P3-01 | `docs/api-stability.md` | DONE | P2-07 | | P3-02 | README Python API section refresh | DONE | P2-07 | | P3-03 | README examples executed by a contract test | DONE | P3-02 | -| P3-04 | `CHANGELOG.md` bootstrap | TODO | — | +| P3-04 | `CHANGELOG.md` bootstrap | DONE | — | **P3-01** — Document: tier-1 list (DD-02), the SemVer promise that activates at first publish, what `_internal` means, how the snapshot test governs API change, pointer to @@ -309,6 +309,16 @@ Template: - Deviations / follow-ups: … ``` +### 2026-07-10 00:25 — P3-04 done +- What: Added `CHANGELOG.md` in Keep a Changelog style with an `[Unreleased]` section covering the Phase 3 governance changes. +- Evidence: initial `make lint` exposed a Black formatting miss in the new README example test; `uv run --frozen --extra dev black tests/suites/integration/architecture/test_readme_examples.py` reformatted it; rerun `make lint` → Ruff/import-lint/Black/isort passed; `rg -n "^# Changelog|^## \\[Unreleased\\]|Keep a Changelog|Semantic Versioning" CHANGELOG.md` → found the expected headings/format notes; changelog branding scan → `no branding strings in changelog`. +- Deviations / follow-ups: none. + +### 2026-07-10 00:23 — P3-04 started +- What: Bootstrap `CHANGELOG.md` in Keep a Changelog style with an `[Unreleased]` section. +- Evidence: `CHANGELOG.md` is absent before this task. +- Deviations / follow-ups: none. + ### 2026-07-10 00:22 — P3-03 done - What: Added a subprocess contract test that extracts README fenced `python` blocks and executes them with a lightweight `ser.api.infer` stub. - Evidence: verifier-lite `019f4a08-3dad-72c1-8e9b-0234161b08bb` ran `rtk uv run --frozen --extra dev pytest -q tests/suites/integration/architecture/test_readme_examples.py` → `1 passed`; parent run of the same command → `1 passed`; synthetic README break from `ser.api.infer` to `ser.api.infer_broken` made the test fail with `AttributeError: module 'ser.api' has no attribute 'infer_broken'`, then the edit was reverted and the same test → `1 passed`. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..bd81acc --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,21 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). +This project will follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html) +after its first published distribution. + +## [Unreleased] + +### Added + +- Public API stability policy for tier-1 modules. +- README Python API example coverage through a contract test. +- Changelog discipline before first publish. + +### Changed + +- README Python API guidance now directs workflow users to `ser.api`. + +[Unreleased]: https://github.com/jsugg/ser/compare/HEAD...HEAD From f9ae53c6db6d45ef3d6a93887be01c265c8975a4 Mon Sep 17 00:00:00 2001 From: Juan Sugg Date: Fri, 10 Jul 2026 11:59:00 -0300 Subject: [PATCH 11/12] fix(tooling): fail closed type-completeness verification Reject nonzero Pyright exits and malformed/nonfinite scores.\nResolve repository configuration from script location. --- scripts/check_type_completeness.py | 66 ++++++--- .../scripts/test_check_type_completeness.py | 126 ++++++++++++++++++ 2 files changed, 177 insertions(+), 15 deletions(-) create mode 100644 tests/suites/unit/scripts/test_check_type_completeness.py diff --git a/scripts/check_type_completeness.py b/scripts/check_type_completeness.py index c4bbedc..90a9956 100644 --- a/scripts/check_type_completeness.py +++ b/scripts/check_type_completeness.py @@ -7,8 +7,12 @@ import subprocess import sys import tomllib +from math import isfinite from pathlib import Path -from typing import Any +from typing import cast + +MINIMUM_COMPLETENESS_THRESHOLD = 0.95 +REPO_ROOT = Path(__file__).resolve().parent.parent PYRIGHT_VERIFYTYPES_COMMAND = ( "pyright", @@ -19,16 +23,38 @@ ) +def _mapping(value: object, description: str) -> dict[str, object]: + """Validates one JSON or TOML mapping at an external boundary.""" + if not isinstance(value, dict): + raise RuntimeError(f"{description} must be a mapping.") + return cast(dict[str, object], value) + + def _load_threshold(repo_root: Path) -> float: """Loads the configured type-completeness threshold from pyproject.""" - pyproject = tomllib.loads((repo_root / "pyproject.toml").read_text(encoding="utf-8")) - threshold = pyproject["tool"]["ser"]["type_completeness"]["threshold"] - if not isinstance(threshold, int | float): + raw_pyproject: object = tomllib.loads( + (repo_root / "pyproject.toml").read_text(encoding="utf-8") + ) + pyproject = _mapping(raw_pyproject, "pyproject.toml") + tool = _mapping(pyproject.get("tool"), "[tool]") + ser = _mapping(tool.get("ser"), "[tool.ser]") + completeness = _mapping(ser.get("type_completeness"), "[tool.ser.type_completeness]") + threshold = completeness.get("threshold") + if isinstance(threshold, bool) or not isinstance(threshold, int | float): raise TypeError("[tool.ser.type_completeness].threshold must be a number.") - return float(threshold) + parsed_threshold = float(threshold) + if ( + not isfinite(parsed_threshold) + or not MINIMUM_COMPLETENESS_THRESHOLD <= parsed_threshold <= 1.0 + ): + raise ValueError( + "[tool.ser.type_completeness].threshold must be finite and between " + f"{MINIMUM_COMPLETENESS_THRESHOLD:.2f} and 1.00." + ) + return parsed_threshold -def _run_pyright(repo_root: Path) -> dict[str, Any]: +def _run_pyright(repo_root: Path) -> dict[str, object]: """Runs pyright verifytypes and returns its JSON payload.""" completed = subprocess.run( PYRIGHT_VERIFYTYPES_COMMAND, @@ -36,42 +62,52 @@ def _run_pyright(repo_root: Path) -> dict[str, Any]: capture_output=True, text=True, ) + if completed.returncode != 0: + diagnostics = completed.stderr.strip() + diagnostic_suffix = f"\n{diagnostics}" if diagnostics else "" + raise RuntimeError( + "pyright --verifytypes exited with status " + f"{completed.returncode}.{diagnostic_suffix}" + ) try: - payload = json.loads(completed.stdout) + raw_payload: object = json.loads(completed.stdout) except json.JSONDecodeError as err: sys.stderr.write(completed.stdout) sys.stderr.write(completed.stderr) raise RuntimeError("pyright --verifytypes did not emit valid JSON.") from err - if not isinstance(payload, dict): + if not isinstance(raw_payload, dict): raise RuntimeError("pyright --verifytypes JSON payload must be an object.") - return payload + return cast(dict[str, object], raw_payload) -def _read_score(payload: dict[str, Any]) -> float: +def _read_score(payload: dict[str, object]) -> float: """Reads the verifytypes completeness score with shape validation.""" completeness = payload.get("typeCompleteness") if not isinstance(completeness, dict): raise RuntimeError("pyright JSON missing typeCompleteness object.") score = completeness.get("completenessScore") - if not isinstance(score, int | float): + if isinstance(score, bool) or not isinstance(score, int | float): raise RuntimeError("pyright JSON missing numeric completenessScore.") - return float(score) + parsed_score = float(score) + if not isfinite(parsed_score) or not 0.0 <= parsed_score <= 1.0: + raise RuntimeError("pyright JSON completenessScore must be finite and between 0.0 and 1.0.") + return parsed_score -def _read_error_count(payload: dict[str, Any]) -> int: +def _read_error_count(payload: dict[str, object]) -> int: """Reads the pyright summary error count.""" summary = payload.get("summary") if not isinstance(summary, dict): raise RuntimeError("pyright JSON missing summary object.") error_count = summary.get("errorCount") - if not isinstance(error_count, int): + if isinstance(error_count, bool) or not isinstance(error_count, int) or error_count < 0: raise RuntimeError("pyright JSON missing integer summary.errorCount.") return error_count def main() -> int: """CLI entry point.""" - repo_root = Path.cwd() + repo_root = REPO_ROOT threshold = _load_threshold(repo_root) payload = _run_pyright(repo_root) error_count = _read_error_count(payload) diff --git a/tests/suites/unit/scripts/test_check_type_completeness.py b/tests/suites/unit/scripts/test_check_type_completeness.py new file mode 100644 index 0000000..3658717 --- /dev/null +++ b/tests/suites/unit/scripts/test_check_type_completeness.py @@ -0,0 +1,126 @@ +"""Regression contracts for the type-completeness verification script.""" + +from __future__ import annotations + +import os +import stat +import subprocess +import sys +from pathlib import Path + + +def test_checker_rejects_nonzero_pyright_with_valid_json(repo_root: Path, tmp_path: Path) -> None: + """A valid-looking payload must not hide a failed pyright process.""" + fake_pyright = tmp_path / "pyright" + fake_pyright.write_text( + "\n".join( + ( + f"#!{sys.executable}", + "import json", + "raise SystemExit((print(json.dumps({'typeCompleteness': {'completenessScore': 1.0}, 'summary': {'errorCount': 0}})) or 7))", + "", + ) + ), + encoding="utf-8", + ) + fake_pyright.chmod(fake_pyright.stat().st_mode | stat.S_IXUSR) + + environment = os.environ.copy() + environment["PATH"] = f"{tmp_path}{os.pathsep}{environment['PATH']}" + completed = subprocess.run( + [sys.executable, str(repo_root / "scripts" / "check_type_completeness.py")], + cwd=tmp_path, + capture_output=True, + text=True, + env=environment, + ) + + assert completed.returncode != 0 + assert "pyright --verifytypes exited with status 7" in completed.stderr + + +def test_checker_rejects_nonfinite_completeness_score(repo_root: Path, tmp_path: Path) -> None: + """A NaN score must fail closed instead of bypassing numeric comparison.""" + fake_pyright = tmp_path / "pyright" + fake_pyright.write_text( + "\n".join( + ( + f"#!{sys.executable}", + 'print(\'{"typeCompleteness": {"completenessScore": NaN}, "summary": {"errorCount": 0}}\')', + "", + ) + ), + encoding="utf-8", + ) + fake_pyright.chmod(fake_pyright.stat().st_mode | stat.S_IXUSR) + + environment = os.environ.copy() + environment["PATH"] = f"{tmp_path}{os.pathsep}{environment['PATH']}" + completed = subprocess.run( + [sys.executable, str(repo_root / "scripts" / "check_type_completeness.py")], + cwd=tmp_path, + capture_output=True, + text=True, + env=environment, + ) + + assert completed.returncode != 0 + assert "completenessScore must be finite" in completed.stderr + + +def test_checker_rejects_malformed_pyright_json(repo_root: Path, tmp_path: Path) -> None: + """Malformed output must not become a passing completeness result.""" + fake_pyright = tmp_path / "pyright" + fake_pyright.write_text( + "\n".join((f"#!{sys.executable}", "print('not JSON')", "")), + encoding="utf-8", + ) + fake_pyright.chmod(fake_pyright.stat().st_mode | stat.S_IXUSR) + + environment = os.environ.copy() + environment["PATH"] = f"{tmp_path}{os.pathsep}{environment['PATH']}" + completed = subprocess.run( + [sys.executable, str(repo_root / "scripts" / "check_type_completeness.py")], + cwd=tmp_path, + capture_output=True, + text=True, + env=environment, + ) + + assert completed.returncode != 0 + assert "did not emit valid JSON" in completed.stderr + + +def test_checker_runs_pyright_from_repository_root(repo_root: Path, tmp_path: Path) -> None: + """The checker must not validate a package resolved from its caller's directory.""" + fake_pyright = tmp_path / "pyright" + fake_pyright.write_text( + "\n".join( + ( + f"#!{sys.executable}", + "import json", + "import os", + "from pathlib import Path", + "Path(os.environ['PYRIGHT_CWD_RECORD']).write_text(str(Path.cwd()), encoding='utf-8')", + "print(json.dumps({'typeCompleteness': {'completenessScore': 1.0}, 'summary': {'errorCount': 0}}))", + "", + ) + ), + encoding="utf-8", + ) + fake_pyright.chmod(fake_pyright.stat().st_mode | stat.S_IXUSR) + + record_path = tmp_path / "pyright-cwd.txt" + environment = os.environ.copy() + environment["PATH"] = f"{tmp_path}{os.pathsep}{environment['PATH']}" + environment["PYRIGHT_CWD_RECORD"] = str(record_path) + completed = subprocess.run( + [sys.executable, str(repo_root / "scripts" / "check_type_completeness.py")], + cwd=tmp_path, + capture_output=True, + text=True, + env=environment, + ) + + assert completed.returncode == 0, completed.stderr + assert record_path.read_text(encoding="utf-8") == str(repo_root) From 811f9dc5e9596366ecd998a70168b0437cea467d Mon Sep 17 00:00:00 2001 From: Juan Sugg Date: Fri, 10 Jul 2026 11:59:14 -0300 Subject: [PATCH 12/12] refactor(api)!: enforce tier-one public surface Move/remove non-tier implementation namespaces and update first-party imports.\nPublish a self-contained ser.api type vocabulary and enforce the public-tree policy.\n\nBREAKING CHANGE: direct imports from legacy implementation paths\n(ser.data, ser.models, ser.runtime.pipeline, ser.transcript, etc.)\nno longer resolve. Use ser.api, ser.config, ser.domain, ser.profiles,\nor ser.utils. --- .local/public-api-surface-improvements.md | 48 + .pre-commit-config.yaml | 2 +- README.md | 8 +- boundary_policy.toml | 29 +- docs/api-stability.md | 5 + docs/codebase-architecture.md | 121 +- docs/refactor-hotspot-checks.md | 68 +- docs/subsystem-dependency-map.md | 2 +- pyproject.toml | 8 +- scripts/check_public_internal_imports.py | 264 ++ scripts/dump_public_api.py | 26 +- scripts/run_import_lint.sh | 1 + ser/__main__.py | 2 +- ser/_internal/api/data.py | 12 +- ser/_internal/api/runtime.py | 6 +- ser/_internal/data/adapters/biic_podcast.py | 2 +- ser/_internal/data/adapters/crema_d.py | 2 +- .../data/adapters/csv_manifest_builder.py | 2 +- ser/_internal/data/adapters/msp_podcast.py | 2 +- ser/_internal/data/adapters/ravdess.py | 2 +- .../data/application/consents.py} | 0 ser/_internal/data/cli.py | 24 +- ser/_internal/data/data_loader.py | 2 +- ser/_internal/data/dataset_prepare.py | 2 +- ser/_internal/data/embedding_cache.py | 2 +- ser/_internal/data/msp_podcast_mirror.py | 2 +- .../data/public_dataset_downloads.py | 2 +- ser/_internal/data/strategies/auto_csv.py | 2 +- ser/_internal/data/strategies/default.py | 2 +- ser/_internal/diagnostics/service.py | 12 +- ser/_internal/features/feature_extractor.py | 2 +- ser/{ => _internal}/license_check.py | 0 .../models/accurate_feature_dataset.py | 2 +- .../models/accurate_training_execution.py | 2 +- ser/_internal/models/artifact_persistence.py | 2 +- ser/_internal/models/dataset_splitting.py | 2 +- ser/_internal/models/emotion_model.py | 4 +- .../models/medium_feature_dataset.py | 2 +- ser/_internal/models/training_entrypoints.py | 4 +- ser/_internal/models/training_preparation.py | 2 +- ser/{ => _internal}/pool/__init__.py | 0 ser/{ => _internal}/pool/stats_pool.py | 2 +- ser/{ => _internal}/pool/windowing.py | 2 +- ser/_internal/repr/emotion2vec.py | 4 +- ser/_internal/repr/hf_whisper.py | 2 +- ser/_internal/repr/hf_xlsr.py | 2 +- ser/_internal/runtime/accurate_execution.py | 2 +- ser/_internal/runtime/accurate_inference.py | 2 +- ser/_internal/runtime/backend_hooks.py | 4 +- ser/_internal/runtime/commands.py | 6 +- ser/_internal/runtime/fast_inference.py | 2 +- ser/_internal/runtime/medium_execution.py | 2 +- ser/_internal/runtime/medium_inference.py | 2 +- ser/_internal/runtime/pipeline.py | 12 +- ser/_internal/runtime/profile_quality_gate.py | 4 +- ser/{ => _internal}/runtime/registry.py | 0 ser/_internal/runtime/restricted_backends.py | 6 +- ser/{ => _internal}/train/__init__.py | 0 ser/{ => _internal}/train/eval.py | 0 ser/{ => _internal}/train/metrics.py | 0 ser/_internal/transcript/backends/__init__.py | 4 +- .../transcript/backends/base.py | 0 ser/_internal/transcript/backends/factory.py | 2 +- .../transcript/backends/faster_whisper.py | 16 +- .../transcript/backends/stable_whisper.py | 24 +- .../stable_whisper_admission_runtime.py | 2 +- .../backends/stable_whisper_torio_probe.py | 2 +- .../stable_whisper_transcribe_admission.py | 2 +- .../stable_whisper_transcribe_execution.py | 2 +- .../stable_whisper_transcribe_kwargs.py | 2 +- .../stable_whisper_transcribe_runtime.py | 2 +- ser/_internal/transcript/mps_admission.py | 2 +- .../transcript/mps_admission_overrides.py | 2 +- ser/_internal/transcript/profiling.py | 2 +- .../transcript/transcript_extractor.py | 2 +- ser/_internal/transcription/compatibility.py | 2 +- .../transcription/in_process_orchestration.py | 2 +- .../transcription/process_isolation.py | 4 +- ser/_internal/transcription/process_worker.py | 2 +- .../transcription/public_boundary_process.py | 2 +- .../transcription/public_boundary_runtime.py | 2 +- .../transcription/public_boundary_support.py | 2 +- .../transcription/runtime_profile.py | 4 +- ser/_internal/utils/audio_utils.py | 2 +- ser/{ => _internal}/utils/common_utils.py | 0 ser/_internal/utils/dsp.py | 2 +- ser/{ => _internal}/utils/logger.py | 0 ser/_internal/utils/subtitles.py | 2 +- ser/_internal/utils/timeline_utils.py | 4 +- ser/api.py | 56 +- ser/data/__init__.py | 30 - ser/diagnostics/__init__.py | 45 +- ser/features/__init__.py | 25 - ser/heads/__init__.py | 19 - ser/models/__init__.py | 25 - ser/models/emotion_model.py | 3 - ser/models/profile_runtime.py | 44 - ser/models/training_entrypoints.py | 3 - ser/profiles.py | 36 +- ser/repr/__init__.py | 31 - ser/runtime/__init__.py | 42 +- ser/runtime/pipeline.py | 3 - ser/transcript/__init__.py | 25 - ser/transcript/backends/__init__.py | 28 - ser/transcript/backends/stable_whisper.py | 3 - ser/transcript/profiling.py | 3 - ser/transcript/transcript_extractor.py | 3 - ser/utils/__init__.py | 9 +- tests/suites/integration/api/test_api.py | 51 +- .../architecture/public_api_snapshot.json | 2500 ++++++++++++++++- .../architecture/test_api_import_boundary.py | 187 +- .../architecture/test_import_lint_policy.py | 1 + .../architecture/test_public_api_snapshot.py | 18 + .../test_public_api_type_vocabulary.py | 128 + .../architecture/test_public_import_cost.py | 4 + .../test_py_typed_distribution.py | 22 + .../architecture/test_readme_examples.py | 170 +- tests/suites/integration/cli/test_cli.py | 6 +- tests/suites/integration/cli/test_data_cli.py | 8 +- .../integration/data/test_data_application.py | 2 +- .../diagnostics/test_diagnostics_service.py | 2 +- .../models/test_emotion_model_contracts.py | 2 +- .../suites/integration/test_license_gates.py | 2 +- .../integration/test_process_isolation.py | 2 +- .../integration/test_runtime_pipeline.py | 20 +- .../integration/test_runtime_registry.py | 4 +- .../test_accurate_training_preparation.py | 2 +- .../unit/models/test_provenance_metadata.py | 2 +- .../unit/repr/test_emotion2vec_backend.py | 2 +- tests/suites/unit/repr/test_pooling.py | 2 +- .../test_check_public_internal_imports.py | 73 + tests/suites/unit/train/test_eval_protocol.py | 2 +- tests/suites/unit/train/test_metrics.py | 2 +- .../backends/test_faster_whisper_adapter.py | 2 +- .../test_process_isolation_internal.py | 2 +- .../process/test_process_worker.py | 2 +- .../process/test_public_boundary_process.py | 2 +- .../test_in_process_orchestration.py | 2 +- .../test_stable_whisper_admission_runtime.py | 2 +- ...est_stable_whisper_transcribe_admission.py | 2 +- ...est_stable_whisper_transcribe_execution.py | 2 +- .../test_stable_whisper_transcribe_kwargs.py | 2 +- .../test_stable_whisper_transcribe_runtime.py | 2 +- .../test_transcription_admission_overrides.py | 2 +- .../test_transcription_backends.py | 10 +- tests/suites/unit/utils/test_common_utils.py | 2 +- tests/suites/unit/utils/test_logger.py | 2 +- tests/utils/helpers/process_spawn_support.py | 2 +- 148 files changed, 3718 insertions(+), 801 deletions(-) create mode 100644 scripts/check_public_internal_imports.py rename ser/{data/application.py => _internal/data/application/consents.py} (100%) rename ser/{ => _internal}/license_check.py (100%) rename ser/{ => _internal}/pool/__init__.py (100%) rename ser/{ => _internal}/pool/stats_pool.py (93%) rename ser/{ => _internal}/pool/windowing.py (97%) rename ser/{ => _internal}/runtime/registry.py (100%) rename ser/{ => _internal}/train/__init__.py (100%) rename ser/{ => _internal}/train/eval.py (100%) rename ser/{ => _internal}/train/metrics.py (100%) rename ser/{ => _internal}/transcript/backends/base.py (100%) rename ser/{ => _internal}/utils/common_utils.py (100%) rename ser/{ => _internal}/utils/logger.py (100%) delete mode 100644 ser/data/__init__.py delete mode 100644 ser/features/__init__.py delete mode 100644 ser/heads/__init__.py delete mode 100644 ser/models/__init__.py delete mode 100644 ser/models/emotion_model.py delete mode 100644 ser/models/profile_runtime.py delete mode 100644 ser/models/training_entrypoints.py delete mode 100644 ser/repr/__init__.py delete mode 100644 ser/runtime/pipeline.py delete mode 100644 ser/transcript/__init__.py delete mode 100644 ser/transcript/backends/__init__.py delete mode 100644 ser/transcript/backends/stable_whisper.py delete mode 100644 ser/transcript/profiling.py delete mode 100644 ser/transcript/transcript_extractor.py create mode 100644 tests/suites/integration/architecture/test_public_api_type_vocabulary.py create mode 100644 tests/suites/integration/architecture/test_py_typed_distribution.py create mode 100644 tests/suites/unit/scripts/test_check_public_internal_imports.py diff --git a/.local/public-api-surface-improvements.md b/.local/public-api-surface-improvements.md index 6b1e357..68a06ad 100644 --- a/.local/public-api-surface-improvements.md +++ b/.local/public-api-surface-improvements.md @@ -718,3 +718,51 @@ Template: | `ser/utils/timeline_utils.py` | move-internal | `ser/_internal/utils/timeline_utils.py` | Implementation/helper module; first-party callers will use the internal owner path. | | `ser/utils/torch_inference.py` | move-internal | `ser/_internal/utils/torch_inference.py` | Implementation/helper module; first-party callers will use the internal owner path. | | `ser/utils/transcription_compat.py` | move-internal | `ser/_internal/utils/transcription_compat.py` | Implementation/helper module; first-party callers will use the internal owner path. | + +### 2026-07-10 audit correction — authoritative final classification + +The preceding table preserves the original planning record. A fresh baseline-tree +inventory found the seven omitted modules below and rejected the listed facade +exceptions as public implementation residue. This addendum supersedes every +conflicting earlier row; all non-tier-1 paths not retained here were removed from the +public tree or moved to their internal owner. + +| Baseline path | Final classification | Destination / final state | Reason | +|---|---|---|---| +| `ser/license_check.py` | move-internal | `ser/_internal/license_check.py` | License enforcement implementation is not a supported workflow API. | +| `ser/pool/__init__.py` | move-internal | `ser/_internal/pool/__init__.py` | Pool implementation package is internal. | +| `ser/pool/stats_pool.py` | move-internal | `ser/_internal/pool/stats_pool.py` | Pool implementation is internal. | +| `ser/pool/windowing.py` | move-internal | `ser/_internal/pool/windowing.py` | Pool implementation is internal. | +| `ser/train/__init__.py` | move-internal | `ser/_internal/train/__init__.py` | Training implementation package is internal. | +| `ser/train/eval.py` | move-internal | `ser/_internal/train/eval.py` | Training evaluation implementation is internal. | +| `ser/train/metrics.py` | move-internal | `ser/_internal/train/metrics.py` | Training metrics implementation is internal. | +| `ser/data/__init__.py` | remove-public | Removed | No genuine facade or annotation contract remained. | +| `ser/data/application.py` | move-internal | `ser/_internal/data/application/consents.py` | Dataset consent workflow implementation is internal. | +| `ser/models/__init__.py` | remove-public | Removed | Package facade advertised unsupported implementation paths. | +| `ser/models/emotion_model.py` | remove-public | Internal model owners retained | Public convenience facade was not a supported workflow entry point. | +| `ser/models/profile_runtime.py` | remove-public | Internal runtime owners retained | Public convenience facade was not a supported workflow entry point. | +| `ser/models/training_entrypoints.py` | remove-public | Internal training owners retained | Public convenience facade was not a supported workflow entry point. | +| `ser/runtime/__init__.py` | keep-package-marker | Docstring-only package marker | Required only to own the retained contract leaves. | +| `ser/runtime/contracts.py` | keep-contract-leaf | Re-exported by `ser.api` | Lightweight annotation vocabulary, not a workflow entry point. | +| `ser/runtime/pipeline.py` | remove-public | `ser/_internal/runtime/pipeline.py` | Pipeline orchestration is internal. | +| `ser/runtime/registry.py` | move-internal | `ser/_internal/runtime/registry.py` | Runtime registry implementation is internal. | +| `ser/runtime/schema.py` | keep-contract-leaf | Re-exported by `ser.api` | Lightweight annotation vocabulary, not a workflow entry point. | +| `ser/transcript/__init__.py` | remove-public | Removed | Package facade advertised unsupported implementation paths. | +| `ser/transcript/backends/__init__.py` | remove-public | Removed | Package facade advertised unsupported implementation paths. | +| `ser/transcript/backends/base.py` | move-internal | `ser/_internal/transcript/backends/base.py` | Backend adapter contract is internal implementation detail. | +| `ser/transcript/backends/stable_whisper.py` | remove-public | Internal backend owner retained | Backend implementation is not a supported workflow entry point. | +| `ser/transcript/profiling.py` | remove-public | Internal profiling owner retained | Profiling implementation is not a supported workflow entry point. | +| `ser/transcript/transcript_extractor.py` | remove-public | Internal transcript owner retained | Transcript implementation is not a supported workflow entry point. | +| `ser/features/__init__.py` | remove-public | Removed | Facade exposed implementation-only feature helpers. | +| `ser/heads/__init__.py` | remove-public | Removed | Facade exposed implementation-only torch helpers. | +| `ser/repr/__init__.py` | remove-public | Removed | Facade exposed implementation-only representation helpers. | +| `ser/diagnostics/__init__.py` | keep-package-marker | Docstring-only package marker | Required only to own the retained diagnostic contract leaf. | +| `ser/diagnostics/domain.py` | keep-contract-leaf | Re-exported by `ser.api` | Lightweight annotation vocabulary, not a workflow entry point. | +| `ser/utils/common_utils.py` | move-internal | `ser/_internal/utils/common_utils.py` | Tier-1 facade delegates directly to the internal helper. | +| `ser/utils/logger.py` | move-internal | `ser/_internal/utils/logger.py` | Tier-1 facade delegates directly to the internal helper. | + +The final public tree is enforced by +`tests/suites/integration/architecture/test_api_import_boundary.py`: six tier-1 +modules, `ser.__main__`, two docstring-only package markers, and the three contract +leaves listed above. The four entries in `boundary_policy.toml` are the only public +modules allowed to delegate into `ser._internal`. diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e679c75..d06f647 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,7 +17,7 @@ repos: stages: [pre-push] - repo: https://github.com/psf/black - rev: 25.12.0 + rev: 26.3.1 hooks: - id: black stages: [pre-commit] diff --git a/README.md b/README.md index 92cad9a..809fd69 100644 --- a/README.md +++ b/README.md @@ -99,8 +99,8 @@ When running from a source checkout without activating an environment, prefix co metadata from `ser`, but call inference, training, dataset, profile, and diagnostics operations through `ser.api`. Everything else — including modules under public-looking paths that are not exported from `ser`, `ser.api`, -`ser.domain`, `ser.config`, or `ser.utils` — is implementation detail and may -change without notice. +`ser.config`, `ser.domain`, `ser.profiles`, or `ser.utils` — is implementation +detail and may change without notice. Minimal inference example: @@ -117,8 +117,8 @@ for segment in execution.emotions: Stability promise: after the first published distribution, symbols exported from `ser.api` (see `ser.api.__all__`), the domain types re-exported from -`ser`/`ser.domain`, the trimmed `ser.config` surface, and the helpers in -`ser.utils` follow semantic versioning. Anything not exported there carries no +`ser`/`ser.domain`, the curated `ser.config`, `ser.profiles`, and `ser.utils` +surfaces follow semantic versioning. Anything not exported there carries no compatibility guarantee. See [docs/api-stability.md](docs/api-stability.md). ## Boundary Checks (Contributors) diff --git a/boundary_policy.toml b/boundary_policy.toml index 17c8580..57c1a56 100644 --- a/boundary_policy.toml +++ b/boundary_policy.toml @@ -17,30 +17,5 @@ path = "ser/config.py" reason = "Public facade over internal config/bootstrap owners." [[public_internal_import]] -path = "ser/data/application.py" -reason = "Public data application facade over internal dataset workflow owners." - -[[public_internal_import]] -path = "ser/models/emotion_model.py" -reason = "Public model facade over internal loading/training/prediction owners." - -[[public_internal_import]] -path = "ser/models/training_entrypoints.py" -reason = "Public training facade over internal model workflow owners." - -[[public_internal_import]] -path = "ser/runtime/pipeline.py" -reason = "Public runtime pipeline facade over internal orchestration owners." - -[[public_internal_import]] -path = "ser/transcript/backends/stable_whisper.py" -reason = "Public stable-whisper backend facade over internal transcription owners." - -[[public_internal_import]] -path = "ser/transcript/profiling.py" -reason = "Public transcription profiling facade over internal profiling owners." - -[[public_internal_import]] -path = "ser/transcript/transcript_extractor.py" -reason = "Public transcription extraction facade over internal transcription owners." - +path = "ser/utils/__init__.py" +reason = "Tier-1 utility facade delegates curated helpers to internal owners." diff --git a/docs/api-stability.md b/docs/api-stability.md index 4cb2287..4cb44a2 100644 --- a/docs/api-stability.md +++ b/docs/api-stability.md @@ -31,6 +31,11 @@ Modules below `ser._internal` are private implementation details. Public-looking subpackage paths that are not tier-1 exports are also implementation details unless they are declared as facade exceptions in [`boundary_policy.toml`](../boundary_policy.toml). +`ser.runtime.contracts`, `ser.runtime.schema`, and `ser.diagnostics.domain` are +implementation-owned contract leaves whose types are re-exported by `ser.api`. +They exist to preserve lightweight type ownership, not as additional workflow entry +points; consumers should import their vocabulary from `ser.api`. + Facade exceptions may import private owners only when the policy file records a specific reason. Contributors should move implementation code under `ser._internal` and keep public facades thin. diff --git a/docs/codebase-architecture.md b/docs/codebase-architecture.md index 608456a..9308270 100644 --- a/docs/codebase-architecture.md +++ b/docs/codebase-architecture.md @@ -8,13 +8,13 @@ formal artifacts in [`docs/architecture-diagram.md`](architecture-diagram.md), ## Scope and current state -These counts are a current working-tree snapshot taken on March 27, 2026. +These counts are a current working-tree snapshot taken on July 10, 2026. - Source modules under `ser/`: `228` -- Test modules under `tests/`: `150` -- Public modules outside `_internal/`: `167` -- Internal owner/helper modules under `_internal/`: `61` -- Public modules importing `_internal` directly: `24` +- Test modules under `tests/`: `169` +- Public modules outside `_internal/`: `12` +- Internal owner/helper modules under `_internal/`: `216` +- Public modules importing `_internal` directly: `4` This is a modular monolith with explicit subsystem seams. It is not a textbook hexagonal architecture, but it is clearly designed: configuration is typed and @@ -22,15 +22,14 @@ immutable, runtime behavior is profile-driven, and operationally sensitive code paths use extracted owner modules for retries, process isolation, environment application, and diagnostics. -Older roadmap references to `ser/models/training_orchestration.py` and -`ser/_internal/apt/runtime.py` do not match the current tree; neither module is -present in this workspace. +Older roadmap references to a training-orchestration module and an APT runtime +module do not match the current tree; neither module is present in this workspace. ## Architectural style The dominant architectural pattern is: -1. thin public facades for stable entrypoints +1. six tier-1 public modules for stable entrypoints 2. `_internal` owner modules for orchestration or shared primitives 3. profile- and backend-specific execution modules for heavy runtime behavior 4. typed configuration and schema models as the shared contract layer @@ -41,7 +40,7 @@ Representative boundaries: - Public API: [`ser/api.py`](../ser/api.py) - Public config: [`ser/config.py`](../ser/config.py) - CLI entrypoint: [`ser/__main__.py`](../ser/__main__.py) -- Runtime pipeline seam: [`ser/runtime/pipeline.py`](../ser/runtime/pipeline.py) +- Runtime pipeline owner: [`ser/_internal/runtime/pipeline.py`](../ser/_internal/runtime/pipeline.py) - Internal API owners: [`ser/_internal/api/runtime.py`](../ser/_internal/api/runtime.py), [`ser/_internal/api/data.py`](../ser/_internal/api/data.py), [`ser/_internal/api/diagnostics.py`](../ser/_internal/api/diagnostics.py) ## Top-level subsystem map @@ -72,11 +71,16 @@ Tradeoff: ## 2. Public API and CLI architecture -The public library surface is intentionally small: +The public library surface is intentionally small: `ser`, `ser.api`, `ser.config`, +`ser.domain`, `ser.profiles`, and `ser.utils`. `ser.api` is the only supported +workflow entry point: +- [`ser/__init__.py`](../ser/__init__.py) - [`ser/api.py`](../ser/api.py) - [`ser/config.py`](../ser/config.py) -- [`ser/__init__.py`](../ser/__init__.py) +- [`ser/domain.py`](../ser/domain.py) +- [`ser/profiles.py`](../ser/profiles.py) +- [`ser/utils/__init__.py`](../ser/utils/__init__.py) The CLI delegates through internal CLI support modules instead of talking directly to the library facade: @@ -98,7 +102,8 @@ library facade. ## 3. Runtime inference architecture -The central orchestration seam is [`ser/runtime/pipeline.py`](../ser/runtime/pipeline.py). +The central orchestration owner is +[`ser/_internal/runtime/pipeline.py`](../ser/_internal/runtime/pipeline.py). It coordinates: - profile resolution @@ -111,7 +116,7 @@ It coordinates: Supporting runtime ownership is split between: - backend hook construction: [`ser/_internal/runtime/backend_hooks.py`](../ser/_internal/runtime/backend_hooks.py) -- capability resolution: [`ser/runtime/registry.py`](../ser/runtime/registry.py) +- capability resolution: [`ser/_internal/runtime/registry.py`](../ser/_internal/runtime/registry.py) - retry policy: [`ser/_internal/runtime/policy.py`](../ser/_internal/runtime/policy.py) - shared retry/error primitives: [`ser/_internal/runtime/retry_primitives.py`](../ser/_internal/runtime/retry_primitives.py) - process env planning: [`ser/_internal/runtime/environment_plan.py`](../ser/_internal/runtime/environment_plan.py) @@ -120,16 +125,16 @@ Supporting runtime ownership is split between: The runtime design is profile-aware, not fully generic. Each profile still has its own execution path: -- fast: [`ser/runtime/fast_inference.py`](../ser/_internal/runtime/fast_inference.py) -- medium: [`ser/runtime/medium_inference.py`](../ser/_internal/runtime/medium_inference.py) -- accurate: [`ser/runtime/accurate_inference.py`](../ser/_internal/runtime/accurate_inference.py) -- accurate-research: [`ser/runtime/accurate_research_inference.py`](../ser/_internal/runtime/accurate_research_inference.py) +- fast: [`ser/_internal/runtime/fast_inference.py`](../ser/_internal/runtime/fast_inference.py) +- medium: [`ser/_internal/runtime/medium_inference.py`](../ser/_internal/runtime/medium_inference.py) +- accurate: [`ser/_internal/runtime/accurate_inference.py`](../ser/_internal/runtime/accurate_inference.py) +- accurate-research: [`ser/_internal/runtime/accurate_research_inference.py`](../ser/_internal/runtime/accurate_research_inference.py) The important architectural improvement is that medium and accurate are no longer monolithic runtime modules. They now delegate heavily into owner modules: - public-boundary orchestration: [`ser/_internal/runtime/accurate_public_boundary.py`](../ser/_internal/runtime/accurate_public_boundary.py), [`ser/_internal/runtime/medium_public_boundary.py`](../ser/_internal/runtime/medium_public_boundary.py) -- execution: [`ser/runtime/medium_execution.py`](../ser/_internal/runtime/medium_execution.py), [`ser/runtime/accurate_execution.py`](../ser/_internal/runtime/accurate_execution.py) +- execution: [`ser/_internal/runtime/medium_execution.py`](../ser/_internal/runtime/medium_execution.py), [`ser/_internal/runtime/accurate_execution.py`](../ser/_internal/runtime/accurate_execution.py) - setup/context: [`ser/_internal/runtime/medium_execution_context.py`](../ser/_internal/runtime/medium_execution_context.py), [`ser/_internal/runtime/accurate_operation_setup.py`](../ser/_internal/runtime/accurate_operation_setup.py) - retry/execution flow: [`ser/_internal/runtime/medium_execution_flow.py`](../ser/_internal/runtime/medium_execution_flow.py), [`ser/_internal/runtime/accurate_execution_flow.py`](../ser/_internal/runtime/accurate_execution_flow.py) - worker lifecycle: [`ser/_internal/runtime/medium_worker_lifecycle.py`](../ser/_internal/runtime/medium_worker_lifecycle.py), [`ser/_internal/runtime/accurate_worker_lifecycle.py`](../ser/_internal/runtime/accurate_worker_lifecycle.py) @@ -146,8 +151,10 @@ remain, but the complex reusable mechanics are extracted and tested separately. ## 4. Model and training architecture -The model subsystem is centered on [`ser/models/emotion_model.py`](../ser/models/emotion_model.py), -which is now a thin public boundary rather than a primary hotspot. It owns: +The model subsystem is centered on +[`ser/_internal/models/emotion_model.py`](../ser/_internal/models/emotion_model.py). +It is reached through `ser.api.train` and `ser.api.infer`, rather than a public +model package. It owns: - fast, medium, accurate, and accurate-research training entrypoints - model loading and compatibility filtering @@ -155,26 +162,26 @@ which is now a thin public boundary rather than a primary hotspot. It owns: There has been meaningful decomposition: -- fast training workflow owner: [`ser/models/fast_training.py`](../ser/_internal/models/fast_training.py) -- public training-entrypoint wiring: [`ser/models/training_entrypoints.py`](../ser/models/training_entrypoints.py) -- accurate-profile preparation and entrypoint helpers: [`ser/models/accurate_training_preparation.py`](../ser/_internal/models/accurate_training_preparation.py) -- accurate-profile prepared-training execution assembly: [`ser/models/accurate_training_execution.py`](../ser/_internal/models/accurate_training_execution.py) -- shared training preparation/evaluation: [`ser/models/training_preparation.py`](../ser/_internal/models/training_preparation.py) -- shared training execution/reporting: [`ser/models/training_execution.py`](../ser/_internal/models/training_execution.py) -- shared boundary helpers: [`ser/models/training_support.py`](../ser/_internal/models/training_support.py) -- shared training contracts: [`ser/models/training_types.py`](../ser/_internal/models/training_types.py) +- fast training workflow owner: [`ser/_internal/models/fast_training.py`](../ser/_internal/models/fast_training.py) +- training-entrypoint wiring: [`ser/_internal/models/training_entrypoints.py`](../ser/_internal/models/training_entrypoints.py) +- accurate-profile preparation and entrypoint helpers: [`ser/_internal/models/accurate_training_preparation.py`](../ser/_internal/models/accurate_training_preparation.py) +- accurate-profile prepared-training execution assembly: [`ser/_internal/models/accurate_training_execution.py`](../ser/_internal/models/accurate_training_execution.py) +- shared training preparation/evaluation: [`ser/_internal/models/training_preparation.py`](../ser/_internal/models/training_preparation.py) +- shared training execution/reporting: [`ser/_internal/models/training_execution.py`](../ser/_internal/models/training_execution.py) +- shared boundary helpers: [`ser/_internal/models/training_support.py`](../ser/_internal/models/training_support.py) +- shared training contracts: [`ser/_internal/models/training_types.py`](../ser/_internal/models/training_types.py) - fast training entrypoint seam: [`ser/_internal/models/fast_training_entrypoints.py`](../ser/_internal/models/fast_training_entrypoints.py) - shared model loading entrypoint: [`ser/_internal/models/model_loading.py`](../ser/_internal/models/model_loading.py) This subsystem is now materially closer to the runtime/data architecture shape: -[`ser/models/emotion_model.py`](../ser/models/emotion_model.py) stays -boundary-only, while profile-specific entrypoint wiring and shared +[`ser/_internal/models/emotion_model.py`](../ser/_internal/models/emotion_model.py) +is internal, while profile-specific entrypoint wiring and shared cross-profile logic live in dedicated `training_*` owner modules, and accurate-profile preparation is now separated from accurate-profile prepared-training execution between -[`ser/models/accurate_training_preparation.py`](../ser/_internal/models/accurate_training_preparation.py) +[`ser/_internal/models/accurate_training_preparation.py`](../ser/_internal/models/accurate_training_preparation.py) and -[`ser/models/accurate_training_execution.py`](../ser/_internal/models/accurate_training_execution.py) +[`ser/_internal/models/accurate_training_execution.py`](../ser/_internal/models/accurate_training_execution.py) instead of reconcentrating inside one mixed-responsibility owner. Internal callers and tests now use canonical owner modules directly instead of routing through a compatibility namespace on `emotion_model.py`. The remaining risk is @@ -182,11 +189,12 @@ boundary drift or reconcentration, not alias sprawl. ## 5. Transcription architecture -Transcription is one of the most mature architectural subsystems. +Transcription is one of the most mature architectural subsystems. Its owners are +internal and are reached through `ser.api.infer`. -Public orchestration lives in: +Core orchestration lives in: -- [`ser/transcript/transcript_extractor.py`](../ser/transcript/transcript_extractor.py) +- [`ser/_internal/transcript/transcript_extractor.py`](../ser/_internal/transcript/transcript_extractor.py) Public-boundary wrapper orchestration now also lives in: @@ -204,11 +212,11 @@ Execution strategy is split between: Backend abstraction lives behind: -- [`ser/transcript/backends/__init__.py`](../ser/transcript/backends/__init__.py) +- [`ser/_internal/transcript/backends/__init__.py`](../ser/_internal/transcript/backends/__init__.py) Profiling and recommendation are also decomposed: -- public profiling orchestrator: [`ser/transcript/profiling.py`](../ser/transcript/profiling.py) +- profiling orchestrator: [`ser/_internal/transcript/profiling.py`](../ser/_internal/transcript/profiling.py) - public-boundary profiling wiring and CLI dispatch: [`ser/_internal/transcription/public_boundary_profiling.py`](../ser/_internal/transcription/public_boundary_profiling.py) - benchmark owner: [`ser/_internal/transcription/default_benchmark.py`](../ser/_internal/transcription/default_benchmark.py) - recommendation owner: [`ser/_internal/transcription/default_recommendation.py`](../ser/_internal/transcription/default_recommendation.py) @@ -221,30 +229,31 @@ calibration rather than burying those concerns in one function. ## 6. Data architecture -The data subsystem is organized around a useful combination of facades, -application workflows, catalog data, strategy objects, and file-format adapters. +The data subsystem is organized around internal application workflows, catalog data, +strategy objects, and file-format adapters. Consumers use the dataset operations on +`ser.api` rather than a public data package. -Public/application layer: +Application layer: -- [`ser/data/application.py`](../ser/data/application.py) +- [`ser/_internal/data/application/consents.py`](../ser/_internal/data/application/consents.py) - [`ser/_internal/data/application/prepare.py`](../ser/_internal/data/application/prepare.py) - [`ser/_internal/data/application/registry_snapshot.py`](../ser/_internal/data/application/registry_snapshot.py) Preparation and registry orchestration: -- [`ser/data/dataset_prepare.py`](../ser/_internal/data/dataset_prepare.py) -- [`ser/data/dataset_registry.py`](../ser/_internal/data/dataset_registry.py) +- [`ser/_internal/data/dataset_prepare.py`](../ser/_internal/data/dataset_prepare.py) +- [`ser/_internal/data/dataset_registry.py`](../ser/_internal/data/dataset_registry.py) Catalog and strategies: -- declarative catalog: [`ser/data/catalog/public_datasets.py`](../ser/_internal/data/catalog/public_datasets.py) -- strategy registry: [`ser/data/strategies/default.py`](../ser/_internal/data/strategies/default.py) -- extracted homogeneous CSV strategies: [`ser/data/strategies/auto_csv.py`](../ser/_internal/data/strategies/auto_csv.py) +- declarative catalog: [`ser/_internal/data/catalog/public_datasets.py`](../ser/_internal/data/catalog/public_datasets.py) +- strategy registry: [`ser/_internal/data/strategies/default.py`](../ser/_internal/data/strategies/default.py) +- extracted homogeneous CSV strategies: [`ser/_internal/data/strategies/auto_csv.py`](../ser/_internal/data/strategies/auto_csv.py) Adapters: -- [`ser/data/adapters/public_csv_datasets.py`](../ser/_internal/data/adapters/public_csv_datasets.py) -- [`ser/data/adapters/csv_manifest_builder.py`](../ser/_internal/data/adapters/csv_manifest_builder.py) +- [`ser/_internal/data/adapters/public_csv_datasets.py`](../ser/_internal/data/adapters/public_csv_datasets.py) +- [`ser/_internal/data/adapters/csv_manifest_builder.py`](../ser/_internal/data/adapters/csv_manifest_builder.py) This subsystem is architecturally strong. Metadata is declarative, behavior is attached to strategies, and application workflows are separated from low-level @@ -277,17 +286,17 @@ Strengths: Limits: -- the `_internal` boundary is not universally enforced -- some public modules still import `_internal` directly by design +- policy-listed public facades may import `_internal` owners only through the + audited `boundary_policy.toml` exception list - several public boundaries still offer optional settings fallbacks for convenience -This is a pragmatic boundary model. It is maintainable, but it depends on team -discipline more than on absolute architectural isolation. +The public-to-private boundary is enforced by Ruff for direct imports and an +AST-aware import-lint check for direct and lazy imports. ## Design patterns in active use -- Facade: `ser.api`, `ser.config`, `ser.data.application` +- Facade: `ser.api`, `ser.config`, `ser.utils` - Strategy: dataset strategy registry and dataset-specific strategy classes - Adapter: transcription adapters, representation backends, backend hooks - Catalog/config-driven runtime selection: profile YAML definitions @@ -307,8 +316,8 @@ discipline more than on absolute architectural isolation. ## Main architectural liabilities -- [`ser/runtime/accurate_inference.py`](../ser/_internal/runtime/accurate_inference.py) and [`ser/runtime/medium_inference.py`](../ser/_internal/runtime/medium_inference.py) remain large public wrappers because they preserve public/runtime signatures, exception surfaces, and worker-entry orchestration seams, even though most compute, retry, and pooling ownership now lives in [`ser/_internal/runtime/accurate_public_boundary.py`](../ser/_internal/runtime/accurate_public_boundary.py), [`ser/_internal/runtime/medium_public_boundary.py`](../ser/_internal/runtime/medium_public_boundary.py), [`ser/runtime/accurate_execution.py`](../ser/_internal/runtime/accurate_execution.py), and [`ser/runtime/medium_execution.py`](../ser/_internal/runtime/medium_execution.py) -- [`ser/transcript/transcript_extractor.py`](../ser/transcript/transcript_extractor.py) remains a large public boundary, but process-isolation, runtime/setup, and wrapper orchestration now route through [`ser/_internal/transcription/public_boundary_process.py`](../ser/_internal/transcription/public_boundary_process.py), [`ser/_internal/transcription/public_boundary_runtime.py`](../ser/_internal/transcription/public_boundary_runtime.py), and [`ser/_internal/transcription/public_boundary_support.py`](../ser/_internal/transcription/public_boundary_support.py) +- [`ser/_internal/runtime/accurate_inference.py`](../ser/_internal/runtime/accurate_inference.py) and [`ser/_internal/runtime/medium_inference.py`](../ser/_internal/runtime/medium_inference.py) remain large internal owner modules because they preserve runtime signatures, exception surfaces, and worker-entry orchestration seams, even though most compute, retry, and pooling ownership lives in [`ser/_internal/runtime/accurate_public_boundary.py`](../ser/_internal/runtime/accurate_public_boundary.py), [`ser/_internal/runtime/medium_public_boundary.py`](../ser/_internal/runtime/medium_public_boundary.py), [`ser/_internal/runtime/accurate_execution.py`](../ser/_internal/runtime/accurate_execution.py), and [`ser/_internal/runtime/medium_execution.py`](../ser/_internal/runtime/medium_execution.py) +- [`ser/_internal/transcript/transcript_extractor.py`](../ser/_internal/transcript/transcript_extractor.py) remains a large internal boundary owner, while process-isolation, runtime/setup, and wrapper orchestration route through [`ser/_internal/transcription/public_boundary_process.py`](../ser/_internal/transcription/public_boundary_process.py), [`ser/_internal/transcription/public_boundary_runtime.py`](../ser/_internal/transcription/public_boundary_runtime.py), and [`ser/_internal/transcription/public_boundary_support.py`](../ser/_internal/transcription/public_boundary_support.py) - [`ser/__main__.py`](../ser/__main__.py) is still a long CLI composition function - optional boundary-level `reload_settings()` fallbacks remain, so DI is still incomplete even though direct `get_settings()` calls have been removed from diff --git a/docs/refactor-hotspot-checks.md b/docs/refactor-hotspot-checks.md index e3dfac8..b8ea192 100644 --- a/docs/refactor-hotspot-checks.md +++ b/docs/refactor-hotspot-checks.md @@ -8,25 +8,25 @@ only as a maintenance heuristic and architecture-reference aid. ## Current hotspot inventory -### Public runtime and model seams +### Internal runtime and model seams - `ser/api.py` - `ser/__main__.py` -- `ser/models/emotion_model.py` -- `ser/models/artifact_metadata.py` -- `ser/models/artifact_persistence.py` -- `ser/models/artifact_loading.py` -- `ser/models/training_types.py` -- `ser/models/accurate_training_execution.py` -- `ser/models/training_preparation.py` -- `ser/models/training_support.py` -- `ser/models/training_execution.py` -- `ser/models/training_reporting.py` -- `ser/runtime/profile_quality_gate.py` -- `ser/runtime/quality_gate_policy.py` -- `ser/runtime/quality_gate_evaluation.py` -- `ser/runtime/quality_gate_reporting.py` -- `ser/runtime/quality_gate_cli.py` +- `ser/_internal/models/emotion_model.py` +- `ser/_internal/models/artifact_metadata.py` +- `ser/_internal/models/artifact_persistence.py` +- `ser/_internal/models/artifact_loading.py` +- `ser/_internal/models/training_types.py` +- `ser/_internal/models/accurate_training_execution.py` +- `ser/_internal/models/training_preparation.py` +- `ser/_internal/models/training_support.py` +- `ser/_internal/models/training_execution.py` +- `ser/_internal/models/training_reporting.py` +- `ser/_internal/runtime/profile_quality_gate.py` +- `ser/_internal/runtime/quality_gate_policy.py` +- `ser/_internal/runtime/quality_gate_evaluation.py` +- `ser/_internal/runtime/quality_gate_reporting.py` +- `ser/_internal/runtime/quality_gate_cli.py` - `ser/_internal/runtime/medium_execution_context.py` - `ser/_internal/runtime/medium_execution_flow.py` - `ser/_internal/runtime/medium_process_operation.py` @@ -35,28 +35,28 @@ only as a maintenance heuristic and architecture-reference aid. - `ser/_internal/runtime/medium_runtime_support.py` - `ser/_internal/runtime/medium_worker_lifecycle.py` - `ser/_internal/runtime/medium_worker_operation.py` -- `ser/runtime/medium_inference.py` -- `ser/runtime/medium_execution.py` -- `ser/runtime/accurate_execution.py` +- `ser/_internal/runtime/medium_inference.py` +- `ser/_internal/runtime/medium_execution.py` +- `ser/_internal/runtime/accurate_execution.py` - `ser/_internal/runtime/accurate_execution_flow.py` - `ser/_internal/runtime/accurate_operation_setup.py` - `ser/_internal/runtime/accurate_runtime_support.py` - `ser/_internal/runtime/accurate_worker_lifecycle.py` - `ser/_internal/runtime/accurate_worker_operation.py` -- `ser/runtime/accurate_inference.py` -- `ser/transcript/transcript_extractor.py` +- `ser/_internal/runtime/accurate_inference.py` +- `ser/_internal/transcript/transcript_extractor.py` ### Data and representation seams -- `ser/utils/dsp.py` -- `ser/features/feature_extractor.py` -- `ser/repr/handcrafted.py` -- `ser/data/application.py` -- `ser/data/catalog/__init__.py` -- `ser/data/catalog/public_datasets.py` -- `ser/data/adapters/csv_manifest_builder.py` -- `ser/data/adapters/public_csv_datasets.py` -- `ser/data/strategies/auto_csv.py` +- `ser/_internal/utils/dsp.py` +- `ser/_internal/features/feature_extractor.py` +- `ser/_internal/repr/handcrafted.py` +- `ser/_internal/data/application/consents.py` +- `ser/_internal/data/catalog/__init__.py` +- `ser/_internal/data/catalog/public_datasets.py` +- `ser/_internal/data/adapters/csv_manifest_builder.py` +- `ser/_internal/data/adapters/public_csv_datasets.py` +- `ser/_internal/data/strategies/auto_csv.py` ### Internal helper seams tied to public boundaries @@ -80,15 +80,15 @@ only as a maintenance heuristic and architecture-reference aid. - `ser/_internal/transcription/ravdess_references.py` - `ser/_internal/transcription/text_metrics.py` - `ser/_internal/transcription/runtime_calibration_workflow.py` -- `ser/transcript/profiling.py` +- `ser/_internal/transcript/profiling.py` ## Recently cleared or obsolete hotspot claims - `ser/config.py` is now a narrow public facade and is no longer a current hotspot. -- `ser/models/emotion_model.py` is now a thin public boundary and is no longer - a primary extraction target. -- `ser/models/training_orchestration.py` is not present in this workspace, so +- Model owners are internal; public workflows should remain on `ser.api` rather + than restoring a public model boundary. +- The removed training-orchestration module is not present in this workspace, so it should not be discussed as a live hotspot here. - `ser/_internal/apt/runtime.py` is not present in this workspace, so it is not a valid current migration target. diff --git a/docs/subsystem-dependency-map.md b/docs/subsystem-dependency-map.md index 8fb4529..2d222b2 100644 --- a/docs/subsystem-dependency-map.md +++ b/docs/subsystem-dependency-map.md @@ -179,7 +179,7 @@ Shared primitives, schemas, utils, external libraries Obsolete references: -- `ser/models/training_orchestration.py` is not present in this workspace +- The removed training-orchestration module is not present in this workspace - `ser/_internal/apt/runtime.py` is not present in this workspace ## Diagnostics subsystem diff --git a/pyproject.toml b/pyproject.toml index 1378471..65e2e76 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -149,13 +149,7 @@ ignore = ["E501"] "ser/__main__.py" = ["TID251"] "ser/api.py" = ["TID251"] "ser/config.py" = ["TID251"] -"ser/data/application.py" = ["TID251"] -"ser/models/emotion_model.py" = ["TID251"] -"ser/models/training_entrypoints.py" = ["TID251"] -"ser/runtime/pipeline.py" = ["TID251"] -"ser/transcript/backends/stable_whisper.py" = ["TID251"] -"ser/transcript/profiling.py" = ["TID251"] -"ser/transcript/transcript_extractor.py" = ["TID251"] +"ser/utils/__init__.py" = ["TID251"] [tool.ruff.format] quote-style = "double" diff --git a/scripts/check_public_internal_imports.py b/scripts/check_public_internal_imports.py new file mode 100644 index 0000000..fedf18f --- /dev/null +++ b/scripts/check_public_internal_imports.py @@ -0,0 +1,264 @@ +#!/usr/bin/env python3 +"""Enforce the public-to-private import boundary from `boundary_policy.toml`.""" + +from __future__ import annotations + +import argparse +import ast +import sys +import tomllib +from dataclasses import dataclass +from pathlib import Path +from typing import cast + +MAX_POLICY_ENTRIES = 10 +PACKAGE_NAME = "ser" +POLICY_FILE_NAME = "boundary_policy.toml" + + +@dataclass(frozen=True) +class _ImportOccurrence: + """One statically identifiable public import of a private module.""" + + source_path: Path + line_number: int + target: str + + +def _mapping(value: object, description: str) -> dict[str, object]: + """Returns a validated mapping from an untrusted TOML boundary.""" + if not isinstance(value, dict): + raise ValueError(f"{description} must be a mapping.") + return cast(dict[str, object], value) + + +def _policy_paths(repo_root: Path) -> set[Path]: + """Loads and validates the exact public files allowed to import private owners.""" + raw_policy: object = tomllib.loads((repo_root / POLICY_FILE_NAME).read_text(encoding="utf-8")) + policy = _mapping(raw_policy, POLICY_FILE_NAME) + raw_entries = policy.get("public_internal_import") + if not isinstance(raw_entries, list): + raise ValueError("boundary policy must define public_internal_import tables.") + if not raw_entries: + raise ValueError("boundary policy must contain at least one public facade.") + if len(raw_entries) > MAX_POLICY_ENTRIES: + raise ValueError( + f"boundary policy has {len(raw_entries)} entries; maximum is {MAX_POLICY_ENTRIES}." + ) + + paths: list[Path] = [] + raw_paths: list[str] = [] + for index, raw_entry in enumerate(raw_entries, start=1): + entry = _mapping(raw_entry, f"boundary policy entry {index}") + raw_path = entry.get("path") + reason = entry.get("reason") + if not isinstance(raw_path, str) or not raw_path.strip(): + raise ValueError(f"boundary policy entry {index} has no non-empty path.") + if not isinstance(reason, str) or not reason.strip(): + raise ValueError(f"boundary policy entry {index} has no non-empty reason.") + normalized_path = raw_path.strip() + if not normalized_path.startswith(f"{PACKAGE_NAME}/") or not normalized_path.endswith( + ".py" + ): + raise ValueError( + f"boundary policy entry {index} must name a public Python source path: {normalized_path!r}." + ) + resolved_path = (repo_root / normalized_path).resolve() + if not resolved_path.is_relative_to(repo_root) or not resolved_path.is_file(): + raise ValueError( + f"boundary policy entry {index} path does not exist: {normalized_path!r}." + ) + if "_internal" in resolved_path.relative_to(repo_root).parts: + raise ValueError(f"boundary policy entry {index} must not name a private source path.") + raw_paths.append(normalized_path) + paths.append(resolved_path) + + if raw_paths != sorted(raw_paths): + raise ValueError("boundary policy entries must be sorted by path.") + if len(set(paths)) != len(paths): + raise ValueError("boundary policy paths must be unique.") + return set(paths) + + +def _module_name(repo_root: Path, source_path: Path) -> str: + """Returns the importable module name for one public source file.""" + relative_path = source_path.relative_to(repo_root).with_suffix("") + parts = list(relative_path.parts) + if parts[-1] == "__init__": + parts.pop() + return ".".join(parts) + + +def _relative_module_name(source_path: Path, repo_root: Path, node: ast.ImportFrom) -> str: + """Resolves a relative import module name without executing source code.""" + if node.level == 0: + return node.module or "" + source_module = _module_name(repo_root, source_path) + source_package = ( + source_module if source_path.name == "__init__.py" else source_module.rpartition(".")[0] + ) + package_parts = source_package.split(".") if source_package else [] + parent_count = node.level - 1 + if parent_count >= len(package_parts): + return node.module or "" + base_parts = package_parts[: len(package_parts) - parent_count] + if node.module: + base_parts.extend(node.module.split(".")) + return ".".join(base_parts) + + +def _is_private_module(module_name: str) -> bool: + """Returns whether an import target is inside the private implementation tree.""" + return module_name == f"{PACKAGE_NAME}._internal" or module_name.startswith( + f"{PACKAGE_NAME}._internal." + ) + + +def _literal_string(node: ast.expr) -> str | None: + """Returns one statically known string import target, if present.""" + return node.value if isinstance(node, ast.Constant) and isinstance(node.value, str) else None + + +def _importlib_aliases(tree: ast.Module) -> tuple[set[str], set[str]]: + """Finds local names that can invoke `importlib.import_module`.""" + module_aliases = {"importlib"} + function_aliases = {"import_module"} + for node in ast.walk(tree): + if isinstance(node, ast.Import): + for alias in node.names: + if alias.name == "importlib": + module_aliases.add(alias.asname or alias.name) + elif isinstance(node, ast.ImportFrom) and node.module == "importlib": + for alias in node.names: + if alias.name == "import_module": + function_aliases.add(alias.asname or alias.name) + return module_aliases, function_aliases + + +def _is_dynamic_import_call( + node: ast.Call, + *, + module_aliases: set[str], + function_aliases: set[str], +) -> bool: + """Returns whether one call performs a dynamic module import.""" + if isinstance(node.func, ast.Name): + return node.func.id in function_aliases or node.func.id == "__import__" + return ( + isinstance(node.func, ast.Attribute) + and isinstance(node.func.value, ast.Name) + and node.func.value.id in module_aliases + and node.func.attr == "import_module" + ) + + +def _discover_private_imports(source_path: Path, repo_root: Path) -> tuple[_ImportOccurrence, ...]: + """Finds direct and dynamic private imports in one public module. + + Dynamic import targets must be literal strings so the boundary remains auditable. + """ + tree = ast.parse(source_path.read_text(encoding="utf-8"), filename=str(source_path)) + module_aliases, function_aliases = _importlib_aliases(tree) + occurrences: list[_ImportOccurrence] = [] + + for node in ast.walk(tree): + if isinstance(node, ast.Import): + for alias in node.names: + if _is_private_module(alias.name): + occurrences.append(_ImportOccurrence(source_path, node.lineno, alias.name)) + elif isinstance(node, ast.ImportFrom): + imported_module = _relative_module_name(source_path, repo_root, node) + if _is_private_module(imported_module): + occurrences.append(_ImportOccurrence(source_path, node.lineno, imported_module)) + elif imported_module == PACKAGE_NAME and any( + alias.name == "_internal" for alias in node.names + ): + occurrences.append( + _ImportOccurrence(source_path, node.lineno, f"{PACKAGE_NAME}._internal") + ) + elif isinstance(node, ast.Call) and _is_dynamic_import_call( + node, + module_aliases=module_aliases, + function_aliases=function_aliases, + ): + if not node.args: + raise ValueError( + f"{source_path}:{node.lineno}: dynamic import has no target argument." + ) + target = _literal_string(node.args[0]) + if target is None: + raise ValueError( + f"{source_path}:{node.lineno}: dynamic import target must be a literal string." + ) + if target.startswith("."): + raise ValueError( + f"{source_path}:{node.lineno}: relative dynamic imports are not permitted in public modules." + ) + if _is_private_module(target): + occurrences.append(_ImportOccurrence(source_path, node.lineno, target)) + return tuple(occurrences) + + +def check_public_internal_imports(repo_root: Path) -> tuple[str, ...]: + """Returns actionable boundary-policy violations for the repository. + + Args: + repo_root: Repository root containing `ser` and `boundary_policy.toml`. + + Returns: + Sorted human-readable violations. An empty tuple means the boundary holds. + """ + policy_paths = _policy_paths(repo_root) + package_root = repo_root / PACKAGE_NAME + violations: list[str] = [] + occurrences: list[_ImportOccurrence] = [] + for source_path in sorted(package_root.rglob("*.py")): + if "_internal" in source_path.relative_to(package_root).parts: + continue + try: + occurrences.extend(_discover_private_imports(source_path, repo_root)) + except (SyntaxError, ValueError) as error: + violations.append(str(error)) + + discovered_paths = {occurrence.source_path.resolve() for occurrence in occurrences} + unexpected_paths = sorted(discovered_paths - policy_paths) + stale_paths = sorted(policy_paths - discovered_paths) + for occurrence in occurrences: + if occurrence.source_path.resolve() in unexpected_paths: + relative_path = occurrence.source_path.relative_to(repo_root) + violations.append( + f"{relative_path}:{occurrence.line_number}: imports private target {occurrence.target!r} " + "without a boundary-policy entry." + ) + for source_path in stale_paths: + violations.append( + f"{source_path.relative_to(repo_root)}: boundary-policy entry is stale; no private import found." + ) + return tuple(sorted(set(violations))) + + +def main() -> int: + """CLI entry point.""" + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--repo-root", + type=Path, + default=Path(__file__).resolve().parent.parent, + help="repository root to validate", + ) + args = parser.parse_args() + repo_root = args.repo_root.resolve() + try: + violations = check_public_internal_imports(repo_root) + except (OSError, ValueError, tomllib.TOMLDecodeError) as error: + print(f"public-to-private import check failed: {error}", file=sys.stderr) + return 1 + if not violations: + return 0 + for violation in violations: + print(violation, file=sys.stderr) + return 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/dump_public_api.py b/scripts/dump_public_api.py index 83155f7..9500ebe 100644 --- a/scripts/dump_public_api.py +++ b/scripts/dump_public_api.py @@ -13,6 +13,7 @@ SCHEMA_VERSION = 1 SNAPSHOT_PATH = Path("tests/suites/integration/architecture/public_api_snapshot.json") +REPO_ROOT = Path(__file__).resolve().parent.parent TIER_ONE_MODULES = ( "ser", "ser.api", @@ -146,12 +147,25 @@ def _class_snapshot(member: Any) -> JsonObject: return snapshot +def _resolved_alias_target(member: Any) -> Any: + """Resolves one Griffe alias chain without exposing implementation paths.""" + target = member + seen_aliases: set[int] = set() + while bool(getattr(target, "is_alias", False)): + target_id = id(target) + if target_id in seen_aliases: + raise ValueError(f"Cyclic public alias resolution for {member.path}.") + seen_aliases.add(target_id) + target = target.target + return target + + def _alias_snapshot(member: Any) -> JsonObject: - """Returns a stable alias snapshot without forcing target resolution.""" - target_path = getattr(member, "target_path", None) + """Returns one alias snapshot with its resolved public contract shape.""" + target = _resolved_alias_target(member) return { - "kind": _kind(member), - "target_path": str(target_path) if target_path is not None else None, + "kind": "alias", + "contract": _member_snapshot(target), } @@ -176,7 +190,7 @@ def dump_public_api(repo_root: Path) -> JsonObject: modules: dict[str, JsonValue] = {} search_paths = [str(repo_root)] for module_name in TIER_ONE_MODULES: - module = griffe.load(module_name, search_paths=search_paths) + module = griffe.load(module_name, search_paths=search_paths, resolve_aliases=True) exports: dict[str, JsonValue] = {} for export_name in _exported_names(module, module_name): member = module.members.get(export_name) @@ -210,7 +224,7 @@ def main() -> int: ) args = parser.parse_args() - repo_root = Path.cwd() + repo_root = REPO_ROOT snapshot_text = _json_text(dump_public_api(repo_root)) if args.write: output_path = repo_root / SNAPSHOT_PATH diff --git a/scripts/run_import_lint.sh b/scripts/run_import_lint.sh index cb5c512..d41a954 100755 --- a/scripts/run_import_lint.sh +++ b/scripts/run_import_lint.sh @@ -7,6 +7,7 @@ mapfile -t IMPORT_LINT_PATHS < <( ) uv run --frozen --extra dev ruff check --select TID251 "${IMPORT_LINT_PATHS[@]}" +uv run --frozen --extra dev python scripts/check_public_internal_imports.py uv run --frozen --extra dev pytest -q \ tests/suites/integration/architecture/test_api_import_boundary.py \ tests/suites/integration/architecture/test_import_lint_policy.py diff --git a/ser/__main__.py b/ser/__main__.py index af49522..e9fa58d 100644 --- a/ser/__main__.py +++ b/ser/__main__.py @@ -30,12 +30,12 @@ run_transcription_runtime_calibration_command, ) from ser._internal.runtime.phase_timing import format_duration +from ser._internal.utils.logger import configure_logging, get_logger from ser._internal.utils.subtitles import SUPPORTED_SUBTITLE_FORMATS from ser.config import AppConfig, reload_settings, settings_override from ser.diagnostics.domain import PreflightMode from ser.profiles import ProfileName from ser.runtime.contracts import SubtitleFormat -from ser.utils.logger import configure_logging, get_logger logger: logging.Logger = get_logger("ser") type CliProfileName = ProfileName diff --git a/ser/_internal/api/data.py b/ser/_internal/api/data.py index 75a8d01..99be565 100644 --- a/ser/_internal/api/data.py +++ b/ser/_internal/api/data.py @@ -6,9 +6,9 @@ from pathlib import Path from typing import TYPE_CHECKING, Literal +from ser._internal.utils.logger import get_logger from ser.config import AppConfig from ser.domain import DatasetConsents -from ser.utils.logger import get_logger logger = get_logger(__name__) @@ -78,7 +78,7 @@ def list_registered_datasets( settings: AppConfig, ) -> tuple[DatasetRegistryRecord, ...]: """Returns persisted dataset registry records ordered by dataset id.""" - from ser.data.application import collect_dataset_registry_snapshot + from ser._internal.data.application.registry_snapshot import collect_dataset_registry_snapshot snapshot = collect_dataset_registry_snapshot(settings=settings) records: list[DatasetRegistryRecord] = [] @@ -102,7 +102,7 @@ def list_dataset_registry_health_issues( settings: AppConfig, ) -> tuple[DatasetRegistryHealthIssueRecord, ...]: """Returns deterministic dataset registry health issues.""" - from ser.data.application import collect_dataset_registry_snapshot + from ser._internal.data.application.registry_snapshot import collect_dataset_registry_snapshot snapshot = collect_dataset_registry_snapshot(settings=settings) return tuple( @@ -171,12 +171,12 @@ def prepare_dataset( settings: AppConfig, ) -> DatasetPrepareResult: """Programmatic dataset acquisition + manifest preparation.""" - from ser._internal.data.dataset_consents import DatasetConsentError - from ser.data.application import ( + from ser._internal.data.application.consents import ( compute_dataset_descriptor_missing_consents, persist_missing_dataset_descriptor_consents, - run_dataset_prepare_workflow, ) + from ser._internal.data.application.prepare import run_dataset_prepare_workflow + from ser._internal.data.dataset_consents import DatasetConsentError if compliance_mode not in {"advisory", "strict"}: raise ValueError( diff --git a/ser/_internal/api/runtime.py b/ser/_internal/api/runtime.py index ed8ffa8..f822fec 100644 --- a/ser/_internal/api/runtime.py +++ b/ser/_internal/api/runtime.py @@ -42,8 +42,8 @@ from ser._internal.runtime.restricted_backends import ( run_restricted_backend_cli_gate as _run_restricted_backend_cli_gate, ) +from ser._internal.utils.logger import get_logger from ser.profiles import ProfileName, get_profile_catalog, resolve_profile_name -from ser.utils.logger import get_logger logger = get_logger(__name__) @@ -243,7 +243,7 @@ def load_profile( ) -> None: """Validates profile availability and dependency capability for execution.""" from ser._internal.runtime.backend_hooks import build_backend_hooks - from ser.runtime.registry import ( + from ser._internal.runtime.registry import ( ensure_profile_supported, resolve_runtime_capability, ) @@ -294,7 +294,7 @@ def run_inference_workflow( pipeline_builder: _RuntimePipelineBuilder | None = None, ) -> InferenceExecution: """Runs CLI-equivalent inference workflow through one API boundary.""" - from ser.runtime import InferenceRequest + from ser.runtime.contracts import InferenceRequest builder = pipeline_builder if pipeline_builder is not None else _build_runtime_pipeline request = InferenceRequest( diff --git a/ser/_internal/data/adapters/biic_podcast.py b/ser/_internal/data/adapters/biic_podcast.py index 52088be..825a50b 100644 --- a/ser/_internal/data/adapters/biic_podcast.py +++ b/ser/_internal/data/adapters/biic_podcast.py @@ -9,7 +9,7 @@ from ser._internal.data.manifest import SplitName, Utterance from ser._internal.data.manifest_jsonl import write_manifest_jsonl from ser._internal.data.ontology import LabelOntology, remap_label -from ser.utils.logger import get_logger +from ser._internal.utils.logger import get_logger logger = get_logger(__name__) diff --git a/ser/_internal/data/adapters/crema_d.py b/ser/_internal/data/adapters/crema_d.py index 1e877a5..c1f426e 100644 --- a/ser/_internal/data/adapters/crema_d.py +++ b/ser/_internal/data/adapters/crema_d.py @@ -9,7 +9,7 @@ from ser._internal.data.manifest import Utterance from ser._internal.data.manifest_jsonl import write_manifest_jsonl from ser._internal.data.ontology import LabelOntology, remap_label -from ser.utils.logger import get_logger +from ser._internal.utils.logger import get_logger logger = get_logger(__name__) diff --git a/ser/_internal/data/adapters/csv_manifest_builder.py b/ser/_internal/data/adapters/csv_manifest_builder.py index 965c82e..7546b80 100644 --- a/ser/_internal/data/adapters/csv_manifest_builder.py +++ b/ser/_internal/data/adapters/csv_manifest_builder.py @@ -10,7 +10,7 @@ from ser._internal.data.manifest import SplitName, Utterance from ser._internal.data.manifest_jsonl import write_manifest_jsonl from ser._internal.data.ontology import LabelOntology, remap_label -from ser.utils.logger import get_logger +from ser._internal.utils.logger import get_logger logger = get_logger(__name__) diff --git a/ser/_internal/data/adapters/msp_podcast.py b/ser/_internal/data/adapters/msp_podcast.py index dca6840..0e0531e 100644 --- a/ser/_internal/data/adapters/msp_podcast.py +++ b/ser/_internal/data/adapters/msp_podcast.py @@ -9,7 +9,7 @@ from ser._internal.data.manifest import SplitName, Utterance from ser._internal.data.manifest_jsonl import write_manifest_jsonl from ser._internal.data.ontology import LabelOntology, remap_label -from ser.utils.logger import get_logger +from ser._internal.utils.logger import get_logger logger = get_logger(__name__) diff --git a/ser/_internal/data/adapters/ravdess.py b/ser/_internal/data/adapters/ravdess.py index adbebb7..ece5f4d 100644 --- a/ser/_internal/data/adapters/ravdess.py +++ b/ser/_internal/data/adapters/ravdess.py @@ -10,7 +10,7 @@ from ser._internal.data.manifest import Utterance from ser._internal.data.manifest_jsonl import write_manifest_jsonl from ser._internal.data.ontology import LabelOntology, remap_label -from ser.utils.logger import get_logger +from ser._internal.utils.logger import get_logger logger = get_logger(__name__) diff --git a/ser/data/application.py b/ser/_internal/data/application/consents.py similarity index 100% rename from ser/data/application.py rename to ser/_internal/data/application/consents.py diff --git a/ser/_internal/data/cli.py b/ser/_internal/data/cli.py index 60b761b..67f41fa 100644 --- a/ser/_internal/data/cli.py +++ b/ser/_internal/data/cli.py @@ -7,6 +7,20 @@ import sys from pathlib import Path +from ser._internal.data.application.capability_snapshot import ( + build_dataset_capability_snapshot_json_payload, + collect_dataset_capability_snapshot, +) +from ser._internal.data.application.consents import ( + compute_dataset_descriptor_missing_consents, + persist_missing_dataset_descriptor_consents, +) +from ser._internal.data.application.prepare import run_dataset_prepare_workflow +from ser._internal.data.application.registry_snapshot import ( + build_dataset_registry_snapshot_json_payload, + collect_dataset_registry_snapshot, +) +from ser._internal.data.application.uninstall import run_dataset_uninstall_workflow from ser._internal.data.dataset_consents import ( DatasetConsentError, load_persisted_dataset_consents, @@ -14,16 +28,6 @@ ) from ser._internal.data.dataset_prepare import SUPPORTED_DATASETS from ser.config import AppConfig -from ser.data.application import ( - build_dataset_capability_snapshot_json_payload, - build_dataset_registry_snapshot_json_payload, - collect_dataset_capability_snapshot, - collect_dataset_registry_snapshot, - compute_dataset_descriptor_missing_consents, - persist_missing_dataset_descriptor_consents, - run_dataset_prepare_workflow, - run_dataset_uninstall_workflow, -) def _is_interactive() -> bool: diff --git a/ser/_internal/data/data_loader.py b/ser/_internal/data/data_loader.py index c7f2e3c..9588c48 100644 --- a/ser/_internal/data/data_loader.py +++ b/ser/_internal/data/data_loader.py @@ -20,8 +20,8 @@ from ser._internal.data.manifest import Utterance from ser._internal.data.manifest_jsonl import load_manifest_jsonl from ser._internal.features.feature_extractor import _extract_feature_for_settings +from ser._internal.utils.logger import get_logger from ser.config import AppConfig, AudioReadConfig, FeatureFlags, reload_settings -from ser.utils.logger import get_logger logger: logging.Logger = get_logger(__name__) diff --git a/ser/_internal/data/dataset_prepare.py b/ser/_internal/data/dataset_prepare.py index f430a90..3aa67ca 100644 --- a/ser/_internal/data/dataset_prepare.py +++ b/ser/_internal/data/dataset_prepare.py @@ -30,8 +30,8 @@ DatasetStrategyRegistry, build_default_dataset_strategies, ) +from ser._internal.utils.logger import get_logger from ser.config import AppConfig -from ser.utils.logger import get_logger logger = get_logger(__name__) diff --git a/ser/_internal/data/embedding_cache.py b/ser/_internal/data/embedding_cache.py index 023a34e..5e22be1 100644 --- a/ser/_internal/data/embedding_cache.py +++ b/ser/_internal/data/embedding_cache.py @@ -15,7 +15,7 @@ from numpy.typing import NDArray from ser._internal.repr import EncodedSequence -from ser.utils.logger import get_logger +from ser._internal.utils.logger import get_logger logger: Logger = get_logger(__name__) diff --git a/ser/_internal/data/msp_podcast_mirror.py b/ser/_internal/data/msp_podcast_mirror.py index 80a4908..0233e78 100644 --- a/ser/_internal/data/msp_podcast_mirror.py +++ b/ser/_internal/data/msp_podcast_mirror.py @@ -18,7 +18,7 @@ from pathlib import Path, PurePosixPath from typing import Any -from ser.utils.logger import get_logger +from ser._internal.utils.logger import get_logger logger = get_logger(__name__) diff --git a/ser/_internal/data/public_dataset_downloads.py b/ser/_internal/data/public_dataset_downloads.py index 92e58ea..9f6c19a 100644 --- a/ser/_internal/data/public_dataset_downloads.py +++ b/ser/_internal/data/public_dataset_downloads.py @@ -42,7 +42,7 @@ infer_label_from_path_tokens as _infer_label_from_path_tokens, ) from ser._internal.data.public_dataset_label_inference import infer_mesd_label as _infer_mesd_label -from ser.utils.logger import get_logger +from ser._internal.utils.logger import get_logger logger = get_logger(__name__) diff --git a/ser/_internal/data/strategies/auto_csv.py b/ser/_internal/data/strategies/auto_csv.py index 3fc6978..79e1a6b 100644 --- a/ser/_internal/data/strategies/auto_csv.py +++ b/ser/_internal/data/strategies/auto_csv.py @@ -37,8 +37,8 @@ prepare_pavoque_from_github_release, prepare_spanish_meacorpus_2023_from_zenodo, ) +from ser._internal.utils.logger import get_logger from ser.config import AppConfig -from ser.utils.logger import get_logger from .base import PreparedManifestResult diff --git a/ser/_internal/data/strategies/default.py b/ser/_internal/data/strategies/default.py index 0fae0db..fd8e38d 100644 --- a/ser/_internal/data/strategies/default.py +++ b/ser/_internal/data/strategies/default.py @@ -22,8 +22,8 @@ from ser._internal.data.public_dataset_downloads import ( prepare_ravdess_from_zenodo, ) +from ser._internal.utils.logger import get_logger from ser.config import AppConfig -from ser.utils.logger import get_logger from .auto_csv import ( AsvpEsdDatasetStrategy, diff --git a/ser/_internal/diagnostics/service.py b/ser/_internal/diagnostics/service.py index 8f341f6..76aa499 100644 --- a/ser/_internal/diagnostics/service.py +++ b/ser/_internal/diagnostics/service.py @@ -9,6 +9,12 @@ from ser._internal.config.bootstrap import resolve_profile_transcription_config from ser._internal.data.dataset_prepare import collect_dataset_registry_health_issues +from ser._internal.runtime.registry import resolve_runtime_capability +from ser._internal.transcript.backends import ( + BackendRuntimeRequest, + CompatibilityIssueImpact, + resolve_transcription_backend_adapter, +) from ser._internal.transcript.runtime_policy import ( DEFAULT_MPS_LOW_MEMORY_THRESHOLD_GB, resolve_transcription_runtime_policy, @@ -24,12 +30,6 @@ PreflightMode, ) from ser.profiles import ProfileName, get_profile_catalog, resolve_profile_name -from ser.runtime.registry import resolve_runtime_capability -from ser.transcript.backends import ( - BackendRuntimeRequest, - CompatibilityIssueImpact, - resolve_transcription_backend_adapter, -) _SUPPORTED_PREFLIGHT_MODES: frozenset[str] = frozenset({"off", "warn", "strict"}) diff --git a/ser/_internal/features/feature_extractor.py b/ser/_internal/features/feature_extractor.py index ff91055..9d823e4 100644 --- a/ser/_internal/features/feature_extractor.py +++ b/ser/_internal/features/feature_extractor.py @@ -11,8 +11,8 @@ from ser._internal.repr.backend import EncodedSequence from ser._internal.utils.audio_utils import read_audio_file from ser._internal.utils.dsp import extract_feature_from_signal as _extract_feature_from_signal +from ser._internal.utils.logger import get_logger from ser.config import AppConfig, AudioReadConfig, FeatureFlags, reload_settings -from ser.utils.logger import get_logger type FeatureVector = NDArray[np.float64] logger = get_logger(__name__) diff --git a/ser/license_check.py b/ser/_internal/license_check.py similarity index 100% rename from ser/license_check.py rename to ser/_internal/license_check.py diff --git a/ser/_internal/models/accurate_feature_dataset.py b/ser/_internal/models/accurate_feature_dataset.py index 218e5ab..99811fa 100644 --- a/ser/_internal/models/accurate_feature_dataset.py +++ b/ser/_internal/models/accurate_feature_dataset.py @@ -25,10 +25,10 @@ from ser._internal.models.profile_training_preparation import ( build_accurate_feature_dataset as _build_prepared_accurate_feature_dataset, ) +from ser._internal.pool import mean_std_pool, temporal_pooling_windows from ser._internal.repr import EncodedSequence, FeatureBackend, PoolingWindow from ser._internal.utils.audio_utils import read_audio_file from ser.config import AppConfig -from ser.pool import mean_std_pool, temporal_pooling_windows _UtteranceT = TypeVar("_UtteranceT", bound=UtteranceLike) _MetaT = TypeVar("_MetaT") diff --git a/ser/_internal/models/accurate_training_execution.py b/ser/_internal/models/accurate_training_execution.py index 9c1c98a..daa7f40 100644 --- a/ser/_internal/models/accurate_training_execution.py +++ b/ser/_internal/models/accurate_training_execution.py @@ -10,6 +10,7 @@ from typing import Any, Protocol, TypeVar from ser._internal.data import Utterance +from ser._internal.license_check import build_provenance_metadata from ser._internal.models import training_support as _training_support from ser._internal.models.artifact_envelope import build_model_artifact as _build_model_artifact from ser._internal.models.artifact_persistence import ( @@ -26,7 +27,6 @@ TrainingEvaluation, ) from ser.config import AppConfig -from ser.license_check import build_provenance_metadata _ModelT = TypeVar("_ModelT") diff --git a/ser/_internal/models/artifact_persistence.py b/ser/_internal/models/artifact_persistence.py index 80b2999..e28d730 100644 --- a/ser/_internal/models/artifact_persistence.py +++ b/ser/_internal/models/artifact_persistence.py @@ -13,7 +13,7 @@ from sklearn.neural_network import MLPClassifier from sklearn.pipeline import Pipeline -from ser.utils.logger import get_logger +from ser._internal.utils.logger import get_logger type EmotionClassifier = MLPClassifier | Pipeline _PersistedArtifactsT = TypeVar("_PersistedArtifactsT") diff --git a/ser/_internal/models/dataset_splitting.py b/ser/_internal/models/dataset_splitting.py index 1a7fbc3..46431b1 100644 --- a/ser/_internal/models/dataset_splitting.py +++ b/ser/_internal/models/dataset_splitting.py @@ -12,8 +12,8 @@ from ser._internal.data import LabeledAudioSample, Utterance from ser._internal.data.data_loader import extract_ravdess_speaker_id_from_path +from ser._internal.train.eval import grouped_train_test_split from ser.config import AppConfig -from ser.train.eval import grouped_train_test_split @dataclass(frozen=True) diff --git a/ser/_internal/models/emotion_model.py b/ser/_internal/models/emotion_model.py index 4e8dcce..6c91156 100644 --- a/ser/_internal/models/emotion_model.py +++ b/ser/_internal/models/emotion_model.py @@ -8,6 +8,7 @@ import ser._internal.models.artifact_envelope as _artifact_envelope import ser._internal.models.model_loading as _model_loading_entrypoints import ser._internal.models.profile_runtime as _profile_runtime +import ser._internal.models.training_entrypoints as _training_entrypoints import ser._internal.models.training_support as _training_support from ser._internal.features import extract_feature_frames from ser._internal.models.artifact_envelope import LoadedModel @@ -22,11 +23,10 @@ from ser._internal.models.model_loading import load_model as _load_model_entrypoint from ser._internal.runtime.environment_plan import build_runtime_environment_plan from ser._internal.runtime.process_env import temporary_process_env +from ser._internal.utils.logger import get_logger from ser.config import AppConfig, reload_settings from ser.domain import EmotionSegment -from ser.models import training_entrypoints as _training_entrypoints from ser.runtime.schema import OUTPUT_SCHEMA_VERSION, InferenceResult, to_legacy_emotion_segments -from ser.utils.logger import get_logger logger: logging.Logger = get_logger(__name__) diff --git a/ser/_internal/models/medium_feature_dataset.py b/ser/_internal/models/medium_feature_dataset.py index 7bc3c42..355373b 100644 --- a/ser/_internal/models/medium_feature_dataset.py +++ b/ser/_internal/models/medium_feature_dataset.py @@ -22,10 +22,10 @@ from ser._internal.models.profile_training_preparation import ( build_medium_feature_dataset as _build_prepared_medium_feature_dataset, ) +from ser._internal.pool import mean_std_pool, temporal_pooling_windows from ser._internal.repr import EncodedSequence, PoolingWindow, XLSRBackend from ser._internal.utils.audio_utils import read_audio_file from ser.config import AppConfig -from ser.pool import mean_std_pool, temporal_pooling_windows _UtteranceT = TypeVar("_UtteranceT", bound=UtteranceLike) _MetaT = TypeVar("_MetaT") diff --git a/ser/_internal/models/training_entrypoints.py b/ser/_internal/models/training_entrypoints.py index 2e46b52..420e78f 100644 --- a/ser/_internal/models/training_entrypoints.py +++ b/ser/_internal/models/training_entrypoints.py @@ -6,6 +6,7 @@ from functools import partial import ser._internal.data.data_loader as _data_loader +import ser._internal.license_check as _license_check import ser._internal.models.accurate_feature_dataset as _accurate_feature_dataset import ser._internal.models.accurate_training_execution as _accurate_training_execution import ser._internal.models.accurate_training_preparation as _accurate_training_preparation @@ -19,11 +20,10 @@ import ser._internal.models.profile_runtime as _profile_runtime import ser._internal.models.training_reporting as _training_reporting import ser._internal.models.training_support as _training_support -import ser.license_check as _license_check from ser._internal.runtime.environment_plan import build_runtime_environment_plan from ser._internal.runtime.process_env import temporary_process_env +from ser._internal.utils.logger import get_logger from ser.config import AppConfig -from ser.utils.logger import get_logger logger = get_logger(__name__) diff --git a/ser/_internal/models/training_preparation.py b/ser/_internal/models/training_preparation.py index a357590..8dc692d 100644 --- a/ser/_internal/models/training_preparation.py +++ b/ser/_internal/models/training_preparation.py @@ -14,7 +14,7 @@ MediumTrainingPreparation, TrainingEvaluation, ) -from ser.train.metrics import compute_grouped_ser_metrics_by_sample, compute_ser_metrics +from ser._internal.train.metrics import compute_grouped_ser_metrics_by_sample, compute_ser_metrics _UtteranceT = TypeVar("_UtteranceT") _SplitMetaT = TypeVar("_SplitMetaT") diff --git a/ser/pool/__init__.py b/ser/_internal/pool/__init__.py similarity index 100% rename from ser/pool/__init__.py rename to ser/_internal/pool/__init__.py diff --git a/ser/pool/stats_pool.py b/ser/_internal/pool/stats_pool.py similarity index 93% rename from ser/pool/stats_pool.py rename to ser/_internal/pool/stats_pool.py index 16a6f67..ba15695 100644 --- a/ser/pool/stats_pool.py +++ b/ser/_internal/pool/stats_pool.py @@ -7,7 +7,7 @@ import numpy as np from numpy.typing import NDArray -from ser.repr import EncodedSequence, PoolingWindow, overlap_frame_mask +from ser._internal.repr import EncodedSequence, PoolingWindow, overlap_frame_mask type PooledFeatureMatrix = NDArray[np.float64] diff --git a/ser/pool/windowing.py b/ser/_internal/pool/windowing.py similarity index 97% rename from ser/pool/windowing.py rename to ser/_internal/pool/windowing.py index 58eb0ce..1a51db8 100644 --- a/ser/pool/windowing.py +++ b/ser/_internal/pool/windowing.py @@ -4,7 +4,7 @@ import numpy as np -from ser.repr import EncodedSequence, PoolingWindow +from ser._internal.repr import EncodedSequence, PoolingWindow def temporal_pooling_windows( diff --git a/ser/_internal/repr/emotion2vec.py b/ser/_internal/repr/emotion2vec.py index 9ade09e..2ab2a43 100644 --- a/ser/_internal/repr/emotion2vec.py +++ b/ser/_internal/repr/emotion2vec.py @@ -22,12 +22,12 @@ ) from ser._internal.runtime.environment_plan import ProcessEnvDelta from ser._internal.runtime.process_env import temporary_process_env -from ser._internal.utils.torch_inference import normalize_torch_device_selector -from ser.utils.logger import ( +from ser._internal.utils.logger import ( DependencyLogPolicy, get_logger, scoped_dependency_log_policy, ) +from ser._internal.utils.torch_inference import normalize_torch_device_selector logger = get_logger(__name__) _NOISY_DEPENDENCY_POLICY: Final[DependencyLogPolicy] = DependencyLogPolicy( diff --git a/ser/_internal/repr/hf_whisper.py b/ser/_internal/repr/hf_whisper.py index ff1f7ec..2123a6e 100644 --- a/ser/_internal/repr/hf_whisper.py +++ b/ser/_internal/repr/hf_whisper.py @@ -19,6 +19,7 @@ PoolingWindow, overlap_frame_mask, ) +from ser._internal.utils.logger import get_logger from ser._internal.utils.torch_inference import ( TorchRuntime, inference_context, @@ -27,7 +28,6 @@ move_model_to_runtime, runtime_with_dtype, ) -from ser.utils.logger import get_logger logger = get_logger(__name__) diff --git a/ser/_internal/repr/hf_xlsr.py b/ser/_internal/repr/hf_xlsr.py index 092b209..34ecd1d 100644 --- a/ser/_internal/repr/hf_xlsr.py +++ b/ser/_internal/repr/hf_xlsr.py @@ -19,6 +19,7 @@ PoolingWindow, overlap_frame_mask, ) +from ser._internal.utils.logger import get_logger from ser._internal.utils.torch_inference import ( TorchRuntime, inference_context, @@ -27,7 +28,6 @@ move_model_to_runtime, runtime_with_dtype, ) -from ser.utils.logger import get_logger logger = get_logger(__name__) diff --git a/ser/_internal/runtime/accurate_execution.py b/ser/_internal/runtime/accurate_execution.py index a6d6bd7..3719566 100644 --- a/ser/_internal/runtime/accurate_execution.py +++ b/ser/_internal/runtime/accurate_execution.py @@ -8,13 +8,13 @@ import numpy as np from numpy.typing import NDArray +from ser._internal.pool import mean_std_pool, temporal_pooling_windows from ser._internal.repr import EncodedSequence, PoolingWindow from ser._internal.runtime.postprocessing import ( build_segment_postprocessing_config, postprocess_frame_predictions, ) from ser.config import ProfileRuntimeConfig -from ser.pool import mean_std_pool, temporal_pooling_windows from ser.runtime.schema import ( OUTPUT_SCHEMA_VERSION, FramePrediction, diff --git a/ser/_internal/runtime/accurate_inference.py b/ser/_internal/runtime/accurate_inference.py index 0d76514..9b05e82 100644 --- a/ser/_internal/runtime/accurate_inference.py +++ b/ser/_internal/runtime/accurate_inference.py @@ -5,10 +5,10 @@ from ser._internal.models.emotion_model import LoadedModel from ser._internal.repr import FeatureBackend from ser._internal.runtime import accurate_public_boundary as _boundary_support +from ser._internal.utils.logger import get_logger from ser.config import AppConfig from ser.runtime.contracts import InferenceRequest from ser.runtime.schema import InferenceResult -from ser.utils.logger import get_logger logger = get_logger(__name__) diff --git a/ser/_internal/runtime/backend_hooks.py b/ser/_internal/runtime/backend_hooks.py index dfa94b8..a6dab1b 100644 --- a/ser/_internal/runtime/backend_hooks.py +++ b/ser/_internal/runtime/backend_hooks.py @@ -7,13 +7,13 @@ from collections.abc import Callable, Mapping from typing import cast -from ser.config import AppConfig -from ser.license_check import ( +from ser._internal.license_check import ( BackendConsentRecord, ensure_backend_access, load_persisted_backend_consents, parse_allowed_restricted_backends_env, ) +from ser.config import AppConfig from ser.profiles import get_profile_catalog from ser.runtime.contracts import BackendInferenceCallable, InferenceRequest from ser.runtime.schema import InferenceResult diff --git a/ser/_internal/runtime/commands.py b/ser/_internal/runtime/commands.py index 8611bd3..e21062e 100644 --- a/ser/_internal/runtime/commands.py +++ b/ser/_internal/runtime/commands.py @@ -44,6 +44,7 @@ def classify_training_exception(err: Exception) -> WorkflowErrorDisposition: def classify_inference_exception(err: Exception) -> WorkflowErrorDisposition: """Classifies one inference exception into CLI logging/exit behavior.""" + from ser._internal.license_check import BackendLicensePolicyError from ser._internal.runtime.accurate_inference import ( AccurateInferenceExecutionError, AccurateInferenceTimeoutError, @@ -64,9 +65,8 @@ def classify_inference_exception(err: Exception) -> WorkflowErrorDisposition: MediumModelUnavailableError, MediumRuntimeDependencyError, ) - from ser.license_check import BackendLicensePolicyError - from ser.runtime import UnsupportedProfileError - from ser.transcript import TranscriptionError + from ser._internal.runtime.registry import UnsupportedProfileError + from ser._internal.transcript.transcript_extractor import TranscriptionError if isinstance(err, UnsupportedProfileError): return WorkflowErrorDisposition(exit_code=2, message=str(err)) diff --git a/ser/_internal/runtime/fast_inference.py b/ser/_internal/runtime/fast_inference.py index 9e7ac6f..2835728 100644 --- a/ser/_internal/runtime/fast_inference.py +++ b/ser/_internal/runtime/fast_inference.py @@ -4,10 +4,10 @@ from ser._internal.models.emotion_model import LoadedModel from ser._internal.runtime import fast_public_boundary as _boundary_support +from ser._internal.utils.logger import get_logger from ser.config import AppConfig from ser.runtime.contracts import InferenceRequest from ser.runtime.schema import InferenceResult -from ser.utils.logger import get_logger logger = get_logger(__name__) diff --git a/ser/_internal/runtime/medium_execution.py b/ser/_internal/runtime/medium_execution.py index f7b5e58..bb9ac09 100644 --- a/ser/_internal/runtime/medium_execution.py +++ b/ser/_internal/runtime/medium_execution.py @@ -8,13 +8,13 @@ import numpy as np from numpy.typing import NDArray +from ser._internal.pool import mean_std_pool, temporal_pooling_windows from ser._internal.repr import EncodedSequence, PoolingWindow from ser._internal.runtime.postprocessing import ( build_segment_postprocessing_config, postprocess_frame_predictions, ) from ser.config import MediumRuntimeConfig -from ser.pool import mean_std_pool, temporal_pooling_windows from ser.runtime.schema import ( OUTPUT_SCHEMA_VERSION, FramePrediction, diff --git a/ser/_internal/runtime/medium_inference.py b/ser/_internal/runtime/medium_inference.py index 64c858f..d3de888 100644 --- a/ser/_internal/runtime/medium_inference.py +++ b/ser/_internal/runtime/medium_inference.py @@ -5,10 +5,10 @@ from ser._internal.models.emotion_model import LoadedModel from ser._internal.repr import XLSRBackend from ser._internal.runtime import medium_public_boundary as _boundary_support +from ser._internal.utils.logger import get_logger from ser.config import AppConfig from ser.runtime.contracts import InferenceRequest from ser.runtime.schema import InferenceResult -from ser.utils.logger import get_logger logger = get_logger(__name__) diff --git a/ser/_internal/runtime/pipeline.py b/ser/_internal/runtime/pipeline.py index 2a697e2..16308c0 100644 --- a/ser/_internal/runtime/pipeline.py +++ b/ser/_internal/runtime/pipeline.py @@ -22,6 +22,12 @@ log_phase_started, ) from ser._internal.runtime.process_env import temporary_process_env +from ser._internal.runtime.registry import ( + RuntimeCapability, + ensure_profile_supported, + resolve_runtime_capability, +) +from ser._internal.utils.logger import get_logger from ser._internal.utils.subtitles import resolve_subtitle_export_request from ser._internal.utils.transcription_compat import ( has_known_faster_whisper_openmp_runtime_conflict, @@ -35,13 +41,7 @@ InferenceRequest, SubtitleFormat, ) -from ser.runtime.registry import ( - RuntimeCapability, - ensure_profile_supported, - resolve_runtime_capability, -) from ser.runtime.schema import InferenceResult, to_legacy_emotion_segments -from ser.utils.logger import get_logger type TrainModelCallable = Callable[[], None] type PredictEmotionsCallable = Callable[[str], list[EmotionSegment]] diff --git a/ser/_internal/runtime/profile_quality_gate.py b/ser/_internal/runtime/profile_quality_gate.py index 197b021..19638d2 100644 --- a/ser/_internal/runtime/profile_quality_gate.py +++ b/ser/_internal/runtime/profile_quality_gate.py @@ -67,11 +67,11 @@ from ser._internal.runtime.quality_gate_reporting import ( write_serialized_report as _gate_write_serialized_report, ) +from ser._internal.train.eval import grouped_train_test_split, speaker_independent_cv +from ser._internal.train.metrics import compute_ser_metrics from ser._internal.utils.segment_canonicalization import canonicalize_segments from ser.config import AppConfig, reload_settings from ser.runtime.contracts import InferenceRequest -from ser.train.eval import grouped_train_test_split, speaker_independent_cv -from ser.train.metrics import compute_ser_metrics type IndexArray = NDArray[np.int64] type FoldIndices = tuple[IndexArray, IndexArray] diff --git a/ser/runtime/registry.py b/ser/_internal/runtime/registry.py similarity index 100% rename from ser/runtime/registry.py rename to ser/_internal/runtime/registry.py diff --git a/ser/_internal/runtime/restricted_backends.py b/ser/_internal/runtime/restricted_backends.py index 5524217..b22687c 100644 --- a/ser/_internal/runtime/restricted_backends.py +++ b/ser/_internal/runtime/restricted_backends.py @@ -7,9 +7,8 @@ from dataclasses import dataclass from typing import Literal -import ser.license_check as license_check -from ser.config import AppConfig -from ser.license_check import ( +import ser._internal.license_check as license_check +from ser._internal.license_check import ( BackendLicensePolicy, BackendLicensePolicyError, LicenseDecision, @@ -19,6 +18,7 @@ parse_allowed_restricted_backends_env, persist_backend_consent, ) +from ser.config import AppConfig from ser.profiles import get_profile_catalog, resolve_profile_name type RestrictedBackendPrompt = Callable[[BackendLicensePolicy], bool] diff --git a/ser/train/__init__.py b/ser/_internal/train/__init__.py similarity index 100% rename from ser/train/__init__.py rename to ser/_internal/train/__init__.py diff --git a/ser/train/eval.py b/ser/_internal/train/eval.py similarity index 100% rename from ser/train/eval.py rename to ser/_internal/train/eval.py diff --git a/ser/train/metrics.py b/ser/_internal/train/metrics.py similarity index 100% rename from ser/train/metrics.py rename to ser/_internal/train/metrics.py diff --git a/ser/_internal/transcript/backends/__init__.py b/ser/_internal/transcript/backends/__init__.py index 2a0fb2c..4b600d5 100644 --- a/ser/_internal/transcript/backends/__init__.py +++ b/ser/_internal/transcript/backends/__init__.py @@ -1,13 +1,13 @@ """Internal transcription backend implementations.""" -from ser._internal.transcript.backends.factory import resolve_transcription_backend_adapter -from ser.transcript.backends.base import ( +from ser._internal.transcript.backends.base import ( BackendRuntimeRequest, CompatibilityIssue, CompatibilityIssueImpact, CompatibilityReport, TranscriptionBackendAdapter, ) +from ser._internal.transcript.backends.factory import resolve_transcription_backend_adapter __all__ = [ "BackendRuntimeRequest", diff --git a/ser/transcript/backends/base.py b/ser/_internal/transcript/backends/base.py similarity index 100% rename from ser/transcript/backends/base.py rename to ser/_internal/transcript/backends/base.py diff --git a/ser/_internal/transcript/backends/factory.py b/ser/_internal/transcript/backends/factory.py index 26e7d09..8093cbb 100644 --- a/ser/_internal/transcript/backends/factory.py +++ b/ser/_internal/transcript/backends/factory.py @@ -4,8 +4,8 @@ from functools import lru_cache +from ser._internal.transcript.backends.base import TranscriptionBackendAdapter from ser.profiles import TranscriptionBackendId -from ser.transcript.backends.base import TranscriptionBackendAdapter @lru_cache(maxsize=2) diff --git a/ser/_internal/transcript/backends/faster_whisper.py b/ser/_internal/transcript/backends/faster_whisper.py index cbcc765..48a8a1a 100644 --- a/ser/_internal/transcript/backends/faster_whisper.py +++ b/ser/_internal/transcript/backends/faster_whisper.py @@ -12,23 +12,23 @@ from typing import TYPE_CHECKING, cast from ser._internal.runtime.phase_contract import PHASE_TRANSCRIPTION -from ser._internal.utils.transcription_compat import ( - FASTER_WHISPER_OPENMP_CONFLICT_ISSUE_CODE, - has_known_faster_whisper_openmp_runtime_conflict, -) -from ser.domain import TranscriptWord -from ser.profiles import TranscriptionBackendId -from ser.transcript.backends.base import ( +from ser._internal.transcript.backends.base import ( BackendRuntimeRequest, CompatibilityIssue, CompatibilityReport, TranscriptionBackendAdapter, ) -from ser.utils.logger import ( +from ser._internal.utils.logger import ( DependencyLogPolicy, DependencyPolicyContext, scoped_dependency_log_policy, ) +from ser._internal.utils.transcription_compat import ( + FASTER_WHISPER_OPENMP_CONFLICT_ISSUE_CODE, + has_known_faster_whisper_openmp_runtime_conflict, +) +from ser.domain import TranscriptWord +from ser.profiles import TranscriptionBackendId if TYPE_CHECKING: from ser.config import AppConfig diff --git a/ser/_internal/transcript/backends/stable_whisper.py b/ser/_internal/transcript/backends/stable_whisper.py index d195a8b..1f6711a 100644 --- a/ser/_internal/transcript/backends/stable_whisper.py +++ b/ser/_internal/transcript/backends/stable_whisper.py @@ -19,6 +19,12 @@ ) from ser._internal.runtime.process_env import temporary_process_env from ser._internal.transcript.backends import stable_whisper_torio_probe +from ser._internal.transcript.backends.base import ( + BackendRuntimeRequest, + CompatibilityIssue, + CompatibilityReport, + TranscriptionBackendAdapter, +) from ser._internal.transcript.backends.stable_whisper_admission_runtime import ( is_compatibility_activation_error, is_retryable_precision_failure, @@ -59,23 +65,17 @@ from ser._internal.transcript.runtime_failures import ( TranscriptionFailureClassification, ) -from ser._internal.utils.transcription_compat import ( - has_known_stable_whisper_sparse_mps_incompatibility, -) -from ser.domain import TranscriptWord -from ser.profiles import TranscriptionBackendId -from ser.transcript.backends.base import ( - BackendRuntimeRequest, - CompatibilityIssue, - CompatibilityReport, - TranscriptionBackendAdapter, -) -from ser.utils.logger import ( +from ser._internal.utils.logger import ( DependencyLogPolicy, DependencyPolicyContext, WarningPolicy, scoped_dependency_log_policy, ) +from ser._internal.utils.transcription_compat import ( + has_known_stable_whisper_sparse_mps_incompatibility, +) +from ser.domain import TranscriptWord +from ser.profiles import TranscriptionBackendId if TYPE_CHECKING: from ser.config import AppConfig diff --git a/ser/_internal/transcript/backends/stable_whisper_admission_runtime.py b/ser/_internal/transcript/backends/stable_whisper_admission_runtime.py index d49f1fd..d82d25b 100644 --- a/ser/_internal/transcript/backends/stable_whisper_admission_runtime.py +++ b/ser/_internal/transcript/backends/stable_whisper_admission_runtime.py @@ -13,8 +13,8 @@ ) if TYPE_CHECKING: + from ser._internal.transcript.backends.base import BackendRuntimeRequest from ser.config import AppConfig - from ser.transcript.backends.base import BackendRuntimeRequest type StableWhisperTranscriptionPhase = Literal["model_load", "transcribe"] type ExceptionPredicate = Callable[[Exception], bool] diff --git a/ser/_internal/transcript/backends/stable_whisper_torio_probe.py b/ser/_internal/transcript/backends/stable_whisper_torio_probe.py index 8c04181..0cf3e93 100644 --- a/ser/_internal/transcript/backends/stable_whisper_torio_probe.py +++ b/ser/_internal/transcript/backends/stable_whisper_torio_probe.py @@ -10,8 +10,8 @@ from pathlib import Path from typing import Protocol +from ser._internal.transcript.backends.base import CompatibilityIssue from ser._internal.utils.transcription_compat import format_torio_ffmpeg_remediation -from ser.transcript.backends.base import CompatibilityIssue class ModuleAvailabilityProbe(Protocol): diff --git a/ser/_internal/transcript/backends/stable_whisper_transcribe_admission.py b/ser/_internal/transcript/backends/stable_whisper_transcribe_admission.py index a82a6d2..29e2ebc 100644 --- a/ser/_internal/transcript/backends/stable_whisper_transcribe_admission.py +++ b/ser/_internal/transcript/backends/stable_whisper_transcribe_admission.py @@ -9,7 +9,7 @@ from ser._internal.transcript.mps_admission import MpsAdmissionDecision if TYPE_CHECKING: - from ser.transcript.backends.base import BackendRuntimeRequest + from ser._internal.transcript.backends.base import BackendRuntimeRequest def resolve_transcribe_runtime_device( diff --git a/ser/_internal/transcript/backends/stable_whisper_transcribe_execution.py b/ser/_internal/transcript/backends/stable_whisper_transcribe_execution.py index 16d7dd9..545540a 100644 --- a/ser/_internal/transcript/backends/stable_whisper_transcribe_execution.py +++ b/ser/_internal/transcript/backends/stable_whisper_transcribe_execution.py @@ -6,12 +6,12 @@ from collections.abc import Callable from typing import TYPE_CHECKING +from ser._internal.transcript.backends.base import BackendRuntimeRequest from ser._internal.transcript.runtime_failures import ( FailureDisposition, TranscriptionFailureClassification, ) from ser.domain import TranscriptWord -from ser.transcript.backends.base import BackendRuntimeRequest if TYPE_CHECKING: from ser.config import AppConfig diff --git a/ser/_internal/transcript/backends/stable_whisper_transcribe_kwargs.py b/ser/_internal/transcript/backends/stable_whisper_transcribe_kwargs.py index 7868cb1..99b2e8b 100644 --- a/ser/_internal/transcript/backends/stable_whisper_transcribe_kwargs.py +++ b/ser/_internal/transcript/backends/stable_whisper_transcribe_kwargs.py @@ -6,7 +6,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from ser.transcript.backends.base import BackendRuntimeRequest + from ser._internal.transcript.backends.base import BackendRuntimeRequest def build_stable_whisper_transcribe_kwargs( diff --git a/ser/_internal/transcript/backends/stable_whisper_transcribe_runtime.py b/ser/_internal/transcript/backends/stable_whisper_transcribe_runtime.py index 0e3a9f7..71c6df6 100644 --- a/ser/_internal/transcript/backends/stable_whisper_transcribe_runtime.py +++ b/ser/_internal/transcript/backends/stable_whisper_transcribe_runtime.py @@ -8,12 +8,12 @@ from types import ModuleType from typing import TYPE_CHECKING +from ser._internal.transcript.backends.base import BackendRuntimeRequest from ser._internal.transcript.runtime_failures import ( FailureDisposition, TranscriptionFailureClassification, classify_stable_whisper_transcription_failure, ) -from ser.transcript.backends.base import BackendRuntimeRequest if TYPE_CHECKING: from ser.config import AppConfig diff --git a/ser/_internal/transcript/mps_admission.py b/ser/_internal/transcript/mps_admission.py index d416ab8..f7198e6 100644 --- a/ser/_internal/transcript/mps_admission.py +++ b/ser/_internal/transcript/mps_admission.py @@ -10,8 +10,8 @@ from typing import TYPE_CHECKING, Literal if TYPE_CHECKING: + from ser._internal.transcript.backends.base import BackendRuntimeRequest from ser.config import AppConfig - from ser.transcript.backends.base import BackendRuntimeRequest DEFAULT_MPS_ADMISSION_MIN_HEADROOM_MB: float = 64.0 DEFAULT_MPS_ADMISSION_SAFETY_MARGIN_MB: float = 64.0 diff --git a/ser/_internal/transcript/mps_admission_overrides.py b/ser/_internal/transcript/mps_admission_overrides.py index 0cfd06e..c4a8937 100644 --- a/ser/_internal/transcript/mps_admission_overrides.py +++ b/ser/_internal/transcript/mps_admission_overrides.py @@ -10,8 +10,8 @@ from pathlib import Path from typing import TYPE_CHECKING, Literal, cast +from ser._internal.transcript.backends.base import BackendRuntimeRequest from ser._internal.transcript.mps_admission import MpsAdmissionDecision -from ser.transcript.backends.base import BackendRuntimeRequest if TYPE_CHECKING: from ser.config import AppConfig diff --git a/ser/_internal/transcript/profiling.py b/ser/_internal/transcript/profiling.py index 98d0f2e..ce4545f 100644 --- a/ser/_internal/transcript/profiling.py +++ b/ser/_internal/transcript/profiling.py @@ -37,6 +37,7 @@ from ser._internal.transcription.public_boundary_profiling import ( run_runtime_calibration_from_public_boundary as _run_runtime_calibration_boundary_impl, ) +from ser._internal.utils.logger import get_logger from ser.config import ( AppConfig, ArtifactProfileName, @@ -45,7 +46,6 @@ ) from ser.domain import TranscriptWord from ser.profiles import TranscriptionBackendId -from ser.utils.logger import get_logger logger: logging.Logger = get_logger(__name__) _PROFILING_OWNER_MODULES = ( diff --git a/ser/_internal/transcript/transcript_extractor.py b/ser/_internal/transcript/transcript_extractor.py index bd6d384..63e8034 100644 --- a/ser/_internal/transcript/transcript_extractor.py +++ b/ser/_internal/transcript/transcript_extractor.py @@ -15,6 +15,7 @@ from ser._internal.transcription.process_worker import ( release_transcription_runtime_memory as _release_transcription_runtime_memory_impl, ) +from ser._internal.utils.logger import get_logger from ser.config import AppConfig, reload_settings from ser.domain import TranscriptWord from ser.profiles import ( @@ -22,7 +23,6 @@ TranscriptionBackendId, get_profile_catalog, ) -from ser.utils.logger import get_logger if TYPE_CHECKING: from stable_whisper.result import WhisperResult diff --git a/ser/_internal/transcription/compatibility.py b/ser/_internal/transcription/compatibility.py index 2a6cd66..d19dbc5 100644 --- a/ser/_internal/transcription/compatibility.py +++ b/ser/_internal/transcription/compatibility.py @@ -10,7 +10,7 @@ from ser.profiles import TranscriptionBackendId if TYPE_CHECKING: - from ser.transcript.backends.base import ( + from ser._internal.transcript.backends.base import ( BackendRuntimeRequest, CompatibilityIssueImpact, CompatibilityReport, diff --git a/ser/_internal/transcription/in_process_orchestration.py b/ser/_internal/transcription/in_process_orchestration.py index 32c34fd..907b68b 100644 --- a/ser/_internal/transcription/in_process_orchestration.py +++ b/ser/_internal/transcription/in_process_orchestration.py @@ -15,7 +15,7 @@ from ser.profiles import TranscriptionBackendId if TYPE_CHECKING: - from ser.transcript.backends import BackendRuntimeRequest + from ser._internal.transcript.backends import BackendRuntimeRequest class _SetupLoadAdapter(Protocol): diff --git a/ser/_internal/transcription/process_isolation.py b/ser/_internal/transcription/process_isolation.py index e81c71a..1ddf6bf 100644 --- a/ser/_internal/transcription/process_isolation.py +++ b/ser/_internal/transcription/process_isolation.py @@ -23,7 +23,7 @@ from ser.profiles import TranscriptionBackendId if TYPE_CHECKING: - from ser.transcript.backends.base import BackendRuntimeRequest + from ser._internal.transcript.backends.base import BackendRuntimeRequest type _WorkerPhase = Literal["setup_complete", "model_loaded"] type WorkerPhaseMessage = tuple[Literal["phase"], _WorkerPhase] @@ -329,7 +329,7 @@ def runtime_request_for_isolated_faster_whisper( logger: logging.Logger, ) -> BackendRuntimeRequest: """Builds one faster-whisper runtime request without importing torch in worker.""" - from ser.transcript.backends.base import BackendRuntimeRequest + from ser._internal.transcript.backends.base import BackendRuntimeRequest if profile.backend_id != "faster_whisper": raise error_factory( diff --git a/ser/_internal/transcription/process_worker.py b/ser/_internal/transcription/process_worker.py index 6b1ac75..2874843 100644 --- a/ser/_internal/transcription/process_worker.py +++ b/ser/_internal/transcription/process_worker.py @@ -14,7 +14,7 @@ from ser.profiles import TranscriptionBackendId if TYPE_CHECKING: - from ser.transcript.backends.base import BackendRuntimeRequest + from ser._internal.transcript.backends.base import BackendRuntimeRequest class ProcessIsolatedProfileLike(Protocol): diff --git a/ser/_internal/transcription/public_boundary_process.py b/ser/_internal/transcription/public_boundary_process.py index e62db3b..3160439 100644 --- a/ser/_internal/transcription/public_boundary_process.py +++ b/ser/_internal/transcription/public_boundary_process.py @@ -16,7 +16,7 @@ from .process_worker import TranscriptionProcessPayload if TYPE_CHECKING: - from ser.transcript.backends.base import BackendRuntimeRequest + from ser._internal.transcript.backends.base import BackendRuntimeRequest class _ProcessIsolatedProfile(Protocol): diff --git a/ser/_internal/transcription/public_boundary_runtime.py b/ser/_internal/transcription/public_boundary_runtime.py index 06e659e..45377a4 100644 --- a/ser/_internal/transcription/public_boundary_runtime.py +++ b/ser/_internal/transcription/public_boundary_runtime.py @@ -11,7 +11,7 @@ from ser.profiles import TranscriptionBackendId if TYPE_CHECKING: - from ser.transcript.backends.base import BackendRuntimeRequest, CompatibilityReport + from ser._internal.transcript.backends.base import BackendRuntimeRequest, CompatibilityReport type _ResolveTranscriptionProfileImpl = Callable[..., object] type _ResolveBackendId = Callable[..., TranscriptionBackendId] diff --git a/ser/_internal/transcription/public_boundary_support.py b/ser/_internal/transcription/public_boundary_support.py index 90d08cd..49b79de 100644 --- a/ser/_internal/transcription/public_boundary_support.py +++ b/ser/_internal/transcription/public_boundary_support.py @@ -120,7 +120,7 @@ from ser.profiles import TranscriptionBackendId if TYPE_CHECKING: - from ser.transcript.backends.base import BackendRuntimeRequest, CompatibilityReport + from ser._internal.transcript.backends.base import BackendRuntimeRequest, CompatibilityReport _CompatibilityIssueKind = Literal["noise", "operational"] diff --git a/ser/_internal/transcription/runtime_profile.py b/ser/_internal/transcription/runtime_profile.py index 16ffa2e..1f08023 100644 --- a/ser/_internal/transcription/runtime_profile.py +++ b/ser/_internal/transcription/runtime_profile.py @@ -9,7 +9,7 @@ from ser.profiles import TranscriptionBackendId if TYPE_CHECKING: - from ser.transcript.backends.base import BackendRuntimeRequest + from ser._internal.transcript.backends.base import BackendRuntimeRequest class _RuntimeProfile(Protocol): @@ -121,7 +121,7 @@ def runtime_request_from_profile( default_mps_low_memory_threshold_gb: float, ) -> BackendRuntimeRequest: """Builds one backend runtime request from transcription profile settings.""" - from ser.transcript.backends.base import BackendRuntimeRequest + from ser._internal.transcript.backends.base import BackendRuntimeRequest torch_runtime = getattr(settings, "torch_runtime", None) transcription_settings = getattr(settings, "transcription", None) diff --git a/ser/_internal/utils/audio_utils.py b/ser/_internal/utils/audio_utils.py index 1091de7..be291b4 100644 --- a/ser/_internal/utils/audio_utils.py +++ b/ser/_internal/utils/audio_utils.py @@ -10,8 +10,8 @@ import soundfile as sf from numpy.typing import NDArray +from ser._internal.utils.logger import get_logger from ser.config import AudioReadConfig -from ser.utils.logger import get_logger logger: logging.Logger = get_logger(__name__) diff --git a/ser/utils/common_utils.py b/ser/_internal/utils/common_utils.py similarity index 100% rename from ser/utils/common_utils.py rename to ser/_internal/utils/common_utils.py diff --git a/ser/_internal/utils/dsp.py b/ser/_internal/utils/dsp.py index c5fd954..0a6786e 100644 --- a/ser/_internal/utils/dsp.py +++ b/ser/_internal/utils/dsp.py @@ -11,8 +11,8 @@ import numpy as np from numpy.typing import NDArray +from ser._internal.utils.logger import get_logger from ser.config import FeatureFlags -from ser.utils.logger import get_logger logger: logging.Logger = get_logger(__name__) diff --git a/ser/utils/logger.py b/ser/_internal/utils/logger.py similarity index 100% rename from ser/utils/logger.py rename to ser/_internal/utils/logger.py diff --git a/ser/_internal/utils/subtitles.py b/ser/_internal/utils/subtitles.py index 19ee115..2293abb 100644 --- a/ser/_internal/utils/subtitles.py +++ b/ser/_internal/utils/subtitles.py @@ -6,9 +6,9 @@ from pathlib import Path from typing import Literal, cast +from ser._internal.utils.logger import get_logger from ser.config import TimelineConfig from ser.domain import TimelineEntry -from ser.utils.logger import get_logger logger = get_logger(__name__) diff --git a/ser/_internal/utils/timeline_utils.py b/ser/_internal/utils/timeline_utils.py index f7dd84c..f04b897 100644 --- a/ser/_internal/utils/timeline_utils.py +++ b/ser/_internal/utils/timeline_utils.py @@ -6,11 +6,11 @@ from pathlib import Path from typing import Protocol +from ser._internal.utils.common_utils import display_elapsed_time +from ser._internal.utils.logger import get_logger from ser._internal.utils.segment_canonicalization import canonicalize_segments from ser.config import TimelineConfig from ser.domain import EmotionSegment, TimelineEntry, TranscriptWord -from ser.utils.common_utils import display_elapsed_time -from ser.utils.logger import get_logger logger: logging.Logger = get_logger(__name__) diff --git a/ser/api.py b/ser/api.py index 3ee46d8..fe489ed 100644 --- a/ser/api.py +++ b/ser/api.py @@ -9,11 +9,36 @@ import ser._internal.api.data as _data_api import ser._internal.api.diagnostics as _diagnostics_api import ser._internal.api.runtime as _runtime_api -from ser.config import AppConfig, reload_settings +from ser._internal.config.schema import FeatureRuntimeBackendOverride +from ser.config import ( + AccurateResearchRuntimeConfig, + AccurateRuntimeConfig, + AppConfig, + AudioReadConfig, + DataLoaderConfig, + DatasetConfig, + FastRuntimeConfig, + FeatureFlags, + FeatureRuntimePolicyConfig, + MediumRuntimeConfig, + MediumTrainingConfig, + ModelsConfig, + NeuralNetConfig, + QualityGateConfig, + RuntimeFlags, + SchemaConfig, + TimelineConfig, + TorchRuntimeConfig, + TrainingConfig, + TranscriptionConfig, + WhisperModelConfig, + reload_settings, +) from ser.diagnostics.domain import DiagnosticFinding, DiagnosticReport, DiagnosticSeverity -from ser.domain import DatasetConsents +from ser.domain import DatasetConsents, EmotionSegment, TimelineEntry, TranscriptWord from ser.profiles import ProfileName from ser.runtime.contracts import InferenceExecution, InferenceRequest, SubtitleFormat +from ser.runtime.schema import FramePrediction, InferenceResult, SegmentPrediction ComplianceMode = _data_api.ComplianceMode DatasetPrepareResult = _data_api.DatasetPrepareResult @@ -190,8 +215,13 @@ def run_startup_preflight( __all__ = [ + "AccurateResearchRuntimeConfig", + "AccurateRuntimeConfig", "AppConfig", + "AudioReadConfig", "ComplianceMode", + "DataLoaderConfig", + "DatasetConfig", "DatasetConsents", "DatasetPrepareResult", "DatasetRegistryHealthIssueRecord", @@ -199,12 +229,34 @@ def run_startup_preflight( "DiagnosticFinding", "DiagnosticReport", "DiagnosticSeverity", + "EmotionSegment", + "FastRuntimeConfig", + "FeatureFlags", + "FeatureRuntimeBackendOverride", + "FeatureRuntimePolicyConfig", + "FramePrediction", "InferenceExecution", "InferenceRequest", + "InferenceResult", + "MediumRuntimeConfig", + "MediumTrainingConfig", + "ModelsConfig", + "NeuralNetConfig", "ProfileName", + "QualityGateConfig", + "RuntimeFlags", "RuntimePipeline", "RuntimePipelineBuilder", + "SchemaConfig", + "SegmentPrediction", "SubtitleFormat", + "TimelineConfig", + "TimelineEntry", + "TorchRuntimeConfig", + "TrainingConfig", + "TranscriptWord", + "TranscriptionConfig", + "WhisperModelConfig", "configure_dataset_consents", "infer", "list_dataset_registry_health_issues", diff --git a/ser/data/__init__.py b/ser/data/__init__.py deleted file mode 100644 index d93c929..0000000 --- a/ser/data/__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -# pyright: reportUnsupportedDunderAll=false -"""Public data package facade with curated training data exports.""" - -from __future__ import annotations - -import importlib -from typing import Any - -_EXPORT_MODULES: dict[str, str] = { - "DataSplit": "ser._internal.data.data_loader", - "EmbeddingCache": "ser._internal.data.embedding_cache", - "EmbeddingCacheEntry": "ser._internal.data.embedding_cache", - "LabeledAudioSample": "ser._internal.data.data_loader", - "Utterance": "ser._internal.data.manifest", - "load_data": "ser._internal.data.data_loader", - "load_labeled_audio_paths": "ser._internal.data.data_loader", - "load_utterances": "ser._internal.data.data_loader", -} - -__all__ = sorted(_EXPORT_MODULES) - - -def __getattr__(name: str) -> Any: - """Lazily resolves one curated data facade export.""" - module_name = _EXPORT_MODULES.get(name) - if module_name is None: - raise AttributeError(f"module 'ser.data' has no attribute {name!r}") - value = getattr(importlib.import_module(module_name), name) - globals()[name] = value - return value diff --git a/ser/diagnostics/__init__.py b/ser/diagnostics/__init__.py index f8b449d..bd6ad3e 100644 --- a/ser/diagnostics/__init__.py +++ b/ser/diagnostics/__init__.py @@ -1,44 +1 @@ -# pyright: reportUnsupportedDunderAll=false -"""Public diagnostics facade.""" - -from __future__ import annotations - -import importlib -from typing import Any - -from ser.diagnostics.domain import ( - DiagnosticFinding, - DiagnosticReport, - DiagnosticSeverity, - PreflightMode, -) - -_DYNAMIC_EXPORTS: dict[str, str] = { - "apply_profile_override_for_diagnostics": "ser._internal.diagnostics.service", - "format_report_brief": "ser._internal.diagnostics.service", - "format_report_json": "ser._internal.diagnostics.service", - "format_report_text": "ser._internal.diagnostics.service", - "format_startup_preflight_one_liner": "ser._internal.diagnostics.service", - "parse_preflight_mode": "ser._internal.diagnostics.service", - "run_doctor_command": "ser._internal.diagnostics.command", - "run_doctor_diagnostics": "ser._internal.diagnostics.service", - "should_fail_preflight": "ser._internal.diagnostics.service", -} - -__all__ = [ - "DiagnosticFinding", - "DiagnosticReport", - "DiagnosticSeverity", - "PreflightMode", - *_DYNAMIC_EXPORTS, -] - - -def __getattr__(name: str) -> Any: - """Lazily resolves one diagnostics facade export.""" - module_name = _DYNAMIC_EXPORTS.get(name) - if module_name is None: - raise AttributeError(f"module 'ser.diagnostics' has no attribute {name!r}") - value = getattr(importlib.import_module(module_name), name) - globals()[name] = value - return value +"""Package container for diagnostics contract types re-exported by `ser.api`.""" diff --git a/ser/features/__init__.py b/ser/features/__init__.py deleted file mode 100644 index df0ef3b..0000000 --- a/ser/features/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -# pyright: reportUnsupportedDunderAll=false -"""Public feature extraction facade.""" - -from __future__ import annotations - -import importlib -from typing import Any - -_EXPORT_MODULE = "ser._internal.features.feature_extractor" -__all__ = [ - "FeatureFrame", - "extract_feature", - "extract_feature_frames", - "extract_feature_from_signal", - "extended_extract_feature", -] - - -def __getattr__(name: str) -> Any: - """Lazily resolves one public feature facade export.""" - if name not in __all__: - raise AttributeError(f"module 'ser.features' has no attribute {name!r}") - value = getattr(importlib.import_module(_EXPORT_MODULE), name) - globals()[name] = value - return value diff --git a/ser/heads/__init__.py b/ser/heads/__init__.py deleted file mode 100644 index b2937da..0000000 --- a/ser/heads/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# pyright: reportUnsupportedDunderAll=false -"""Public classifier-head facade.""" - -from __future__ import annotations - -import importlib -from typing import Any - -_EXPORT_MODULE = "ser._internal.heads.torch_head" -__all__ = ["build_torch_mlp_head", "forward_torch_head"] - - -def __getattr__(name: str) -> Any: - """Lazily resolves one public classifier-head export.""" - if name not in __all__: - raise AttributeError(f"module 'ser.heads' has no attribute {name!r}") - value = getattr(importlib.import_module(_EXPORT_MODULE), name) - globals()[name] = value - return value diff --git a/ser/models/__init__.py b/ser/models/__init__.py deleted file mode 100644 index 06476c1..0000000 --- a/ser/models/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -# pyright: reportUnsupportedDunderAll=false -"""Model facades for artifact loading, profile runtimes, and training entrypoints. - -Only the modules listed in ``__all__`` are intended facades; every other module -in this package is implementation detail. Facade modules are imported lazily to -keep package import free of heavy model dependencies. -""" - -from __future__ import annotations - -import importlib -from types import ModuleType - -__all__ = [ - "emotion_model", - "profile_runtime", - "training_entrypoints", -] - - -def __getattr__(name: str) -> ModuleType: - """Lazily imports one intended facade module on first attribute access.""" - if name in __all__: - return importlib.import_module(f"ser.models.{name}") - raise AttributeError(f"module 'ser.models' has no attribute {name!r}") diff --git a/ser/models/emotion_model.py b/ser/models/emotion_model.py deleted file mode 100644 index 851eeaf..0000000 --- a/ser/models/emotion_model.py +++ /dev/null @@ -1,3 +0,0 @@ -"""Public facade for `ser._internal.models.emotion_model`.""" - -from ser._internal.models.emotion_model import * # noqa: F403 diff --git a/ser/models/profile_runtime.py b/ser/models/profile_runtime.py deleted file mode 100644 index e8c8566..0000000 --- a/ser/models/profile_runtime.py +++ /dev/null @@ -1,44 +0,0 @@ -# pyright: reportUnsupportedDunderAll=false -"""Public profile-runtime facade.""" - -from __future__ import annotations - -import importlib -from typing import Any - -_IMPL_MODULE = "ser._internal.models.profile_runtime" -_EXPORT_NAMES: tuple[str, ...] = ( - "ACCURATE_BACKEND_ID", - "ACCURATE_MODEL_ID", - "ACCURATE_POOLING_STRATEGY", - "ACCURATE_PROFILE_ID", - "ACCURATE_RESEARCH_BACKEND_ID", - "ACCURATE_RESEARCH_MODEL_ID", - "ACCURATE_RESEARCH_PROFILE_ID", - "MEDIUM_BACKEND_ID", - "MEDIUM_FRAME_SIZE_SECONDS", - "MEDIUM_FRAME_STRIDE_SECONDS", - "MEDIUM_MODEL_ID", - "MEDIUM_POOLING_STRATEGY", - "MEDIUM_PROFILE_ID", - "build_accurate_backend_for_settings", - "build_accurate_research_backend_for_settings", - "build_medium_backend_for_settings", - "resolve_accurate_model_id", - "resolve_accurate_research_model_id", - "resolve_medium_model_id", - "resolve_model_id_from_settings", - "resolve_runtime_selectors_for_backend_id", -) -__all__ = list(_EXPORT_NAMES) - - -def __getattr__(name: str) -> Any: - """Lazily resolves one public profile-runtime export.""" - if name not in _EXPORT_NAMES: - raise AttributeError( - f"module 'ser._internal.models.profile_runtime' has no attribute {name!r}" - ) - value = getattr(importlib.import_module(_IMPL_MODULE), name) - globals()[name] = value - return value diff --git a/ser/models/training_entrypoints.py b/ser/models/training_entrypoints.py deleted file mode 100644 index bec59cc..0000000 --- a/ser/models/training_entrypoints.py +++ /dev/null @@ -1,3 +0,0 @@ -"""Public facade for `ser._internal.models.training_entrypoints`.""" - -from ser._internal.models.training_entrypoints import * # noqa: F403 diff --git a/ser/profiles.py b/ser/profiles.py index fbb8490..38ace0f 100644 --- a/ser/profiles.py +++ b/ser/profiles.py @@ -3,7 +3,8 @@ from __future__ import annotations import importlib -from collections.abc import Callable, Mapping +import re +from collections.abc import Mapping from dataclasses import dataclass from pathlib import Path from types import MappingProxyType @@ -20,6 +21,24 @@ ] type TranscriptionBackendId = Literal["stable_whisper", "faster_whisper"] +__all__ = [ + "ProfileCatalogEntry", + "ProfileEnableFlag", + "ProfileFeatureRuntimeDefaults", + "ProfileModelDefinition", + "ProfileName", + "ProfileRuntimeDefaults", + "ProfileRuntimeEnvDefinition", + "ProfileTranscriptionDefaults", + "ProfileTranscriptionEnvDefinition", + "RuntimeProfile", + "TranscriptionBackendId", + "available_profiles", + "get_profile_catalog", + "resolve_profile", + "resolve_profile_name", +] + @dataclass(frozen=True) class RuntimeProfile: @@ -140,6 +159,8 @@ class ProfileCatalogEntry: _ALLOWED_TORCH_DTYPE_SELECTORS: frozenset[str] = frozenset( {"auto", "float16", "float32", "bfloat16"} ) +_SUPPORTED_TORCH_DEVICE_SELECTORS: frozenset[str] = frozenset({"auto", "cpu", "mps", "cuda", "xpu"}) +_INDEXED_TORCH_DEVICE_SELECTOR_PATTERN = re.compile(r"(cuda|xpu):\d+") _PROFILE_DEFS_SCHEMA_VERSION = 1 @@ -228,13 +249,12 @@ def _read_required_int( def _normalize_torch_device_selector(value: str) -> str | None: """Normalizes one torch device selector or returns None when invalid.""" - normalizer = cast( - Callable[[str], str | None], - importlib.import_module( - "ser._internal.utils.torch_inference" - ).normalize_torch_device_selector, - ) - return normalizer(value) + normalized = value.strip().lower() + if normalized in _SUPPORTED_TORCH_DEVICE_SELECTORS: + return normalized + if _INDEXED_TORCH_DEVICE_SELECTOR_PATTERN.fullmatch(normalized): + return normalized + return None def _normalize_torch_dtype_selector(value: str) -> str | None: diff --git a/ser/repr/__init__.py b/ser/repr/__init__.py deleted file mode 100644 index b16f3bb..0000000 --- a/ser/repr/__init__.py +++ /dev/null @@ -1,31 +0,0 @@ -# pyright: reportUnsupportedDunderAll=false -"""Public representation backend facade.""" - -from __future__ import annotations - -import importlib -from typing import Any - -_EXPORT_MODULES: dict[str, str] = { - "EncodedSequence": "ser._internal.repr.backend", - "Emotion2VecBackend": "ser._internal.repr.emotion2vec", - "FeatureBackend": "ser._internal.repr.backend", - "HandcraftedBackend": "ser._internal.repr.handcrafted", - "PoolingWindow": "ser._internal.repr.backend", - "WhisperBackend": "ser._internal.repr.hf_whisper", - "XLSRBackend": "ser._internal.repr.hf_xlsr", - "VectorFeatureBackend": "ser._internal.repr.backend", - "overlap_frame_mask": "ser._internal.repr.backend", -} - -__all__ = sorted(_EXPORT_MODULES) - - -def __getattr__(name: str) -> Any: - """Lazily resolves one public representation facade export.""" - module_name = _EXPORT_MODULES.get(name) - if module_name is None: - raise AttributeError(f"module 'ser.repr' has no attribute {name!r}") - value = getattr(importlib.import_module(module_name), name) - globals()[name] = value - return value diff --git a/ser/runtime/__init__.py b/ser/runtime/__init__.py index c239aa1..8e4efc8 100644 --- a/ser/runtime/__init__.py +++ b/ser/runtime/__init__.py @@ -1,41 +1 @@ -# pyright: reportUnsupportedDunderAll=false -"""Runtime helpers for profiling, orchestration, and benchmarks.""" - -from __future__ import annotations - -import importlib -from typing import Any - -from ser.runtime.contracts import InferenceExecution, InferenceRequest -from ser.runtime.registry import ( - RuntimeCapability, - UnsupportedProfileError, - ensure_profile_supported, - resolve_runtime_capability, -) - -_DYNAMIC_EXPORTS: dict[str, str] = { - "RuntimePipeline": "ser.runtime.pipeline", - "create_runtime_pipeline": "ser.runtime.pipeline", -} - -__all__ = [ - "InferenceExecution", - "InferenceRequest", - "RuntimeCapability", - "RuntimePipeline", - "UnsupportedProfileError", - "create_runtime_pipeline", - "ensure_profile_supported", - "resolve_runtime_capability", -] - - -def __getattr__(name: str) -> Any: - """Lazily resolves runtime facade exports that import orchestration code.""" - module_name = _DYNAMIC_EXPORTS.get(name) - if module_name is None: - raise AttributeError(f"module 'ser.runtime' has no attribute {name!r}") - value = getattr(importlib.import_module(module_name), name) - globals()[name] = value - return value +"""Package container for runtime contract types re-exported by `ser.api`.""" diff --git a/ser/runtime/pipeline.py b/ser/runtime/pipeline.py deleted file mode 100644 index 2242148..0000000 --- a/ser/runtime/pipeline.py +++ /dev/null @@ -1,3 +0,0 @@ -"""Public facade for `ser._internal.runtime.pipeline`.""" - -from ser._internal.runtime.pipeline import * # noqa: F403 diff --git a/ser/transcript/__init__.py b/ser/transcript/__init__.py deleted file mode 100644 index ee9a35e..0000000 --- a/ser/transcript/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -# pyright: reportUnsupportedDunderAll=false -"""Public transcript extraction facade.""" - -from __future__ import annotations - -import importlib -from typing import Any - -_EXPORT_MODULE = "ser.transcript.transcript_extractor" -__all__ = [ - "extract_transcript", - "TranscriptionError", - "TranscriptionProfile", - "load_whisper_model", - "transcribe_with_model", -] - - -def __getattr__(name: str) -> Any: - """Lazily resolves one transcript facade export.""" - if name not in __all__: - raise AttributeError(f"module 'ser.transcript' has no attribute {name!r}") - value = getattr(importlib.import_module(_EXPORT_MODULE), name) - globals()[name] = value - return value diff --git a/ser/transcript/backends/__init__.py b/ser/transcript/backends/__init__.py deleted file mode 100644 index 70baa46..0000000 --- a/ser/transcript/backends/__init__.py +++ /dev/null @@ -1,28 +0,0 @@ -# pyright: reportUnsupportedDunderAll=false -"""Public transcription backend facade package.""" - -from __future__ import annotations - -import importlib -from typing import Any - -_EXPORT_MODULES: dict[str, str] = { - "BackendRuntimeRequest": "ser.transcript.backends.base", - "CompatibilityIssue": "ser.transcript.backends.base", - "CompatibilityIssueImpact": "ser.transcript.backends.base", - "CompatibilityReport": "ser.transcript.backends.base", - "TranscriptionBackendAdapter": "ser.transcript.backends.base", - "resolve_transcription_backend_adapter": "ser._internal.transcript.backends.factory", -} - -__all__ = sorted(_EXPORT_MODULES) - - -def __getattr__(name: str) -> Any: - """Lazily resolves one public backend facade export.""" - module_name = _EXPORT_MODULES.get(name) - if module_name is None: - raise AttributeError(f"module 'ser.transcript.backends' has no attribute {name!r}") - value = getattr(importlib.import_module(module_name), name) - globals()[name] = value - return value diff --git a/ser/transcript/backends/stable_whisper.py b/ser/transcript/backends/stable_whisper.py deleted file mode 100644 index 95e0365..0000000 --- a/ser/transcript/backends/stable_whisper.py +++ /dev/null @@ -1,3 +0,0 @@ -"""Public facade for `ser._internal.transcript.backends.stable_whisper`.""" - -from ser._internal.transcript.backends.stable_whisper import * # noqa: F403 diff --git a/ser/transcript/profiling.py b/ser/transcript/profiling.py deleted file mode 100644 index e7911c1..0000000 --- a/ser/transcript/profiling.py +++ /dev/null @@ -1,3 +0,0 @@ -"""Public facade for `ser._internal.transcript.profiling`.""" - -from ser._internal.transcript.profiling import * # noqa: F403 diff --git a/ser/transcript/transcript_extractor.py b/ser/transcript/transcript_extractor.py deleted file mode 100644 index b15c52f..0000000 --- a/ser/transcript/transcript_extractor.py +++ /dev/null @@ -1,3 +0,0 @@ -"""Public facade for `ser._internal.transcript.transcript_extractor`.""" - -from ser._internal.transcript.transcript_extractor import * # noqa: F403 diff --git a/ser/utils/__init__.py b/ser/utils/__init__.py index 26cda3b..a0a975b 100644 --- a/ser/utils/__init__.py +++ b/ser/utils/__init__.py @@ -6,11 +6,12 @@ from collections.abc import Callable from typing import TYPE_CHECKING, cast -from .common_utils import display_elapsed_time -from .logger import get_logger +from ser._internal.utils.common_utils import display_elapsed_time +from ser._internal.utils.logger import get_logger if TYPE_CHECKING: import numpy as np + from numpy.typing import NDArray from ser.config import AppConfig from ser.domain import EmotionSegment, TimelineEntry, TranscriptWord @@ -28,10 +29,10 @@ def read_audio_file( *, start_seconds: float | None = None, duration_seconds: float | None = None, -) -> tuple[np.ndarray, int]: +) -> tuple[NDArray[np.float32], int]: """Reads and normalizes an audio file.""" reader = cast( - Callable[..., tuple[np.ndarray, int]], + Callable[..., tuple[NDArray[np.float32], int]], importlib.import_module("ser._internal.utils.audio_utils").read_audio_file, ) return reader( diff --git a/tests/suites/integration/api/test_api.py b/tests/suites/integration/api/test_api.py index 20f8d26..5f4842c 100644 --- a/tests/suites/integration/api/test_api.py +++ b/tests/suites/integration/api/test_api.py @@ -16,19 +16,19 @@ import ser._internal.runtime.restricted_backends as restricted_backends_module import ser.api as api import ser.config as config_module -from ser._internal.data.dataset_consents import DatasetConsentError -from ser._internal.data.dataset_registry import upsert_dataset_registry_entry -from ser.config import AppConfig -from ser.data.application import ( +from ser._internal.data.application.models import ( DatasetRegistrySnapshot, DatasetRegistrySnapshotEntry, DatasetRegistrySnapshotIssue, ) +from ser._internal.data.dataset_consents import DatasetConsentError +from ser._internal.data.dataset_registry import upsert_dataset_registry_entry +from ser._internal.license_check import BackendLicensePolicyError +from ser._internal.runtime.registry import UnsupportedProfileError +from ser._internal.transcript.transcript_extractor import TranscriptionError +from ser.config import AppConfig from ser.diagnostics.domain import DiagnosticFinding, DiagnosticReport -from ser.license_check import BackendLicensePolicyError -from ser.runtime import InferenceExecution, InferenceRequest -from ser.runtime.registry import UnsupportedProfileError -from ser.transcript import TranscriptionError +from ser.runtime.contracts import InferenceExecution, InferenceRequest def _settings(tmp_path: Path) -> AppConfig: @@ -423,7 +423,7 @@ def test_prepare_dataset_strict_mode_requires_explicit_consents( settings = _settings(tmp_path) manifest_path = tmp_path / "manifests" / "msp-podcast.jsonl" monkeypatch.setattr( - "ser.data.application.run_dataset_prepare_workflow", + "ser._internal.data.application.prepare.run_dataset_prepare_workflow", lambda **_kwargs: (_ for _ in ()).throw(AssertionError("unreachable")), ) @@ -460,7 +460,7 @@ def _run_dataset_prepare_workflow(**kwargs: object) -> object: ) monkeypatch.setattr( - "ser.data.application.run_dataset_prepare_workflow", + "ser._internal.data.application.prepare.run_dataset_prepare_workflow", _run_dataset_prepare_workflow, ) @@ -550,7 +550,7 @@ def _capture_workflow_kwargs(**kwargs: object) -> object: ) monkeypatch.setattr( - "ser.data.application.run_dataset_prepare_workflow", + "ser._internal.data.application.prepare.run_dataset_prepare_workflow", _capture_workflow_kwargs, ) @@ -627,7 +627,7 @@ def test_list_dataset_registry_health_issues_exposes_issue_records( """Health issue API should surface typed registry issue records.""" settings = _settings(tmp_path) monkeypatch.setattr( - "ser.data.application.collect_dataset_registry_snapshot", + "ser._internal.data.application.registry_snapshot.collect_dataset_registry_snapshot", lambda **kwargs: DatasetRegistrySnapshot( entries=( DatasetRegistrySnapshotEntry( @@ -1116,8 +1116,13 @@ def test_api_public_surface_includes_user_oriented_entrypoints() -> None: def test_api_public_surface_snapshot_matches_expected_contract() -> None: """Public API export snapshot should only change via explicit contract updates.""" assert api.__all__ == [ + "AccurateResearchRuntimeConfig", + "AccurateRuntimeConfig", "AppConfig", + "AudioReadConfig", "ComplianceMode", + "DataLoaderConfig", + "DatasetConfig", "DatasetConsents", "DatasetPrepareResult", "DatasetRegistryHealthIssueRecord", @@ -1125,12 +1130,34 @@ def test_api_public_surface_snapshot_matches_expected_contract() -> None: "DiagnosticFinding", "DiagnosticReport", "DiagnosticSeverity", + "EmotionSegment", + "FastRuntimeConfig", + "FeatureFlags", + "FeatureRuntimeBackendOverride", + "FeatureRuntimePolicyConfig", + "FramePrediction", "InferenceExecution", "InferenceRequest", + "InferenceResult", + "MediumRuntimeConfig", + "MediumTrainingConfig", + "ModelsConfig", + "NeuralNetConfig", "ProfileName", + "QualityGateConfig", + "RuntimeFlags", "RuntimePipeline", "RuntimePipelineBuilder", + "SchemaConfig", + "SegmentPrediction", "SubtitleFormat", + "TimelineConfig", + "TimelineEntry", + "TorchRuntimeConfig", + "TrainingConfig", + "TranscriptWord", + "TranscriptionConfig", + "WhisperModelConfig", "configure_dataset_consents", "infer", "list_dataset_registry_health_issues", diff --git a/tests/suites/integration/architecture/public_api_snapshot.json b/tests/suites/integration/architecture/public_api_snapshot.json index 0a81f90..4607b04 100644 --- a/tests/suites/integration/architecture/public_api_snapshot.json +++ b/tests/suites/integration/architecture/public_api_snapshot.json @@ -3,16 +3,82 @@ "ser": { "exports": { "EmotionSegment": { - "kind": "class", - "target_path": "ser.domain.EmotionSegment" + "contract": { + "bases": [ + "NamedTuple" + ], + "kind": "class", + "members": { + "emotion": { + "annotation": "str", + "kind": "attribute", + "value": null + }, + "end_seconds": { + "annotation": "float", + "kind": "attribute", + "value": null + }, + "start_seconds": { + "annotation": "float", + "kind": "attribute", + "value": null + } + } + }, + "kind": "alias" }, "TimelineEntry": { - "kind": "class", - "target_path": "ser.domain.TimelineEntry" + "contract": { + "bases": [ + "NamedTuple" + ], + "kind": "class", + "members": { + "emotion": { + "annotation": "str", + "kind": "attribute", + "value": null + }, + "speech": { + "annotation": "str", + "kind": "attribute", + "value": null + }, + "timestamp_seconds": { + "annotation": "float", + "kind": "attribute", + "value": null + } + } + }, + "kind": "alias" }, "TranscriptWord": { - "kind": "class", - "target_path": "ser.domain.TranscriptWord" + "contract": { + "bases": [ + "NamedTuple" + ], + "kind": "class", + "members": { + "end_seconds": { + "annotation": "float", + "kind": "attribute", + "value": null + }, + "start_seconds": { + "annotation": "float", + "kind": "attribute", + "value": null + }, + "word": { + "annotation": "str", + "kind": "attribute", + "value": null + } + } + }, + "kind": "alias" }, "__version__": { "annotation": "str", @@ -24,18 +90,354 @@ }, "ser.api": { "exports": { + "AccurateResearchRuntimeConfig": { + "contract": { + "bases": [ + "ProfileRuntimeConfig" + ], + "kind": "class", + "members": { + "max_timeout_retries": { + "annotation": "int", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_max_timeout_retries('accurate-research')))" + }, + "max_transient_retries": { + "annotation": "int", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_max_transient_retries('accurate-research')))" + }, + "pool_window_size_seconds": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_pool_window_size_seconds('accurate-research')))" + }, + "pool_window_stride_seconds": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_pool_window_stride_seconds('accurate-research')))" + }, + "post_hysteresis_enter_confidence": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_post_hysteresis_enter_confidence('accurate-research')))" + }, + "post_hysteresis_exit_confidence": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_post_hysteresis_exit_confidence('accurate-research')))" + }, + "post_min_segment_duration_seconds": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_post_min_segment_duration_seconds('accurate-research')))" + }, + "post_smoothing_window_frames": { + "annotation": "int", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_post_smoothing_window_frames('accurate-research')))" + }, + "process_isolation": { + "annotation": "bool", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_process_isolation('accurate-research')))" + }, + "retry_backoff_seconds": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_retry_backoff_seconds('accurate-research')))" + }, + "timeout_seconds": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_timeout_seconds('accurate-research')))" + } + } + }, + "kind": "alias" + }, + "AccurateRuntimeConfig": { + "contract": { + "bases": [ + "ProfileRuntimeConfig" + ], + "kind": "class", + "members": { + "max_timeout_retries": { + "annotation": "int", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_max_timeout_retries('accurate')))" + }, + "max_transient_retries": { + "annotation": "int", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_max_transient_retries('accurate')))" + }, + "pool_window_size_seconds": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_pool_window_size_seconds('accurate')))" + }, + "pool_window_stride_seconds": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_pool_window_stride_seconds('accurate')))" + }, + "post_hysteresis_enter_confidence": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_post_hysteresis_enter_confidence('accurate')))" + }, + "post_hysteresis_exit_confidence": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_post_hysteresis_exit_confidence('accurate')))" + }, + "post_min_segment_duration_seconds": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_post_min_segment_duration_seconds('accurate')))" + }, + "post_smoothing_window_frames": { + "annotation": "int", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_post_smoothing_window_frames('accurate')))" + }, + "process_isolation": { + "annotation": "bool", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_process_isolation('accurate')))" + }, + "retry_backoff_seconds": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_retry_backoff_seconds('accurate')))" + }, + "timeout_seconds": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_timeout_seconds('accurate')))" + } + } + }, + "kind": "alias" + }, "AppConfig": { - "kind": "class", - "target_path": "ser.config.AppConfig" + "contract": { + "bases": [], + "kind": "class", + "members": { + "accurate_research_runtime": { + "annotation": "AccurateResearchRuntimeConfig", + "kind": "attribute", + "value": "field(default_factory=AccurateResearchRuntimeConfig)" + }, + "accurate_runtime": { + "annotation": "AccurateRuntimeConfig", + "kind": "attribute", + "value": "field(default_factory=AccurateRuntimeConfig)" + }, + "audio_read": { + "annotation": "AudioReadConfig", + "kind": "attribute", + "value": "field(default_factory=AudioReadConfig)" + }, + "data_loader": { + "annotation": "DataLoaderConfig", + "kind": "attribute", + "value": "field(default_factory=DataLoaderConfig)" + }, + "dataset": { + "annotation": "DatasetConfig", + "kind": "attribute", + "value": "field(default_factory=(lambda: DatasetConfig(folder=(Path('ser/dataset/ravdess')))))" + }, + "default_language": { + "annotation": "str", + "kind": "attribute", + "value": "'en'" + }, + "emotions": { + "annotation": "Mapping[str, str]", + "kind": "attribute", + "value": null + }, + "fast_runtime": { + "annotation": "FastRuntimeConfig", + "kind": "attribute", + "value": "field(default_factory=FastRuntimeConfig)" + }, + "feature_flags": { + "annotation": "FeatureFlags", + "kind": "attribute", + "value": "field(default_factory=FeatureFlags)" + }, + "feature_runtime_policy": { + "annotation": "FeatureRuntimePolicyConfig", + "kind": "attribute", + "value": "field(default_factory=FeatureRuntimePolicyConfig)" + }, + "medium_runtime": { + "annotation": "MediumRuntimeConfig", + "kind": "attribute", + "value": "field(default_factory=MediumRuntimeConfig)" + }, + "medium_training": { + "annotation": "MediumTrainingConfig", + "kind": "attribute", + "value": "field(default_factory=MediumTrainingConfig)" + }, + "models": { + "annotation": "ModelsConfig", + "kind": "attribute", + "value": "field(default_factory=ModelsConfig)" + }, + "nn": { + "annotation": "NeuralNetConfig", + "kind": "attribute", + "value": "field(default_factory=NeuralNetConfig)" + }, + "quality_gate": { + "annotation": "QualityGateConfig", + "kind": "attribute", + "value": "field(default_factory=QualityGateConfig)" + }, + "runtime_flags": { + "annotation": "RuntimeFlags", + "kind": "attribute", + "value": "field(default_factory=RuntimeFlags)" + }, + "schema": { + "annotation": "SchemaConfig", + "kind": "attribute", + "value": "field(default_factory=SchemaConfig)" + }, + "timeline": { + "annotation": "TimelineConfig", + "kind": "attribute", + "value": "field(default_factory=TimelineConfig)" + }, + "tmp_folder": { + "annotation": "Path", + "kind": "attribute", + "value": "field(default_factory=_default_tmp_folder)" + }, + "torch_runtime": { + "annotation": "TorchRuntimeConfig", + "kind": "attribute", + "value": "field(default_factory=TorchRuntimeConfig)" + }, + "training": { + "annotation": "TrainingConfig", + "kind": "attribute", + "value": "field(default_factory=TrainingConfig)" + }, + "transcription": { + "annotation": "TranscriptionConfig", + "kind": "attribute", + "value": "field(default_factory=TranscriptionConfig)" + } + } + }, + "kind": "alias" + }, + "AudioReadConfig": { + "contract": { + "bases": [], + "kind": "class", + "members": { + "max_retries": { + "annotation": "int", + "kind": "attribute", + "value": "3" + }, + "retry_delay_seconds": { + "annotation": "float", + "kind": "attribute", + "value": "1.0" + } + } + }, + "kind": "alias" }, "ComplianceMode": { "annotation": null, "kind": "attribute", "value": "_data_api.ComplianceMode" }, + "DataLoaderConfig": { + "contract": { + "bases": [], + "kind": "class", + "members": { + "max_failed_file_ratio": { + "annotation": "float", + "kind": "attribute", + "value": "0.01" + }, + "max_workers": { + "annotation": "int", + "kind": "attribute", + "value": "8" + } + } + }, + "kind": "alias" + }, + "DatasetConfig": { + "contract": { + "bases": [], + "kind": "class", + "members": { + "extension": { + "annotation": "str", + "kind": "attribute", + "value": "'*.wav'" + }, + "folder": { + "annotation": "Path", + "kind": "attribute", + "value": null + }, + "glob_pattern": { + "annotation": "str", + "kind": "attribute", + "value": null + }, + "manifest_paths": { + "annotation": "tuple[Path, ...]", + "kind": "attribute", + "value": "()" + }, + "subfolder_prefix": { + "annotation": "str", + "kind": "attribute", + "value": "'Actor_*'" + } + } + }, + "kind": "alias" + }, "DatasetConsents": { - "kind": "class", - "target_path": "ser.domain.DatasetConsents" + "contract": { + "bases": [ + "NamedTuple" + ], + "kind": "class", + "members": { + "license_ids": { + "annotation": "tuple[str, ...]", + "kind": "attribute", + "value": null + }, + "policy_ids": { + "annotation": "tuple[str, ...]", + "kind": "attribute", + "value": null + } + } + }, + "kind": "alias" }, "DatasetPrepareResult": { "annotation": null, @@ -53,28 +455,739 @@ "value": "_data_api.DatasetRegistryRecord" }, "DiagnosticFinding": { - "kind": "class", - "target_path": "ser.diagnostics.domain.DiagnosticFinding" + "contract": { + "bases": [], + "kind": "class", + "members": { + "blocking": { + "annotation": "bool", + "kind": "attribute", + "value": "False" + }, + "code": { + "annotation": "str", + "kind": "attribute", + "value": null + }, + "message": { + "annotation": "str", + "kind": "attribute", + "value": null + }, + "remediation": { + "annotation": "tuple[str, ...]", + "kind": "attribute", + "value": "()" + }, + "severity": { + "annotation": "DiagnosticSeverity", + "kind": "attribute", + "value": null + } + } + }, + "kind": "alias" }, "DiagnosticReport": { - "kind": "class", - "target_path": "ser.diagnostics.domain.DiagnosticReport" + "contract": { + "bases": [], + "kind": "class", + "members": { + "counts_by_severity": { + "kind": "function", + "parameters": [ + { + "annotation": null, + "default": null, + "kind": "positional or keyword", + "name": "self" + } + ], + "returns": "dict[DiagnosticSeverity, int]", + "signature": "(self) -> dict[DiagnosticSeverity, int]" + }, + "findings": { + "annotation": "tuple[DiagnosticFinding, ...]", + "kind": "attribute", + "value": null + }, + "has_blocking_findings": { + "annotation": "bool", + "kind": "attribute", + "value": null + }, + "has_error": { + "annotation": "bool", + "kind": "attribute", + "value": null + }, + "has_warning_or_higher": { + "annotation": "bool", + "kind": "attribute", + "value": null + }, + "to_dict": { + "kind": "function", + "parameters": [ + { + "annotation": null, + "default": null, + "kind": "positional or keyword", + "name": "self" + } + ], + "returns": "dict[str, object]", + "signature": "(self) -> dict[str, object]" + } + } + }, + "kind": "alias" }, "DiagnosticSeverity": { - "kind": "type alias", - "target_path": "ser.diagnostics.domain.DiagnosticSeverity" + "contract": { + "annotation": null, + "kind": "type alias", + "value": "Literal['info', 'warning', 'error']" + }, + "kind": "alias" + }, + "EmotionSegment": { + "contract": { + "bases": [ + "NamedTuple" + ], + "kind": "class", + "members": { + "emotion": { + "annotation": "str", + "kind": "attribute", + "value": null + }, + "end_seconds": { + "annotation": "float", + "kind": "attribute", + "value": null + }, + "start_seconds": { + "annotation": "float", + "kind": "attribute", + "value": null + } + } + }, + "kind": "alias" + }, + "FastRuntimeConfig": { + "contract": { + "bases": [ + "ProfileRuntimeConfig" + ], + "kind": "class", + "members": { + "max_timeout_retries": { + "annotation": "int", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_max_timeout_retries('fast')))" + }, + "max_transient_retries": { + "annotation": "int", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_max_transient_retries('fast')))" + }, + "pool_window_size_seconds": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_pool_window_size_seconds('fast')))" + }, + "pool_window_stride_seconds": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_pool_window_stride_seconds('fast')))" + }, + "post_hysteresis_enter_confidence": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_post_hysteresis_enter_confidence('fast')))" + }, + "post_hysteresis_exit_confidence": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_post_hysteresis_exit_confidence('fast')))" + }, + "post_min_segment_duration_seconds": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_post_min_segment_duration_seconds('fast')))" + }, + "post_smoothing_window_frames": { + "annotation": "int", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_post_smoothing_window_frames('fast')))" + }, + "process_isolation": { + "annotation": "bool", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_process_isolation('fast')))" + }, + "retry_backoff_seconds": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_retry_backoff_seconds('fast')))" + }, + "timeout_seconds": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_timeout_seconds('fast')))" + } + } + }, + "kind": "alias" + }, + "FeatureFlags": { + "contract": { + "bases": [], + "kind": "class", + "members": { + "chroma": { + "annotation": "bool", + "kind": "attribute", + "value": "True" + }, + "contrast": { + "annotation": "bool", + "kind": "attribute", + "value": "True" + }, + "mel": { + "annotation": "bool", + "kind": "attribute", + "value": "True" + }, + "mfcc": { + "annotation": "bool", + "kind": "attribute", + "value": "True" + }, + "tonnetz": { + "annotation": "bool", + "kind": "attribute", + "value": "True" + } + } + }, + "kind": "alias" + }, + "FeatureRuntimeBackendOverride": { + "contract": { + "bases": [], + "kind": "class", + "members": { + "device": { + "annotation": "str | None", + "kind": "attribute", + "value": "None" + }, + "dtype": { + "annotation": "str | None", + "kind": "attribute", + "value": "None" + } + } + }, + "kind": "alias" + }, + "FeatureRuntimePolicyConfig": { + "contract": { + "bases": [], + "kind": "class", + "members": { + "backend_overrides": { + "annotation": "tuple[tuple[str, FeatureRuntimeBackendOverride], ...]", + "kind": "attribute", + "value": "()" + }, + "for_backend": { + "kind": "function", + "parameters": [ + { + "annotation": null, + "default": null, + "kind": "positional or keyword", + "name": "self" + }, + { + "annotation": "str", + "default": null, + "kind": "positional or keyword", + "name": "backend_id" + } + ], + "returns": "FeatureRuntimeBackendOverride | None", + "signature": "(self, backend_id: str) -> FeatureRuntimeBackendOverride | None" + } + } + }, + "kind": "alias" + }, + "FramePrediction": { + "contract": { + "bases": [], + "kind": "class", + "members": { + "confidence": { + "annotation": "float", + "kind": "attribute", + "value": null + }, + "emotion": { + "annotation": "str", + "kind": "attribute", + "value": null + }, + "end_seconds": { + "annotation": "float", + "kind": "attribute", + "value": null + }, + "probabilities": { + "annotation": "dict[str, float] | None", + "kind": "attribute", + "value": null + }, + "start_seconds": { + "annotation": "float", + "kind": "attribute", + "value": null + } + } + }, + "kind": "alias" }, "InferenceExecution": { - "kind": "class", - "target_path": "ser.runtime.contracts.InferenceExecution" + "contract": { + "bases": [], + "kind": "class", + "members": { + "backend_id": { + "annotation": "str", + "kind": "attribute", + "value": null + }, + "detailed_result": { + "annotation": "InferenceResult | None", + "kind": "attribute", + "value": "None" + }, + "emotions": { + "annotation": "list[EmotionSegment]", + "kind": "attribute", + "value": null + }, + "output_schema_version": { + "annotation": "str", + "kind": "attribute", + "value": null + }, + "phase_timings_seconds": { + "annotation": "dict[str, float]", + "kind": "attribute", + "value": "field(default_factory=dict)" + }, + "profile": { + "annotation": "ProfileName", + "kind": "attribute", + "value": null + }, + "subtitle_path": { + "annotation": "str | None", + "kind": "attribute", + "value": "None" + }, + "timeline": { + "annotation": "list[TimelineEntry]", + "kind": "attribute", + "value": null + }, + "timeline_csv_path": { + "annotation": "str | None", + "kind": "attribute", + "value": "None" + }, + "transcript": { + "annotation": "list[TranscriptWord]", + "kind": "attribute", + "value": null + }, + "used_backend_path": { + "annotation": "bool", + "kind": "attribute", + "value": "False" + } + } + }, + "kind": "alias" }, "InferenceRequest": { - "kind": "class", - "target_path": "ser.runtime.contracts.InferenceRequest" + "contract": { + "bases": [], + "kind": "class", + "members": { + "file_path": { + "annotation": "str", + "kind": "attribute", + "value": null + }, + "include_transcript": { + "annotation": "bool", + "kind": "attribute", + "value": "True" + }, + "language": { + "annotation": "str", + "kind": "attribute", + "value": null + }, + "save_transcript": { + "annotation": "bool", + "kind": "attribute", + "value": "False" + }, + "subtitle_format": { + "annotation": "SubtitleFormat | None", + "kind": "attribute", + "value": "None" + }, + "subtitle_output_path": { + "annotation": "str | None", + "kind": "attribute", + "value": "None" + } + } + }, + "kind": "alias" + }, + "InferenceResult": { + "contract": { + "bases": [], + "kind": "class", + "members": { + "frames": { + "annotation": "list[FramePrediction]", + "kind": "attribute", + "value": null + }, + "schema_version": { + "annotation": "str", + "kind": "attribute", + "value": null + }, + "segments": { + "annotation": "list[SegmentPrediction]", + "kind": "attribute", + "value": null + } + } + }, + "kind": "alias" + }, + "MediumRuntimeConfig": { + "contract": { + "bases": [ + "ProfileRuntimeConfig" + ], + "kind": "class", + "members": { + "max_timeout_retries": { + "annotation": "int", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_max_timeout_retries('medium')))" + }, + "max_transient_retries": { + "annotation": "int", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_max_transient_retries('medium')))" + }, + "pool_window_size_seconds": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_pool_window_size_seconds('medium')))" + }, + "pool_window_stride_seconds": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_pool_window_stride_seconds('medium')))" + }, + "post_hysteresis_enter_confidence": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_post_hysteresis_enter_confidence('medium')))" + }, + "post_hysteresis_exit_confidence": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_post_hysteresis_exit_confidence('medium')))" + }, + "post_min_segment_duration_seconds": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_post_min_segment_duration_seconds('medium')))" + }, + "post_smoothing_window_frames": { + "annotation": "int", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_post_smoothing_window_frames('medium')))" + }, + "process_isolation": { + "annotation": "bool", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_process_isolation('medium')))" + }, + "retry_backoff_seconds": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_retry_backoff_seconds('medium')))" + }, + "timeout_seconds": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_timeout_seconds('medium')))" + } + } + }, + "kind": "alias" + }, + "MediumTrainingConfig": { + "contract": { + "bases": [], + "kind": "class", + "members": { + "max_windows_per_clip": { + "annotation": "int", + "kind": "attribute", + "value": "0" + }, + "min_window_std": { + "annotation": "float", + "kind": "attribute", + "value": "0.0" + } + } + }, + "kind": "alias" + }, + "ModelsConfig": { + "contract": { + "bases": [], + "kind": "class", + "members": { + "accurate_model_id": { + "annotation": "str", + "kind": "attribute", + "value": "field(default_factory=(lambda: default_profile_model_id('accurate')))" + }, + "accurate_research_model_id": { + "annotation": "str", + "kind": "attribute", + "value": "field(default_factory=(lambda: default_profile_model_id('accurate-research')))" + }, + "folder": { + "annotation": "Path", + "kind": "attribute", + "value": "field(default_factory=_default_models_folder)" + }, + "huggingface_cache_root": { + "annotation": "Path", + "kind": "attribute", + "value": null + }, + "medium_model_id": { + "annotation": "str", + "kind": "attribute", + "value": "field(default_factory=(lambda: default_profile_model_id('medium')))" + }, + "model_cache_dir": { + "annotation": "Path", + "kind": "attribute", + "value": "field(default_factory=_default_model_cache_root)" + }, + "model_file": { + "annotation": "Path", + "kind": "attribute", + "value": null + }, + "model_file_name": { + "annotation": "str", + "kind": "attribute", + "value": "'ser_model.pkl'" + }, + "modelscope_cache_root": { + "annotation": "Path", + "kind": "attribute", + "value": null + }, + "num_cores": { + "annotation": "int", + "kind": "attribute", + "value": "1" + }, + "secure_model_file": { + "annotation": "Path", + "kind": "attribute", + "value": null + }, + "secure_model_file_name": { + "annotation": "str", + "kind": "attribute", + "value": "'ser_model.skops'" + }, + "torch_cache_root": { + "annotation": "Path", + "kind": "attribute", + "value": null + }, + "training_report_file": { + "annotation": "Path", + "kind": "attribute", + "value": null + }, + "training_report_file_name": { + "annotation": "str", + "kind": "attribute", + "value": "'training_report.json'" + }, + "whisper_download_root": { + "annotation": "Path", + "kind": "attribute", + "value": null + }, + "whisper_model": { + "annotation": "WhisperModelConfig", + "kind": "attribute", + "value": "field(default_factory=WhisperModelConfig)" + } + } + }, + "kind": "alias" + }, + "NeuralNetConfig": { + "contract": { + "bases": [], + "kind": "class", + "members": { + "alpha": { + "annotation": "float", + "kind": "attribute", + "value": "0.01" + }, + "batch_size": { + "annotation": "int | Literal['auto']", + "kind": "attribute", + "value": "256" + }, + "epsilon": { + "annotation": "float", + "kind": "attribute", + "value": "1e-08" + }, + "hidden_layer_sizes": { + "annotation": "tuple[int, ...]", + "kind": "attribute", + "value": "(300,)" + }, + "learning_rate": { + "annotation": "Literal['constant', 'invscaling', 'adaptive']", + "kind": "attribute", + "value": "'adaptive'" + }, + "max_iter": { + "annotation": "int", + "kind": "attribute", + "value": "500" + }, + "random_state": { + "annotation": "int", + "kind": "attribute", + "value": "42" + } + } + }, + "kind": "alias" }, "ProfileName": { - "kind": "type alias", - "target_path": "ser.profiles.ProfileName" + "contract": { + "annotation": null, + "kind": "type alias", + "value": "Literal['fast', 'medium', 'accurate', 'accurate-research']" + }, + "kind": "alias" + }, + "QualityGateConfig": { + "contract": { + "bases": [], + "kind": "class", + "members": { + "max_medium_segments_per_minute": { + "annotation": "float", + "kind": "attribute", + "value": "25.0" + }, + "min_macro_f1_delta": { + "annotation": "float", + "kind": "attribute", + "value": "0.0025" + }, + "min_medium_median_segment_duration_seconds": { + "annotation": "float", + "kind": "attribute", + "value": "2.5" + }, + "min_uar_delta": { + "annotation": "float", + "kind": "attribute", + "value": "0.0025" + } + } + }, + "kind": "alias" + }, + "RuntimeFlags": { + "contract": { + "bases": [], + "kind": "class", + "members": { + "accurate_profile": { + "annotation": "bool", + "kind": "attribute", + "value": "False" + }, + "accurate_research_profile": { + "annotation": "bool", + "kind": "attribute", + "value": "False" + }, + "medium_profile": { + "annotation": "bool", + "kind": "attribute", + "value": "False" + }, + "new_output_schema": { + "annotation": "bool", + "kind": "attribute", + "value": "False" + }, + "profile_pipeline": { + "annotation": "bool", + "kind": "attribute", + "value": "False" + }, + "restricted_backends": { + "annotation": "bool", + "kind": "attribute", + "value": "False" + } + } + }, + "kind": "alias" }, "RuntimePipeline": { "bases": [ @@ -121,9 +1234,268 @@ "kind": "type alias", "value": "Callable[[AppConfig], RuntimePipeline]" }, + "SchemaConfig": { + "contract": { + "bases": [], + "kind": "class", + "members": { + "artifact_schema_version": { + "annotation": "str", + "kind": "attribute", + "value": "'v2'" + }, + "output_schema_version": { + "annotation": "str", + "kind": "attribute", + "value": "'v1'" + } + } + }, + "kind": "alias" + }, + "SegmentPrediction": { + "contract": { + "bases": [], + "kind": "class", + "members": { + "confidence": { + "annotation": "float", + "kind": "attribute", + "value": null + }, + "emotion": { + "annotation": "str", + "kind": "attribute", + "value": null + }, + "end_seconds": { + "annotation": "float", + "kind": "attribute", + "value": null + }, + "probabilities": { + "annotation": "dict[str, float] | None", + "kind": "attribute", + "value": "None" + }, + "start_seconds": { + "annotation": "float", + "kind": "attribute", + "value": null + } + } + }, + "kind": "alias" + }, "SubtitleFormat": { - "kind": "type alias", - "target_path": "ser.runtime.contracts.SubtitleFormat" + "contract": { + "annotation": null, + "kind": "type alias", + "value": "Literal['ass', 'srt', 'vtt']" + }, + "kind": "alias" + }, + "TimelineConfig": { + "contract": { + "bases": [], + "kind": "class", + "members": { + "folder": { + "annotation": "Path", + "kind": "attribute", + "value": "field(default_factory=_default_transcripts_folder)" + } + } + }, + "kind": "alias" + }, + "TimelineEntry": { + "contract": { + "bases": [ + "NamedTuple" + ], + "kind": "class", + "members": { + "emotion": { + "annotation": "str", + "kind": "attribute", + "value": null + }, + "speech": { + "annotation": "str", + "kind": "attribute", + "value": null + }, + "timestamp_seconds": { + "annotation": "float", + "kind": "attribute", + "value": null + } + } + }, + "kind": "alias" + }, + "TorchRuntimeConfig": { + "contract": { + "bases": [], + "kind": "class", + "members": { + "device": { + "annotation": "str", + "kind": "attribute", + "value": "'auto'" + }, + "dtype": { + "annotation": "str", + "kind": "attribute", + "value": "'auto'" + }, + "enable_mps_fallback": { + "annotation": "bool", + "kind": "attribute", + "value": "False" + } + } + }, + "kind": "alias" + }, + "TrainingConfig": { + "contract": { + "bases": [], + "kind": "class", + "members": { + "random_state": { + "annotation": "int", + "kind": "attribute", + "value": "42" + }, + "stratify_split": { + "annotation": "bool", + "kind": "attribute", + "value": "True" + }, + "test_size": { + "annotation": "float", + "kind": "attribute", + "value": "0.25" + } + } + }, + "kind": "alias" + }, + "TranscriptWord": { + "contract": { + "bases": [ + "NamedTuple" + ], + "kind": "class", + "members": { + "end_seconds": { + "annotation": "float", + "kind": "attribute", + "value": null + }, + "start_seconds": { + "annotation": "float", + "kind": "attribute", + "value": null + }, + "word": { + "annotation": "str", + "kind": "attribute", + "value": null + } + } + }, + "kind": "alias" + }, + "TranscriptionConfig": { + "contract": { + "bases": [], + "kind": "class", + "members": { + "backend_id": { + "annotation": "TranscriptionBackendId", + "kind": "attribute", + "value": "field(default_factory=_default_fast_transcription_backend_id)" + }, + "mps_admission_calibration_min_confidence": { + "annotation": "Literal['high', 'medium', 'low']", + "kind": "attribute", + "value": "'high'" + }, + "mps_admission_calibration_overrides_enabled": { + "annotation": "bool", + "kind": "attribute", + "value": "True" + }, + "mps_admission_calibration_report_max_age_hours": { + "annotation": "float", + "kind": "attribute", + "value": "168.0" + }, + "mps_admission_calibration_report_path": { + "annotation": "Path | None", + "kind": "attribute", + "value": "None" + }, + "mps_admission_control_enabled": { + "annotation": "bool", + "kind": "attribute", + "value": "True" + }, + "mps_admission_min_headroom_mb": { + "annotation": "float", + "kind": "attribute", + "value": "64.0" + }, + "mps_admission_safety_margin_mb": { + "annotation": "float", + "kind": "attribute", + "value": "64.0" + }, + "mps_hard_oom_shortcut_enabled": { + "annotation": "bool", + "kind": "attribute", + "value": "True" + }, + "mps_low_memory_threshold_gb": { + "annotation": "float", + "kind": "attribute", + "value": "16.0" + }, + "use_demucs": { + "annotation": "bool", + "kind": "attribute", + "value": "field(default_factory=_default_fast_transcription_use_demucs)" + }, + "use_vad": { + "annotation": "bool", + "kind": "attribute", + "value": "field(default_factory=_default_fast_transcription_use_vad)" + } + } + }, + "kind": "alias" + }, + "WhisperModelConfig": { + "contract": { + "bases": [], + "kind": "class", + "members": { + "name": { + "annotation": "str", + "kind": "attribute", + "value": "field(default_factory=_default_fast_whisper_model_name)" + }, + "relative_path": { + "annotation": "Path", + "kind": "attribute", + "value": "Path('OpenAI/whisper')" + } + } + }, + "kind": "alias" }, "configure_dataset_consents": { "kind": "function", @@ -416,112 +1788,1034 @@ "ser.config": { "exports": { "APP_NAME": { - "kind": "attribute", - "target_path": "ser._internal.config.schema.APP_NAME" + "contract": { + "annotation": null, + "kind": "attribute", + "value": "'ser'" + }, + "kind": "alias" }, "AccurateResearchRuntimeConfig": { - "kind": "class", - "target_path": "ser._internal.config.schema.AccurateResearchRuntimeConfig" + "contract": { + "bases": [ + "ProfileRuntimeConfig" + ], + "kind": "class", + "members": { + "max_timeout_retries": { + "annotation": "int", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_max_timeout_retries('accurate-research')))" + }, + "max_transient_retries": { + "annotation": "int", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_max_transient_retries('accurate-research')))" + }, + "pool_window_size_seconds": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_pool_window_size_seconds('accurate-research')))" + }, + "pool_window_stride_seconds": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_pool_window_stride_seconds('accurate-research')))" + }, + "post_hysteresis_enter_confidence": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_post_hysteresis_enter_confidence('accurate-research')))" + }, + "post_hysteresis_exit_confidence": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_post_hysteresis_exit_confidence('accurate-research')))" + }, + "post_min_segment_duration_seconds": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_post_min_segment_duration_seconds('accurate-research')))" + }, + "post_smoothing_window_frames": { + "annotation": "int", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_post_smoothing_window_frames('accurate-research')))" + }, + "process_isolation": { + "annotation": "bool", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_process_isolation('accurate-research')))" + }, + "retry_backoff_seconds": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_retry_backoff_seconds('accurate-research')))" + }, + "timeout_seconds": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_timeout_seconds('accurate-research')))" + } + } + }, + "kind": "alias" }, "AccurateRuntimeConfig": { - "kind": "class", - "target_path": "ser._internal.config.schema.AccurateRuntimeConfig" + "contract": { + "bases": [ + "ProfileRuntimeConfig" + ], + "kind": "class", + "members": { + "max_timeout_retries": { + "annotation": "int", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_max_timeout_retries('accurate')))" + }, + "max_transient_retries": { + "annotation": "int", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_max_transient_retries('accurate')))" + }, + "pool_window_size_seconds": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_pool_window_size_seconds('accurate')))" + }, + "pool_window_stride_seconds": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_pool_window_stride_seconds('accurate')))" + }, + "post_hysteresis_enter_confidence": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_post_hysteresis_enter_confidence('accurate')))" + }, + "post_hysteresis_exit_confidence": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_post_hysteresis_exit_confidence('accurate')))" + }, + "post_min_segment_duration_seconds": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_post_min_segment_duration_seconds('accurate')))" + }, + "post_smoothing_window_frames": { + "annotation": "int", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_post_smoothing_window_frames('accurate')))" + }, + "process_isolation": { + "annotation": "bool", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_process_isolation('accurate')))" + }, + "retry_backoff_seconds": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_retry_backoff_seconds('accurate')))" + }, + "timeout_seconds": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_timeout_seconds('accurate')))" + } + } + }, + "kind": "alias" }, "AppConfig": { - "kind": "class", - "target_path": "ser._internal.config.schema.AppConfig" + "contract": { + "bases": [], + "kind": "class", + "members": { + "accurate_research_runtime": { + "annotation": "AccurateResearchRuntimeConfig", + "kind": "attribute", + "value": "field(default_factory=AccurateResearchRuntimeConfig)" + }, + "accurate_runtime": { + "annotation": "AccurateRuntimeConfig", + "kind": "attribute", + "value": "field(default_factory=AccurateRuntimeConfig)" + }, + "audio_read": { + "annotation": "AudioReadConfig", + "kind": "attribute", + "value": "field(default_factory=AudioReadConfig)" + }, + "data_loader": { + "annotation": "DataLoaderConfig", + "kind": "attribute", + "value": "field(default_factory=DataLoaderConfig)" + }, + "dataset": { + "annotation": "DatasetConfig", + "kind": "attribute", + "value": "field(default_factory=(lambda: DatasetConfig(folder=(Path('ser/dataset/ravdess')))))" + }, + "default_language": { + "annotation": "str", + "kind": "attribute", + "value": "'en'" + }, + "emotions": { + "annotation": "Mapping[str, str]", + "kind": "attribute", + "value": null + }, + "fast_runtime": { + "annotation": "FastRuntimeConfig", + "kind": "attribute", + "value": "field(default_factory=FastRuntimeConfig)" + }, + "feature_flags": { + "annotation": "FeatureFlags", + "kind": "attribute", + "value": "field(default_factory=FeatureFlags)" + }, + "feature_runtime_policy": { + "annotation": "FeatureRuntimePolicyConfig", + "kind": "attribute", + "value": "field(default_factory=FeatureRuntimePolicyConfig)" + }, + "medium_runtime": { + "annotation": "MediumRuntimeConfig", + "kind": "attribute", + "value": "field(default_factory=MediumRuntimeConfig)" + }, + "medium_training": { + "annotation": "MediumTrainingConfig", + "kind": "attribute", + "value": "field(default_factory=MediumTrainingConfig)" + }, + "models": { + "annotation": "ModelsConfig", + "kind": "attribute", + "value": "field(default_factory=ModelsConfig)" + }, + "nn": { + "annotation": "NeuralNetConfig", + "kind": "attribute", + "value": "field(default_factory=NeuralNetConfig)" + }, + "quality_gate": { + "annotation": "QualityGateConfig", + "kind": "attribute", + "value": "field(default_factory=QualityGateConfig)" + }, + "runtime_flags": { + "annotation": "RuntimeFlags", + "kind": "attribute", + "value": "field(default_factory=RuntimeFlags)" + }, + "schema": { + "annotation": "SchemaConfig", + "kind": "attribute", + "value": "field(default_factory=SchemaConfig)" + }, + "timeline": { + "annotation": "TimelineConfig", + "kind": "attribute", + "value": "field(default_factory=TimelineConfig)" + }, + "tmp_folder": { + "annotation": "Path", + "kind": "attribute", + "value": "field(default_factory=_default_tmp_folder)" + }, + "torch_runtime": { + "annotation": "TorchRuntimeConfig", + "kind": "attribute", + "value": "field(default_factory=TorchRuntimeConfig)" + }, + "training": { + "annotation": "TrainingConfig", + "kind": "attribute", + "value": "field(default_factory=TrainingConfig)" + }, + "transcription": { + "annotation": "TranscriptionConfig", + "kind": "attribute", + "value": "field(default_factory=TranscriptionConfig)" + } + } + }, + "kind": "alias" }, "ArtifactProfileName": { - "kind": "type alias", - "target_path": "ser._internal.config.schema.ArtifactProfileName" + "contract": { + "annotation": null, + "kind": "type alias", + "value": "Literal['fast', 'medium', 'accurate', 'accurate-research']" + }, + "kind": "alias" }, "AudioReadConfig": { - "kind": "class", - "target_path": "ser._internal.config.schema.AudioReadConfig" + "contract": { + "bases": [], + "kind": "class", + "members": { + "max_retries": { + "annotation": "int", + "kind": "attribute", + "value": "3" + }, + "retry_delay_seconds": { + "annotation": "float", + "kind": "attribute", + "value": "1.0" + } + } + }, + "kind": "alias" }, "DataLoaderConfig": { - "kind": "class", - "target_path": "ser._internal.config.schema.DataLoaderConfig" + "contract": { + "bases": [], + "kind": "class", + "members": { + "max_failed_file_ratio": { + "annotation": "float", + "kind": "attribute", + "value": "0.01" + }, + "max_workers": { + "annotation": "int", + "kind": "attribute", + "value": "8" + } + } + }, + "kind": "alias" }, "DatasetConfig": { - "kind": "class", - "target_path": "ser._internal.config.schema.DatasetConfig" + "contract": { + "bases": [], + "kind": "class", + "members": { + "extension": { + "annotation": "str", + "kind": "attribute", + "value": "'*.wav'" + }, + "folder": { + "annotation": "Path", + "kind": "attribute", + "value": null + }, + "glob_pattern": { + "annotation": "str", + "kind": "attribute", + "value": null + }, + "manifest_paths": { + "annotation": "tuple[Path, ...]", + "kind": "attribute", + "value": "()" + }, + "subfolder_prefix": { + "annotation": "str", + "kind": "attribute", + "value": "'Actor_*'" + } + } + }, + "kind": "alias" }, "FastRuntimeConfig": { - "kind": "class", - "target_path": "ser._internal.config.schema.FastRuntimeConfig" + "contract": { + "bases": [ + "ProfileRuntimeConfig" + ], + "kind": "class", + "members": { + "max_timeout_retries": { + "annotation": "int", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_max_timeout_retries('fast')))" + }, + "max_transient_retries": { + "annotation": "int", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_max_transient_retries('fast')))" + }, + "pool_window_size_seconds": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_pool_window_size_seconds('fast')))" + }, + "pool_window_stride_seconds": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_pool_window_stride_seconds('fast')))" + }, + "post_hysteresis_enter_confidence": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_post_hysteresis_enter_confidence('fast')))" + }, + "post_hysteresis_exit_confidence": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_post_hysteresis_exit_confidence('fast')))" + }, + "post_min_segment_duration_seconds": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_post_min_segment_duration_seconds('fast')))" + }, + "post_smoothing_window_frames": { + "annotation": "int", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_post_smoothing_window_frames('fast')))" + }, + "process_isolation": { + "annotation": "bool", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_process_isolation('fast')))" + }, + "retry_backoff_seconds": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_retry_backoff_seconds('fast')))" + }, + "timeout_seconds": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_timeout_seconds('fast')))" + } + } + }, + "kind": "alias" }, "FeatureFlags": { - "kind": "class", - "target_path": "ser._internal.config.schema.FeatureFlags" + "contract": { + "bases": [], + "kind": "class", + "members": { + "chroma": { + "annotation": "bool", + "kind": "attribute", + "value": "True" + }, + "contrast": { + "annotation": "bool", + "kind": "attribute", + "value": "True" + }, + "mel": { + "annotation": "bool", + "kind": "attribute", + "value": "True" + }, + "mfcc": { + "annotation": "bool", + "kind": "attribute", + "value": "True" + }, + "tonnetz": { + "annotation": "bool", + "kind": "attribute", + "value": "True" + } + } + }, + "kind": "alias" }, "FeatureRuntimePolicyConfig": { - "kind": "class", - "target_path": "ser._internal.config.schema.FeatureRuntimePolicyConfig" + "contract": { + "bases": [], + "kind": "class", + "members": { + "backend_overrides": { + "annotation": "tuple[tuple[str, FeatureRuntimeBackendOverride], ...]", + "kind": "attribute", + "value": "()" + }, + "for_backend": { + "kind": "function", + "parameters": [ + { + "annotation": null, + "default": null, + "kind": "positional or keyword", + "name": "self" + }, + { + "annotation": "str", + "default": null, + "kind": "positional or keyword", + "name": "backend_id" + } + ], + "returns": "FeatureRuntimeBackendOverride | None", + "signature": "(self, backend_id: str) -> FeatureRuntimeBackendOverride | None" + } + } + }, + "kind": "alias" }, "MediumRuntimeConfig": { - "kind": "class", - "target_path": "ser._internal.config.schema.MediumRuntimeConfig" + "contract": { + "bases": [ + "ProfileRuntimeConfig" + ], + "kind": "class", + "members": { + "max_timeout_retries": { + "annotation": "int", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_max_timeout_retries('medium')))" + }, + "max_transient_retries": { + "annotation": "int", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_max_transient_retries('medium')))" + }, + "pool_window_size_seconds": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_pool_window_size_seconds('medium')))" + }, + "pool_window_stride_seconds": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_pool_window_stride_seconds('medium')))" + }, + "post_hysteresis_enter_confidence": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_post_hysteresis_enter_confidence('medium')))" + }, + "post_hysteresis_exit_confidence": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_post_hysteresis_exit_confidence('medium')))" + }, + "post_min_segment_duration_seconds": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_post_min_segment_duration_seconds('medium')))" + }, + "post_smoothing_window_frames": { + "annotation": "int", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_post_smoothing_window_frames('medium')))" + }, + "process_isolation": { + "annotation": "bool", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_process_isolation('medium')))" + }, + "retry_backoff_seconds": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_retry_backoff_seconds('medium')))" + }, + "timeout_seconds": { + "annotation": "float", + "kind": "attribute", + "value": "field(default_factory=(lambda: _default_runtime_timeout_seconds('medium')))" + } + } + }, + "kind": "alias" }, "MediumTrainingConfig": { - "kind": "class", - "target_path": "ser._internal.config.schema.MediumTrainingConfig" + "contract": { + "bases": [], + "kind": "class", + "members": { + "max_windows_per_clip": { + "annotation": "int", + "kind": "attribute", + "value": "0" + }, + "min_window_std": { + "annotation": "float", + "kind": "attribute", + "value": "0.0" + } + } + }, + "kind": "alias" }, "ModelsConfig": { - "kind": "class", - "target_path": "ser._internal.config.schema.ModelsConfig" + "contract": { + "bases": [], + "kind": "class", + "members": { + "accurate_model_id": { + "annotation": "str", + "kind": "attribute", + "value": "field(default_factory=(lambda: default_profile_model_id('accurate')))" + }, + "accurate_research_model_id": { + "annotation": "str", + "kind": "attribute", + "value": "field(default_factory=(lambda: default_profile_model_id('accurate-research')))" + }, + "folder": { + "annotation": "Path", + "kind": "attribute", + "value": "field(default_factory=_default_models_folder)" + }, + "huggingface_cache_root": { + "annotation": "Path", + "kind": "attribute", + "value": null + }, + "medium_model_id": { + "annotation": "str", + "kind": "attribute", + "value": "field(default_factory=(lambda: default_profile_model_id('medium')))" + }, + "model_cache_dir": { + "annotation": "Path", + "kind": "attribute", + "value": "field(default_factory=_default_model_cache_root)" + }, + "model_file": { + "annotation": "Path", + "kind": "attribute", + "value": null + }, + "model_file_name": { + "annotation": "str", + "kind": "attribute", + "value": "'ser_model.pkl'" + }, + "modelscope_cache_root": { + "annotation": "Path", + "kind": "attribute", + "value": null + }, + "num_cores": { + "annotation": "int", + "kind": "attribute", + "value": "1" + }, + "secure_model_file": { + "annotation": "Path", + "kind": "attribute", + "value": null + }, + "secure_model_file_name": { + "annotation": "str", + "kind": "attribute", + "value": "'ser_model.skops'" + }, + "torch_cache_root": { + "annotation": "Path", + "kind": "attribute", + "value": null + }, + "training_report_file": { + "annotation": "Path", + "kind": "attribute", + "value": null + }, + "training_report_file_name": { + "annotation": "str", + "kind": "attribute", + "value": "'training_report.json'" + }, + "whisper_download_root": { + "annotation": "Path", + "kind": "attribute", + "value": null + }, + "whisper_model": { + "annotation": "WhisperModelConfig", + "kind": "attribute", + "value": "field(default_factory=WhisperModelConfig)" + } + } + }, + "kind": "alias" }, "NeuralNetConfig": { - "kind": "class", - "target_path": "ser._internal.config.schema.NeuralNetConfig" + "contract": { + "bases": [], + "kind": "class", + "members": { + "alpha": { + "annotation": "float", + "kind": "attribute", + "value": "0.01" + }, + "batch_size": { + "annotation": "int | Literal['auto']", + "kind": "attribute", + "value": "256" + }, + "epsilon": { + "annotation": "float", + "kind": "attribute", + "value": "1e-08" + }, + "hidden_layer_sizes": { + "annotation": "tuple[int, ...]", + "kind": "attribute", + "value": "(300,)" + }, + "learning_rate": { + "annotation": "Literal['constant', 'invscaling', 'adaptive']", + "kind": "attribute", + "value": "'adaptive'" + }, + "max_iter": { + "annotation": "int", + "kind": "attribute", + "value": "500" + }, + "random_state": { + "annotation": "int", + "kind": "attribute", + "value": "42" + } + } + }, + "kind": "alias" }, "ProfileRuntimeConfig": { - "kind": "class", - "target_path": "ser._internal.config.schema.ProfileRuntimeConfig" + "contract": { + "bases": [], + "kind": "class", + "members": { + "max_timeout_retries": { + "annotation": "int", + "kind": "attribute", + "value": null + }, + "max_transient_retries": { + "annotation": "int", + "kind": "attribute", + "value": null + }, + "pool_window_size_seconds": { + "annotation": "float", + "kind": "attribute", + "value": null + }, + "pool_window_stride_seconds": { + "annotation": "float", + "kind": "attribute", + "value": null + }, + "post_hysteresis_enter_confidence": { + "annotation": "float", + "kind": "attribute", + "value": null + }, + "post_hysteresis_exit_confidence": { + "annotation": "float", + "kind": "attribute", + "value": null + }, + "post_min_segment_duration_seconds": { + "annotation": "float", + "kind": "attribute", + "value": null + }, + "post_smoothing_window_frames": { + "annotation": "int", + "kind": "attribute", + "value": null + }, + "process_isolation": { + "annotation": "bool", + "kind": "attribute", + "value": null + }, + "retry_backoff_seconds": { + "annotation": "float", + "kind": "attribute", + "value": null + }, + "timeout_seconds": { + "annotation": "float", + "kind": "attribute", + "value": null + } + } + }, + "kind": "alias" }, "QualityGateConfig": { - "kind": "class", - "target_path": "ser._internal.config.schema.QualityGateConfig" + "contract": { + "bases": [], + "kind": "class", + "members": { + "max_medium_segments_per_minute": { + "annotation": "float", + "kind": "attribute", + "value": "25.0" + }, + "min_macro_f1_delta": { + "annotation": "float", + "kind": "attribute", + "value": "0.0025" + }, + "min_medium_median_segment_duration_seconds": { + "annotation": "float", + "kind": "attribute", + "value": "2.5" + }, + "min_uar_delta": { + "annotation": "float", + "kind": "attribute", + "value": "0.0025" + } + } + }, + "kind": "alias" }, "RuntimeFlags": { - "kind": "class", - "target_path": "ser._internal.config.schema.RuntimeFlags" + "contract": { + "bases": [], + "kind": "class", + "members": { + "accurate_profile": { + "annotation": "bool", + "kind": "attribute", + "value": "False" + }, + "accurate_research_profile": { + "annotation": "bool", + "kind": "attribute", + "value": "False" + }, + "medium_profile": { + "annotation": "bool", + "kind": "attribute", + "value": "False" + }, + "new_output_schema": { + "annotation": "bool", + "kind": "attribute", + "value": "False" + }, + "profile_pipeline": { + "annotation": "bool", + "kind": "attribute", + "value": "False" + }, + "restricted_backends": { + "annotation": "bool", + "kind": "attribute", + "value": "False" + } + } + }, + "kind": "alias" }, "SchemaConfig": { - "kind": "class", - "target_path": "ser._internal.config.schema.SchemaConfig" + "contract": { + "bases": [], + "kind": "class", + "members": { + "artifact_schema_version": { + "annotation": "str", + "kind": "attribute", + "value": "'v2'" + }, + "output_schema_version": { + "annotation": "str", + "kind": "attribute", + "value": "'v1'" + } + } + }, + "kind": "alias" }, "TimelineConfig": { - "kind": "class", - "target_path": "ser._internal.config.schema.TimelineConfig" + "contract": { + "bases": [], + "kind": "class", + "members": { + "folder": { + "annotation": "Path", + "kind": "attribute", + "value": "field(default_factory=_default_transcripts_folder)" + } + } + }, + "kind": "alias" }, "TorchRuntimeConfig": { - "kind": "class", - "target_path": "ser._internal.config.schema.TorchRuntimeConfig" + "contract": { + "bases": [], + "kind": "class", + "members": { + "device": { + "annotation": "str", + "kind": "attribute", + "value": "'auto'" + }, + "dtype": { + "annotation": "str", + "kind": "attribute", + "value": "'auto'" + }, + "enable_mps_fallback": { + "annotation": "bool", + "kind": "attribute", + "value": "False" + } + } + }, + "kind": "alias" }, "TrainingConfig": { - "kind": "class", - "target_path": "ser._internal.config.schema.TrainingConfig" + "contract": { + "bases": [], + "kind": "class", + "members": { + "random_state": { + "annotation": "int", + "kind": "attribute", + "value": "42" + }, + "stratify_split": { + "annotation": "bool", + "kind": "attribute", + "value": "True" + }, + "test_size": { + "annotation": "float", + "kind": "attribute", + "value": "0.25" + } + } + }, + "kind": "alias" }, "TranscriptionConfig": { - "kind": "class", - "target_path": "ser._internal.config.schema.TranscriptionConfig" + "contract": { + "bases": [], + "kind": "class", + "members": { + "backend_id": { + "annotation": "TranscriptionBackendId", + "kind": "attribute", + "value": "field(default_factory=_default_fast_transcription_backend_id)" + }, + "mps_admission_calibration_min_confidence": { + "annotation": "Literal['high', 'medium', 'low']", + "kind": "attribute", + "value": "'high'" + }, + "mps_admission_calibration_overrides_enabled": { + "annotation": "bool", + "kind": "attribute", + "value": "True" + }, + "mps_admission_calibration_report_max_age_hours": { + "annotation": "float", + "kind": "attribute", + "value": "168.0" + }, + "mps_admission_calibration_report_path": { + "annotation": "Path | None", + "kind": "attribute", + "value": "None" + }, + "mps_admission_control_enabled": { + "annotation": "bool", + "kind": "attribute", + "value": "True" + }, + "mps_admission_min_headroom_mb": { + "annotation": "float", + "kind": "attribute", + "value": "64.0" + }, + "mps_admission_safety_margin_mb": { + "annotation": "float", + "kind": "attribute", + "value": "64.0" + }, + "mps_hard_oom_shortcut_enabled": { + "annotation": "bool", + "kind": "attribute", + "value": "True" + }, + "mps_low_memory_threshold_gb": { + "annotation": "float", + "kind": "attribute", + "value": "16.0" + }, + "use_demucs": { + "annotation": "bool", + "kind": "attribute", + "value": "field(default_factory=_default_fast_transcription_use_demucs)" + }, + "use_vad": { + "annotation": "bool", + "kind": "attribute", + "value": "field(default_factory=_default_fast_transcription_use_vad)" + } + } + }, + "kind": "alias" }, "WhisperModelConfig": { - "kind": "class", - "target_path": "ser._internal.config.schema.WhisperModelConfig" + "contract": { + "bases": [], + "kind": "class", + "members": { + "name": { + "annotation": "str", + "kind": "attribute", + "value": "field(default_factory=_default_fast_whisper_model_name)" + }, + "relative_path": { + "annotation": "Path", + "kind": "attribute", + "value": "Path('OpenAI/whisper')" + } + } + }, + "kind": "alias" }, "get_settings": { - "kind": "function", - "target_path": "ser._internal.config.bootstrap.get_settings" + "contract": { + "kind": "function", + "parameters": [], + "returns": "AppConfig", + "signature": "() -> AppConfig" + }, + "kind": "alias" }, "reload_settings": { - "kind": "function", - "target_path": "ser._internal.config.bootstrap.reload_settings" + "contract": { + "kind": "function", + "parameters": [], + "returns": "AppConfig", + "signature": "() -> AppConfig" + }, + "kind": "alias" }, "settings_override": { - "kind": "function", - "target_path": "ser._internal.config.bootstrap.settings_override" + "contract": { + "kind": "function", + "parameters": [ + { + "annotation": "AppConfig", + "default": null, + "kind": "positional or keyword", + "name": "settings" + } + ], + "returns": "Iterator[AppConfig]", + "signature": "(settings: AppConfig) -> Iterator[AppConfig]" + }, + "kind": "alias" } }, "source": "explicit __all__" @@ -962,7 +3256,7 @@ "signature": "(settings: AppConfig) -> ProfileName" } }, - "source": "public definitions" + "source": "explicit __all__" }, "ser.utils": { "exports": { @@ -986,12 +3280,48 @@ "signature": "(text_with_timestamps: list[TranscriptWord], emotion_with_timestamps: list[EmotionSegment]) -> list[TimelineEntry]" }, "display_elapsed_time": { - "kind": "function", - "target_path": "ser.utils.common_utils.display_elapsed_time" + "contract": { + "kind": "function", + "parameters": [ + { + "annotation": "float", + "default": null, + "kind": "positional or keyword", + "name": "elapsed_time" + }, + { + "annotation": "str", + "default": "'long'", + "kind": "positional or keyword", + "name": "_format" + } + ], + "returns": "str", + "signature": "(elapsed_time: float, _format: str = 'long') -> str" + }, + "kind": "alias" }, "get_logger": { - "kind": "function", - "target_path": "ser.utils.logger.get_logger" + "contract": { + "kind": "function", + "parameters": [ + { + "annotation": "str", + "default": null, + "kind": "positional or keyword", + "name": "name" + }, + { + "annotation": "str | int | None", + "default": "None", + "kind": "positional or keyword", + "name": "level" + } + ], + "returns": "logging.Logger", + "signature": "(name: str, level: str | int | None = None) -> logging.Logger" + }, + "kind": "alias" }, "print_timeline": { "kind": "function", @@ -1028,8 +3358,8 @@ "name": "duration_seconds" } ], - "returns": "tuple[np.ndarray, int]", - "signature": "(file_path: str, *, start_seconds: float | None = None, duration_seconds: float | None = None) -> tuple[np.ndarray, int]" + "returns": "tuple[NDArray[np.float32], int]", + "signature": "(file_path: str, *, start_seconds: float | None = None, duration_seconds: float | None = None) -> tuple[NDArray[np.float32], int]" }, "save_timeline_to_csv": { "kind": "function", diff --git a/tests/suites/integration/architecture/test_api_import_boundary.py b/tests/suites/integration/architecture/test_api_import_boundary.py index 92604be..0b682fc 100644 --- a/tests/suites/integration/architecture/test_api_import_boundary.py +++ b/tests/suites/integration/architecture/test_api_import_boundary.py @@ -2,8 +2,11 @@ from __future__ import annotations +import ast import importlib import re +import subprocess +import sys import tomllib from dataclasses import dataclass from pathlib import Path @@ -12,6 +15,33 @@ pytestmark = pytest.mark.topology_contract +_TIER_ONE_SOURCE_FILES = frozenset( + { + "ser/__init__.py", + "ser/api.py", + "ser/config.py", + "ser/domain.py", + "ser/profiles.py", + "ser/utils/__init__.py", + } +) +_ALLOWED_NON_TIER_ONE_PUBLIC_FILES = frozenset( + { + "ser/__main__.py", + "ser/diagnostics/__init__.py", + "ser/diagnostics/domain.py", + "ser/runtime/__init__.py", + "ser/runtime/contracts.py", + "ser/runtime/schema.py", + } +) +_PACKAGE_MARKER_FILES = frozenset( + { + "ser/diagnostics/__init__.py", + "ser/runtime/__init__.py", + } +) + @dataclass(frozen=True) class _BoundaryPolicyEntry: @@ -58,18 +88,11 @@ def _resolve_repo_path(repo_root: Path, relative_path: str) -> Path: return (repo_root / relative_path).resolve() -def _explicit_allowlist_paths(repo_root: Path) -> set[Path]: - """Loads the authoritative public-to-internal allowlist for contract enforcement.""" - return { - _resolve_repo_path(repo_root, entry.relative_path) - for entry in _load_boundary_policy_entries(repo_root) - } - - def test_boundary_policy_file_is_well_formed(repo_root: Path) -> None: - """Boundary policy entries should be sorted, unique, and point to public modules.""" + """Boundary policy entries should be bounded, sorted, unique, and public.""" entries = _load_boundary_policy_entries(repo_root) assert entries, "boundary_policy.toml must declare at least one allowed public module." + assert len(entries) <= 10, "boundary_policy.toml must contain at most ten facade entries." relative_paths = [entry.relative_path for entry in entries] assert relative_paths == sorted( @@ -112,17 +135,40 @@ def test_no_first_party_module_imports_internal_api_directly(repo_root: Path) -> ) -def test_public_to_internal_imports_match_explicit_allowlist(repo_root: Path) -> None: - """Public modules importing `_internal` should match the authoritative allowlist.""" - package_root = repo_root / "ser" - import_pattern = re.compile(r"^\s*(?:from\s+ser\._internal|import\s+ser\._internal)", re.M) - discovered_files = { - source_path.resolve() - for source_path in package_root.rglob("*.py") - if "_internal" not in source_path.parts - and import_pattern.search(source_path.read_text(encoding="utf-8")) +def test_public_python_tree_contains_only_tier_one_and_justified_contract_leaves( + repo_root: Path, +) -> None: + """Public package residue must stay limited to documented contract paths.""" + public_python_files = { + source_path.relative_to(repo_root).as_posix() + for source_path in (repo_root / "ser").rglob("*.py") + if "_internal" not in source_path.relative_to(repo_root / "ser").parts } - assert discovered_files == _explicit_allowlist_paths(repo_root) + expected_files = _TIER_ONE_SOURCE_FILES | _ALLOWED_NON_TIER_ONE_PUBLIC_FILES + assert public_python_files == expected_files, ( + "Public Python tree drifted. Move implementation under ser._internal or add a narrowly " + "justified tier-one/contract-leaf rule with architecture review." + ) + + for relative_path in _PACKAGE_MARKER_FILES: + tree = ast.parse((repo_root / relative_path).read_text(encoding="utf-8")) + assert ( + len(tree.body) == 1 + and isinstance(tree.body[0], ast.Expr) + and isinstance(tree.body[0].value, ast.Constant) + and isinstance(tree.body[0].value.value, str) + ), f"{relative_path} must remain a package marker, not public implementation." + + +def test_public_to_internal_imports_match_explicit_allowlist(repo_root: Path) -> None: + """Static and dynamic private imports should match the authoritative allowlist.""" + completed = subprocess.run( + [sys.executable, "scripts/check_public_internal_imports.py"], + cwd=repo_root, + capture_output=True, + text=True, + ) + assert completed.returncode == 0, completed.stderr or completed.stdout def test_cli_main_uses_internal_cli_support_modules_for_runtime_policy(repo_root: Path) -> None: @@ -167,6 +213,38 @@ def test_legacy_api_implementation_modules_are_not_publicly_importable( importlib.import_module(module_name) +@pytest.mark.parametrize( + "module_name", + ( + "ser.data", + "ser.data.application", + "ser.features", + "ser.heads", + "ser.license_check", + "ser.models", + "ser.models.emotion_model", + "ser.models.profile_runtime", + "ser.models.training_entrypoints", + "ser.pool", + "ser.pool.stats_pool", + "ser.repr", + "ser.runtime.pipeline", + "ser.runtime.registry", + "ser.train", + "ser.train.eval", + "ser.transcript", + "ser.transcript.backends.base", + "ser.transcript.profiling", + "ser.utils.common_utils", + "ser.utils.logger", + ), +) +def test_removed_public_implementation_paths_are_not_importable(module_name: str) -> None: + """Moved implementation owners must not remain accidentally importable in public paths.""" + with pytest.raises(ModuleNotFoundError): + importlib.import_module(module_name) + + _TIER_ONE_PUBLIC_EXPORTS: dict[str, tuple[str, ...]] = { "ser": ( "EmotionSegment", @@ -175,8 +253,13 @@ def test_legacy_api_implementation_modules_are_not_publicly_importable( "__version__", ), "ser.api": ( + "AccurateResearchRuntimeConfig", + "AccurateRuntimeConfig", "AppConfig", + "AudioReadConfig", "ComplianceMode", + "DataLoaderConfig", + "DatasetConfig", "DatasetConsents", "DatasetPrepareResult", "DatasetRegistryHealthIssueRecord", @@ -184,12 +267,34 @@ def test_legacy_api_implementation_modules_are_not_publicly_importable( "DiagnosticFinding", "DiagnosticReport", "DiagnosticSeverity", + "EmotionSegment", + "FastRuntimeConfig", + "FeatureFlags", + "FeatureRuntimeBackendOverride", + "FeatureRuntimePolicyConfig", + "FramePrediction", "InferenceExecution", "InferenceRequest", + "InferenceResult", + "MediumRuntimeConfig", + "MediumTrainingConfig", + "ModelsConfig", + "NeuralNetConfig", "ProfileName", + "QualityGateConfig", + "RuntimeFlags", "RuntimePipeline", "RuntimePipelineBuilder", + "SchemaConfig", + "SegmentPrediction", "SubtitleFormat", + "TimelineConfig", + "TimelineEntry", + "TorchRuntimeConfig", + "TrainingConfig", + "TranscriptWord", + "TranscriptionConfig", + "WhisperModelConfig", "configure_dataset_consents", "infer", "list_dataset_registry_health_issues", @@ -237,6 +342,23 @@ def test_legacy_api_implementation_modules_are_not_publicly_importable( "TimelineEntry", "TranscriptWord", ), + "ser.profiles": ( + "ProfileCatalogEntry", + "ProfileEnableFlag", + "ProfileFeatureRuntimeDefaults", + "ProfileModelDefinition", + "ProfileName", + "ProfileRuntimeDefaults", + "ProfileRuntimeEnvDefinition", + "ProfileTranscriptionDefaults", + "ProfileTranscriptionEnvDefinition", + "RuntimeProfile", + "TranscriptionBackendId", + "available_profiles", + "get_profile_catalog", + "resolve_profile", + "resolve_profile_name", + ), "ser.utils": ( "build_timeline", "display_elapsed_time", @@ -271,3 +393,30 @@ def test_root_package_exposes_metadata_version() -> None: import ser assert ser.__version__ == "1.0.0" + + +def test_root_package_uses_exact_metadata_fallback_without_heavy_imports(repo_root: Path) -> None: + """Source-tree imports should retain the documented metadata fallback.""" + script = """ +import importlib.metadata +import sys + + +def _missing_distribution(_distribution_name: str) -> str: + raise importlib.metadata.PackageNotFoundError + + +importlib.metadata.version = _missing_distribution +import ser + +assert ser.__version__ == \"0.0.0.dev0\" +assert \"__version__\" in ser.__all__ +assert \"torch\" not in sys.modules +""" + completed = subprocess.run( + [sys.executable, "-c", script], + cwd=repo_root, + capture_output=True, + text=True, + ) + assert completed.returncode == 0, completed.stderr or completed.stdout diff --git a/tests/suites/integration/architecture/test_import_lint_policy.py b/tests/suites/integration/architecture/test_import_lint_policy.py index 9259af8..d9f469d 100644 --- a/tests/suites/integration/architecture/test_import_lint_policy.py +++ b/tests/suites/integration/architecture/test_import_lint_policy.py @@ -65,6 +65,7 @@ def test_import_lint_lane_runs_boundary_contract_tests(repo_root: Path) -> None: assert "find ser -path 'ser/_internal' -prune -o -name '*.py' -print | sort" in script assert "ruff check --select TID251" in script + assert "python scripts/check_public_internal_imports.py" in script assert "tests/suites/integration/architecture/test_api_import_boundary.py" in script assert "tests/suites/integration/architecture/test_import_lint_policy.py" in script diff --git a/tests/suites/integration/architecture/test_public_api_snapshot.py b/tests/suites/integration/architecture/test_public_api_snapshot.py index cb38ebc..c7fda99 100644 --- a/tests/suites/integration/architecture/test_public_api_snapshot.py +++ b/tests/suites/integration/architecture/test_public_api_snapshot.py @@ -3,6 +3,7 @@ from __future__ import annotations import difflib +import json import subprocess import sys from pathlib import Path @@ -46,3 +47,20 @@ def test_public_api_snapshot_matches_current_surface(repo_root: Path) -> None: "and review the JSON diff.\n" f"{diff[:12000]}" ) + + +def test_public_api_snapshot_records_reexported_type_contracts(repo_root: Path) -> None: + """Re-exported API classes must include fields, not only owner-module paths.""" + completed = subprocess.run( + [sys.executable, "scripts/dump_public_api.py"], + cwd=repo_root, + capture_output=True, + text=True, + ) + assert completed.returncode == 0, completed.stderr + snapshot = json.loads(completed.stdout) + inference_request = snapshot["modules"]["ser.api"]["exports"]["InferenceRequest"] + assert inference_request["kind"] == "alias" + contract = inference_request["contract"] + assert contract["kind"] == "class" + assert "file_path" in contract["members"] diff --git a/tests/suites/integration/architecture/test_public_api_type_vocabulary.py b/tests/suites/integration/architecture/test_public_api_type_vocabulary.py new file mode 100644 index 0000000..6e1c94b --- /dev/null +++ b/tests/suites/integration/architecture/test_public_api_type_vocabulary.py @@ -0,0 +1,128 @@ +"""Contracts for the self-sufficient `ser.api` type vocabulary.""" + +from __future__ import annotations + +import inspect +import typing + +import ser.api as api + + +def _type_hints(value: object, path: str) -> dict[str, object]: + """Resolves annotations or raises an actionable public-contract failure.""" + try: + return dict(typing.get_type_hints(value)) + except (NameError, TypeError) as error: + raise AssertionError(f"Unable to resolve public annotation at {path}: {error}") from error + + +def _walk_annotation( + value: object, + *, + path: str, + visited_classes: set[int], + missing: dict[tuple[str, str], set[str]], +) -> None: + """Records first-party types reachable through one public annotation value.""" + if isinstance(value, (tuple, list, set, frozenset)): + for item in value: + _walk_annotation( + item, + path=path, + visited_classes=visited_classes, + missing=missing, + ) + return + + origin = typing.get_origin(value) + if origin is not None: + for argument in typing.get_args(value): + _walk_annotation( + argument, + path=path, + visited_classes=visited_classes, + missing=missing, + ) + return + + if type(value).__name__ == "TypeAliasType": + alias_value = getattr(value, "__value__", None) + if alias_value is not None: + _walk_annotation( + alias_value, + path=path, + visited_classes=visited_classes, + missing=missing, + ) + return + + if inspect.isclass(value): + module_name = getattr(value, "__module__", "") + type_name = getattr(value, "__name__", "") + if ( + isinstance(module_name, str) + and module_name.startswith("ser") + and isinstance(type_name, str) + ): + if getattr(api, type_name, None) is not value: + missing.setdefault((module_name, type_name), set()).add(path) + + if id(value) in visited_classes: + return + visited_classes.add(id(value)) + for annotation_name, annotation in _type_hints(value, path).items(): + _walk_annotation( + annotation, + path=f"{path}.{annotation_name}", + visited_classes=visited_classes, + missing=missing, + ) + for member_name, member in value.__dict__.items(): + if member_name.startswith("_"): + continue + if isinstance(member, (classmethod, staticmethod)): + member = member.__func__ + if isinstance(member, property): + if member.fget is not None: + _walk_annotation( + member.fget, + path=f"{path}.{member_name}", + visited_classes=visited_classes, + missing=missing, + ) + elif inspect.isfunction(member): + _walk_annotation( + member, + path=f"{path}.{member_name}", + visited_classes=visited_classes, + missing=missing, + ) + return + + if inspect.isfunction(value): + for annotation_name, annotation in _type_hints(value, path).items(): + _walk_annotation( + annotation, + path=f"{path}.{annotation_name}", + visited_classes=visited_classes, + missing=missing, + ) + + +def test_public_api_reexports_every_reachable_first_party_annotation_type() -> None: + """Consumers should never need an implementation-side import for API annotations.""" + missing: dict[tuple[str, str], set[str]] = {} + visited_classes: set[int] = set() + + for export_name in api.__all__: + _walk_annotation( + getattr(api, export_name), + path=f"ser.api.{export_name}", + visited_classes=visited_classes, + missing=missing, + ) + + assert not missing, "Missing direct ser.api type exports:\n" + "\n".join( + f"- {module_name}.{type_name}: {', '.join(sorted(paths))}" + for (module_name, type_name), paths in sorted(missing.items()) + ) diff --git a/tests/suites/integration/architecture/test_public_import_cost.py b/tests/suites/integration/architecture/test_public_import_cost.py index 3dcb566..9319f9c 100644 --- a/tests/suites/integration/architecture/test_public_import_cost.py +++ b/tests/suites/integration/architecture/test_public_import_cost.py @@ -23,6 +23,10 @@ def test_tier_one_public_imports_do_not_import_torch(repo_root: Path) -> None: import ser.profiles import ser.utils +api_exports = __import__("ser.api", fromlist=ser.api.__all__) +for api_export_name in ser.api.__all__: + getattr(api_exports, api_export_name) + if "torch" in sys.modules: raise SystemExit("torch imported during tier-1 public imports") """ diff --git a/tests/suites/integration/architecture/test_py_typed_distribution.py b/tests/suites/integration/architecture/test_py_typed_distribution.py new file mode 100644 index 0000000..b640b55 --- /dev/null +++ b/tests/suites/integration/architecture/test_py_typed_distribution.py @@ -0,0 +1,22 @@ +"""Distribution contracts for the PEP 561 public typing marker.""" + +from __future__ import annotations + +import subprocess +import zipfile +from pathlib import Path + + +def test_wheel_contains_py_typed_marker(repo_root: Path, tmp_path: Path) -> None: + """A fresh wheel must ship the marker used by downstream type checkers.""" + subprocess.run( + ["uv", "build", "--wheel", "--out-dir", str(tmp_path)], + cwd=repo_root, + check=True, + capture_output=True, + text=True, + ) + wheel_paths = tuple(tmp_path.glob("ser-*.whl")) + assert len(wheel_paths) == 1 + with zipfile.ZipFile(wheel_paths[0]) as wheel: + assert "ser/py.typed" in wheel.namelist() diff --git a/tests/suites/integration/architecture/test_readme_examples.py b/tests/suites/integration/architecture/test_readme_examples.py index 150e0f3..512f52c 100644 --- a/tests/suites/integration/architecture/test_readme_examples.py +++ b/tests/suites/integration/architecture/test_readme_examples.py @@ -2,7 +2,6 @@ from __future__ import annotations -import re import subprocess import sys import textwrap @@ -12,47 +11,119 @@ pytestmark = pytest.mark.topology_contract -_README_PYTHON_BLOCK_RE = re.compile(r"```python\n(.*?)\n```", re.DOTALL) +_FENCE_OPEN_PREFIX = "```" +_PYTHON_FENCE_LANGUAGES = frozenset({"python", "python3"}) +_NOEXEC_DIRECTIVE = "noexec" def _readme_python_blocks(readme_path: Path) -> tuple[str, ...]: - """Extracts fenced Python examples from README.""" - return tuple( - match.group(1).strip() - for match in _README_PYTHON_BLOCK_RE.finditer(readme_path.read_text(encoding="utf-8")) - ) + """Extracts executable fenced Python examples and rejects malformed fences.""" + examples: list[str] = [] + open_fence: tuple[int, bool, list[str]] | None = None + + for line_number, line in enumerate( + readme_path.read_text(encoding="utf-8").splitlines(), start=1 + ): + stripped_line = line.strip() + if open_fence is not None: + opened_at, excluded, source_lines = open_fence + if stripped_line == _FENCE_OPEN_PREFIX: + source = "\n".join(source_lines).strip() + if not excluded: + if not source: + raise AssertionError( + f"README Python fence opened on line {opened_at} is empty." + ) + examples.append(source) + open_fence = None + else: + source_lines.append(line) + continue + + if not stripped_line.startswith(_FENCE_OPEN_PREFIX): + continue + info = stripped_line.removeprefix(_FENCE_OPEN_PREFIX).strip() + if not info: + continue + language, *directives = info.split() + if language.lower() not in _PYTHON_FENCE_LANGUAGES: + continue + if directives not in ([], [_NOEXEC_DIRECTIVE]): + raise AssertionError( + f"README Python fence on line {line_number} has unsupported directives: " + f"{', '.join(directives)}. Use `{_NOEXEC_DIRECTIVE}` to exclude an illustrative block." + ) + open_fence = (line_number, directives == [_NOEXEC_DIRECTIVE], []) + + if open_fence is not None: + opened_at, _, _ = open_fence + raise AssertionError( + f"README Python fence opened on line {opened_at} has no closing fence." + ) + + return tuple(examples) def _example_runner_source(example_source: str, example_index: int) -> str: - """Builds an isolated runner with a lightweight inference stub.""" + """Builds an isolated runner that stubs only the inference owner seam.""" return textwrap.dedent(f""" from __future__ import annotations - from types import SimpleNamespace - import ser.api + import ser.domain + + + def _fake_runtime_infer( + file_path: object, + *, + profile: object = None, + language: object = None, + save_transcript: object = False, + include_transcript: object = True, + subtitle_output_path: object = None, + subtitle_format: object = None, + settings: object = None, + pipeline_builder: object = None, + ) -> ser.api.InferenceExecution: + if file_path != "sample.wav" or profile != "fast": + raise AssertionError("README example did not call ser.api.infer as documented.") + return ser.api.InferenceExecution( + profile="fast", + output_schema_version="README-test", + backend_id="README-test", + emotions=[ser.domain.EmotionSegment("neutral", 0.0, 1.0)], + transcript=[], + timeline=[], + ) - class _Emotion: - def __init__(self, emotion: str, start_seconds: float, end_seconds: float) -> None: - self.emotion = emotion - self.start_seconds = start_seconds - self.end_seconds = end_seconds - - - def _fake_infer(*_args: object, **_kwargs: object) -> SimpleNamespace: - return SimpleNamespace(emotions=(_Emotion("neutral", 0.0, 1.0),)) - - - ser.api.infer = _fake_infer _example_source = {example_source!r} - exec( - compile(_example_source, "README.md python block {example_index}", "exec"), - {{"__name__": "__readme_example__"}}, - ) + original_runtime_infer = ser.api._runtime_api.infer + try: + ser.api._runtime_api.infer = _fake_runtime_infer + exec( + compile(_example_source, "README.md python block {example_index}", "exec"), + {{"__name__": "__readme_example__"}}, + ) + finally: + ser.api._runtime_api.infer = original_runtime_infer """) +def _run_example( + repo_root: Path, + example_source: str, + example_index: int = 1, +) -> subprocess.CompletedProcess[str]: + """Runs one generated README example runner in a fresh interpreter.""" + return subprocess.run( + [sys.executable, "-c", _example_runner_source(example_source, example_index)], + cwd=repo_root, + capture_output=True, + text=True, + ) + + def test_readme_python_examples_execute(repo_root: Path) -> None: """README Python blocks should stay executable with documented public APIs.""" examples = _readme_python_blocks(repo_root / "README.md") @@ -60,12 +131,7 @@ def test_readme_python_examples_execute(repo_root: Path) -> None: failures: list[str] = [] for example_index, example_source in enumerate(examples, start=1): - completed = subprocess.run( - [sys.executable, "-c", _example_runner_source(example_source, example_index)], - cwd=repo_root, - capture_output=True, - text=True, - ) + completed = _run_example(repo_root, example_source, example_index) if completed.returncode != 0: failures.append( "\n".join( @@ -78,3 +144,43 @@ def test_readme_python_examples_execute(repo_root: Path) -> None: ) assert failures == [] + + +def test_readme_python_blocks_reject_unclosed_or_unsupported_fences(tmp_path: Path) -> None: + """Executable Python fences should not be silently skipped.""" + readme_path = tmp_path / "README.md" + + readme_path.write_text("```python3\nprint('missing close')\n", encoding="utf-8") + with pytest.raises(AssertionError, match="no closing fence"): + _readme_python_blocks(readme_path) + + readme_path.write_text("```python skip\nprint('unsupported')\n```\n", encoding="utf-8") + with pytest.raises(AssertionError, match="unsupported directives"): + _readme_python_blocks(readme_path) + + +def test_readme_python_blocks_allow_explicit_non_executable_examples(tmp_path: Path) -> None: + """Illustrative Python blocks require the explicit `noexec` directive.""" + readme_path = tmp_path / "README.md" + readme_path.write_text( + "```python noexec\nnot valid Python\n```\n\n```python\nprint('checked')\n```\n", + encoding="utf-8", + ) + + assert _readme_python_blocks(readme_path) == ("print('checked')",) + + +def test_readme_runner_keeps_the_real_public_infer_contract(repo_root: Path) -> None: + """The fixture must not mask missing facade functions or invalid API arguments.""" + identity_check = _run_example( + repo_root, + "import ser.api\nassert ser.api.infer.__module__ == 'ser.api'", + ) + assert identity_check.returncode == 0, identity_check.stderr or identity_check.stdout + + invalid_arguments = _run_example( + repo_root, + "import ser.api\nser.api.infer('sample.wav', unsupported_keyword=True)", + ) + assert invalid_arguments.returncode != 0 + assert "unsupported_keyword" in invalid_arguments.stderr diff --git a/tests/suites/integration/cli/test_cli.py b/tests/suites/integration/cli/test_cli.py index 2903407..2ee39e4 100644 --- a/tests/suites/integration/cli/test_cli.py +++ b/tests/suites/integration/cli/test_cli.py @@ -11,11 +11,11 @@ import ser._internal.models.emotion_model as emotion_model import ser.config as config_module from ser._internal.config.schema import profile_artifact_file_names +from ser._internal.license_check import load_persisted_backend_consents from ser._internal.runtime.accurate_inference import AccurateRuntimeDependencyError from ser._internal.runtime.medium_inference import MediumRuntimeDependencyError -from ser.license_check import load_persisted_backend_consents -from ser.runtime import InferenceRequest -from ser.runtime.registry import UnsupportedProfileError +from ser._internal.runtime.registry import UnsupportedProfileError +from ser.runtime.contracts import InferenceRequest def _patch_common_cli_dependencies(monkeypatch: pytest.MonkeyPatch) -> None: diff --git a/tests/suites/integration/cli/test_data_cli.py b/tests/suites/integration/cli/test_data_cli.py index 8084715..00df422 100644 --- a/tests/suites/integration/cli/test_data_cli.py +++ b/tests/suites/integration/cli/test_data_cli.py @@ -9,10 +9,7 @@ import pytest import ser._internal.data.cli as data_cli -from ser._internal.data.dataset_consents import PersistedDatasetConsents -from ser._internal.data.dataset_prepare import DatasetDescriptor -from ser.config import AppConfig -from ser.data.application import ( +from ser._internal.data.application.models import ( DatasetCapabilitySnapshotEntry, DatasetPrepareWorkflowResult, DatasetRegistrySnapshot, @@ -20,6 +17,9 @@ DatasetRegistrySnapshotIssue, DatasetUninstallWorkflowResult, ) +from ser._internal.data.dataset_consents import PersistedDatasetConsents +from ser._internal.data.dataset_prepare import DatasetDescriptor +from ser.config import AppConfig def _settings(tmp_path: Path) -> AppConfig: diff --git a/tests/suites/integration/data/test_data_application.py b/tests/suites/integration/data/test_data_application.py index 1b3a27f..1be7ba1 100644 --- a/tests/suites/integration/data/test_data_application.py +++ b/tests/suites/integration/data/test_data_application.py @@ -8,12 +8,12 @@ import pytest +from ser._internal.data.application import consents as data_application from ser._internal.data.dataset_capabilities import list_dataset_capability_profiles from ser._internal.data.dataset_consents import load_persisted_dataset_consents from ser._internal.data.dataset_prepare import SUPPORTED_DATASETS, default_dataset_root from ser._internal.data.dataset_registry import upsert_dataset_registry_entry from ser.config import AppConfig -from ser.data import application as data_application def _settings(tmp_path: Path) -> AppConfig: diff --git a/tests/suites/integration/diagnostics/test_diagnostics_service.py b/tests/suites/integration/diagnostics/test_diagnostics_service.py index 3d2f517..37a5403 100644 --- a/tests/suites/integration/diagnostics/test_diagnostics_service.py +++ b/tests/suites/integration/diagnostics/test_diagnostics_service.py @@ -19,9 +19,9 @@ run_doctor_diagnostics, should_fail_preflight, ) +from ser._internal.transcript.backends import CompatibilityIssue, CompatibilityReport from ser._internal.utils.transcription_compat import FASTER_WHISPER_OPENMP_CONFLICT_ISSUE_CODE from ser.diagnostics.domain import DiagnosticFinding, DiagnosticReport -from ser.transcript.backends import CompatibilityIssue, CompatibilityReport def test_parse_preflight_mode_falls_back_to_warn_for_invalid_values() -> None: diff --git a/tests/suites/integration/models/test_emotion_model_contracts.py b/tests/suites/integration/models/test_emotion_model_contracts.py index 8a99cef..ea1c202 100644 --- a/tests/suites/integration/models/test_emotion_model_contracts.py +++ b/tests/suites/integration/models/test_emotion_model_contracts.py @@ -14,6 +14,7 @@ from sklearn.neural_network import MLPClassifier import ser._internal.data.data_loader as data_loader +import ser._internal.license_check as license_check import ser._internal.models.accurate_training_execution as accurate_training_execution import ser._internal.models.accurate_training_preparation as accurate_training_preparation import ser._internal.models.artifact_envelope as artifact_envelope @@ -25,7 +26,6 @@ import ser._internal.models.training_entrypoints as training_entrypoints import ser._internal.models.training_reporting as training_reporting import ser._internal.models.training_support as training_support -import ser.license_check as license_check from ser._internal.features import FeatureFrame from ser._internal.models.fast_training import FastTrainingHooks from ser.runtime.schema import InferenceResult diff --git a/tests/suites/integration/test_license_gates.py b/tests/suites/integration/test_license_gates.py index 004f183..31c5557 100644 --- a/tests/suites/integration/test_license_gates.py +++ b/tests/suites/integration/test_license_gates.py @@ -8,7 +8,7 @@ import ser._internal.runtime.backend_hooks as backend_hooks import ser.config as config -from ser.license_check import ( +from ser._internal.license_check import ( BackendLicensePolicyError, ensure_backend_access, evaluate_backend_access, diff --git a/tests/suites/integration/test_process_isolation.py b/tests/suites/integration/test_process_isolation.py index c7267c2..9784465 100644 --- a/tests/suites/integration/test_process_isolation.py +++ b/tests/suites/integration/test_process_isolation.py @@ -24,7 +24,7 @@ from ser.domain import TranscriptWord if TYPE_CHECKING: - from ser.transcript.backends.base import BackendRuntimeRequest + from ser._internal.transcript.backends.base import BackendRuntimeRequest else: BackendRuntimeRequest = object diff --git a/tests/suites/integration/test_runtime_pipeline.py b/tests/suites/integration/test_runtime_pipeline.py index 73afb7e..37e1ff0 100644 --- a/tests/suites/integration/test_runtime_pipeline.py +++ b/tests/suites/integration/test_runtime_pipeline.py @@ -12,11 +12,11 @@ import ser._internal.runtime.pipeline as runtime_pipeline_module import ser.config as config from ser._internal.runtime.pipeline import RuntimePipeline, create_runtime_pipeline +from ser._internal.runtime.registry import RuntimeCapability, UnsupportedProfileError from ser.config import AppConfig, TimelineConfig from ser.domain import EmotionSegment, TimelineEntry, TranscriptWord from ser.profiles import RuntimeProfile from ser.runtime.contracts import InferenceRequest -from ser.runtime.registry import RuntimeCapability, UnsupportedProfileError from ser.runtime.schema import ( OUTPUT_SCHEMA_VERSION, FramePrediction, @@ -518,7 +518,7 @@ def test_create_runtime_pipeline_marks_medium_available_when_hook_registry_is_re """Factory should treat medium as available when hook registry is populated.""" monkeypatch.setenv("SER_ENABLE_MEDIUM_PROFILE", "true") monkeypatch.setattr( - "ser.runtime.registry._missing_optional_modules", + "ser._internal.runtime.registry._missing_optional_modules", lambda _required_modules: (), ) @@ -548,7 +548,7 @@ def test_create_runtime_pipeline_marks_accurate_available_when_hook_registry_is_ """Factory should treat accurate as available when hook registry is populated.""" monkeypatch.setenv("SER_ENABLE_ACCURATE_PROFILE", "true") monkeypatch.setattr( - "ser.runtime.registry._missing_optional_modules", + "ser._internal.runtime.registry._missing_optional_modules", lambda _required_modules: (), ) @@ -579,7 +579,7 @@ def test_create_runtime_pipeline_marks_accurate_research_available_when_hook_reg monkeypatch.setenv("SER_ENABLE_ACCURATE_RESEARCH_PROFILE", "true") monkeypatch.setenv("SER_ENABLE_RESTRICTED_BACKENDS", "true") monkeypatch.setattr( - "ser.runtime.registry._missing_optional_modules", + "ser._internal.runtime.registry._missing_optional_modules", lambda _required_modules: (), ) @@ -658,7 +658,7 @@ def test_create_runtime_pipeline_uses_profile_specific_transcription_profile( for name, value in env.items(): monkeypatch.setenv(name, value) monkeypatch.setattr( - "ser.runtime.registry._missing_optional_modules", + "ser._internal.runtime.registry._missing_optional_modules", lambda _required_modules: (), ) monkeypatch.setattr( @@ -784,7 +784,7 @@ def test_create_runtime_pipeline_retains_faster_whisper_on_openmp_conflict( monkeypatch.delenv("WHISPER_BACKEND", raising=False) monkeypatch.delenv("WHISPER_MODEL", raising=False) monkeypatch.setattr( - "ser.runtime.registry._missing_optional_modules", + "ser._internal.runtime.registry._missing_optional_modules", lambda _required_modules: (), ) monkeypatch.setattr( @@ -842,7 +842,7 @@ def test_create_runtime_pipeline_respects_explicit_faster_backend_override( lambda: True, ) monkeypatch.setattr( - "ser.runtime.registry._missing_optional_modules", + "ser._internal.runtime.registry._missing_optional_modules", lambda _required_modules: (), ) monkeypatch.setattr( @@ -893,7 +893,7 @@ def test_create_runtime_pipeline_uses_medium_training_callable_when_medium_selec """Medium profile should route training through medium training entrypoint.""" monkeypatch.setenv("SER_ENABLE_MEDIUM_PROFILE", "true") monkeypatch.setattr( - "ser.runtime.registry._missing_optional_modules", + "ser._internal.runtime.registry._missing_optional_modules", lambda _required_modules: (), ) @@ -942,7 +942,7 @@ def test_create_runtime_pipeline_uses_accurate_training_callable_when_selected( """Accurate profile should route training through accurate training entrypoint.""" monkeypatch.setenv("SER_ENABLE_ACCURATE_PROFILE", "true") monkeypatch.setattr( - "ser.runtime.registry._missing_optional_modules", + "ser._internal.runtime.registry._missing_optional_modules", lambda _required_modules: (), ) @@ -999,7 +999,7 @@ def test_create_runtime_pipeline_uses_accurate_research_training_callable_when_s monkeypatch.setenv("SER_ENABLE_ACCURATE_RESEARCH_PROFILE", "true") monkeypatch.setenv("SER_ENABLE_RESTRICTED_BACKENDS", "true") monkeypatch.setattr( - "ser.runtime.registry._missing_optional_modules", + "ser._internal.runtime.registry._missing_optional_modules", lambda _required_modules: (), ) diff --git a/tests/suites/integration/test_runtime_registry.py b/tests/suites/integration/test_runtime_registry.py index 75e89d9..3ce6579 100644 --- a/tests/suites/integration/test_runtime_registry.py +++ b/tests/suites/integration/test_runtime_registry.py @@ -2,9 +2,9 @@ import pytest +import ser._internal.runtime.registry as registry import ser.config as config -import ser.runtime.registry as registry -from ser.runtime.registry import ( +from ser._internal.runtime.registry import ( UnsupportedProfileError, ensure_profile_supported, resolve_runtime_capability, diff --git a/tests/suites/unit/models/test_accurate_training_preparation.py b/tests/suites/unit/models/test_accurate_training_preparation.py index 665b631..bec2c5f 100644 --- a/tests/suites/unit/models/test_accurate_training_preparation.py +++ b/tests/suites/unit/models/test_accurate_training_preparation.py @@ -11,13 +11,13 @@ import numpy as np import pytest +import ser._internal.license_check as license_check import ser._internal.models.accurate_training_execution as ate import ser._internal.models.accurate_training_preparation as atp import ser._internal.models.artifact_envelope as artifact_envelope import ser._internal.models.artifact_persistence as artifact_persistence import ser._internal.models.training_reporting as training_reporting import ser._internal.models.training_support as training_support -import ser.license_check as license_check from ser._internal.data.manifest import MANIFEST_SCHEMA_VERSION, Utterance from ser._internal.models.dataset_splitting import MediumSplitMetadata from ser.config import AppConfig diff --git a/tests/suites/unit/models/test_provenance_metadata.py b/tests/suites/unit/models/test_provenance_metadata.py index 7574c9a..604ce0d 100644 --- a/tests/suites/unit/models/test_provenance_metadata.py +++ b/tests/suites/unit/models/test_provenance_metadata.py @@ -12,7 +12,7 @@ import ser._internal.models.artifact_envelope as artifact_envelope import ser._internal.models.training_support as training_support from ser import config -from ser.license_check import build_provenance_metadata, persist_backend_consent +from ser._internal.license_check import build_provenance_metadata, persist_backend_consent def _classifier() -> MLPClassifier: diff --git a/tests/suites/unit/repr/test_emotion2vec_backend.py b/tests/suites/unit/repr/test_emotion2vec_backend.py index fefdcd8..923d1eb 100644 --- a/tests/suites/unit/repr/test_emotion2vec_backend.py +++ b/tests/suites/unit/repr/test_emotion2vec_backend.py @@ -16,7 +16,7 @@ from ser._internal.repr import Emotion2VecBackend from ser._internal.runtime.process_env import temporary_process_env -from ser.utils.logger import ( +from ser._internal.utils.logger import ( DependencyLogFilter, DependencyLogPolicy, is_dependency_log_record, diff --git a/tests/suites/unit/repr/test_pooling.py b/tests/suites/unit/repr/test_pooling.py index b980f21..7070735 100644 --- a/tests/suites/unit/repr/test_pooling.py +++ b/tests/suites/unit/repr/test_pooling.py @@ -5,8 +5,8 @@ import numpy as np import pytest +from ser._internal.pool import mean_std_pool, temporal_pooling_windows from ser._internal.repr import EncodedSequence, PoolingWindow -from ser.pool import mean_std_pool, temporal_pooling_windows def test_mean_std_pool_returns_expected_stats_per_window() -> None: diff --git a/tests/suites/unit/scripts/test_check_public_internal_imports.py b/tests/suites/unit/scripts/test_check_public_internal_imports.py new file mode 100644 index 0000000..6b010dd --- /dev/null +++ b/tests/suites/unit/scripts/test_check_public_internal_imports.py @@ -0,0 +1,73 @@ +"""Behavioral contracts for public-to-private import boundary checking.""" + +from __future__ import annotations + +import subprocess +import sys +from pathlib import Path + + +def _write_policy(repo_root: Path, paths: tuple[str, ...]) -> None: + """Writes one minimal valid boundary policy for a synthetic repository.""" + entries = "\n".join( + ( + "[[public_internal_import]]\n" + f'path = "{path}"\n' + 'reason = "Synthetic facade for boundary-check regression coverage."\n' + ) + for path in paths + ) + (repo_root / "boundary_policy.toml").write_text(f"version = 1\n\n{entries}", encoding="utf-8") + + +def _run_checker(repo_root: Path, project_root: Path) -> subprocess.CompletedProcess[str]: + """Runs the real checker against one synthetic repository root.""" + return subprocess.run( + [ + sys.executable, + str(project_root / "scripts" / "check_public_internal_imports.py"), + "--repo-root", + str(repo_root), + ], + capture_output=True, + text=True, + ) + + +def test_checker_rejects_unlisted_dynamic_private_import(repo_root: Path, tmp_path: Path) -> None: + """A literal `importlib.import_module` bypass must fail the boundary check.""" + package_root = tmp_path / "ser" + package_root.mkdir() + (package_root / "__init__.py").write_text("", encoding="utf-8") + (package_root / "allowed.py").write_text( + "from ser._internal.owner import allowed\n", + encoding="utf-8", + ) + (package_root / "facade.py").write_text( + 'import importlib\nimportlib.import_module("ser._internal.owner")\n', + encoding="utf-8", + ) + _write_policy(tmp_path, ("ser/allowed.py",)) + + completed = _run_checker(tmp_path, repo_root) + + assert completed.returncode != 0 + assert "ser/facade.py:2: imports private target" in completed.stderr + + +def test_checker_accepts_policy_listed_dynamic_private_import( + repo_root: Path, tmp_path: Path +) -> None: + """A policy-listed literal lazy facade import remains valid and auditable.""" + package_root = tmp_path / "ser" + package_root.mkdir() + (package_root / "__init__.py").write_text("", encoding="utf-8") + (package_root / "facade.py").write_text( + 'from importlib import import_module\nimport_module("ser._internal.owner")\n', + encoding="utf-8", + ) + _write_policy(tmp_path, ("ser/facade.py",)) + + completed = _run_checker(tmp_path, repo_root) + + assert completed.returncode == 0, completed.stderr or completed.stdout diff --git a/tests/suites/unit/train/test_eval_protocol.py b/tests/suites/unit/train/test_eval_protocol.py index 1f0cab3..e968820 100644 --- a/tests/suites/unit/train/test_eval_protocol.py +++ b/tests/suites/unit/train/test_eval_protocol.py @@ -3,7 +3,7 @@ import numpy as np import pytest -from ser.train.eval import ( +from ser._internal.train.eval import ( extract_ravdess_speaker_id, grouped_train_test_split, speaker_independent_cv, diff --git a/tests/suites/unit/train/test_metrics.py b/tests/suites/unit/train/test_metrics.py index 4b49734..de44cbd 100644 --- a/tests/suites/unit/train/test_metrics.py +++ b/tests/suites/unit/train/test_metrics.py @@ -2,7 +2,7 @@ import pytest -from ser.train.metrics import ( +from ser._internal.train.metrics import ( compute_grouped_ser_metrics_by_sample, compute_ser_metrics, ) diff --git a/tests/suites/unit/transcription/backends/test_faster_whisper_adapter.py b/tests/suites/unit/transcription/backends/test_faster_whisper_adapter.py index 65f02dc..752e642 100644 --- a/tests/suites/unit/transcription/backends/test_faster_whisper_adapter.py +++ b/tests/suites/unit/transcription/backends/test_faster_whisper_adapter.py @@ -11,10 +11,10 @@ import pytest import ser._internal.transcript.backends.faster_whisper as faster_whisper_backend +from ser._internal.transcript.backends.base import BackendRuntimeRequest from ser._internal.transcript.backends.faster_whisper import FasterWhisperAdapter from ser.config import AppConfig from ser.domain import TranscriptWord -from ser.transcript.backends.base import BackendRuntimeRequest pytestmark = pytest.mark.unit diff --git a/tests/suites/unit/transcription/process/test_process_isolation_internal.py b/tests/suites/unit/transcription/process/test_process_isolation_internal.py index 1f74a4f..fc7c0c3 100644 --- a/tests/suites/unit/transcription/process/test_process_isolation_internal.py +++ b/tests/suites/unit/transcription/process/test_process_isolation_internal.py @@ -22,7 +22,7 @@ from ser.profiles import TranscriptionBackendId if TYPE_CHECKING: - from ser.transcript.backends.base import BackendRuntimeRequest + from ser._internal.transcript.backends.base import BackendRuntimeRequest else: BackendRuntimeRequest = object diff --git a/tests/suites/unit/transcription/process/test_process_worker.py b/tests/suites/unit/transcription/process/test_process_worker.py index e4e6766..ba76805 100644 --- a/tests/suites/unit/transcription/process/test_process_worker.py +++ b/tests/suites/unit/transcription/process/test_process_worker.py @@ -16,7 +16,7 @@ from ser.profiles import TranscriptionBackendId if TYPE_CHECKING: - from ser.transcript.backends.base import BackendRuntimeRequest + from ser._internal.transcript.backends.base import BackendRuntimeRequest else: BackendRuntimeRequest = object diff --git a/tests/suites/unit/transcription/process/test_public_boundary_process.py b/tests/suites/unit/transcription/process/test_public_boundary_process.py index a5094d2..97ca527 100644 --- a/tests/suites/unit/transcription/process/test_public_boundary_process.py +++ b/tests/suites/unit/transcription/process/test_public_boundary_process.py @@ -21,7 +21,7 @@ from ser.profiles import TranscriptionBackendId if TYPE_CHECKING: - from ser.transcript.backends.base import BackendRuntimeRequest + from ser._internal.transcript.backends.base import BackendRuntimeRequest else: BackendRuntimeRequest = object diff --git a/tests/suites/unit/transcription/test_in_process_orchestration.py b/tests/suites/unit/transcription/test_in_process_orchestration.py index b1022f3..b326569 100644 --- a/tests/suites/unit/transcription/test_in_process_orchestration.py +++ b/tests/suites/unit/transcription/test_in_process_orchestration.py @@ -8,9 +8,9 @@ import pytest +from ser._internal.transcript.backends.base import BackendRuntimeRequest from ser._internal.transcription import in_process_orchestration as ipo from ser.config import AppConfig -from ser.transcript.backends.base import BackendRuntimeRequest pytestmark = pytest.mark.unit diff --git a/tests/suites/unit/transcription/test_stable_whisper_admission_runtime.py b/tests/suites/unit/transcription/test_stable_whisper_admission_runtime.py index 956f822..42f51cb 100644 --- a/tests/suites/unit/transcription/test_stable_whisper_admission_runtime.py +++ b/tests/suites/unit/transcription/test_stable_whisper_admission_runtime.py @@ -7,8 +7,8 @@ from typing import Any, cast import ser._internal.transcript.backends.stable_whisper_admission_runtime as admission_runtime +from ser._internal.transcript.backends.base import BackendRuntimeRequest from ser._internal.transcript.mps_admission import MpsAdmissionDecision -from ser.transcript.backends.base import BackendRuntimeRequest def test_is_retryable_precision_failure_detects_sparsemps_notimplemented() -> None: diff --git a/tests/suites/unit/transcription/test_stable_whisper_transcribe_admission.py b/tests/suites/unit/transcription/test_stable_whisper_transcribe_admission.py index 01f9f52..8c12e5c 100644 --- a/tests/suites/unit/transcription/test_stable_whisper_transcribe_admission.py +++ b/tests/suites/unit/transcription/test_stable_whisper_transcribe_admission.py @@ -5,11 +5,11 @@ import logging from typing import Literal +from ser._internal.transcript.backends.base import BackendRuntimeRequest from ser._internal.transcript.backends.stable_whisper_transcribe_admission import ( resolve_transcribe_runtime_device, ) from ser._internal.transcript.mps_admission import MpsAdmissionDecision -from ser.transcript.backends.base import BackendRuntimeRequest def _runtime_request(*, device_type: str = "mps") -> BackendRuntimeRequest: diff --git a/tests/suites/unit/transcription/test_stable_whisper_transcribe_execution.py b/tests/suites/unit/transcription/test_stable_whisper_transcribe_execution.py index f91c466..3dd112b 100644 --- a/tests/suites/unit/transcription/test_stable_whisper_transcribe_execution.py +++ b/tests/suites/unit/transcription/test_stable_whisper_transcribe_execution.py @@ -8,6 +8,7 @@ import pytest +from ser._internal.transcript.backends.base import BackendRuntimeRequest from ser._internal.transcript.backends.stable_whisper_transcribe_execution import ( run_stable_whisper_transcribe_with_retry, ) @@ -16,7 +17,6 @@ TranscriptionFailureClassification, ) from ser.config import AppConfig -from ser.transcript.backends.base import BackendRuntimeRequest def _runtime_request(*, device_type: str, precision: tuple[str, ...]) -> BackendRuntimeRequest: diff --git a/tests/suites/unit/transcription/test_stable_whisper_transcribe_kwargs.py b/tests/suites/unit/transcription/test_stable_whisper_transcribe_kwargs.py index 9fe35ac..b3225d9 100644 --- a/tests/suites/unit/transcription/test_stable_whisper_transcribe_kwargs.py +++ b/tests/suites/unit/transcription/test_stable_whisper_transcribe_kwargs.py @@ -7,10 +7,10 @@ import pytest import ser._internal.transcript.backends.stable_whisper as stable_whisper +from ser._internal.transcript.backends.base import BackendRuntimeRequest from ser._internal.transcript.backends.stable_whisper_transcribe_kwargs import ( build_stable_whisper_transcribe_kwargs, ) -from ser.transcript.backends.base import BackendRuntimeRequest def _runtime_request(*, use_demucs: bool) -> BackendRuntimeRequest: diff --git a/tests/suites/unit/transcription/test_stable_whisper_transcribe_runtime.py b/tests/suites/unit/transcription/test_stable_whisper_transcribe_runtime.py index eec25c2..fc6edd0 100644 --- a/tests/suites/unit/transcription/test_stable_whisper_transcribe_runtime.py +++ b/tests/suites/unit/transcription/test_stable_whisper_transcribe_runtime.py @@ -9,12 +9,12 @@ import ser._internal.transcript.backends.stable_whisper as stable_whisper import ser._internal.transcript.backends.stable_whisper_transcribe_runtime as runtime_helpers +from ser._internal.transcript.backends.base import BackendRuntimeRequest from ser._internal.transcript.runtime_failures import ( FailureDisposition, TranscriptionFailureClassification, ) from ser.config import AppConfig -from ser.transcript.backends.base import BackendRuntimeRequest def test_classify_transcription_failure_for_runtime_downgrades_when_shortcut_disabled( diff --git a/tests/suites/unit/transcription/test_transcription_admission_overrides.py b/tests/suites/unit/transcription/test_transcription_admission_overrides.py index 39546f6..24943b1 100644 --- a/tests/suites/unit/transcription/test_transcription_admission_overrides.py +++ b/tests/suites/unit/transcription/test_transcription_admission_overrides.py @@ -8,12 +8,12 @@ from types import SimpleNamespace from typing import cast +from ser._internal.transcript.backends.base import BackendRuntimeRequest from ser._internal.transcript.mps_admission import MpsAdmissionDecision from ser._internal.transcript.mps_admission_overrides import ( apply_calibrated_mps_admission_override, ) from ser.config import AppConfig -from ser.transcript.backends.base import BackendRuntimeRequest def _settings( diff --git a/tests/suites/unit/transcription/test_transcription_backends.py b/tests/suites/unit/transcription/test_transcription_backends.py index 5fe0947..846befa 100644 --- a/tests/suites/unit/transcription/test_transcription_backends.py +++ b/tests/suites/unit/transcription/test_transcription_backends.py @@ -13,6 +13,11 @@ import pytest from ser._internal.transcript.backends import stable_whisper_torio_probe +from ser._internal.transcript.backends.base import ( + BackendRuntimeRequest, + CompatibilityIssue, + CompatibilityReport, +) from ser._internal.transcript.backends.faster_whisper import FasterWhisperAdapter from ser._internal.transcript.backends.stable_whisper import StableWhisperAdapter from ser._internal.transcript.backends.stable_whisper_torio_probe import ( @@ -21,11 +26,6 @@ ) from ser._internal.utils.transcription_compat import FASTER_WHISPER_OPENMP_CONFLICT_ISSUE_CODE from ser.config import AppConfig -from ser.transcript.backends.base import ( - BackendRuntimeRequest, - CompatibilityIssue, - CompatibilityReport, -) @pytest.fixture(autouse=True) diff --git a/tests/suites/unit/utils/test_common_utils.py b/tests/suites/unit/utils/test_common_utils.py index 33d8c3f..d3f0c28 100644 --- a/tests/suites/unit/utils/test_common_utils.py +++ b/tests/suites/unit/utils/test_common_utils.py @@ -4,7 +4,7 @@ import pytest -from ser.utils.common_utils import display_elapsed_time +from ser._internal.utils.common_utils import display_elapsed_time pytestmark = pytest.mark.unit diff --git a/tests/suites/unit/utils/test_logger.py b/tests/suites/unit/utils/test_logger.py index 71c5890..2a34052 100644 --- a/tests/suites/unit/utils/test_logger.py +++ b/tests/suites/unit/utils/test_logger.py @@ -5,7 +5,7 @@ import pytest -import ser.utils.logger as logger_utils +import ser._internal.utils.logger as logger_utils def test_configure_logging_defaults_to_info( diff --git a/tests/utils/helpers/process_spawn_support.py b/tests/utils/helpers/process_spawn_support.py index 2949638..fac8e4c 100644 --- a/tests/utils/helpers/process_spawn_support.py +++ b/tests/utils/helpers/process_spawn_support.py @@ -11,7 +11,7 @@ from ser.profiles import TranscriptionBackendId if TYPE_CHECKING: - from ser.transcript.backends.base import BackendRuntimeRequest + from ser._internal.transcript.backends.base import BackendRuntimeRequest @dataclass(frozen=True, slots=True)