featrixsphere 0.2.1206__py3-none-any.whl → 0.2.1229__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.1206"
41
+ __version__ = "0.2.1229"
42
42
  __author__ = "Featrix"
43
43
  __email__ = "support@featrix.com"
44
44
  __license__ = "MIT"
featrixsphere/client.py CHANGED
@@ -633,13 +633,97 @@ class FeatrixSphereClient:
633
633
  _client=self
634
634
  )
635
635
 
636
- def get_model_card(self, session_id: str, max_retries: int = None) -> Dict[str, Any]:
636
+ def update_user_metadata(self, session_id: str, metadata: Dict[str, Any], write_mode: str = "merge") -> Dict[str, Any]:
637
+ """
638
+ Update user metadata for a session.
639
+
640
+ Args:
641
+ session_id: The session ID to update metadata for
642
+ metadata: Dictionary of metadata to update (max 32KB total)
643
+ write_mode: How to update metadata:
644
+ - "merge" (default): Merge new metadata with existing (existing keys are updated, new keys are added)
645
+ - "overwrite": Replace all user_metadata with the new dictionary
646
+
647
+ Returns:
648
+ Dictionary containing the updated session information
649
+
650
+ Raises:
651
+ requests.exceptions.HTTPError: If the request fails
652
+ ValueError: If write_mode is not "merge" or "overwrite"
653
+
654
+ Example:
655
+ >>> # Merge new metadata with existing
656
+ >>> client.update_user_metadata(
657
+ ... session_id="abc123",
658
+ ... metadata={"new_key": "value", "existing_key": "updated_value"},
659
+ ... write_mode="merge"
660
+ ... )
661
+
662
+ >>> # Replace all metadata
663
+ >>> client.update_user_metadata(
664
+ ... session_id="abc123",
665
+ ... metadata={"only_key": "only_value"},
666
+ ... write_mode="overwrite"
667
+ ... )
668
+ """
669
+ if write_mode not in ["merge", "overwrite"]:
670
+ raise ValueError(f"write_mode must be 'merge' or 'overwrite', got '{write_mode}'")
671
+
672
+ request_data = {
673
+ "user_metadata": metadata,
674
+ "write_mode": write_mode
675
+ }
676
+
677
+ response_data = self._post_json(f"/session/{session_id}/update_user_metadata", request_data)
678
+ return response_data
679
+
680
+ def is_foundation_model_ready(self, session_id: str, max_retries: int = None) -> Tuple[bool, str]:
681
+ """
682
+ Check if a foundation model session is ready to use (training completed).
683
+
684
+ Args:
685
+ session_id: The session ID to check
686
+ max_retries: Maximum number of retries (defaults to client default)
687
+
688
+ Returns:
689
+ Tuple of (is_ready: bool, status_message: str)
690
+ - is_ready: True if session is done and model card is available
691
+ - status_message: Human-readable status message
692
+
693
+ Example:
694
+ >>> is_ready, message = client.is_foundation_model_ready("session_123")
695
+ >>> if not is_ready:
696
+ ... print(f"Foundation model not ready: {message}")
697
+ """
698
+ try:
699
+ session_status = self.get_session_status(session_id, max_retries=max_retries)
700
+
701
+ if session_status.status in ["done", "DONE"]:
702
+ # Check if model card exists
703
+ try:
704
+ self.get_model_card(session_id, max_retries=max_retries, check_status_first=False)
705
+ return True, "Foundation model is ready"
706
+ except (requests.exceptions.HTTPError, FileNotFoundError):
707
+ return False, "Session is done but model card is not available yet"
708
+ else:
709
+ return False, f"Session is still {session_status.status}. Training may still be in progress."
710
+
711
+ except requests.exceptions.HTTPError as e:
712
+ if e.response.status_code == 404:
713
+ return False, f"Session {session_id} not found"
714
+ return False, f"Error checking session status: {e}"
715
+ except Exception as e:
716
+ return False, f"Error checking foundation model: {e}"
717
+
718
+ def get_model_card(self, session_id: str, max_retries: int = None, check_status_first: bool = True) -> Dict[str, Any]:
637
719
  """
638
720
  Get the model card JSON for a given session.
639
721
 
640
722
  Args:
641
723
  session_id: The session ID to get the model card for
642
724
  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.
643
727
 
644
728
  Returns:
645
729
  Dictionary containing the model card JSON data
@@ -647,12 +731,31 @@ class FeatrixSphereClient:
647
731
  Raises:
648
732
  requests.exceptions.HTTPError: If the request fails
649
733
  FileNotFoundError: If the model card doesn't exist (404)
734
+ ValueError: If session is not ready and check_status_first is True
650
735
 
651
736
  Example:
652
737
  >>> client = FeatrixSphereClient()
653
738
  >>> model_card = client.get_model_card("session_123")
654
739
  >>> print(model_card["model_details"]["name"])
655
740
  """
741
+ # Check session status first to provide better error messages
742
+ if check_status_first:
743
+ 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
+ )
751
+ 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
+
656
759
  response = self._make_request(
657
760
  "GET",
658
761
  f"/session/{session_id}/model_card",
@@ -774,8 +877,8 @@ class FeatrixSphereClient:
774
877
  >>> print(f"Model card recreated: {model_card['model_info']['name']}")
775
878
  """
776
879
  response = self._make_request(
777
- "POST",
778
- f"/session/{session_id}/model_card",
880
+ "GET",
881
+ f"/compute/session/{session_id}/model_card",
779
882
  max_retries=max_retries
780
883
  )
781
884
  return response.json()
@@ -4008,6 +4111,7 @@ class FeatrixSphereClient:
4008
4111
 
4009
4112
  def train_on_foundational_model(self, foundation_model_id: str, target_column: str, target_column_type: str,
4010
4113
  input_filename: str = None,
4114
+ df = None,
4011
4115
  name: str = None,
4012
4116
  session_name_prefix: str = None,
4013
4117
  epochs: int = 0, batch_size: int = 0, learning_rate: float = 0.001,
@@ -4029,6 +4133,8 @@ class FeatrixSphereClient:
4029
4133
  target_column: Name of the target column to predict
4030
4134
  target_column_type: Type of target column ("set" or "scalar")
4031
4135
  input_filename: Optional input data file (uses foundation model's data if not provided)
4136
+ df: Optional pandas DataFrame with training data (uses foundation model's data if not provided).
4137
+ Use input_filename OR df (not both) to train predictor on different data than the foundation model.
4032
4138
  name: Optional name for the new session
4033
4139
  session_name_prefix: Optional prefix for session ID. Session will be named <prefix>-<uuid>
4034
4140
  epochs: Number of training epochs (default: 0; automatic)
@@ -4048,6 +4154,48 @@ class FeatrixSphereClient:
4048
4154
  print(f"Training predictor on foundation model {foundation_model_id}...")
4049
4155
  print(f" Target: {target_column} ({target_column_type})")
4050
4156
 
4157
+ # Validate that only one data source is provided
4158
+ if input_filename and df is not None:
4159
+ raise ValueError("Provide either input_filename or df, not both")
4160
+
4161
+ # If DataFrame provided, save to temp file and upload it
4162
+ temp_file = None
4163
+ if df is not None:
4164
+ import pandas as pd
4165
+ import tempfile
4166
+ import os
4167
+
4168
+ if not isinstance(df, pd.DataFrame):
4169
+ raise ValueError("df must be a pandas DataFrame")
4170
+
4171
+ if verbose:
4172
+ print(f"📊 Using provided DataFrame ({len(df)} rows, {len(df.columns)} columns)")
4173
+
4174
+ # Create temporary CSV file
4175
+ temp_file = tempfile.NamedTemporaryFile(mode='w', suffix='.csv', delete=False)
4176
+ temp_file_path = temp_file.name
4177
+ temp_file.close()
4178
+
4179
+ # Save DataFrame to temp file
4180
+ df.to_csv(temp_file_path, index=False)
4181
+
4182
+ if verbose:
4183
+ print(f"📁 Saved to temporary file: {os.path.basename(temp_file_path)}")
4184
+ print(f"📤 Uploading file to server...")
4185
+
4186
+ # Upload the file
4187
+ uploaded_filename = self.upload_file(temp_file_path)
4188
+ input_filename = uploaded_filename
4189
+
4190
+ if verbose:
4191
+ print(f"✅ File uploaded: {input_filename}")
4192
+
4193
+ # Clean up temp file
4194
+ try:
4195
+ os.unlink(temp_file_path)
4196
+ except Exception:
4197
+ pass # Ignore cleanup errors
4198
+
4051
4199
  data = {
4052
4200
  "foundation_model_id": foundation_model_id,
4053
4201
  "target_column": target_column,
@@ -4059,17 +4207,26 @@ class FeatrixSphereClient:
4059
4207
  }
4060
4208
 
4061
4209
  if input_filename:
4062
- # Clean up input_filename: extract just the filename if it's an absolute path
4063
- # The file should be uploaded first or already exist on the server
4210
+ # If absolute path provided, upload the file first
4064
4211
  from pathlib import Path
4065
4212
  input_path = Path(input_filename)
4066
4213
  if input_path.is_absolute():
4067
- # Extract just the filename - client should upload file first
4068
- cleaned_filename = input_path.name
4069
- print(f"⚠️ Note: Extracted filename '{cleaned_filename}' from absolute path '{input_filename}'")
4070
- print(f" Make sure the file has been uploaded to the server first")
4071
- data["input_filename"] = cleaned_filename
4214
+ # Upload the file first, then use the uploaded filename
4215
+ if not input_path.exists():
4216
+ raise FileNotFoundError(f"Input file not found: {input_filename}")
4217
+
4218
+ if verbose:
4219
+ print(f"📤 Uploading file from absolute path: {input_filename}")
4220
+
4221
+ # Upload the file
4222
+ uploaded_filename = self.upload_file(str(input_path))
4223
+
4224
+ if verbose:
4225
+ print(f"✅ File uploaded as: {uploaded_filename}")
4226
+
4227
+ data["input_filename"] = uploaded_filename
4072
4228
  else:
4229
+ # Relative filename - assume it's already on the server
4073
4230
  data["input_filename"] = input_filename
4074
4231
  if name:
4075
4232
  data["name"] = name
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: featrixsphere
3
- Version: 0.2.1206
3
+ Version: 0.2.1229
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=DEdcEduSBsw68Ph6BuxpHiBXauP0gGVZUk4w5krgwU4,1888
2
+ featrixsphere/cli.py,sha256=AW9O3vCvCNJ2UxVGN66eRmeN7XLSiHJlvK6JLZ9UJXc,13358
3
+ featrixsphere/client.py,sha256=y5x_zTSryHMy-R7pleySQnkpdoztcSxq8D2cdtwNLpM,381173
4
+ featrixsphere/test_client.py,sha256=4SiRbib0ms3poK0UpnUv4G0HFQSzidF3Iswo_J2cjLk,11981
5
+ featrixsphere-0.2.1229.dist-info/METADATA,sha256=Y5AwjOmojP46zRuFAx9VRYksJcMaZ08lSHTqHloVZPk,16232
6
+ featrixsphere-0.2.1229.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
7
+ featrixsphere-0.2.1229.dist-info/entry_points.txt,sha256=QreJeYfD_VWvbEqPmMXZ3pqqlFlJ1qZb-NtqnyhEldc,51
8
+ featrixsphere-0.2.1229.dist-info/top_level.txt,sha256=AyN4wjfzlD0hWnDieuEHX0KckphIk_aC73XCG4df5uU,14
9
+ featrixsphere-0.2.1229.dist-info/RECORD,,
@@ -1,9 +0,0 @@
1
- featrixsphere/__init__.py,sha256=6sjz6sAZEsBAotAiefePXdOHnoBmovpFqVy4vq4wONE,1888
2
- featrixsphere/cli.py,sha256=AW9O3vCvCNJ2UxVGN66eRmeN7XLSiHJlvK6JLZ9UJXc,13358
3
- featrixsphere/client.py,sha256=C9b_x2aRrGJJI0UCN2GtAdoSjIsMzAR9EKGbJvAzzPE,374039
4
- featrixsphere/test_client.py,sha256=4SiRbib0ms3poK0UpnUv4G0HFQSzidF3Iswo_J2cjLk,11981
5
- featrixsphere-0.2.1206.dist-info/METADATA,sha256=SjuRakp3SS59KnY4N7r65ZnlLp7Lg0O1DAq_NgQZ1fo,16232
6
- featrixsphere-0.2.1206.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
7
- featrixsphere-0.2.1206.dist-info/entry_points.txt,sha256=QreJeYfD_VWvbEqPmMXZ3pqqlFlJ1qZb-NtqnyhEldc,51
8
- featrixsphere-0.2.1206.dist-info/top_level.txt,sha256=AyN4wjfzlD0hWnDieuEHX0KckphIk_aC73XCG4df5uU,14
9
- featrixsphere-0.2.1206.dist-info/RECORD,,