featrixsphere 0.2.6708__py3-none-any.whl → 0.2.6710__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
@@ -57,7 +57,7 @@ TWO API OPTIONS:
57
57
  >>> print(result['prediction'])
58
58
  """
59
59
 
60
- __version__ = "0.2.6708"
60
+ __version__ = "0.2.6710"
61
61
  __author__ = "Featrix"
62
62
  __email__ = "support@featrix.com"
63
63
  __license__ = "MIT"
@@ -342,6 +342,34 @@ class FeatrixSphere(HTTPClientMixin):
342
342
  ground_truth=ground_truth
343
343
  )
344
344
 
345
+ def list_sessions(
346
+ self,
347
+ name_prefix: str = "",
348
+ ) -> List[str]:
349
+ """
350
+ List sessions matching a name prefix/search term.
351
+
352
+ Searches session directory names on the compute cluster for
353
+ partial matches (not just prefix).
354
+
355
+ Args:
356
+ name_prefix: Term to match in session names
357
+
358
+ Returns:
359
+ List of matching session ID strings
360
+
361
+ Example:
362
+ sessions = featrix.list_sessions(name_prefix="customer")
363
+ for sid in sessions:
364
+ fm = featrix.foundational_model(sid)
365
+ print(f"{sid}: {fm.status}")
366
+ """
367
+ params = {}
368
+ if name_prefix:
369
+ params['name_prefix'] = name_prefix
370
+ response = self._get_json("/compute/sessions-for-org", params=params)
371
+ return response.get('sessions', [])
372
+
345
373
  def health_check(self) -> Dict[str, Any]:
346
374
  """
347
375
  Check if the API server is healthy.
@@ -22,6 +22,21 @@ from .reference_record import ReferenceRecord
22
22
  logger = logging.getLogger(__name__)
23
23
 
24
24
 
25
+ def _parse_datetime(value) -> Optional[datetime]:
26
+ """Parse a datetime from ISO string or return as-is if already datetime."""
27
+ if value is None:
28
+ return None
29
+ if isinstance(value, datetime):
30
+ return value
31
+ if isinstance(value, str):
32
+ try:
33
+ # Handle ISO format with or without timezone
34
+ return datetime.fromisoformat(value.replace('Z', '+00:00'))
35
+ except (ValueError, AttributeError):
36
+ return None
37
+ return None
38
+
39
+
25
40
  @dataclass
26
41
  class FoundationalModel:
27
42
  """
@@ -68,6 +83,11 @@ class FoundationalModel:
68
83
  epochs: Optional[int] = None
69
84
  final_loss: Optional[float] = None
70
85
  created_at: Optional[datetime] = None
86
+ updated_at: Optional[datetime] = None
87
+ session_type: Optional[str] = None
88
+ compute_cluster: Optional[str] = None
89
+ error_message: Optional[str] = None
90
+ training_progress: Optional[Dict[str, Any]] = None
71
91
 
72
92
  # Internal
73
93
  _ctx: Optional['ClientContext'] = field(default=None, repr=False)
@@ -88,7 +108,9 @@ class FoundationalModel:
88
108
  dimensions=response.get('d_model') or response.get('dimensions'),
89
109
  epochs=response.get('epochs') or response.get('final_epoch'),
90
110
  final_loss=response.get('final_loss'),
91
- created_at=datetime.now(),
111
+ created_at=_parse_datetime(response.get('created_at')),
112
+ session_type=response.get('session_type'),
113
+ compute_cluster=response.get('compute_cluster'),
92
114
  _ctx=ctx,
93
115
  )
94
116
 
@@ -99,19 +121,22 @@ class FoundationalModel:
99
121
  ctx: 'ClientContext'
100
122
  ) -> 'FoundationalModel':
101
123
  """Load FoundationalModel from session ID."""
102
- # Get session info
103
- session_data = ctx.get_json(f"/compute/session/{session_id}")
124
+ # Get session info - response has {"session": {...}, "jobs": {...}}
125
+ response_data = ctx.get_json(f"/compute/session/{session_id}")
126
+ session = response_data.get('session', response_data)
104
127
 
105
128
  fm = cls(
106
129
  id=session_id,
107
- name=session_data.get('name'),
108
- status=session_data.get('status'),
109
- created_at=datetime.now(),
130
+ name=session.get('name'),
131
+ status=session.get('status'),
132
+ created_at=_parse_datetime(session.get('created_at')),
133
+ session_type=session.get('session_type'),
134
+ compute_cluster=session.get('compute_cluster'),
110
135
  _ctx=ctx,
111
136
  )
112
137
 
113
- # Try to get model info
114
- fm._update_from_session(session_data)
138
+ # Extract model info, training stats, jobs, error_message
139
+ fm._update_from_session(response_data)
115
140
 
116
141
  return fm
117
142
 
@@ -439,10 +464,11 @@ class FoundationalModel:
439
464
  last_status = None
440
465
 
441
466
  while time.time() - start_time < max_wait_time:
442
- # Get session status
443
- session_data = self._ctx.get_json(f"/compute/session/{self.id}")
467
+ # Get session status - response has {"session": {...}, "jobs": {...}}
468
+ response_data = self._ctx.get_json(f"/compute/session/{self.id}")
469
+ session_data = response_data.get('session', response_data)
444
470
  status = session_data.get('status', 'unknown')
445
- jobs = session_data.get('jobs', {})
471
+ jobs = response_data.get('jobs', {})
446
472
 
447
473
  # Look for ES training job
448
474
  es_job = None
@@ -475,7 +501,7 @@ class FoundationalModel:
475
501
  # Check completion
476
502
  if job_status == 'done' or status == 'done':
477
503
  self.status = 'done'
478
- self._update_from_session(session_data)
504
+ self._update_from_session(response_data)
479
505
  if show_progress:
480
506
  print(f"Training complete!")
481
507
  if self.dimensions:
@@ -627,27 +653,73 @@ class FoundationalModel:
627
653
 
628
654
  return predictors
629
655
 
630
- def _update_from_session(self, session_data: Dict[str, Any]) -> None:
631
- """Update fields from session data."""
632
- # Try to get model info from various places
633
- model_info = session_data.get('model_info', {})
634
- training_stats = session_data.get('training_stats', {})
656
+ def _update_from_session(self, response_data: Dict[str, Any]) -> None:
657
+ """Update fields from session API response.
658
+
659
+ The response from GET /session/{id} has structure:
660
+ {"session": {...}, "jobs": {...}, ...}
661
+ """
662
+ # Handle both nested and flat response formats
663
+ session = response_data.get('session', response_data)
664
+ jobs = response_data.get('jobs', {})
665
+
666
+ # Core session fields
667
+ if session.get('name') and not self.name:
668
+ self.name = session['name']
669
+ if session.get('status'):
670
+ self.status = session['status']
671
+ if session.get('session_type'):
672
+ self.session_type = session['session_type']
673
+ if session.get('compute_cluster'):
674
+ self.compute_cluster = session['compute_cluster']
675
+ if session.get('created_at') and not self.created_at:
676
+ self.created_at = _parse_datetime(session['created_at'])
677
+ if session.get('finished_at'):
678
+ self.updated_at = _parse_datetime(session['finished_at'])
679
+ elif session.get('started_at'):
680
+ self.updated_at = _parse_datetime(session['started_at'])
681
+
682
+ # Model info from session
683
+ model_info = session.get('model_info', {})
684
+ training_stats = session.get('training_stats', {})
635
685
 
636
686
  self.dimensions = (
637
687
  model_info.get('d_model') or
638
688
  model_info.get('embedding_dim') or
639
- session_data.get('d_model')
689
+ session.get('d_model')
640
690
  )
641
691
  self.epochs = (
642
692
  training_stats.get('final_epoch') or
643
693
  training_stats.get('epochs_trained') or
644
- session_data.get('epochs')
694
+ session.get('epochs')
645
695
  )
646
696
  self.final_loss = (
647
697
  training_stats.get('final_loss') or
648
- session_data.get('final_loss')
698
+ session.get('final_loss')
649
699
  )
650
700
 
701
+ # Extract error_message and training_progress from jobs
702
+ for job_id, job in jobs.items():
703
+ job_type = job.get('job_type', '')
704
+ job_status = job.get('status', '')
705
+
706
+ # Training progress from ES training job
707
+ if job_type in ('train_embedding_space', 'train_es', 'training'):
708
+ current_epoch = job.get('current_epoch') or job.get('epoch')
709
+ total_epochs = job.get('total_epochs') or job.get('epochs')
710
+ if current_epoch or total_epochs:
711
+ self.training_progress = {
712
+ 'current_epoch': current_epoch,
713
+ 'total_epochs': total_epochs,
714
+ 'job_status': job_status,
715
+ }
716
+
717
+ # Error message from any failed job
718
+ if job_status in ('failed', 'error'):
719
+ err = job.get('error') or job.get('error_message')
720
+ if err:
721
+ self.error_message = err
722
+
651
723
  def _clean_record(self, record: Dict[str, Any]) -> Dict[str, Any]:
652
724
  """Clean a record for API submission."""
653
725
  import math
@@ -684,6 +756,17 @@ class FoundationalModel:
684
756
  """Column names in this foundational model's embedding space."""
685
757
  return self.get_columns()
686
758
 
759
+ @property
760
+ def schema_metadata(self) -> Dict[str, Any]:
761
+ """Get schema metadata including column names and types.
762
+
763
+ Returns:
764
+ Dict with 'column_names', 'column_types', and 'num_columns'
765
+ """
766
+ if not self._ctx:
767
+ raise ValueError("FoundationalModel not connected to client")
768
+ return self._ctx.get_json(f"/compute/session/{self.id}/columns")
769
+
687
770
  def clone(
688
771
  self,
689
772
  target_compute_cluster: Optional[str] = None,
@@ -749,7 +832,6 @@ class FoundationalModel:
749
832
 
750
833
  data = self._ctx.get_json(f"/compute/session/{self.id}")
751
834
  self._update_from_session(data)
752
- self.status = data.get('status', self.status)
753
835
  return data
754
836
 
755
837
  def is_ready(self) -> bool:
@@ -767,9 +849,8 @@ class FoundationalModel:
767
849
  raise ValueError("FoundationalModel not connected to client")
768
850
 
769
851
  data = self._ctx.get_json(f"/compute/session/{self.id}")
770
- status = data.get('status', 'unknown')
771
- self.status = status
772
- return status == 'done'
852
+ self._update_from_session(data)
853
+ return self.status == 'done'
773
854
 
774
855
  def publish(
775
856
  self,
@@ -938,6 +1019,11 @@ class FoundationalModel:
938
1019
  'epochs': self.epochs,
939
1020
  'final_loss': self.final_loss,
940
1021
  'created_at': self.created_at.isoformat() if self.created_at else None,
1022
+ 'updated_at': self.updated_at.isoformat() if self.updated_at else None,
1023
+ 'session_type': self.session_type,
1024
+ 'compute_cluster': self.compute_cluster,
1025
+ 'error_message': self.error_message,
1026
+ 'training_progress': self.training_progress,
941
1027
  }
942
1028
 
943
1029
  def __repr__(self) -> str:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: featrixsphere
3
- Version: 0.2.6708
3
+ Version: 0.2.6710
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
@@ -1,17 +1,17 @@
1
- featrixsphere/__init__.py,sha256=NPVY3_tG74OqiLz1MYbrb9R4Wff68xFCI6zr3gVRTyQ,2190
1
+ featrixsphere/__init__.py,sha256=QQglKOYv0bjonuO0-wOkeyyXMWhv3yK0_s5Uaap-GVk,2190
2
2
  featrixsphere/client.py,sha256=JNYAHDFtxmhQVuclO3dWEphJuxNFLi-PfvWylZWKF_4,452929
3
3
  featrixsphere/api/__init__.py,sha256=quyvuPphVj9wb6v8Dio0SMG9iHgJAmY3asHk3f_zF10,1269
4
4
  featrixsphere/api/api_endpoint.py,sha256=i3eCWuaUXftnH1Ai6MFZ7md7pC2FcRAIRO87CBZhyEQ,9000
5
- featrixsphere/api/client.py,sha256=TdpujNsJxO4GfPMI_KoemQWV89go3KuK6OPAo9jX6Bs,12574
6
- featrixsphere/api/foundational_model.py,sha256=wf5-VvVUXYoiyKr3y4Ok8OnwMhtaUuYLXwdgCwFuM-k,31065
5
+ featrixsphere/api/client.py,sha256=TvNqrzSPQdw0A4kW48M0S3SDrBRmkc6kTY8UkzO4eRs,13426
6
+ featrixsphere/api/foundational_model.py,sha256=0ZFO-mJs66nVRXQbM0o1fB4HmhzLBXUqbTCF46LVH1k,34925
7
7
  featrixsphere/api/http_client.py,sha256=q59-41fHua_7AwtPFCvshlSUKJ-fS0X337L9Ooyn0DI,8440
8
8
  featrixsphere/api/notebook_helper.py,sha256=xY9jsao26eaNiFh2s0_TlRZnR8xZ4P_e0EOKr2PtoVs,20060
9
9
  featrixsphere/api/prediction_result.py,sha256=HQsJdr89zWxdRx395nevN3aP7ZXZuZxB4UGX5Ykhkfk,12235
10
10
  featrixsphere/api/predictor.py,sha256=1v0ffkEjmrO3BP0PNWAXtiAU-AlOQJSiDICmW1bQbGU,20300
11
11
  featrixsphere/api/reference_record.py,sha256=-XOTF6ynznB3ouz06w3AF8X9SVId0g_dO20VvGNesUQ,7095
12
12
  featrixsphere/api/vector_database.py,sha256=BplxKkPnAbcBX1A4KxFBJVb3qkQ-FH9zi9v2dWG5CgY,7976
13
- featrixsphere-0.2.6708.dist-info/METADATA,sha256=IpNh40w32ZlavhKaRkBFRLvpXyXmoNddYMjbc2NpBGI,16232
14
- featrixsphere-0.2.6708.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
15
- featrixsphere-0.2.6708.dist-info/entry_points.txt,sha256=QreJeYfD_VWvbEqPmMXZ3pqqlFlJ1qZb-NtqnyhEldc,51
16
- featrixsphere-0.2.6708.dist-info/top_level.txt,sha256=AyN4wjfzlD0hWnDieuEHX0KckphIk_aC73XCG4df5uU,14
17
- featrixsphere-0.2.6708.dist-info/RECORD,,
13
+ featrixsphere-0.2.6710.dist-info/METADATA,sha256=_gDwyRsSfEa0thWd6IjhMeCEZF5dMc8BfLBL4J2b5HQ,16232
14
+ featrixsphere-0.2.6710.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
15
+ featrixsphere-0.2.6710.dist-info/entry_points.txt,sha256=QreJeYfD_VWvbEqPmMXZ3pqqlFlJ1qZb-NtqnyhEldc,51
16
+ featrixsphere-0.2.6710.dist-info/top_level.txt,sha256=AyN4wjfzlD0hWnDieuEHX0KckphIk_aC73XCG4df5uU,14
17
+ featrixsphere-0.2.6710.dist-info/RECORD,,