featrixsphere 0.2.1238__py3-none-any.whl → 0.2.1438__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.1238"
41
+ __version__ = "0.2.1438"
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
@@ -4117,7 +4201,6 @@ class FeatrixSphereClient:
4117
4201
  epochs: int = 0,
4118
4202
  rare_label_value: str = None,
4119
4203
  class_imbalance: dict = None,
4120
- optimize_for: str = "balanced",
4121
4204
  poll_interval: int = 30, max_poll_time: int = 3600,
4122
4205
  verbose: bool = True,
4123
4206
  webhooks: Dict[str, str] = None) -> SessionInfo:
@@ -4140,7 +4223,6 @@ class FeatrixSphereClient:
4140
4223
  epochs: Number of training epochs (default: 0; automatic)
4141
4224
  rare_label_value: For binary classification, which class is the rare/minority class for metrics (default: None)
4142
4225
  class_imbalance: Expected class ratios/counts from real world for sampled data (default: None)
4143
- optimize_for: Optimization target - "balanced" (F1 score), "precision", or "recall" (default: "balanced")
4144
4226
  poll_interval: Seconds between status checks when job is already running (default: 30)
4145
4227
  max_poll_time: Maximum time to poll in seconds (default: 3600 = 1 hour)
4146
4228
  verbose: Whether to print status updates during polling (default: True)
@@ -4178,7 +4260,6 @@ class FeatrixSphereClient:
4178
4260
  "target_column": target_column,
4179
4261
  "target_column_type": target_column_type,
4180
4262
  "epochs": str(epochs),
4181
- "optimize_for": optimize_for,
4182
4263
  }
4183
4264
 
4184
4265
  # Handle file upload - send file directly in multipart form
@@ -4297,7 +4378,6 @@ class FeatrixSphereClient:
4297
4378
  validation_ignore_columns: List[str] = None,
4298
4379
  rare_label_value: str = None,
4299
4380
  class_imbalance: dict = None,
4300
- optimize_for: str = "balanced",
4301
4381
  cost_false_positive: float = None,
4302
4382
  cost_false_negative: float = None,
4303
4383
  poll_interval: int = 30, max_poll_time: int = 3600,
@@ -4477,24 +4557,6 @@ class FeatrixSphereClient:
4477
4557
  This happens automatically - no configuration needed. The system invests a few seconds
4478
4558
  in analysis to deliver significantly better models.
4479
4559
 
4480
- Understanding optimize_for:
4481
- ---------------------------
4482
- The optimize_for parameter controls which loss function and training strategy is used,
4483
- optimizing for different aspects of model performance:
4484
-
4485
- - "balanced" (default): Optimizes for F1 score (harmonic mean of precision and recall).
4486
- Uses FocalLoss with class weights. Best for general-purpose classification where you
4487
- want balanced performance across all classes.
4488
-
4489
- - "precision": Optimizes for precision (minimizing false positives). Uses FocalLoss with
4490
- class weights, which focuses training on hard-to-classify examples. Best when false
4491
- positives are costly (e.g., fraud detection where flagging legitimate transactions
4492
- as fraud is expensive).
4493
-
4494
- - "recall": Optimizes for recall (minimizing false negatives). Uses CrossEntropyLoss
4495
- with class weights that strongly boost the minority class. Best when false negatives
4496
- are costly (e.g., medical diagnosis where missing a disease is dangerous).
4497
-
4498
4560
  Understanding class_imbalance:
4499
4561
  ------------------------------
4500
4562
  For imbalanced datasets where your training data doesn't reflect real-world class
@@ -4615,7 +4677,6 @@ class FeatrixSphereClient:
4615
4677
  target_column='approved',
4616
4678
  target_column_type='set',
4617
4679
  class_imbalance={'approved': 0.97, 'rejected': 0.03},
4618
- optimize_for='recall', # Don't miss rejections
4619
4680
  rare_label_value='rejected'
4620
4681
  )
4621
4682
 
@@ -4636,7 +4697,6 @@ class FeatrixSphereClient:
4636
4697
  target_column='is_fraud',
4637
4698
  target_column_type='set',
4638
4699
  rare_label_value='fraud',
4639
- optimize_for='precision', # Minimize false alarms
4640
4700
  class_imbalance={'legitimate': 0.999, 'fraud': 0.001}
4641
4701
  )
4642
4702
  ```
@@ -4650,8 +4710,7 @@ class FeatrixSphereClient:
4650
4710
  session_id=session.session_id,
4651
4711
  target_column='has_disease',
4652
4712
  target_column_type='set',
4653
- rare_label_value='positive',
4654
- optimize_for='recall' # Don't miss any cases
4713
+ rare_label_value='positive'
4655
4714
  )
4656
4715
  ```
4657
4716
 
@@ -4693,14 +4752,6 @@ class FeatrixSphereClient:
4693
4752
  validation_ignore_columns: List of column names to exclude from validation queries (default: None)
4694
4753
  rare_label_value: For binary classification, which class is the rare/minority class for metrics (default: None)
4695
4754
  class_imbalance: Expected class ratios/counts from real world for sampled data (default: None)
4696
- optimize_for: Optimization target - "balanced" (F1 score), "precision", or "recall" (default: "balanced").
4697
- Ignored if cost_false_positive and cost_false_negative are provided.
4698
- cost_false_positive: Cost of a false positive (predicting positive when actually negative).
4699
- Must be specified together with cost_false_negative. Only valid for target_column_type="set".
4700
- When provided, overrides optimize_for and uses cost-based optimization.
4701
- cost_false_negative: Cost of a false negative (predicting negative when actually positive).
4702
- Must be specified together with cost_false_positive. Only valid for target_column_type="set".
4703
- When provided, overrides optimize_for and uses cost-based optimization.
4704
4755
  poll_interval: Seconds between status checks when job is already running (default: 30)
4705
4756
  max_poll_time: Maximum time to poll in seconds (default: 3600 = 1 hour)
4706
4757
  verbose: Whether to print status updates during polling (default: True)
@@ -4727,7 +4778,6 @@ class FeatrixSphereClient:
4727
4778
  raise ValueError("cost_false_positive and cost_false_negative must be positive numbers")
4728
4779
  if verbose:
4729
4780
  print(f"💰 Cost-based optimization enabled: FP cost={cost_false_positive}, FN cost={cost_false_negative}")
4730
- print(f" (optimize_for='{optimize_for}' will be ignored)")
4731
4781
 
4732
4782
  # If DataFrame provided, save to temp file and use file_path logic
4733
4783
  temp_file = None
@@ -4761,7 +4811,6 @@ class FeatrixSphereClient:
4761
4811
  epochs=epochs,
4762
4812
  rare_label_value=rare_label_value,
4763
4813
  class_imbalance=class_imbalance,
4764
- optimize_for=optimize_for,
4765
4814
  cost_false_positive=cost_false_positive,
4766
4815
  cost_false_negative=cost_false_negative,
4767
4816
  verbose=verbose,
@@ -4775,8 +4824,7 @@ class FeatrixSphereClient:
4775
4824
  "epochs": epochs,
4776
4825
  "validation_ignore_columns": validation_ignore_columns or [],
4777
4826
  "rare_label_value": rare_label_value,
4778
- "class_imbalance": class_imbalance,
4779
- "optimize_for": optimize_for
4827
+ "class_imbalance": class_imbalance
4780
4828
  }
4781
4829
  if cost_false_positive is not None and cost_false_negative is not None:
4782
4830
  data["cost_false_positive"] = cost_false_positive
@@ -5353,7 +5401,6 @@ class FeatrixSphereClient:
5353
5401
  epochs: int,
5354
5402
  rare_label_value: str,
5355
5403
  class_imbalance: dict,
5356
- optimize_for: str,
5357
5404
  cost_false_positive: float = None,
5358
5405
  cost_false_negative: float = None,
5359
5406
  verbose: bool = True,
@@ -5381,8 +5428,7 @@ class FeatrixSphereClient:
5381
5428
  data = {
5382
5429
  'target_column': target_column,
5383
5430
  'target_column_type': target_column_type,
5384
- 'epochs': str(epochs),
5385
- 'optimize_for': optimize_for,
5431
+ 'epochs': str(epochs)
5386
5432
  }
5387
5433
 
5388
5434
  if rare_label_value:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: featrixsphere
3
- Version: 0.2.1238
3
+ Version: 0.2.1438
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=0gIgPqNrcFK35jlMQqSLQg9TqBwaiNU_PhZ4dQ3JOsw,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.1438.dist-info/METADATA,sha256=tNsudbOj5N1zFcjNA2iHZf6WJa4jxz1_MfhctYs8-qI,16232
6
+ featrixsphere-0.2.1438.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
7
+ featrixsphere-0.2.1438.dist-info/entry_points.txt,sha256=QreJeYfD_VWvbEqPmMXZ3pqqlFlJ1qZb-NtqnyhEldc,51
8
+ featrixsphere-0.2.1438.dist-info/top_level.txt,sha256=AyN4wjfzlD0hWnDieuEHX0KckphIk_aC73XCG4df5uU,14
9
+ featrixsphere-0.2.1438.dist-info/RECORD,,
@@ -1,9 +0,0 @@
1
- featrixsphere/__init__.py,sha256=-zD2ZilYZFjYDB5rwnqr0jYNDB9zkO-hRgiD0LiXUGM,1888
2
- featrixsphere/cli.py,sha256=AW9O3vCvCNJ2UxVGN66eRmeN7XLSiHJlvK6JLZ9UJXc,13358
3
- featrixsphere/client.py,sha256=9GmwNP8Z4-HCQWEBDIXp54rbymRtJqwmBeQZZu5g27E,382816
4
- featrixsphere/test_client.py,sha256=4SiRbib0ms3poK0UpnUv4G0HFQSzidF3Iswo_J2cjLk,11981
5
- featrixsphere-0.2.1238.dist-info/METADATA,sha256=ILxoFLdu7zaEt1RzGAoCX_QGKMRCzv9hcB1zGr6_9FM,16232
6
- featrixsphere-0.2.1238.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
7
- featrixsphere-0.2.1238.dist-info/entry_points.txt,sha256=QreJeYfD_VWvbEqPmMXZ3pqqlFlJ1qZb-NtqnyhEldc,51
8
- featrixsphere-0.2.1238.dist-info/top_level.txt,sha256=AyN4wjfzlD0hWnDieuEHX0KckphIk_aC73XCG4df5uU,14
9
- featrixsphere-0.2.1238.dist-info/RECORD,,