opensportslib 0.3.1.dev5__tar.gz → 0.3.1.dev7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {opensportslib-0.3.1.dev5/opensportslib.egg-info → opensportslib-0.3.1.dev7}/PKG-INFO +6 -1
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/README.md +5 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/__init__.py +6 -1
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/apis/__init__.py +3 -0
- opensportslib-0.3.1.dev7/opensportslib/remote_registry.py +124 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7/opensportslib.egg-info}/PKG-INFO +6 -1
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib.egg-info/SOURCES.txt +5 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/pyproject.toml +1 -1
- opensportslib-0.3.1.dev7/scripts/release_versions.py +84 -0
- opensportslib-0.3.1.dev7/tests/test_release_versions.py +56 -0
- opensportslib-0.3.1.dev7/tests/test_remote_model_registry.py +94 -0
- opensportslib-0.3.1.dev7/tests/test_server_model_registry.py +100 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/LICENSE +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/LICENSE-COMMERCIAL +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/MANIFEST.in +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/examples/quickstart/basic_classification.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/examples/quickstart/basic_localization.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/examples/quickstart/basic_vqa.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/adaptation/__init__.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/adaptation/spotta.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/apis/base_task_model.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/apis/classification.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/apis/config.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/apis/configuration.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/apis/localization.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/apis/vqa.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/cli.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/configs/classification/default.yaml +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/configs/classification/sngar_frames.yaml +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/configs/classification/sngar_tracking.yaml +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/configs/classification/video.yaml +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/configs/default.yaml +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/configs/localization/calf_resnetpca512.yaml +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/configs/localization/default.yaml +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/configs/localization/e2e_spotta.yaml +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/configs/localization/h5_header_distance.yaml +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/configs/localization/h5_header_skeleton.yaml +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/configs/localization/netvladpp_resnetpca512.yaml +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/configs/localization/tracking_action_spotting.yaml +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/configs/localization/video_dali.yaml +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/configs/localization/video_ocv.yaml +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/configs/vqa/default.yaml +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/configs/vqa/qwen.yaml +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/configs/vqa/qwen3_vl_native.yaml +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/configs/vqa/qwen_lora.yaml +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/configs/vqa/qwen_sngar_frames.yaml +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/configs/vqa/xvars.yaml +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/core/__init__.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/core/config/__init__.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/core/config/accessors.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/core/config/conflicts.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/core/config/editable.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/core/config/loader.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/core/config/migrate.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/core/config/migrations/__init__.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/core/config/migrations/legacy_to_canonical.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/core/config/rule_variants.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/core/config/runtime_adapter.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/core/config/schema.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/core/config/schemas/__init__.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/core/config/schemas/schema_canonical.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/core/config/schemas/schema_legacy.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/core/config/validate.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/core/loss/__init__.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/core/loss/builder.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/core/loss/calf.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/core/loss/ce.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/core/loss/combine.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/core/loss/nll.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/core/optimizer/__init__.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/core/optimizer/builder.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/core/sampler/weighted_sampler.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/core/scheduler/__init__.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/core/scheduler/builder.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/core/trainer/__init__.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/core/trainer/classification_trainer.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/core/trainer/localization_trainer.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/core/trainer/vqa_trainer.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/core/utils/checkpoint.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/core/utils/config.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/core/utils/config_normalize.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/core/utils/data.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/core/utils/ddp.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/core/utils/default_args.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/core/utils/direct_video.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/core/utils/hf_runtime.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/core/utils/lightning.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/core/utils/load_annotations.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/core/utils/seed.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/core/utils/video_processing.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/core/utils/wandb.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/datasets/__init__.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/datasets/builder.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/datasets/classification_dataset.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/datasets/localization_dataset.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/datasets/utils/__init__.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/datasets/utils/h5_tracking.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/datasets/utils/tracking.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/datasets/vqa_dataset.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/legacy_config/classification.yaml +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/legacy_config/localization-e2e-ocv.yaml +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/legacy_config/localization-json_calf_resnetpca512.yaml +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/legacy_config/localization-json_netvlad++_resnetpca512.yaml +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/legacy_config/localization.yaml +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/legacy_config/sngar-frames.yaml +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/legacy_config/sngar-tracking.yaml +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/metrics/classification_metric.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/metrics/localization_metric.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/metrics/vqa_metric.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/models/__init__.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/models/backbones/builder.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/models/base/contextaware.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/models/base/e2e.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/models/base/learnablepooling.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/models/base/qwen_vl_native.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/models/base/qwen_xvars.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/models/base/rule_based.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/models/base/tracking.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/models/base/vars.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/models/base/video.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/models/base/video_chatgpt_compat.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/models/base/video_mae.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/models/base/xvars_videochatgpt.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/models/builder.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/models/heads/builder.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/models/neck/builder.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/models/utils/common.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/models/utils/impl/__init__.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/models/utils/impl/asformer.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/models/utils/impl/calf.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/models/utils/impl/gsm.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/models/utils/impl/gtad.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/models/utils/impl/tsm.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/models/utils/litebase.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/models/utils/modules.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/models/utils/shift.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/models/utils/utils.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/models/utils/vqa_prediction_priors.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/models/utils/vqa_prompting.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/models/utils/xvars_clip_index.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/setup/setup.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/tools/__init__.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/tools/_common.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/tools/hf_transfer.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/tools/osl_json_to_parquet.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/tools/parquet_to_osl_json.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib.egg-info/dependency_links.txt +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib.egg-info/entry_points.txt +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib.egg-info/requires.txt +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib.egg-info/top_level.txt +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/scripts/run_h5_header_rule_inference.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/setup.cfg +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tests/conftest.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tests/release/__init__.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tests/release/_release_common.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tests/release/test_classification_release.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tests/release/test_localization_release.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tests/release/test_vqa_release.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tests/test_classification_dataset_paths.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tests/test_classification_trainer_dataloader.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tests/test_config_architecture.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tests/test_config_split_override_sync.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tests/test_config_utils_smoke.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tests/test_conversion_tools.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tests/test_editable_config.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tests/test_extract_xvars_features.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tests/test_h5_header_rule_spotter.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tests/test_h5_header_skeleton_spotter.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tests/test_h5_tracking_dataset.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tests/test_hf_transfer_tools.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tests/test_localization_dali_filenames.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tests/test_localization_hf_backend_override.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tests/test_localization_intervals.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tests/test_optional_hf_config.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tests/test_package_smoke.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tests/test_pretrained_config_merge_policy.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tests/test_public_apis_smoke.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tests/test_setup_cli.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tests/test_spotta_e2e.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tests/test_subset_train_infer_integration.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tests/test_task_model_api_contract.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tests/test_vqa_api.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tests/test_vqa_metrics_semantic.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tests/test_vqa_qwen_xvars.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tests/test_vqa_training_lora.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tests/test_vqa_xvars_videochatgpt.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tools/convert/build_sn_vqa_2026_vqa.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tools/convert/build_sngar_spotting.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tools/convert/build_soccernet_gar.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tools/convert/build_soccernet_gar_action_spotting.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tools/convert/build_soccernet_gar_vqa.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tools/convert/build_xvars_indexes.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tools/convert/extract_xvars_clip_features.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tools/convert/osl_json_to_parquet_webdataset.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tools/convert/parquet_webdataset_to_osl_json.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tools/convert/sngar_dataset_card.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tools/convert/sngar_events.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tools/convert/verify_sngar_spotting.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tools/download/download_hf_repo.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tools/download/download_osl_hf.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tools/download/push_sngar_spotting.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tools/download/upload_osl_hf.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tools/training/classification.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tools/training/localization.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tools/training/vqa.py +0 -0
- {opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/tools/upload/upload_model_hf.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: opensportslib
|
|
3
|
-
Version: 0.3.1.
|
|
3
|
+
Version: 0.3.1.dev7
|
|
4
4
|
Summary: OpenSportsLib is the professional library, designed for advanced video understanding in sports. It provides state-of-the-art tools for action recognition, spotting, retrieval, and captioning, making it ideal for researchers, analysts, and developers working with sports video data.
|
|
5
5
|
Author: Jeet Vora
|
|
6
6
|
Requires-Python: >=3.12
|
|
@@ -36,6 +36,11 @@ Dynamic: license-file
|
|
|
36
36
|
|
|
37
37
|
# OpenSportsLib
|
|
38
38
|
|
|
39
|
+
OpenSportsLib inference servers support runtime model registration through
|
|
40
|
+
`RemoteModelRegistry`. Its unified `register_model()` method accepts Hugging
|
|
41
|
+
Face or server-local weights; the returned model ID is then used by the existing
|
|
42
|
+
remote task APIs. Unregistered model IDs are rejected.
|
|
43
|
+
|
|
39
44
|
## Configuration From Hugging Face
|
|
40
45
|
|
|
41
46
|
Prepare and inspect configuration before model weights are allocated:
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# OpenSportsLib
|
|
2
2
|
|
|
3
|
+
OpenSportsLib inference servers support runtime model registration through
|
|
4
|
+
`RemoteModelRegistry`. Its unified `register_model()` method accepts Hugging
|
|
5
|
+
Face or server-local weights; the returned model ID is then used by the existing
|
|
6
|
+
remote task APIs. Unregistered model IDs are rejected.
|
|
7
|
+
|
|
3
8
|
## Configuration From Hugging Face
|
|
4
9
|
|
|
5
10
|
Prepare and inspect configuration before model weights are allocated:
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import importlib
|
|
2
2
|
|
|
3
|
+
from opensportslib.remote_registry import RemoteModelRegistry, RemoteRegistryError
|
|
4
|
+
|
|
3
5
|
|
|
4
6
|
def __getattr__(name):
|
|
5
7
|
if name == "model":
|
|
@@ -15,4 +17,7 @@ def __getattr__(name):
|
|
|
15
17
|
raise AttributeError(f"module 'opensportslib' has no attribute '{name}'")
|
|
16
18
|
|
|
17
19
|
|
|
18
|
-
__all__ = [
|
|
20
|
+
__all__ = [
|
|
21
|
+
"model", "metrics", "datasets", "core", "tools",
|
|
22
|
+
"RemoteModelRegistry", "RemoteRegistryError",
|
|
23
|
+
]
|
|
@@ -6,6 +6,7 @@ from opensportslib.apis.base_task_model import BaseTaskModel
|
|
|
6
6
|
from opensportslib.apis.classification import ClassificationModel
|
|
7
7
|
from opensportslib.apis.localization import LocalizationModel
|
|
8
8
|
from opensportslib.apis.vqa import VQAModel
|
|
9
|
+
from opensportslib.remote_registry import RemoteModelRegistry, RemoteRegistryError
|
|
9
10
|
import warnings
|
|
10
11
|
warnings.filterwarnings("ignore")
|
|
11
12
|
|
|
@@ -16,4 +17,6 @@ __all__ = [
|
|
|
16
17
|
"ClassificationModel",
|
|
17
18
|
"LocalizationModel",
|
|
18
19
|
"VQAModel",
|
|
20
|
+
"RemoteModelRegistry",
|
|
21
|
+
"RemoteRegistryError",
|
|
19
22
|
]
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import time
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
from urllib import error, parse, request
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class RemoteRegistryError(RuntimeError):
|
|
11
|
+
def __init__(self, status_code: int, detail: Any):
|
|
12
|
+
self.status_code = status_code
|
|
13
|
+
self.detail = detail
|
|
14
|
+
super().__init__(f"Remote model registry returned HTTP {status_code}: {detail}")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class RemoteModelRegistry:
|
|
18
|
+
"""Administrative client for an OpenSportsLib server model registry."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, remote: str, admin_token: str, timeout: float = 30.0):
|
|
21
|
+
if not remote:
|
|
22
|
+
raise ValueError("remote is required.")
|
|
23
|
+
if not admin_token:
|
|
24
|
+
raise ValueError("admin_token is required.")
|
|
25
|
+
self.remote = remote.rstrip("/")
|
|
26
|
+
self.admin_token = admin_token
|
|
27
|
+
self.timeout = float(timeout)
|
|
28
|
+
if self.timeout <= 0:
|
|
29
|
+
raise ValueError("timeout must be positive.")
|
|
30
|
+
|
|
31
|
+
def register_model(
|
|
32
|
+
self,
|
|
33
|
+
*,
|
|
34
|
+
task_type: str,
|
|
35
|
+
huggingface_model_id: str | None = None,
|
|
36
|
+
weights_path: str | Path | None = None,
|
|
37
|
+
config_path: str | Path | None = None,
|
|
38
|
+
model_id: str | None = None,
|
|
39
|
+
) -> dict[str, Any]:
|
|
40
|
+
"""Register exactly one Hugging Face or server-local model source."""
|
|
41
|
+
if (huggingface_model_id is None) == (weights_path is None):
|
|
42
|
+
raise ValueError("Provide exactly one of huggingface_model_id or weights_path.")
|
|
43
|
+
if huggingface_model_id is not None:
|
|
44
|
+
if config_path is not None:
|
|
45
|
+
raise ValueError("config_path applies only to local models.")
|
|
46
|
+
if model_id is not None and model_id != huggingface_model_id:
|
|
47
|
+
raise ValueError("A Hugging Face model uses its repository ID as model_id.")
|
|
48
|
+
payload = {
|
|
49
|
+
"task_type": task_type,
|
|
50
|
+
"source": {"type": "huggingface", "model_id": huggingface_model_id},
|
|
51
|
+
}
|
|
52
|
+
else:
|
|
53
|
+
payload = {
|
|
54
|
+
"task_type": task_type,
|
|
55
|
+
"model_id": model_id,
|
|
56
|
+
"source": {
|
|
57
|
+
"type": "local",
|
|
58
|
+
"weights_path": str(weights_path),
|
|
59
|
+
"config_path": str(config_path) if config_path is not None else None,
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
return self._request("POST", "/models", payload, authenticated=True)
|
|
63
|
+
|
|
64
|
+
def list_models(self) -> list[dict[str, Any]]:
|
|
65
|
+
return self._request("GET", "/models")["models"]
|
|
66
|
+
|
|
67
|
+
def get_model(self, model_id: str) -> dict[str, Any]:
|
|
68
|
+
query = parse.urlencode({"model_id": model_id})
|
|
69
|
+
return self._request("GET", f"/models/status?{query}")
|
|
70
|
+
|
|
71
|
+
def get_operation(self, operation_id: str) -> dict[str, Any]:
|
|
72
|
+
return self._request("GET", f"/model-operations/{parse.quote(operation_id, safe='')}", authenticated=True)
|
|
73
|
+
|
|
74
|
+
def wait_for_operation(
|
|
75
|
+
self,
|
|
76
|
+
operation_id: str,
|
|
77
|
+
*,
|
|
78
|
+
timeout: float = 7200.0,
|
|
79
|
+
poll_interval: float = 1.0,
|
|
80
|
+
) -> dict[str, Any]:
|
|
81
|
+
deadline = time.monotonic() + timeout
|
|
82
|
+
while True:
|
|
83
|
+
operation = self.get_operation(operation_id)
|
|
84
|
+
if operation["status"] == "succeeded":
|
|
85
|
+
return operation
|
|
86
|
+
if operation["status"] == "failed":
|
|
87
|
+
raise RuntimeError(operation.get("error") or "Model operation failed.")
|
|
88
|
+
if time.monotonic() >= deadline:
|
|
89
|
+
raise TimeoutError(f"Timed out waiting for model operation `{operation_id}`.")
|
|
90
|
+
time.sleep(poll_interval)
|
|
91
|
+
|
|
92
|
+
def set_default(self, task_type: str, model_id: str) -> dict[str, Any]:
|
|
93
|
+
task = parse.quote(task_type, safe="")
|
|
94
|
+
return self._request("PUT", f"/models/defaults/{task}", {"model_id": model_id}, authenticated=True)
|
|
95
|
+
|
|
96
|
+
def unregister_model(self, model_id: str) -> dict[str, Any]:
|
|
97
|
+
encoded = parse.quote(model_id, safe="")
|
|
98
|
+
return self._request("DELETE", f"/models/{encoded}", authenticated=True)
|
|
99
|
+
|
|
100
|
+
def _request(
|
|
101
|
+
self,
|
|
102
|
+
method: str,
|
|
103
|
+
endpoint: str,
|
|
104
|
+
payload: dict[str, Any] | None = None,
|
|
105
|
+
authenticated: bool = False,
|
|
106
|
+
) -> dict[str, Any]:
|
|
107
|
+
body = json.dumps(payload).encode() if payload is not None else None
|
|
108
|
+
headers = {"Content-Type": "application/json"}
|
|
109
|
+
if authenticated:
|
|
110
|
+
headers["Authorization"] = f"Bearer {self.admin_token}"
|
|
111
|
+
outgoing = request.Request(self.remote + endpoint, data=body, method=method, headers=headers)
|
|
112
|
+
try:
|
|
113
|
+
with request.urlopen(outgoing, timeout=self.timeout) as response:
|
|
114
|
+
return json.loads(response.read().decode())
|
|
115
|
+
except error.HTTPError as exc:
|
|
116
|
+
detail = exc.read().decode(errors="replace")
|
|
117
|
+
try:
|
|
118
|
+
parsed = json.loads(detail)
|
|
119
|
+
detail = parsed.get("detail", parsed)
|
|
120
|
+
except json.JSONDecodeError:
|
|
121
|
+
pass
|
|
122
|
+
raise RemoteRegistryError(exc.code, detail) from exc
|
|
123
|
+
except error.URLError as exc:
|
|
124
|
+
raise ConnectionError(f"Could not reach remote server `{self.remote}`: {exc.reason}") from exc
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: opensportslib
|
|
3
|
-
Version: 0.3.1.
|
|
3
|
+
Version: 0.3.1.dev7
|
|
4
4
|
Summary: OpenSportsLib is the professional library, designed for advanced video understanding in sports. It provides state-of-the-art tools for action recognition, spotting, retrieval, and captioning, making it ideal for researchers, analysts, and developers working with sports video data.
|
|
5
5
|
Author: Jeet Vora
|
|
6
6
|
Requires-Python: >=3.12
|
|
@@ -36,6 +36,11 @@ Dynamic: license-file
|
|
|
36
36
|
|
|
37
37
|
# OpenSportsLib
|
|
38
38
|
|
|
39
|
+
OpenSportsLib inference servers support runtime model registration through
|
|
40
|
+
`RemoteModelRegistry`. Its unified `register_model()` method accepts Hugging
|
|
41
|
+
Face or server-local weights; the returned model ID is then used by the existing
|
|
42
|
+
remote task APIs. Unregistered model IDs are rejected.
|
|
43
|
+
|
|
39
44
|
## Configuration From Hugging Face
|
|
40
45
|
|
|
41
46
|
Prepare and inspect configuration before model weights are allocated:
|
|
@@ -8,6 +8,7 @@ examples/quickstart/basic_localization.py
|
|
|
8
8
|
examples/quickstart/basic_vqa.py
|
|
9
9
|
opensportslib/__init__.py
|
|
10
10
|
opensportslib/cli.py
|
|
11
|
+
opensportslib/remote_registry.py
|
|
11
12
|
opensportslib.egg-info/PKG-INFO
|
|
12
13
|
opensportslib.egg-info/SOURCES.txt
|
|
13
14
|
opensportslib.egg-info/dependency_links.txt
|
|
@@ -142,6 +143,7 @@ opensportslib/tools/_common.py
|
|
|
142
143
|
opensportslib/tools/hf_transfer.py
|
|
143
144
|
opensportslib/tools/osl_json_to_parquet.py
|
|
144
145
|
opensportslib/tools/parquet_to_osl_json.py
|
|
146
|
+
scripts/release_versions.py
|
|
145
147
|
scripts/run_h5_header_rule_inference.py
|
|
146
148
|
tests/conftest.py
|
|
147
149
|
tests/test_classification_dataset_paths.py
|
|
@@ -163,6 +165,9 @@ tests/test_optional_hf_config.py
|
|
|
163
165
|
tests/test_package_smoke.py
|
|
164
166
|
tests/test_pretrained_config_merge_policy.py
|
|
165
167
|
tests/test_public_apis_smoke.py
|
|
168
|
+
tests/test_release_versions.py
|
|
169
|
+
tests/test_remote_model_registry.py
|
|
170
|
+
tests/test_server_model_registry.py
|
|
166
171
|
tests/test_setup_cli.py
|
|
167
172
|
tests/test_spotta_e2e.py
|
|
168
173
|
tests/test_subset_train_infer_integration.py
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "opensportslib"
|
|
7
|
-
version = "0.3.1.
|
|
7
|
+
version = "0.3.1.dev7"
|
|
8
8
|
description = "OpenSportsLib is the professional library, designed for advanced video understanding in sports. It provides state-of-the-art tools for action recognition, spotting, retrieval, and captioning, making it ideal for researchers, analysts, and developers working with sports video data."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.12"
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Keep the library version and the server's library pin in sync."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import re
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
STABLE_VERSION = re.compile(r"^(?P<version>\d+\.\d+\.\d+)$")
|
|
11
|
+
DEV_VERSION = re.compile(r"^(?P<base>\d+\.\d+\.\d+)\.dev(?P<number>\d+)$")
|
|
12
|
+
PROJECT_VERSION = re.compile(
|
|
13
|
+
r'(?ms)(^\[project\]\s*.*?^version\s*=\s*")[^"]+("\s*$)'
|
|
14
|
+
)
|
|
15
|
+
SERVER_PIN = re.compile(r'("opensportslib==)[^"]+("[,\s]*$)', re.MULTILINE)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def next_dev_version(current: str) -> str:
|
|
19
|
+
"""Return the next development release for an existing dev version."""
|
|
20
|
+
match = DEV_VERSION.fullmatch(current)
|
|
21
|
+
if not match:
|
|
22
|
+
raise ValueError(f"expected X.Y.Z.devN, got {current!r}")
|
|
23
|
+
return f"{match.group('base')}.dev{int(match.group('number')) + 1}"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def stable_version(tag: str) -> str:
|
|
27
|
+
"""Return a stable version from a vX.Y.Z release tag."""
|
|
28
|
+
if not tag.startswith("v"):
|
|
29
|
+
raise ValueError(f"release tag must start with 'v', got {tag!r}")
|
|
30
|
+
version = tag[1:]
|
|
31
|
+
if not STABLE_VERSION.fullmatch(version):
|
|
32
|
+
raise ValueError(f"release tag must have the form vX.Y.Z, got {tag!r}")
|
|
33
|
+
return version
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def read_project_version(pyproject: Path) -> str:
|
|
37
|
+
text = pyproject.read_text()
|
|
38
|
+
match = PROJECT_VERSION.search(text)
|
|
39
|
+
if not match:
|
|
40
|
+
raise ValueError(f"project.version not found in {pyproject}")
|
|
41
|
+
return text[match.start(0) + len(match.group(1)) : match.end(0) - len(match.group(2))]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def synchronize(version: str, root_pyproject: Path, server_pyproject: Path) -> None:
|
|
45
|
+
"""Write one OpenSportsLib version to both release metadata files."""
|
|
46
|
+
if not (STABLE_VERSION.fullmatch(version) or DEV_VERSION.fullmatch(version)):
|
|
47
|
+
raise ValueError(f"unsupported release version {version!r}")
|
|
48
|
+
|
|
49
|
+
root_text = root_pyproject.read_text()
|
|
50
|
+
server_text = server_pyproject.read_text()
|
|
51
|
+
if len(PROJECT_VERSION.findall(root_text)) != 1:
|
|
52
|
+
raise ValueError(f"expected one project.version in {root_pyproject}")
|
|
53
|
+
if len(SERVER_PIN.findall(server_text)) != 1:
|
|
54
|
+
raise ValueError(f"expected one opensportslib dependency pin in {server_pyproject}")
|
|
55
|
+
|
|
56
|
+
root_updated = PROJECT_VERSION.sub(rf"\g<1>{version}\g<2>", root_text)
|
|
57
|
+
server_updated = SERVER_PIN.sub(rf"\g<1>{version}\g<2>", server_text)
|
|
58
|
+
root_pyproject.write_text(root_updated)
|
|
59
|
+
server_pyproject.write_text(server_updated)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def main() -> None:
|
|
63
|
+
parser = argparse.ArgumentParser()
|
|
64
|
+
parser.add_argument("command", choices=("bump-dev", "set-stable"))
|
|
65
|
+
parser.add_argument("value", nargs="?", help="vX.Y.Z tag for set-stable")
|
|
66
|
+
parser.add_argument("--root", type=Path, default=Path("pyproject.toml"))
|
|
67
|
+
parser.add_argument("--server", type=Path, default=Path("server/pyproject.toml"))
|
|
68
|
+
args = parser.parse_args()
|
|
69
|
+
|
|
70
|
+
if args.command == "bump-dev":
|
|
71
|
+
if args.value is not None:
|
|
72
|
+
parser.error("bump-dev does not accept a value")
|
|
73
|
+
version = next_dev_version(read_project_version(args.root))
|
|
74
|
+
else:
|
|
75
|
+
if args.value is None:
|
|
76
|
+
parser.error("set-stable requires a vX.Y.Z tag")
|
|
77
|
+
version = stable_version(args.value)
|
|
78
|
+
|
|
79
|
+
synchronize(version, args.root, args.server)
|
|
80
|
+
print(version)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
if __name__ == "__main__":
|
|
84
|
+
main()
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import importlib.util
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
SCRIPT = Path(__file__).parents[1] / "scripts" / "release_versions.py"
|
|
8
|
+
SPEC = importlib.util.spec_from_file_location("release_versions", SCRIPT)
|
|
9
|
+
release_versions = importlib.util.module_from_spec(SPEC)
|
|
10
|
+
assert SPEC.loader is not None
|
|
11
|
+
SPEC.loader.exec_module(release_versions)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_next_dev_version_increments_counter():
|
|
15
|
+
assert release_versions.next_dev_version("0.3.1.dev5") == "0.3.1.dev6"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@pytest.mark.parametrize("version", ["0.3.1", "0.3.1rc1", "invalid"])
|
|
19
|
+
def test_next_dev_version_rejects_non_dev_versions(version):
|
|
20
|
+
with pytest.raises(ValueError, match="expected X.Y.Z.devN"):
|
|
21
|
+
release_versions.next_dev_version(version)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_stable_version_requires_stable_v_tag():
|
|
25
|
+
assert release_versions.stable_version("v0.3.1") == "0.3.1"
|
|
26
|
+
with pytest.raises(ValueError, match="vX.Y.Z"):
|
|
27
|
+
release_versions.stable_version("v0.3.1.dev1")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_synchronize_updates_root_and_server_pin_only(tmp_path):
|
|
31
|
+
root = tmp_path / "pyproject.toml"
|
|
32
|
+
server = tmp_path / "server.toml"
|
|
33
|
+
root.write_text('[project]\nname = "opensportslib"\nversion = "0.3.1.dev5"\n')
|
|
34
|
+
server.write_text(
|
|
35
|
+
'[project]\nname = "opensportslib-server"\nversion = "0.1.0"\n'
|
|
36
|
+
'dependencies = [\n "opensportslib==0.3.1.dev5",\n "fastapi",\n]\n'
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
release_versions.synchronize("0.3.1.dev6", root, server)
|
|
40
|
+
|
|
41
|
+
assert 'version = "0.3.1.dev6"' in root.read_text()
|
|
42
|
+
assert 'version = "0.1.0"' in server.read_text()
|
|
43
|
+
assert '"opensportslib==0.3.1.dev6"' in server.read_text()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_synchronize_fails_before_writing_when_server_pin_is_missing(tmp_path):
|
|
47
|
+
root = tmp_path / "pyproject.toml"
|
|
48
|
+
server = tmp_path / "server.toml"
|
|
49
|
+
original = '[project]\nversion = "0.3.1.dev5"\n'
|
|
50
|
+
root.write_text(original)
|
|
51
|
+
server.write_text('[project]\nversion = "0.1.0"\n')
|
|
52
|
+
|
|
53
|
+
with pytest.raises(ValueError, match="dependency pin"):
|
|
54
|
+
release_versions.synchronize("0.3.1.dev6", root, server)
|
|
55
|
+
|
|
56
|
+
assert root.read_text() == original
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from unittest.mock import patch
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from opensportslib import RemoteModelRegistry, RemoteRegistryError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class FakeResponse:
|
|
10
|
+
def __init__(self, payload):
|
|
11
|
+
self.payload = payload
|
|
12
|
+
|
|
13
|
+
def __enter__(self):
|
|
14
|
+
return self
|
|
15
|
+
|
|
16
|
+
def __exit__(self, *args):
|
|
17
|
+
return False
|
|
18
|
+
|
|
19
|
+
def read(self):
|
|
20
|
+
return json.dumps(self.payload).encode()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_register_huggingface_uses_one_registration_method():
|
|
24
|
+
client = RemoteModelRegistry("http://server", "secret")
|
|
25
|
+
with patch("opensportslib.remote_registry.request.urlopen") as urlopen:
|
|
26
|
+
urlopen.return_value = FakeResponse({"model_id": "org/model", "status": "registering"})
|
|
27
|
+
result = client.register_model(task_type="classification", huggingface_model_id="org/model")
|
|
28
|
+
|
|
29
|
+
outgoing = urlopen.call_args.args[0]
|
|
30
|
+
assert json.loads(outgoing.data) == {
|
|
31
|
+
"task_type": "classification",
|
|
32
|
+
"source": {"type": "huggingface", "model_id": "org/model"},
|
|
33
|
+
}
|
|
34
|
+
assert outgoing.headers["Authorization"] == "Bearer secret"
|
|
35
|
+
assert result["model_id"] == "org/model"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_register_local_supports_custom_or_generated_model_id():
|
|
39
|
+
client = RemoteModelRegistry("http://server", "secret")
|
|
40
|
+
with patch("opensportslib.remote_registry.request.urlopen") as urlopen:
|
|
41
|
+
urlopen.return_value = FakeResponse({"model_id": "custom", "status": "registering"})
|
|
42
|
+
client.register_model(
|
|
43
|
+
task_type="localization",
|
|
44
|
+
model_id="custom",
|
|
45
|
+
weights_path="/models/custom/model.pth",
|
|
46
|
+
config_path="/models/custom/config.yaml",
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
payload = json.loads(urlopen.call_args.args[0].data)
|
|
50
|
+
assert payload["model_id"] == "custom"
|
|
51
|
+
assert payload["source"] == {
|
|
52
|
+
"type": "local",
|
|
53
|
+
"weights_path": "/models/custom/model.pth",
|
|
54
|
+
"config_path": "/models/custom/config.yaml",
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@pytest.mark.parametrize(
|
|
59
|
+
"kwargs",
|
|
60
|
+
[
|
|
61
|
+
{},
|
|
62
|
+
{"huggingface_model_id": "org/model", "weights_path": "/models/model"},
|
|
63
|
+
],
|
|
64
|
+
)
|
|
65
|
+
def test_register_model_requires_exactly_one_source(kwargs):
|
|
66
|
+
client = RemoteModelRegistry("http://server", "secret")
|
|
67
|
+
with pytest.raises(ValueError, match="exactly one"):
|
|
68
|
+
client.register_model(task_type="classification", **kwargs)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def test_huggingface_model_cannot_be_renamed():
|
|
72
|
+
client = RemoteModelRegistry("http://server", "secret")
|
|
73
|
+
with pytest.raises(ValueError, match="repository ID"):
|
|
74
|
+
client.register_model(
|
|
75
|
+
task_type="classification",
|
|
76
|
+
huggingface_model_id="org/model",
|
|
77
|
+
model_id="alias",
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def test_http_errors_preserve_structured_detail():
|
|
82
|
+
from urllib.error import HTTPError
|
|
83
|
+
from io import BytesIO
|
|
84
|
+
|
|
85
|
+
client = RemoteModelRegistry("http://server", "secret")
|
|
86
|
+
response = HTTPError(
|
|
87
|
+
"http://server/models", 409, "Conflict", {},
|
|
88
|
+
BytesIO(json.dumps({"detail": {"code": "MODEL_NOT_READY"}}).encode()),
|
|
89
|
+
)
|
|
90
|
+
with patch("opensportslib.remote_registry.request.urlopen", side_effect=response):
|
|
91
|
+
with pytest.raises(RemoteRegistryError) as raised:
|
|
92
|
+
client.get_model("model")
|
|
93
|
+
assert raised.value.status_code == 409
|
|
94
|
+
assert raised.value.detail == {"code": "MODEL_NOT_READY"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from types import SimpleNamespace
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
SERVER_ROOT = Path(__file__).parents[1] / "server"
|
|
9
|
+
sys.path.insert(0, str(SERVER_ROOT))
|
|
10
|
+
|
|
11
|
+
from config.model_registry import ( # noqa: E402
|
|
12
|
+
ModelRegistry,
|
|
13
|
+
generated_local_model_id,
|
|
14
|
+
public_record,
|
|
15
|
+
resolve_local_source,
|
|
16
|
+
validate_local_model_id,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class FakeRedis:
|
|
21
|
+
def __init__(self):
|
|
22
|
+
self.hashes = {}
|
|
23
|
+
|
|
24
|
+
def hget(self, key, field):
|
|
25
|
+
return self.hashes.get(key, {}).get(field)
|
|
26
|
+
|
|
27
|
+
def hvals(self, key):
|
|
28
|
+
return list(self.hashes.get(key, {}).values())
|
|
29
|
+
|
|
30
|
+
def hset(self, key, field, value):
|
|
31
|
+
self.hashes.setdefault(key, {})[field] = value
|
|
32
|
+
|
|
33
|
+
def hsetnx(self, key, field, value):
|
|
34
|
+
values = self.hashes.setdefault(key, {})
|
|
35
|
+
if field in values:
|
|
36
|
+
return 0
|
|
37
|
+
values[field] = value
|
|
38
|
+
return 1
|
|
39
|
+
|
|
40
|
+
def hdel(self, key, field):
|
|
41
|
+
self.hashes.get(key, {}).pop(field, None)
|
|
42
|
+
|
|
43
|
+
def hgetall(self, key):
|
|
44
|
+
return dict(self.hashes.get(key, {}))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def record(model_id="model", source="org/model"):
|
|
48
|
+
return {
|
|
49
|
+
"model_id": model_id,
|
|
50
|
+
"task_type": "classification",
|
|
51
|
+
"source_type": "huggingface",
|
|
52
|
+
"source": source,
|
|
53
|
+
"config_path": None,
|
|
54
|
+
"status": "ready",
|
|
55
|
+
"generation": 1,
|
|
56
|
+
"created_at": "now",
|
|
57
|
+
"updated_at": "now",
|
|
58
|
+
"error": None,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_registry_reserves_idempotently_and_rejects_conflicts():
|
|
63
|
+
registry = ModelRegistry(SimpleNamespace(redis_url="redis://unused"), FakeRedis())
|
|
64
|
+
_, created = registry.reserve(record())
|
|
65
|
+
assert created
|
|
66
|
+
existing, created = registry.reserve(record())
|
|
67
|
+
assert not created
|
|
68
|
+
assert existing["source"] == "org/model"
|
|
69
|
+
with pytest.raises(FileExistsError):
|
|
70
|
+
registry.reserve(record(source="other/model"))
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def test_local_source_stays_inside_model_root(tmp_path):
|
|
74
|
+
root = tmp_path / "models"
|
|
75
|
+
folder = root / "custom"
|
|
76
|
+
folder.mkdir(parents=True)
|
|
77
|
+
(folder / "config.yaml").write_text("MODEL: {}")
|
|
78
|
+
settings = SimpleNamespace(model_root=root)
|
|
79
|
+
|
|
80
|
+
weights, config = resolve_local_source(settings, str(folder), None)
|
|
81
|
+
assert weights == folder
|
|
82
|
+
assert config == folder / "config.yaml"
|
|
83
|
+
assert generated_local_model_id(folder).startswith("local:custom-")
|
|
84
|
+
|
|
85
|
+
outside = tmp_path / "outside"
|
|
86
|
+
outside.mkdir()
|
|
87
|
+
with pytest.raises(ValueError, match="OSL_MODEL_ROOT"):
|
|
88
|
+
resolve_local_source(settings, str(outside), None)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_custom_id_and_public_local_record_validation():
|
|
92
|
+
assert validate_local_model_id("football-model:v2") == "football-model:v2"
|
|
93
|
+
with pytest.raises(ValueError):
|
|
94
|
+
validate_local_model_id("folder/model")
|
|
95
|
+
local = record()
|
|
96
|
+
local.update({"source_type": "local", "source": "/secret/model", "config_path": "/secret/config", "error": "at /secret/model"})
|
|
97
|
+
visible = public_record(local)
|
|
98
|
+
assert "source" not in visible
|
|
99
|
+
assert "config_path" not in visible
|
|
100
|
+
assert "/secret" not in visible["error"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/examples/quickstart/basic_classification.py
RENAMED
|
File without changes
|
{opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/examples/quickstart/basic_localization.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{opensportslib-0.3.1.dev5 → opensportslib-0.3.1.dev7}/opensportslib/configs/vqa/default.yaml
RENAMED
|
File without changes
|
|
File without changes
|