opensportslib 0.3.1.dev26__tar.gz → 0.3.1.dev27__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.
Files changed (250) hide show
  1. {opensportslib-0.3.1.dev26/opensportslib.egg-info → opensportslib-0.3.1.dev27}/PKG-INFO +2 -2
  2. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/README.md +1 -1
  3. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27/opensportslib.egg-info}/PKG-INFO +2 -2
  4. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib.egg-info/SOURCES.txt +2 -0
  5. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/pyproject.toml +2 -2
  6. opensportslib-0.3.1.dev27/tests/helpers/causal_lm.py +110 -0
  7. opensportslib-0.3.1.dev27/tests/unit/api/test_server_jobs.py +121 -0
  8. opensportslib-0.3.1.dev27/tests/unit/api/test_server_registry.py +314 -0
  9. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/unit/contracts/test_package.py +3 -2
  10. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/unit/core/test_classification_trainer.py +90 -0
  11. opensportslib-0.3.1.dev27/tests/unit/core/test_hf_runtime.py +159 -0
  12. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/unit/data/test_localization_intervals.py +45 -1
  13. opensportslib-0.3.1.dev27/tests/unit/models/test_localization_metrics.py +129 -0
  14. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/unit/models/test_vqa_qwen_xvars.py +1 -103
  15. opensportslib-0.3.1.dev26/tests/unit/api/test_server_jobs.py +0 -63
  16. opensportslib-0.3.1.dev26/tests/unit/api/test_server_registry.py +0 -131
  17. opensportslib-0.3.1.dev26/tests/unit/models/test_localization_metrics.py +0 -25
  18. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/LICENSE +0 -0
  19. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/LICENSE-COMMERCIAL +0 -0
  20. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/MANIFEST.in +0 -0
  21. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/examples/quickstart/basic_classification.py +0 -0
  22. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/examples/quickstart/basic_localization.py +0 -0
  23. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/examples/quickstart/basic_vqa.py +0 -0
  24. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/__init__.py +0 -0
  25. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/adaptation/__init__.py +0 -0
  26. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/adaptation/spotta.py +0 -0
  27. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/apis/__init__.py +0 -0
  28. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/apis/base_task_model.py +0 -0
  29. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/apis/classification.py +0 -0
  30. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/apis/config.py +0 -0
  31. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/apis/configuration.py +0 -0
  32. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/apis/localization.py +0 -0
  33. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/apis/vqa.py +0 -0
  34. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/cli.py +0 -0
  35. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/configs/classification/default.yaml +0 -0
  36. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/configs/classification/sngar_frames.yaml +0 -0
  37. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/configs/classification/sngar_tracking.yaml +0 -0
  38. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/configs/classification/video.yaml +0 -0
  39. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/configs/default.yaml +0 -0
  40. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/configs/localization/calf_resnetpca512.yaml +0 -0
  41. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/configs/localization/default.yaml +0 -0
  42. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/configs/localization/e2e_spotta.yaml +0 -0
  43. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/configs/localization/h5_header_distance.yaml +0 -0
  44. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/configs/localization/h5_header_skeleton.yaml +0 -0
  45. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/configs/localization/netvladpp_resnetpca512.yaml +0 -0
  46. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/configs/localization/tracking_action_spotting.yaml +0 -0
  47. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/configs/localization/video_dali.yaml +0 -0
  48. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/configs/localization/video_ocv.yaml +0 -0
  49. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/configs/vqa/default.yaml +0 -0
  50. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/configs/vqa/qwen.yaml +0 -0
  51. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/configs/vqa/qwen3_vl_native.yaml +0 -0
  52. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/configs/vqa/qwen_lora.yaml +0 -0
  53. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/configs/vqa/qwen_sngar_frames.yaml +0 -0
  54. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/configs/vqa/xvars.yaml +0 -0
  55. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/core/__init__.py +0 -0
  56. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/core/config/__init__.py +0 -0
  57. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/core/config/accessors.py +0 -0
  58. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/core/config/conflicts.py +0 -0
  59. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/core/config/editable.py +0 -0
  60. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/core/config/loader.py +0 -0
  61. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/core/config/migrate.py +0 -0
  62. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/core/config/migrations/__init__.py +0 -0
  63. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/core/config/migrations/legacy_to_canonical.py +0 -0
  64. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/core/config/rule_variants.py +0 -0
  65. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/core/config/runtime_adapter.py +0 -0
  66. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/core/config/schema.py +0 -0
  67. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/core/config/schemas/__init__.py +0 -0
  68. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/core/config/schemas/schema_canonical.py +0 -0
  69. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/core/config/schemas/schema_legacy.py +0 -0
  70. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/core/config/validate.py +0 -0
  71. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/core/loss/__init__.py +0 -0
  72. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/core/loss/builder.py +0 -0
  73. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/core/loss/calf.py +0 -0
  74. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/core/loss/ce.py +0 -0
  75. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/core/loss/combine.py +0 -0
  76. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/core/loss/nll.py +0 -0
  77. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/core/optimizer/__init__.py +0 -0
  78. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/core/optimizer/builder.py +0 -0
  79. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/core/sampler/weighted_sampler.py +0 -0
  80. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/core/scheduler/__init__.py +0 -0
  81. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/core/scheduler/builder.py +0 -0
  82. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/core/trainer/__init__.py +0 -0
  83. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/core/trainer/classification_trainer.py +0 -0
  84. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/core/trainer/localization_trainer.py +0 -0
  85. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/core/trainer/vqa_trainer.py +0 -0
  86. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/core/utils/checkpoint.py +0 -0
  87. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/core/utils/config.py +0 -0
  88. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/core/utils/config_normalize.py +0 -0
  89. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/core/utils/data.py +0 -0
  90. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/core/utils/ddp.py +0 -0
  91. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/core/utils/default_args.py +0 -0
  92. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/core/utils/direct_video.py +0 -0
  93. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/core/utils/hf_runtime.py +0 -0
  94. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/core/utils/lightning.py +0 -0
  95. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/core/utils/load_annotations.py +0 -0
  96. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/core/utils/seed.py +0 -0
  97. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/core/utils/video_processing.py +0 -0
  98. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/core/utils/wandb.py +0 -0
  99. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/datasets/__init__.py +0 -0
  100. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/datasets/builder.py +0 -0
  101. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/datasets/classification_dataset.py +0 -0
  102. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/datasets/hf_json.py +0 -0
  103. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/datasets/hf_tracking.py +0 -0
  104. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/datasets/localization_dataset.py +0 -0
  105. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/datasets/utils/__init__.py +0 -0
  106. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/datasets/utils/h5_tracking.py +0 -0
  107. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/datasets/utils/tracking.py +0 -0
  108. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/datasets/vqa_dataset.py +0 -0
  109. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/legacy_config/classification.yaml +0 -0
  110. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/legacy_config/localization-e2e-ocv.yaml +0 -0
  111. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/legacy_config/localization-json_calf_resnetpca512.yaml +0 -0
  112. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/legacy_config/localization-json_netvlad++_resnetpca512.yaml +0 -0
  113. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/legacy_config/localization.yaml +0 -0
  114. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/legacy_config/sngar-frames.yaml +0 -0
  115. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/legacy_config/sngar-tracking.yaml +0 -0
  116. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/metrics/classification_metric.py +0 -0
  117. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/metrics/localization_metric.py +0 -0
  118. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/metrics/vqa_metric.py +0 -0
  119. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/models/__init__.py +0 -0
  120. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/models/backbones/builder.py +0 -0
  121. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/models/base/contextaware.py +0 -0
  122. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/models/base/e2e.py +0 -0
  123. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/models/base/learnablepooling.py +0 -0
  124. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/models/base/qwen_vl_native.py +0 -0
  125. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/models/base/qwen_xvars.py +0 -0
  126. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/models/base/rule_based.py +0 -0
  127. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/models/base/tracking.py +0 -0
  128. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/models/base/vars.py +0 -0
  129. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/models/base/video.py +0 -0
  130. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/models/base/video_chatgpt_compat.py +0 -0
  131. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/models/base/video_mae.py +0 -0
  132. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/models/base/xvars_videochatgpt.py +0 -0
  133. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/models/builder.py +0 -0
  134. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/models/heads/builder.py +0 -0
  135. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/models/neck/builder.py +0 -0
  136. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/models/utils/common.py +0 -0
  137. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/models/utils/impl/__init__.py +0 -0
  138. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/models/utils/impl/asformer.py +0 -0
  139. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/models/utils/impl/calf.py +0 -0
  140. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/models/utils/impl/gsm.py +0 -0
  141. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/models/utils/impl/gtad.py +0 -0
  142. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/models/utils/impl/tsm.py +0 -0
  143. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/models/utils/litebase.py +0 -0
  144. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/models/utils/modules.py +0 -0
  145. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/models/utils/shift.py +0 -0
  146. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/models/utils/utils.py +0 -0
  147. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/models/utils/vqa_prediction_priors.py +0 -0
  148. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/models/utils/vqa_prompting.py +0 -0
  149. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/models/utils/xvars_clip_index.py +0 -0
  150. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/remote_registry.py +0 -0
  151. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/setup/setup.py +0 -0
  152. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/tools/__init__.py +0 -0
  153. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/tools/_common.py +0 -0
  154. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/tools/hf_transfer.py +0 -0
  155. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/tools/osl_json_to_parquet.py +0 -0
  156. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib/tools/parquet_to_osl_json.py +0 -0
  157. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib.egg-info/dependency_links.txt +0 -0
  158. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib.egg-info/entry_points.txt +0 -0
  159. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib.egg-info/requires.txt +0 -0
  160. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/opensportslib.egg-info/top_level.txt +0 -0
  161. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/scripts/redact_test_stream.py +0 -0
  162. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/scripts/release_versions.py +0 -0
  163. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/scripts/run_h5_header_rule_inference.py +0 -0
  164. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/scripts/summarize_test_report.py +0 -0
  165. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/scripts/verify_release_manifests.py +0 -0
  166. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/scripts/write_release_manifest.py +0 -0
  167. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/setup.cfg +0 -0
  168. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/__init__.py +0 -0
  169. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/conftest.py +0 -0
  170. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/helpers/__init__.py +0 -0
  171. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/helpers/configs.py +0 -0
  172. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/helpers/pipeline_stubs.py +0 -0
  173. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/integration/classification/test_pipeline.py +0 -0
  174. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/integration/localization/test_pipeline.py +0 -0
  175. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/integration/localization/test_spotta.py +0 -0
  176. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/integration/vqa/test_pipeline.py +0 -0
  177. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/release/__init__.py +0 -0
  178. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/release/_release_common.py +0 -0
  179. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/release/test_00_environment.py +0 -0
  180. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/release/test_classification_release.py +0 -0
  181. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/release/test_localization_release.py +0 -0
  182. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/release/test_vqa_release.py +0 -0
  183. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/smoke/test_cli.py +0 -0
  184. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/smoke/test_config_utils.py +0 -0
  185. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/smoke/test_environment.py +0 -0
  186. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/smoke/test_package.py +0 -0
  187. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/smoke/test_public_apis.py +0 -0
  188. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/unit/api/test_remote_registry.py +0 -0
  189. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/unit/api/test_remote_sessions.py +0 -0
  190. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/unit/api/test_server_startup_scripts.py +0 -0
  191. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/unit/api/test_task_model_contract.py +0 -0
  192. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/unit/api/test_vqa.py +0 -0
  193. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/unit/config/test_architecture.py +0 -0
  194. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/unit/config/test_editable.py +0 -0
  195. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/unit/config/test_optional_hf.py +0 -0
  196. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/unit/config/test_pretrained_merge.py +0 -0
  197. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/unit/config/test_split_overrides.py +0 -0
  198. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/unit/contracts/test_architecture.py +0 -0
  199. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/unit/contracts/test_dependencies.py +0 -0
  200. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/unit/contracts/test_release_versions.py +0 -0
  201. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/unit/contracts/test_runner_contract.py +0 -0
  202. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/unit/core/test_localization_hf_backend.py +0 -0
  203. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/unit/core/test_training_components.py +0 -0
  204. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/unit/core/test_video_processing.py +0 -0
  205. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/unit/data/test_classification_paths.py +0 -0
  206. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/unit/data/test_h5_tracking.py +0 -0
  207. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/unit/data/test_hf_json_spotting.py +0 -0
  208. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/unit/data/test_hf_json_tracking_spotting.py +0 -0
  209. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/unit/data/test_hf_tracking_dataset.py +0 -0
  210. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/unit/data/test_localization_dali.py +0 -0
  211. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/unit/data/test_osl_formats.py +0 -0
  212. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/unit/models/test_classification_metrics.py +0 -0
  213. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/unit/models/test_header_rule_spotter.py +0 -0
  214. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/unit/models/test_header_skeleton_spotter.py +0 -0
  215. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/unit/models/test_temporal_shift.py +0 -0
  216. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/unit/models/test_vqa_metrics.py +0 -0
  217. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/unit/models/test_vqa_training_lora.py +0 -0
  218. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/unit/models/test_vqa_xvars_videochatgpt.py +0 -0
  219. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/unit/tools/test_conversion.py +0 -0
  220. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/unit/tools/test_extract_xvars_features.py +0 -0
  221. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/unit/tools/test_hf_transfer.py +0 -0
  222. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/unit/tools/test_release_manifest.py +0 -0
  223. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/unit/tools/test_report_summary.py +0 -0
  224. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/unit/tools/test_setup.py +0 -0
  225. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tests/unit/tools/test_spotting_classification.py +0 -0
  226. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tools/convert/build_sn_vqa_2026_vqa.py +0 -0
  227. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tools/convert/build_sngar_spotting.py +0 -0
  228. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tools/convert/build_soccernet_gar.py +0 -0
  229. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tools/convert/build_soccernet_gar_action_spotting.py +0 -0
  230. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tools/convert/build_soccernet_gar_vqa.py +0 -0
  231. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tools/convert/build_xvars_indexes.py +0 -0
  232. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tools/convert/extract_xvars_clip_features.py +0 -0
  233. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tools/convert/osl_json_to_parquet_webdataset.py +0 -0
  234. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tools/convert/parquet_webdataset_to_osl_json.py +0 -0
  235. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tools/convert/sngar_dataset_card.py +0 -0
  236. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tools/convert/sngar_events.py +0 -0
  237. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tools/convert/spotting_to_classification.py +0 -0
  238. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tools/convert/spotting_tracking_to_classification.py +0 -0
  239. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tools/convert/verify_sngar_spotting.py +0 -0
  240. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tools/download/download_hf_repo.py +0 -0
  241. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tools/download/download_osl_hf.py +0 -0
  242. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tools/download/push_sngar_spotting.py +0 -0
  243. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tools/download/upload_osl_hf.py +0 -0
  244. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tools/train/train_config.py +0 -0
  245. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tools/training/classification.py +0 -0
  246. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tools/training/localization.py +0 -0
  247. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tools/training/vqa.py +0 -0
  248. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tools/upload/upload_model_hf.py +0 -0
  249. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tools/upload/upload_sngar_tracking_full.py +0 -0
  250. {opensportslib-0.3.1.dev26 → opensportslib-0.3.1.dev27}/tools/upload/upload_sngar_videos.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opensportslib
3
- Version: 0.3.1.dev26
3
+ Version: 0.3.1.dev27
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
@@ -67,7 +67,7 @@ OpenSportsLib is designed for **researchers, ML engineers, and sports analytics
67
67
 
68
68
  - **Documentation:** https://opensportslab.github.io/opensportslib/
69
69
  - **OSL JSON format:** https://opensportslab.github.io/opensportslib/data/osl-json-format/
70
- - **Inference server:** [server/README.md](server/README.md)
70
+ - **Inference server:** [server/README.md](server/README.md), including checkpoint and config-only rule-model registration
71
71
  - **PyPI:** https://pypi.org/project/opensportslib/
72
72
  - **Issues:** https://github.com/OpenSportsLab/opensportslib/issues
73
73
 
@@ -28,7 +28,7 @@ OpenSportsLib is designed for **researchers, ML engineers, and sports analytics
28
28
 
29
29
  - **Documentation:** https://opensportslab.github.io/opensportslib/
30
30
  - **OSL JSON format:** https://opensportslab.github.io/opensportslib/data/osl-json-format/
31
- - **Inference server:** [server/README.md](server/README.md)
31
+ - **Inference server:** [server/README.md](server/README.md), including checkpoint and config-only rule-model registration
32
32
  - **PyPI:** https://pypi.org/project/opensportslib/
33
33
  - **Issues:** https://github.com/OpenSportsLab/opensportslib/issues
34
34
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opensportslib
3
- Version: 0.3.1.dev26
3
+ Version: 0.3.1.dev27
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
@@ -67,7 +67,7 @@ OpenSportsLib is designed for **researchers, ML engineers, and sports analytics
67
67
 
68
68
  - **Documentation:** https://opensportslab.github.io/opensportslib/
69
69
  - **OSL JSON format:** https://opensportslab.github.io/opensportslib/data/osl-json-format/
70
- - **Inference server:** [server/README.md](server/README.md)
70
+ - **Inference server:** [server/README.md](server/README.md), including checkpoint and config-only rule-model registration
71
71
  - **PyPI:** https://pypi.org/project/opensportslib/
72
72
  - **Issues:** https://github.com/OpenSportsLab/opensportslib/issues
73
73
 
@@ -154,6 +154,7 @@ scripts/write_release_manifest.py
154
154
  tests/__init__.py
155
155
  tests/conftest.py
156
156
  tests/helpers/__init__.py
157
+ tests/helpers/causal_lm.py
157
158
  tests/helpers/configs.py
158
159
  tests/helpers/pipeline_stubs.py
159
160
  tests/integration/classification/test_pipeline.py
@@ -189,6 +190,7 @@ tests/unit/contracts/test_package.py
189
190
  tests/unit/contracts/test_release_versions.py
190
191
  tests/unit/contracts/test_runner_contract.py
191
192
  tests/unit/core/test_classification_trainer.py
193
+ tests/unit/core/test_hf_runtime.py
192
194
  tests/unit/core/test_localization_hf_backend.py
193
195
  tests/unit/core/test_training_components.py
194
196
  tests/unit/core/test_video_processing.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "opensportslib"
7
- version = "0.3.1.dev26"
7
+ version = "0.3.1.dev27"
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"
@@ -22,7 +22,7 @@ opensportslib = "opensportslib.cli:main"
22
22
  opensportslib = [ "legacy_config/*.yaml", "configs/**/*.yaml",]
23
23
 
24
24
  [tool.coverage.run]
25
- source = [ "opensportslib",]
25
+ source = [ "opensportslib", "server",]
26
26
  branch = true
27
27
  omit = [ "*/.ipynb_checkpoints/*",]
28
28
 
@@ -0,0 +1,110 @@
1
+ """Small tokenizer and causal model doubles for offline generation tests."""
2
+
3
+ from types import SimpleNamespace
4
+
5
+ import torch
6
+
7
+
8
+ class TinyTokenizer:
9
+ pad_token_id = 0
10
+ eos_token_id = 1
11
+ pad_token = "<pad>"
12
+ eos_token = "</s>"
13
+
14
+ def __init__(self):
15
+ self.vocab = {"<pad>": 0, "</s>": 1, "<vid_patch>": 2, "<vid_start>": 3, "<vid_end>": 4}
16
+
17
+ def get_vocab(self):
18
+ return dict(self.vocab)
19
+
20
+ def add_special_tokens(self, payload):
21
+ added = 0
22
+ for token in payload.get("additional_special_tokens", []):
23
+ if token not in self.vocab:
24
+ self.vocab[token] = len(self.vocab)
25
+ added += 1
26
+ return added
27
+
28
+ def convert_tokens_to_ids(self, token):
29
+ return self.vocab[token]
30
+
31
+ def __len__(self):
32
+ return len(self.vocab)
33
+
34
+ def __call__(self, text, return_tensors=None, **kwargs):
35
+ del kwargs
36
+ if isinstance(text, list):
37
+ text = text[0]
38
+ tokens = []
39
+ i = 0
40
+ while i < len(text):
41
+ matched = False
42
+ for token in ("<vid_start>", "<vid_patch>", "<vid_end>", "</s>"):
43
+ if text.startswith(token, i):
44
+ tokens.append(token)
45
+ i += len(token)
46
+ matched = True
47
+ break
48
+ if matched:
49
+ continue
50
+ if text[i].isspace():
51
+ i += 1
52
+ continue
53
+ j = i
54
+ while j < len(text) and not text[j].isspace() and not any(
55
+ text.startswith(token, j) for token in ("<vid_start>", "<vid_patch>", "<vid_end>", "</s>")
56
+ ):
57
+ j += 1
58
+ tokens.append(text[i:j])
59
+ i = j
60
+ ids = []
61
+ for token in tokens:
62
+ if token not in self.vocab:
63
+ self.vocab[token] = len(self.vocab)
64
+ ids.append(self.vocab[token])
65
+ attn = [1] * len(ids)
66
+ if return_tensors == "pt":
67
+ return {"input_ids": torch.tensor([ids]), "attention_mask": torch.tensor([attn])}
68
+ return SimpleNamespace(input_ids=ids, attention_mask=attn)
69
+
70
+ def batch_decode(self, sequences, skip_special_tokens=True):
71
+ del sequences, skip_special_tokens
72
+ return ["qwen answer </s>"]
73
+
74
+
75
+ class TinyGenerateLM(torch.nn.Module):
76
+ def __init__(self):
77
+ super().__init__()
78
+ self.config = SimpleNamespace(hidden_size=4, vocab_size=64, use_cache=True)
79
+ self.generation_config = SimpleNamespace(use_cache=False)
80
+ self.emb = torch.nn.Embedding(128, 4)
81
+ self.lm = torch.nn.Linear(4, 64)
82
+ self.seen_inputs_embeds = None
83
+ self.generate_kwargs = None
84
+
85
+ def get_input_embeddings(self):
86
+ return self.emb
87
+
88
+ def resize_token_embeddings(self, size):
89
+ return self.emb
90
+
91
+ def save_pretrained(self, output_dir):
92
+ del output_dir
93
+
94
+ def forward(self, input_ids=None, inputs_embeds=None, attention_mask=None, labels=None, **kwargs):
95
+ del input_ids, attention_mask, kwargs
96
+ self.seen_inputs_embeds = inputs_embeds.detach().clone()
97
+ logits = self.lm(inputs_embeds)
98
+ loss = logits.sum() * 0
99
+ if labels is not None:
100
+ loss = loss + 0.123
101
+ return SimpleNamespace(loss=loss, logits=logits)
102
+
103
+ def generate(self, input_ids=None, inputs_embeds=None, attention_mask=None, **kwargs):
104
+ del attention_mask
105
+ self.generate_kwargs = {"input_ids": input_ids, "inputs_embeds": inputs_embeds, **kwargs}
106
+ extra = torch.tensor([[9, 1]], device=input_ids.device if input_ids is not None else inputs_embeds.device)
107
+ if input_ids is None:
108
+ prefix = torch.zeros((1, inputs_embeds.shape[1]), dtype=torch.long, device=extra.device)
109
+ return torch.cat((prefix, extra), dim=1)
110
+ return torch.cat((input_ids, extra), dim=1)
@@ -0,0 +1,121 @@
1
+ import sys
2
+ from pathlib import Path
3
+ from types import SimpleNamespace
4
+
5
+ import pytest
6
+
7
+
8
+ SERVER_ROOT = Path(__file__).resolve().parents[3] / "server"
9
+ sys.path.insert(0, str(SERVER_ROOT))
10
+
11
+ from app.schemas import JobStatus, PredictRequest # noqa: E402
12
+ from storage.jobs import JobStore # noqa: E402
13
+
14
+
15
+ def store(tmp_path):
16
+ runtime = tmp_path / "runtime"
17
+ settings = SimpleNamespace(
18
+ runtime_dir=runtime,
19
+ tmp_dir=runtime / "tmp",
20
+ jobs_dir=runtime / "jobs",
21
+ results_dir=runtime / "results",
22
+ )
23
+ settings.jobs_dir.mkdir(parents=True)
24
+ settings.tmp_dir.mkdir(parents=True)
25
+ settings.results_dir.mkdir(parents=True)
26
+ return JobStore(settings)
27
+
28
+
29
+ def request():
30
+ return PredictRequest(task_type="classification", video_path="/tmp/clip.mp4")
31
+
32
+
33
+ def test_metadata_is_valid_json_after_create(tmp_path):
34
+ jobs = store(tmp_path)
35
+ metadata = jobs.create_job(request(), "model")
36
+
37
+ loaded = jobs.load_metadata(metadata.job_id)
38
+ assert loaded is not None
39
+ assert loaded.status is JobStatus.QUEUED
40
+ assert jobs.metadata_path(metadata.job_id).read_text(encoding="utf-8").strip()
41
+
42
+
43
+ def test_metadata_replacement_never_leaves_partial_file(tmp_path):
44
+ jobs = store(tmp_path)
45
+ metadata = jobs.create_job(request(), "model")
46
+
47
+ for status in (JobStatus.RUNNING, JobStatus.SUCCEEDED, JobStatus.FAILED):
48
+ metadata.status = status
49
+ jobs.save_metadata(metadata)
50
+ loaded = jobs.load_metadata(metadata.job_id)
51
+ assert loaded is not None
52
+ assert loaded.status is status
53
+ assert not list(jobs.settings.jobs_dir.glob(f".{metadata.job_id}.*.tmp"))
54
+
55
+
56
+ def test_invalid_metadata_is_treated_as_missing(tmp_path):
57
+ jobs = store(tmp_path)
58
+ path = jobs.metadata_path("broken")
59
+ path.write_text("", encoding="utf-8")
60
+
61
+ assert jobs.load_metadata("broken") is None
62
+ with pytest.raises(FileNotFoundError, match="broken"):
63
+ jobs.mark_running("broken")
64
+
65
+
66
+ def test_job_lifecycle_persists_request_result_and_clears_failure(tmp_path):
67
+ import json
68
+
69
+ jobs = store(tmp_path)
70
+ metadata = jobs.create_job(request(), "model", session_id="session", input_source="uploaded.mp4")
71
+ job_id = metadata.job_id
72
+ assert jobs.load_result(job_id) is None
73
+ assert jobs.mark_running(job_id).status is JobStatus.RUNNING
74
+ assert jobs.mark_failed(job_id, "temporary failure").error == "temporary failure"
75
+ assert jobs.load_metadata(job_id).status is JobStatus.FAILED
76
+ payload = {"predictions": [{"label": "PASS", "confidence": .9}]}
77
+ succeeded = jobs.mark_succeeded(job_id, payload)
78
+ assert succeeded.status is JobStatus.SUCCEEDED and succeeded.error is None
79
+ assert jobs.load_result(job_id) == payload
80
+ assert jobs.load_metadata(job_id).session_id == "session"
81
+ assert jobs.load_metadata(job_id).input_source == "uploaded.mp4"
82
+ saved = json.loads((jobs.settings.tmp_dir / job_id / "request.json").read_text())
83
+ assert saved["video_path"] == "/tmp/clip.mp4"
84
+
85
+
86
+ @pytest.mark.parametrize("source", ["video_path", "media_url", "test_set_path"])
87
+ def test_updating_request_updates_persisted_input_source(tmp_path, source):
88
+ import json
89
+
90
+ jobs = store(tmp_path)
91
+ metadata = jobs.create_job(request(), "model")
92
+ replacement = PredictRequest(task_type="classification", **{source: "new-input"})
93
+ jobs.update_request_payload(metadata.job_id, replacement)
94
+ assert jobs.load_metadata(metadata.job_id).input_source == "new-input"
95
+ saved = json.loads((jobs.settings.tmp_dir / metadata.job_id / "request.json").read_text())
96
+ assert saved[source] == "new-input"
97
+
98
+
99
+ def test_atomic_metadata_failure_preserves_last_valid_record(tmp_path, monkeypatch):
100
+ import storage.jobs as module
101
+
102
+ jobs = store(tmp_path)
103
+ metadata = jobs.create_job(request(), "model")
104
+ previous = jobs.metadata_path(metadata.job_id).read_bytes()
105
+ def fail_replace(*args):
106
+ raise OSError("disk replacement failed")
107
+ monkeypatch.setattr(module.os, "replace", fail_replace)
108
+ with pytest.raises(OSError, match="replacement failed"):
109
+ jobs.mark_running(metadata.job_id)
110
+ assert jobs.metadata_path(metadata.job_id).read_bytes() == previous
111
+ assert not list(jobs.settings.jobs_dir.glob("*.tmp"))
112
+ assert jobs.load_metadata(metadata.job_id).status is JobStatus.QUEUED
113
+
114
+
115
+ @pytest.mark.parametrize("operation", ["mark_running", "mark_failed", "mark_succeeded"])
116
+ def test_updates_to_missing_job_fail_without_creating_results(tmp_path, operation):
117
+ jobs = store(tmp_path)
118
+ args = {"mark_running": (), "mark_failed": ("failure",), "mark_succeeded": ({},)}[operation]
119
+ with pytest.raises(FileNotFoundError, match="metadata missing"):
120
+ getattr(jobs, operation)("missing", *args)
121
+ assert jobs.load_result("missing") is None
@@ -0,0 +1,314 @@
1
+ import sys
2
+ from pathlib import Path
3
+ from types import SimpleNamespace
4
+ from unittest.mock import Mock
5
+
6
+ import pytest
7
+
8
+
9
+ SERVER_ROOT = Path(__file__).parents[3] / "server"
10
+ sys.path.insert(0, str(SERVER_ROOT))
11
+
12
+ from config.model_registry import ( # noqa: E402
13
+ ModelRegistry,
14
+ generated_local_model_id,
15
+ public_record,
16
+ resolve_local_source,
17
+ resolve_huggingface_source,
18
+ infer_source_mode,
19
+ require_api_key,
20
+ validate_local_model_id,
21
+ )
22
+
23
+
24
+ class FakeRedis:
25
+ def __init__(self):
26
+ self.hashes = {}
27
+
28
+ def hget(self, key, field):
29
+ return self.hashes.get(key, {}).get(field)
30
+
31
+ def hvals(self, key):
32
+ return list(self.hashes.get(key, {}).values())
33
+
34
+ def hset(self, key, field, value):
35
+ self.hashes.setdefault(key, {})[field] = value
36
+
37
+ def hsetnx(self, key, field, value):
38
+ values = self.hashes.setdefault(key, {})
39
+ if field in values:
40
+ return 0
41
+ values[field] = value
42
+ return 1
43
+
44
+ def hdel(self, key, field):
45
+ self.hashes.get(key, {}).pop(field, None)
46
+
47
+ def hgetall(self, key):
48
+ return dict(self.hashes.get(key, {}))
49
+
50
+ def setex(self, key, ttl, value):
51
+ self.hashes.setdefault("strings", {})[key] = value
52
+
53
+ def getdel(self, key):
54
+ return self.hashes.setdefault("strings", {}).pop(key, None)
55
+
56
+ def delete(self, key):
57
+ self.hashes.setdefault("strings", {}).pop(key, None)
58
+
59
+
60
+ def record(model_id="model", source="org/model"):
61
+ return {
62
+ "model_id": model_id,
63
+ "task_type": "classification",
64
+ "source_type": "huggingface",
65
+ "source": source,
66
+ "config_path": None,
67
+ "status": "ready",
68
+ "generation": 1,
69
+ "created_at": "now",
70
+ "updated_at": "now",
71
+ "error": None,
72
+ }
73
+
74
+
75
+ def test_registry_reserves_idempotently_and_rejects_conflicts():
76
+ registry = ModelRegistry(SimpleNamespace(redis_url="redis://unused"), FakeRedis())
77
+ _, created = registry.reserve(record())
78
+ assert created
79
+ existing, created = registry.reserve(record())
80
+ assert not created
81
+ assert existing["source"] == "org/model"
82
+ with pytest.raises(FileExistsError):
83
+ registry.reserve(record(source="other/model"))
84
+
85
+
86
+ def test_local_source_stays_inside_model_root(tmp_path):
87
+ root = tmp_path / "models"
88
+ folder = root / "custom"
89
+ folder.mkdir(parents=True)
90
+ (folder / "config.yaml").write_text("MODEL: {}")
91
+ settings = SimpleNamespace(model_root=root)
92
+
93
+ weights, config = resolve_local_source(settings, str(folder), None)
94
+ assert weights == folder
95
+ assert config == folder / "config.yaml"
96
+ assert generated_local_model_id(folder).startswith("local:custom-")
97
+
98
+ outside = tmp_path / "outside"
99
+ outside.mkdir()
100
+ with pytest.raises(ValueError, match="OSL_MODEL_ROOT"):
101
+ resolve_local_source(settings, str(outside), None)
102
+
103
+
104
+ def test_custom_id_and_public_local_record_validation():
105
+ assert validate_local_model_id("football-model:v2") == "football-model:v2"
106
+ with pytest.raises(ValueError):
107
+ validate_local_model_id("folder/model")
108
+ local = record()
109
+ local.update({"source_type": "local", "source": "/secret/model", "config_path": "/secret/config", "error": "at /secret/model"})
110
+ visible = public_record(local)
111
+ assert "source" not in visible
112
+ assert "config_path" not in visible
113
+ assert "/secret" not in visible["error"]
114
+
115
+
116
+ @pytest.mark.parametrize("runner_section", ["MODEL", "TRAIN"])
117
+ def test_config_driven_runner_is_weightless(tmp_path, runner_section):
118
+ config = tmp_path / "config.yaml"
119
+ config.write_text(f"{runner_section}:\n runner:\n type: runner_h5_header_rule\n")
120
+ assert infer_source_mode(str(config), "org/config-only") == "config_only"
121
+
122
+
123
+ def test_ordinary_config_requires_checkpoint(tmp_path):
124
+ config = tmp_path / "config.yaml"
125
+ config.write_text("TRAIN:\n runner:\n type: default\n")
126
+ assert infer_source_mode(str(config), "org/model") == "checkpoint"
127
+
128
+
129
+ def test_operation_secret_is_consumed_and_not_part_of_records():
130
+ settings = SimpleNamespace(redis_url="redis://unused", model_operation_timeout_seconds=60)
131
+ registry = ModelRegistry(settings, FakeRedis())
132
+ registry.store_operation_secret("operation", "hf_secret")
133
+ assert registry.consume_operation_secret("operation") == "hf_secret"
134
+ assert registry.consume_operation_secret("operation") is None
135
+ assert registry.list() == []
136
+
137
+
138
+ def _mock_hub(monkeypatch, config_path, files):
139
+ import huggingface_hub
140
+
141
+ download = Mock(return_value=str(config_path))
142
+ listing = Mock(return_value=files)
143
+ monkeypatch.setattr(huggingface_hub, "hf_hub_download", download)
144
+ monkeypatch.setattr(huggingface_hub, "list_repo_files", listing)
145
+ return download, listing
146
+
147
+
148
+ @pytest.mark.parametrize("variant", [
149
+ "h5_header_skeleton", "h5_header_skeleton_recall", "h5_header_skeleton_max_recall",
150
+ ])
151
+ def test_hf_rule_source_loads_localization_service_without_weights(tmp_path, monkeypatch, variant):
152
+ from opensportslib.core.config import load_config
153
+ from opensportslib.core.config.loader import save_config
154
+ from services.localization_service import LocalizationService
155
+
156
+ canonical = load_config(
157
+ SERVER_ROOT.parent / "opensportslib/configs/localization/h5_header_skeleton.yaml",
158
+ as_namespace=False,
159
+ )
160
+ canonical["MODEL"]["components"]["rule"]["source"]["name"] = variant
161
+ canonical["SYSTEM"]["paths"]["save_dir"] = str(tmp_path / "outputs")
162
+ canonical["DATA"]["common"]["data_root"] = str(tmp_path)
163
+ canonical["DATA"]["common"]["splits"]["test"]["source_path"] = str(tmp_path)
164
+ config_path = tmp_path / "config.yaml"
165
+ save_config(canonical, config_path)
166
+ download, listing = _mock_hub(monkeypatch, config_path, ["config.yaml"])
167
+ model_id = "example/skeleton"
168
+
169
+ resolved_path, source_mode, weights = resolve_huggingface_source(model_id, token="test-token")
170
+ assert (resolved_path, source_mode, weights) == (str(config_path), "config_only", None)
171
+ download.assert_called_once_with(repo_id=model_id, filename="config.yaml", token="test-token")
172
+ listing.assert_called_once_with(model_id, token="test-token")
173
+
174
+ registry = ModelRegistry(SimpleNamespace(redis_url="redis://unused"), FakeRedis())
175
+ registered = record(model_id=model_id, source=model_id)
176
+ registered.update(task_type="localization", config_path=resolved_path, source_mode=source_mode)
177
+ settings = registry.model_settings(registered)
178
+ assert settings.weights is None
179
+ service = LocalizationService(settings)
180
+ service.preload()
181
+ assert service.model.last_loaded_weights is None
182
+ assert service.model.config.MODEL.components.rule.source.name == variant
183
+ assert service.label_schema["action"]["labels"] == ["header"]
184
+ # Older persisted records without source_mode must infer the same behavior.
185
+ registered.pop("source_mode")
186
+ assert registry.model_settings(registered).weights is None
187
+ assert download.call_count == 1
188
+
189
+
190
+ @pytest.mark.parametrize("checkpoint", ["model.pt", "model.pth.tar", "adapter_model.safetensors"])
191
+ def test_hf_checkpoint_source_keeps_weights(tmp_path, monkeypatch, checkpoint):
192
+ config_path = tmp_path / "config.yaml"
193
+ config_path.write_text("MODEL:\n runner:\n type: runner_e2e\n")
194
+ _mock_hub(monkeypatch, config_path, ["config.yaml", checkpoint])
195
+ model_id = "example/checkpoint"
196
+
197
+ path, mode, weights = resolve_huggingface_source(model_id)
198
+ assert (path, mode, weights) == (str(config_path), "checkpoint", model_id)
199
+ registry = ModelRegistry(SimpleNamespace(redis_url="redis://unused"), FakeRedis())
200
+ registered = record(model_id=model_id, source=model_id)
201
+ registered.update(config_path=path, source_mode=mode)
202
+ assert registry.model_settings(registered).weights == model_id
203
+
204
+
205
+ @pytest.mark.parametrize("content", [
206
+ "MODEL:\n runner:\n type: runner_e2e\n",
207
+ "{}",
208
+ "[]",
209
+ "MODEL: [",
210
+ ])
211
+ def test_hf_weightless_source_without_supported_runner_is_rejected(tmp_path, monkeypatch, content):
212
+ config_path = tmp_path / "config.yaml"
213
+ config_path.write_text(content)
214
+ _mock_hub(monkeypatch, config_path, ["config.yaml"])
215
+
216
+ with pytest.raises(ValueError, match="no supported checkpoint"):
217
+ resolve_huggingface_source("example/unsupported")
218
+
219
+
220
+ def test_hf_repository_access_failure_is_reported(tmp_path, monkeypatch):
221
+ config_path = tmp_path / "config.yaml"
222
+ config_path.write_text("MODEL: {}")
223
+ _, listing = _mock_hub(monkeypatch, config_path, [])
224
+ listing.side_effect = RuntimeError("repository access denied")
225
+ with pytest.raises(ValueError, match="Could not access Hugging Face repository"):
226
+ resolve_huggingface_source("example/gated", token="test-token")
227
+
228
+
229
+ def test_default_selection_and_deletion_preserve_other_tasks():
230
+ registry = ModelRegistry(SimpleNamespace(redis_url="redis://unused"), FakeRedis())
231
+ registry.put(record("classifier"))
232
+ localizer = record("localizer")
233
+ localizer["task_type"] = "localization"
234
+ registry.put(localizer)
235
+ registry.set_default("classification", "classifier")
236
+ registry.set_default("localization", "localizer")
237
+ assert registry.resolve("classification", None)["model_id"] == "classifier"
238
+ assert registry.resolve("localization", None)["model_id"] == "localizer"
239
+ assert [item["model_id"] for item in registry.list()] == ["classifier", "localizer"]
240
+ registry.delete("classifier")
241
+ assert registry.resolve("classification", None) is None
242
+ assert registry.resolve("localization", None)["model_id"] == "localizer"
243
+ assert registry.get("classifier") is None
244
+
245
+
246
+ @pytest.mark.parametrize("status", ["registering", "failed", "unregistering"])
247
+ def test_only_ready_models_can_be_defaults(status):
248
+ registry = ModelRegistry(SimpleNamespace(redis_url="redis://unused"), FakeRedis())
249
+ candidate = record()
250
+ candidate["status"] = status
251
+ registry.put(candidate)
252
+ with pytest.raises(ValueError, match="READY"):
253
+ registry.set_default("classification", "model")
254
+ assert registry.resolve("classification", None) is None
255
+
256
+
257
+ def test_unknown_and_wrong_task_models_cannot_be_selected_as_defaults():
258
+ registry = ModelRegistry(SimpleNamespace(redis_url="redis://unused"), FakeRedis())
259
+ with pytest.raises(KeyError):
260
+ registry.set_default("classification", "missing")
261
+ registry.put(record())
262
+ with pytest.raises(ValueError, match="requested task"):
263
+ registry.set_default("localization", "model")
264
+ with pytest.raises(TypeError, match="not `localization`"):
265
+ registry.resolve("localization", "model")
266
+ assert registry.resolve("classification", "missing") is None
267
+
268
+
269
+ def test_operations_round_trip_and_secrets_can_be_removed():
270
+ registry = ModelRegistry(SimpleNamespace(redis_url="redis://unused", model_operation_timeout_seconds=60), FakeRedis())
271
+ assert registry.get_operation("missing") is None
272
+ operation = registry.create_operation("register", "model")
273
+ assert operation["status"] == "queued"
274
+ operation.update(status="failed", error="missing config")
275
+ registry.save_operation(operation)
276
+ assert registry.get_operation(operation["operation_id"])["error"] == "missing config"
277
+ registry.store_operation_secret(operation["operation_id"], "temporary")
278
+ registry.delete_operation_secret(operation["operation_id"])
279
+ assert registry.consume_operation_secret(operation["operation_id"]) is None
280
+
281
+
282
+ @pytest.mark.parametrize("authorization", [None, "Bearer wrong", "Bearer"])
283
+ def test_api_key_rejects_missing_or_wrong_credentials(authorization):
284
+ with pytest.raises(PermissionError, match="Invalid API key"):
285
+ require_api_key(SimpleNamespace(api_key="expected"), authorization)
286
+
287
+
288
+ def test_api_key_accepts_valid_bearer_and_disables_unconfigured_admin():
289
+ require_api_key(SimpleNamespace(api_key="expected"), "Bearer expected")
290
+ with pytest.raises(RuntimeError, match="disabled"):
291
+ require_api_key(SimpleNamespace(api_key=""), "Bearer expected")
292
+
293
+
294
+ def test_local_source_rejects_symlink_escape_and_requires_file_config(tmp_path):
295
+ root = tmp_path / "models"
296
+ root.mkdir()
297
+ outside = tmp_path / "outside.pt"
298
+ outside.write_bytes(b"checkpoint")
299
+ escaped = root / "escape.pt"
300
+ escaped.symlink_to(outside)
301
+ settings = SimpleNamespace(model_root=root)
302
+ with pytest.raises(ValueError, match="OSL_MODEL_ROOT"):
303
+ resolve_local_source(settings, str(escaped), None)
304
+ checkpoint = root / "model.pt"
305
+ checkpoint.write_bytes(b"checkpoint")
306
+ with pytest.raises(ValueError, match="config_path is required"):
307
+ resolve_local_source(settings, str(checkpoint), None)
308
+ with pytest.raises(ValueError, match="config does not exist"):
309
+ resolve_local_source(settings, str(checkpoint), str(root / "missing.yaml"))
310
+ with pytest.raises(ValueError, match="weights do not exist"):
311
+ resolve_local_source(settings, str(root / "missing.pt"), None)
312
+ config = root / "config.yaml"
313
+ config.write_text("MODEL: {}")
314
+ assert resolve_local_source(settings, str(checkpoint), str(config)) == (checkpoint, config)
@@ -47,8 +47,9 @@ def test_packaging_includes_all_yaml_presets():
47
47
  assert experiment_config_paths(), f"No task presets found under {CONFIG_ROOT}"
48
48
 
49
49
 
50
- def test_coverage_baseline_is_a_valid_committed_percentage():
51
- raw = (REPOSITORY_ROOT / "scripts" / "coverage-baseline.txt").read_text(encoding="utf-8").strip()
50
+ @pytest.mark.parametrize("filename", ["coverage-baseline.txt", "server-coverage-baseline.txt"])
51
+ def test_coverage_baseline_is_a_valid_committed_percentage(filename):
52
+ raw = (REPOSITORY_ROOT / "scripts" / filename).read_text(encoding="utf-8").strip()
52
53
  assert raw.isdigit(), "coverage-baseline.txt must contain one integer percentage"
53
54
  assert 1 <= int(raw) <= 100
54
55