featrixsphere 0.2.1314__py3-none-any.whl → 0.2.1439__py3-none-any.whl

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.
featrixsphere/__init__.py CHANGED
@@ -38,7 +38,7 @@ Example:
38
38
  ... labels=['Experiment A', 'Experiment B'])
39
39
  """
40
40
 
41
- __version__ = "0.2.1314"
41
+ __version__ = "0.2.1439"
42
42
  __author__ = "Featrix"
43
43
  __email__ = "support@featrix.com"
44
44
  __license__ = "MIT"
featrixsphere/client.py CHANGED
@@ -715,53 +715,101 @@ class FeatrixSphereClient:
715
715
  except Exception as e:
716
716
  return False, f"Error checking foundation model: {e}"
717
717
 
718
- def get_model_card(self, session_id: str, max_retries: int = None, check_status_first: bool = True) -> Dict[str, Any]:
718
+ def get_model_card(self, session_id: str, max_retries: int = None) -> Dict[str, Any]:
719
719
  """
720
720
  Get the model card JSON for a given session.
721
721
 
722
+ Handles on-demand model card generation:
723
+ - If model card is being generated (202), waits and retries
724
+ - If model card doesn't exist (404) but session is DONE, retries (on-demand creation may be in progress)
725
+
722
726
  Args:
723
727
  session_id: The session ID to get the model card for
724
728
  max_retries: Maximum number of retries (defaults to client default)
725
- check_status_first: If True, check session status before fetching model card.
726
- Provides better error messages if session is still training.
727
729
 
728
730
  Returns:
729
731
  Dictionary containing the model card JSON data
730
732
 
731
733
  Raises:
732
- requests.exceptions.HTTPError: If the request fails
733
- FileNotFoundError: If the model card doesn't exist (404)
734
- ValueError: If session is not ready and check_status_first is True
734
+ requests.exceptions.HTTPError: If the request fails after all retries
735
+ FileNotFoundError: If the model card doesn't exist (404) and can't be created
735
736
 
736
737
  Example:
737
738
  >>> client = FeatrixSphereClient()
738
739
  >>> model_card = client.get_model_card("session_123")
739
740
  >>> print(model_card["model_details"]["name"])
740
741
  """
741
- # Check session status first to provide better error messages
742
- if check_status_first:
742
+ if max_retries is None:
743
+ max_retries = self.default_max_retries
744
+
745
+ # Use a longer retry window for model card requests (on-demand generation can take time)
746
+ max_retry_time = 60.0 # 60 seconds for model card generation
747
+ start_time = time.time()
748
+ attempt = 0
749
+
750
+ while True:
751
+ attempt += 1
752
+ elapsed = time.time() - start_time
753
+
743
754
  try:
744
- session_status = self.get_session_status(session_id, max_retries=max_retries)
745
- if session_status.status not in ["done", "DONE"]:
746
- raise ValueError(
747
- f"Session {session_id} is not ready (status: {session_status.status}). "
748
- f"Model card is only available after training completes. "
749
- f"Use wait_for_session_completion() to wait for training to finish."
750
- )
755
+ response = self._make_request(
756
+ "GET",
757
+ f"/session/{session_id}/model_card",
758
+ max_retries=1, # Don't use default retries, handle manually
759
+ max_retry_time=None
760
+ )
761
+
762
+ # Check for 202 Accepted (generation in progress)
763
+ if response.status_code == 202:
764
+ response_data = response.json()
765
+ message = response_data.get("message", "Model card generation in progress")
766
+
767
+ # If we have time left, wait and retry
768
+ if elapsed < max_retry_time and attempt <= max_retries:
769
+ wait_time = min(5.0, max_retry_time - elapsed) # Wait up to 5 seconds
770
+ if wait_time > 0:
771
+ print(f"⏳ Model card generation in progress (attempt {attempt}/{max_retries}), waiting {wait_time:.1f}s...")
772
+ time.sleep(wait_time)
773
+ continue
774
+ else:
775
+ # Out of time or retries
776
+ raise requests.exceptions.HTTPError(
777
+ f"Model card generation timed out after {elapsed:.1f}s. {message}",
778
+ response=response
779
+ )
780
+
781
+ # Success - return the model card
782
+ return response.json()
783
+
751
784
  except requests.exceptions.HTTPError as e:
752
- # If we can't get status, continue and let the model_card request fail
753
- # This handles cases where the session doesn't exist
754
- if e.response.status_code == 404:
755
- raise FileNotFoundError(f"Session {session_id} not found") from e
756
- # For other HTTP errors, continue to try model_card request
757
- pass
758
-
759
- response = self._make_request(
760
- "GET",
761
- f"/session/{session_id}/model_card",
762
- max_retries=max_retries
763
- )
764
- return response.json()
785
+ if e.response is not None and e.response.status_code == 404:
786
+ # 404 - model card doesn't exist
787
+ # Check if we should retry (on-demand generation might be starting)
788
+ if elapsed < max_retry_time and attempt <= max_retries:
789
+ # Check session status to see if it's DONE (model card should exist or be creatable)
790
+ try:
791
+ session_status = self.get_session_status(session_id, max_retries=1)
792
+ if session_status.status in ["done", "DONE"]:
793
+ # Session is done, model card should be creatable - retry after a short delay
794
+ wait_time = min(3.0, max_retry_time - elapsed)
795
+ if wait_time > 0:
796
+ print(f"⏳ Model card not found for DONE session (attempt {attempt}/{max_retries}), retrying in {wait_time:.1f}s (on-demand generation may be starting)...")
797
+ time.sleep(wait_time)
798
+ continue
799
+ except Exception:
800
+ # If we can't check session status, just retry once more
801
+ if attempt <= 2:
802
+ wait_time = min(2.0, max_retry_time - elapsed)
803
+ if wait_time > 0:
804
+ print(f"⏳ Model card not found (attempt {attempt}/{max_retries}), retrying in {wait_time:.1f}s...")
805
+ time.sleep(wait_time)
806
+ continue
807
+
808
+ # Out of retries or session not DONE - raise the 404
809
+ raise FileNotFoundError(f"Model card not found for session {session_id}") from e
810
+ else:
811
+ # Other HTTP errors - re-raise
812
+ raise
765
813
 
766
814
  def publish_session(self, session_id: str) -> Dict[str, Any]:
767
815
  """
@@ -915,7 +963,7 @@ class FeatrixSphereClient:
915
963
 
916
964
  def wait_for_session_completion(self, session_id: str, max_wait_time: int = 3600,
917
965
  check_interval: int = 10, show_live_training_movie: bool = None,
918
- training_interval_movie: int = 3) -> SessionInfo:
966
+ training_interval_movie: int = 3, status_callback: callable = None) -> SessionInfo:
919
967
  """
920
968
  Wait for a session to complete, with smart progress display.
921
969
 
@@ -926,10 +974,16 @@ class FeatrixSphereClient:
926
974
  show_live_training_movie: If True, show live training visualization as epochs progress.
927
975
  If None, auto-enable in notebook environments (default: None)
928
976
  training_interval_movie: Show training movie updates every N epochs (default: 3)
977
+ status_callback: Optional callback function(session_info, elapsed_seconds) called on each status check.
978
+ If provided, uses this instead of default display methods.
929
979
 
930
980
  Returns:
931
981
  Final SessionInfo when session completes or times out
932
982
  """
983
+ # If callback provided, use it instead of display methods
984
+ if status_callback is not None:
985
+ return self._wait_with_callback(session_id, max_wait_time, check_interval, status_callback)
986
+
933
987
  # Auto-enable live training movie in notebooks if not explicitly set
934
988
  if show_live_training_movie is None:
935
989
  show_live_training_movie = self._is_notebook()
@@ -977,6 +1031,14 @@ class FeatrixSphereClient:
977
1031
  def _wait_with_smart_display(self, session_id: str, max_wait_time: int, check_interval: int, show_live_training_movie: bool = False, training_interval_movie: int = 3) -> SessionInfo:
978
1032
  """Smart progress display that adapts to environment."""
979
1033
 
1034
+ # Check if we're in a thread (not main thread) - Rich doesn't support multiple live displays
1035
+ import threading
1036
+ is_main_thread = threading.current_thread() is threading.main_thread()
1037
+
1038
+ # If not in main thread, use simple display to avoid Rich LiveError
1039
+ if not is_main_thread:
1040
+ return self._wait_with_simple_display(session_id, max_wait_time, check_interval)
1041
+
980
1042
  if self._is_notebook():
981
1043
  return self._wait_with_notebook_display(session_id, max_wait_time, check_interval, show_live_training_movie, training_interval_movie)
982
1044
  elif self._has_rich():
@@ -1156,6 +1218,28 @@ class FeatrixSphereClient:
1156
1218
  # Fallback if rich not available
1157
1219
  return self._wait_with_simple_display(session_id, max_wait_time, check_interval)
1158
1220
 
1221
+ def _wait_with_callback(self, session_id: str, max_wait_time: int, check_interval: int, status_callback: callable) -> SessionInfo:
1222
+ """Wait for session completion using a custom callback for status updates."""
1223
+ import time
1224
+
1225
+ start_time = time.time()
1226
+
1227
+ while time.time() - start_time < max_wait_time:
1228
+ session_info = self.get_session_status(session_id)
1229
+ elapsed = time.time() - start_time
1230
+
1231
+ # Call the callback with current status
1232
+ status_callback(session_info, elapsed)
1233
+
1234
+ # Check if completed
1235
+ if session_info.status in ['completed', 'done', 'DONE']:
1236
+ return session_info
1237
+
1238
+ time.sleep(check_interval)
1239
+
1240
+ # Timeout - return final status
1241
+ return self.get_session_status(session_id)
1242
+
1159
1243
  def _wait_with_simple_display(self, session_id: str, max_wait_time: int, check_interval: int) -> SessionInfo:
1160
1244
  """Simple display with line overwriting for basic terminals."""
1161
1245
  import sys
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: featrixsphere
3
- Version: 0.2.1314
3
+ Version: 0.2.1439
4
4
  Summary: Transform any CSV into a production-ready ML model in minutes, not months.
5
5
  Home-page: https://github.com/Featrix/sphere
6
6
  Author: Featrix
@@ -0,0 +1,9 @@
1
+ featrixsphere/__init__.py,sha256=7qEuBqtpDfJZYvEJQAVVrtdL-nZvI74oPx7tcVJN9Jo,1888
2
+ featrixsphere/cli.py,sha256=AW9O3vCvCNJ2UxVGN66eRmeN7XLSiHJlvK6JLZ9UJXc,13358
3
+ featrixsphere/client.py,sha256=NM-dBRANvN0GpcLf0BPqUxpqwMhzHP6hJktD8FndyCM,384646
4
+ featrixsphere/test_client.py,sha256=4SiRbib0ms3poK0UpnUv4G0HFQSzidF3Iswo_J2cjLk,11981
5
+ featrixsphere-0.2.1439.dist-info/METADATA,sha256=_KJ6nguFKv3xMG1kbWWS5_7hN2DjQEa-z3sloLIh5z4,16232
6
+ featrixsphere-0.2.1439.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
7
+ featrixsphere-0.2.1439.dist-info/entry_points.txt,sha256=QreJeYfD_VWvbEqPmMXZ3pqqlFlJ1qZb-NtqnyhEldc,51
8
+ featrixsphere-0.2.1439.dist-info/top_level.txt,sha256=AyN4wjfzlD0hWnDieuEHX0KckphIk_aC73XCG4df5uU,14
9
+ featrixsphere-0.2.1439.dist-info/RECORD,,
@@ -1,9 +0,0 @@
1
- featrixsphere/__init__.py,sha256=S0XWndX6ycMk8j03X_Dt_GpMuCMG2k0uwzoVJ6EbbnA,1888
2
- featrixsphere/cli.py,sha256=AW9O3vCvCNJ2UxVGN66eRmeN7XLSiHJlvK6JLZ9UJXc,13358
3
- featrixsphere/client.py,sha256=c1axFTTB6Hvdu2cWngN0VnkBdU0W0neTDKwzIU-IFXc,380183
4
- featrixsphere/test_client.py,sha256=4SiRbib0ms3poK0UpnUv4G0HFQSzidF3Iswo_J2cjLk,11981
5
- featrixsphere-0.2.1314.dist-info/METADATA,sha256=gu4_0nXC3gk8GfKB-lDbgrZPhdvTQCRcUEIlIMtWy-I,16232
6
- featrixsphere-0.2.1314.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
7
- featrixsphere-0.2.1314.dist-info/entry_points.txt,sha256=QreJeYfD_VWvbEqPmMXZ3pqqlFlJ1qZb-NtqnyhEldc,51
8
- featrixsphere-0.2.1314.dist-info/top_level.txt,sha256=AyN4wjfzlD0hWnDieuEHX0KckphIk_aC73XCG4df5uU,14
9
- featrixsphere-0.2.1314.dist-info/RECORD,,