howso-engine 65.2.1__tar.gz → 65.3.0__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 (113) hide show
  1. {howso_engine-65.2.1/howso_engine.egg-info → howso_engine-65.3.0}/PKG-INFO +1 -1
  2. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/utilities/__init__.py +4 -0
  3. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/utilities/feature_attributes/abstract_data.py +1 -5
  4. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/utilities/feature_attributes/base.py +20 -0
  5. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/utilities/feature_attributes/infer_feature_attributes.py +2 -1
  6. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/utilities/feature_attributes/pandas.py +2 -7
  7. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/utilities/feature_attributes/suggestions.py +72 -0
  8. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/utilities/feature_attributes/time_series.py +6 -0
  9. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/utilities/progress.py +106 -15
  10. {howso_engine-65.2.1 → howso_engine-65.3.0/howso_engine.egg-info}/PKG-INFO +1 -1
  11. {howso_engine-65.2.1 → howso_engine-65.3.0}/.coveragerc +0 -0
  12. {howso_engine-65.2.1 → howso_engine-65.3.0}/.flake8 +0 -0
  13. {howso_engine-65.2.1 → howso_engine-65.3.0}/.github/CODEOWNERS +0 -0
  14. {howso_engine-65.2.1 → howso_engine-65.3.0}/.github/templates/version_summary.md +0 -0
  15. {howso_engine-65.2.1 → howso_engine-65.3.0}/.github/workflows/build-pr.yml +0 -0
  16. {howso_engine-65.2.1 → howso_engine-65.3.0}/.github/workflows/build-release.yml +0 -0
  17. {howso_engine-65.2.1 → howso_engine-65.3.0}/.github/workflows/build.yml +0 -0
  18. {howso_engine-65.2.1 → howso_engine-65.3.0}/.github/workflows/rebuild-requirements.yml +0 -0
  19. {howso_engine-65.2.1 → howso_engine-65.3.0}/.github/workflows/version-update.yml +0 -0
  20. {howso_engine-65.2.1 → howso_engine-65.3.0}/.gitignore +0 -0
  21. {howso_engine-65.2.1 → howso_engine-65.3.0}/.gitmodules +0 -0
  22. {howso_engine-65.2.1 → howso_engine-65.3.0}/.pylintrc +0 -0
  23. {howso_engine-65.2.1 → howso_engine-65.3.0}/CONTRIBUTING.md +0 -0
  24. {howso_engine-65.2.1 → howso_engine-65.3.0}/LICENSE-3RD-PARTY.txt +0 -0
  25. {howso_engine-65.2.1 → howso_engine-65.3.0}/LICENSE.txt +0 -0
  26. {howso_engine-65.2.1 → howso_engine-65.3.0}/MANIFEST.in +0 -0
  27. {howso_engine-65.2.1 → howso_engine-65.3.0}/Makefile +0 -0
  28. {howso_engine-65.2.1 → howso_engine-65.3.0}/README.md +0 -0
  29. {howso_engine-65.2.1 → howso_engine-65.3.0}/bin/build.sh +0 -0
  30. {howso_engine-65.2.1 → howso_engine-65.3.0}/config/latest-mt-debug-howso.yml +0 -0
  31. {howso_engine-65.2.1 → howso_engine-65.3.0}/config/latest-mt-howso.yml +0 -0
  32. {howso_engine-65.2.1 → howso_engine-65.3.0}/config/latest-mt-noavx-traces-howso.yml +0 -0
  33. {howso_engine-65.2.1 → howso_engine-65.3.0}/config/latest-mt-traces-howso.yml +0 -0
  34. {howso_engine-65.2.1 → howso_engine-65.3.0}/config/latest-st-debug-howso.yml +0 -0
  35. {howso_engine-65.2.1 → howso_engine-65.3.0}/config/latest-st-howso.yml +0 -0
  36. {howso_engine-65.2.1 → howso_engine-65.3.0}/config/latest-st-traces-howso.yml +0 -0
  37. {howso_engine-65.2.1 → howso_engine-65.3.0}/config/powershell/Download-Tzdata.ps1 +0 -0
  38. {howso_engine-65.2.1 → howso_engine-65.3.0}/config/powershell/Helper-Functions.ps1 +0 -0
  39. {howso_engine-65.2.1 → howso_engine-65.3.0}/hatch.toml +0 -0
  40. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/__main__.py +0 -0
  41. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/client/__init__.py +0 -0
  42. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/client/api.py +0 -0
  43. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/client/base.py +0 -0
  44. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/client/cache.py +0 -0
  45. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/client/client.py +0 -0
  46. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/client/configuration.py +0 -0
  47. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/client/exceptions.py +0 -0
  48. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/client/feature_flags.py +0 -0
  49. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/client/pandas/__init__.py +0 -0
  50. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/client/pandas/client.py +0 -0
  51. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/client/protocols.py +0 -0
  52. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/client/schemas/__init__.py +0 -0
  53. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/client/schemas/aggregate_reaction.py +0 -0
  54. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/client/schemas/base.py +0 -0
  55. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/client/schemas/group_reaction.py +0 -0
  56. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/client/schemas/project.py +0 -0
  57. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/client/schemas/reaction.py +0 -0
  58. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/client/schemas/session.py +0 -0
  59. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/client/schemas/trainee.py +0 -0
  60. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/client/schemas/version.py +0 -0
  61. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/client/typing.py +0 -0
  62. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/direct/__init__.py +0 -0
  63. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/direct/_utilities.py +0 -0
  64. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/direct/client.py +0 -0
  65. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/direct/schemas/__init__.py +0 -0
  66. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/direct/schemas/trainee.py +0 -0
  67. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/engine/__init__.py +0 -0
  68. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/engine/client.py +0 -0
  69. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/engine/project.py +0 -0
  70. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/engine/session.py +0 -0
  71. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/engine/trainee.py +0 -0
  72. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/howso-engine/howso.caml +0 -0
  73. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/howso-engine/migrations/migrations.caml +0 -0
  74. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/howso-engine/version.json +0 -0
  75. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/scikit/__init__.py +0 -0
  76. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/scikit/scikit.py +0 -0
  77. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/utilities/decorators.py +0 -0
  78. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/utilities/fanout_features.py +0 -0
  79. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/utilities/feature_attributes/__init__.py +0 -0
  80. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/utilities/feature_attributes/protocols.py +0 -0
  81. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/utilities/feature_attributes/relational.py +0 -0
  82. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/utilities/feature_attributes/serializers.py +0 -0
  83. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/utilities/feature_attributes/warnings.py +0 -0
  84. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/utilities/features.py +0 -0
  85. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/utilities/installation_verification.py +0 -0
  86. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/utilities/internals.py +0 -0
  87. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/utilities/json_wrapper.py +0 -0
  88. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/utilities/locale.py +0 -0
  89. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/utilities/monitors.py +0 -0
  90. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/utilities/posix.py +0 -0
  91. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/utilities/random.py +0 -0
  92. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/utilities/reaction.py +0 -0
  93. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/utilities/testing.py +0 -0
  94. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/utilities/tokenizing.py +0 -0
  95. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso/utilities/utilities.py +0 -0
  96. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso_engine.egg-info/SOURCES.txt +0 -0
  97. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso_engine.egg-info/dependency_links.txt +0 -0
  98. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso_engine.egg-info/entry_points.txt +0 -0
  99. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso_engine.egg-info/requires.txt +0 -0
  100. {howso_engine-65.2.1 → howso_engine-65.3.0}/howso_engine.egg-info/top_level.txt +0 -0
  101. {howso_engine-65.2.1 → howso_engine-65.3.0}/pyproject.toml +0 -0
  102. {howso_engine-65.2.1 → howso_engine-65.3.0}/pyrightconfig.json +0 -0
  103. {howso_engine-65.2.1 → howso_engine-65.3.0}/requirements-3.11-dev.txt +0 -0
  104. {howso_engine-65.2.1 → howso_engine-65.3.0}/requirements-3.11.txt +0 -0
  105. {howso_engine-65.2.1 → howso_engine-65.3.0}/requirements-3.12-dev.txt +0 -0
  106. {howso_engine-65.2.1 → howso_engine-65.3.0}/requirements-3.12.txt +0 -0
  107. {howso_engine-65.2.1 → howso_engine-65.3.0}/requirements-3.13-dev.txt +0 -0
  108. {howso_engine-65.2.1 → howso_engine-65.3.0}/requirements-3.13.txt +0 -0
  109. {howso_engine-65.2.1 → howso_engine-65.3.0}/requirements-3.14-dev.txt +0 -0
  110. {howso_engine-65.2.1 → howso_engine-65.3.0}/requirements-3.14.txt +0 -0
  111. {howso_engine-65.2.1 → howso_engine-65.3.0}/ruff.toml +0 -0
  112. {howso_engine-65.2.1 → howso_engine-65.3.0}/setup.cfg +0 -0
  113. {howso_engine-65.2.1 → howso_engine-65.3.0}/version.json +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: howso-engine
3
- Version: 65.2.1
3
+ Version: 65.3.0
4
4
  Summary: The Howso Engine™ is a natively and fully explainable ML engine, serving as an alternative to black box AI neural networks.
5
5
  Author: Howso Incorporated
6
6
  Author-email: support@howso.com
@@ -34,7 +34,9 @@ from howso.utilities.progress import (
34
34
  disable_auto_progress,
35
35
  enable_auto_progress,
36
36
  engine_polling_supported,
37
+ print_status,
37
38
  reset_auto_progress,
39
+ status_console,
38
40
  with_progress,
39
41
  )
40
42
  from howso.utilities.tokenizing import (
@@ -137,6 +139,7 @@ __all__ = [
137
139
  "lazy_map",
138
140
  "matrix_processing",
139
141
  "num_list_dimensions",
142
+ "print_status",
140
143
  "replace_doublemax_with_infinity",
141
144
  "replace_nan_with_none",
142
145
  "replace_none_with_nan",
@@ -145,6 +148,7 @@ __all__ = [
145
148
  "seconds_to_time",
146
149
  "serialize_cases",
147
150
  "serialize_datetimes",
151
+ "status_console",
148
152
  "stringify_json",
149
153
  "time_to_seconds",
150
154
  "validate_case_indices",
@@ -91,11 +91,7 @@ class InferFeatureAttributesAbstractData(InferFeatureAttributesBase):
91
91
  feature_attributes = self._process(**kwargs)
92
92
 
93
93
  self.warnings_collector.emit_all()
94
-
95
- if self.suggestions_collector.suggestions:
96
- warnings.warn("You have one or more suggestions to consider for your feature attributes "
97
- "configuration. Please view them by printing the `suggestions` property of your "
98
- "returned feature attributes object (`your_attributes_object.suggestions`).")
94
+ self._emit_summary()
99
95
 
100
96
  return SingleTableFeatureAttributes(
101
97
  feature_attributes, params=kwargs,
@@ -810,6 +810,26 @@ class InferFeatureAttributesBase(ABC):
810
810
  warnings_collector: IFAWarningCollector = IFAWarningCollector()
811
811
  suggestions_collector: IFASuggestionCollector = IFASuggestionCollector()
812
812
 
813
+ _summarize: bool = True
814
+ """
815
+ Whether :meth:`_emit_summary` prints anything.
816
+
817
+ An inferrer that delegates to another — time series wraps the DataFrame or
818
+ AbstractData inferrer — turns this off on the inner instance so the summary
819
+ is printed once, by the outermost call, after every suggestion has been
820
+ collected.
821
+ """
822
+
823
+ def _emit_summary(self) -> None:
824
+ """
825
+ Print the feature attributes summary, once inference has completed.
826
+
827
+ Nothing is printed when there are no suggestions, so a clean run stays
828
+ silent, and nothing is printed when :attr:`_summarize` is off.
829
+ """
830
+ if self._summarize and self.suggestions_collector.suggestions:
831
+ self.suggestions_collector.print_summary()
832
+
813
833
  def _process(self,
814
834
  attempt_infer_extended_nominals: bool = False,
815
835
  max_distilled_cases: int | None = None,
@@ -101,7 +101,8 @@ def infer_feature_attributes(
101
101
  of what was inferred and so that any inaccuracies can be corrected before data ingestion. This method makes a best
102
102
  guess at the feature attributes and in some cases may not automatically set certain attributes if there is
103
103
  ambiguity, instead providing suggestions for further consideration which can be reviewed by accessing the
104
- :attr:`~howso.utilities.feature_attributes.base.FeatureAttributesBase.suggestions` attribute.
104
+ :attr:`~howso.utilities.feature_attributes.base.FeatureAttributesBase.suggestions` attribute. When any
105
+ suggestions were produced, a short summary of what was found is printed once inference completes.
105
106
 
106
107
  Parameters
107
108
  ----------
@@ -91,9 +91,6 @@ class InferFeatureAttributesDataFrame(InferFeatureAttributesBase):
91
91
  if prod(self.data.shape) < 25_000_000 and max_workers is None:
92
92
  max_workers = 0
93
93
 
94
- suggestion_warning = ("You have one or more suggestions to consider for your feature attributes "
95
- "configuration. Please view them by printing the `suggestions` property of your "
96
- "returned feature attributes object (`your_attributes_object.suggestions`).")
97
94
  if max_workers is None or max_workers >= 1:
98
95
  mp_context = mp.get_context("spawn")
99
96
  futures: dict[Future, str] = dict()
@@ -130,8 +127,7 @@ class InferFeatureAttributesDataFrame(InferFeatureAttributesBase):
130
127
  for collector in processed_suggestions:
131
128
  self.suggestions_collector.merge(collector)
132
129
 
133
- if self.suggestions_collector.suggestions:
134
- warnings.warn(suggestion_warning, UserWarning)
130
+ self._emit_summary()
135
131
 
136
132
  return SingleTableFeatureAttributes(
137
133
  feature_attributes=feature_attributes, params=kwargs,
@@ -141,8 +137,7 @@ class InferFeatureAttributesDataFrame(InferFeatureAttributesBase):
141
137
  else:
142
138
  feature_attributes = self._process(**kwargs)
143
139
  self.warnings_collector.emit_all()
144
- if self.suggestions_collector.suggestions:
145
- warnings.warn(suggestion_warning, UserWarning)
140
+ self._emit_summary()
146
141
  return SingleTableFeatureAttributes(
147
142
  feature_attributes, params=kwargs,
148
143
  unsupported=self.unsupported,
@@ -9,6 +9,8 @@ import warnings
9
9
  from rich.console import Console
10
10
  from rich.table import Table
11
11
 
12
+ from howso.utilities.progress import print_status
13
+
12
14
  if TYPE_CHECKING:
13
15
  from howso.client.typing import FullPreserveRareValuesConfig, PreserveRareValuesMap
14
16
 
@@ -26,6 +28,12 @@ def wrap_text(text: str, width: int) -> str:
26
28
  )) or text
27
29
 
28
30
 
31
+ def _count(value: int, singular: str, plural: str | None = None) -> str:
32
+ """Render ``value`` with the correctly pluralized noun, e.g. ``"1 key"`` or ``"3 keys"``."""
33
+ noun = singular if value == 1 else (plural or f"{singular}s")
34
+ return f"{value:,} {noun}"
35
+
36
+
29
37
  def wrap_paragraphs(text: str, width: int) -> str:
30
38
  """Wrap text on newlines."""
31
39
  out = []
@@ -67,6 +75,16 @@ class IFASuggestion(ABC):
67
75
  """A brief description of the suggestion."""
68
76
  ...
69
77
 
78
+ @property
79
+ def summary(self) -> str:
80
+ """
81
+ A one-line statement of what was found, for the summary printed when inference completes.
82
+
83
+ Should read as a finding rather than an instruction, e.g. ``"Found 3 fan-out features
84
+ across 1 key"``. Defaults to :attr:`description` for suggestions that do not override it.
85
+ """
86
+ return self.description
87
+
70
88
  @abstractmethod
71
89
  def apply(self, attributes: dict) -> None:
72
90
  """Apply this suggestion to the FeatureAttributesBase object."""
@@ -173,6 +191,13 @@ class FanoutFeaturesSuggestion(IFASuggestion):
173
191
  """A brief description of this suggestion."""
174
192
  return "Configure fan-out features so that the Howso Engine can more accurately measure uncertainty."
175
193
 
194
+ @property
195
+ def summary(self) -> str:
196
+ """A one-line statement of the fan-out features found."""
197
+ num_features = sum(len(cols) for cols in self._fanout_features.values())
198
+ return (f"Found {_count(num_features, 'fan-out feature')} across "
199
+ f"{_count(len(self._fanout_features), 'column')}")
200
+
176
201
  def apply(self, attributes: dict) -> None:
177
202
  """Apply the computed fanout features config to the FeatureAttributesBase object."""
178
203
  for key_features, fanout_features in self._fanout_features.items():
@@ -310,6 +335,13 @@ class PRVSuggestion(IFASuggestion):
310
335
  """A brief description of this suggestion."""
311
336
  return "Configure rare values to avoid losing their signal during data distillation."
312
337
 
338
+ @property
339
+ def summary(self) -> str:
340
+ """A one-line statement of the rare values found."""
341
+ num_values = sum(len(cfg["protected_values_multipliers"]) for cfg in self._prvc.values())
342
+ return (f"Found {_count(num_values, 'rare value')} across {_count(len(self._prvc), 'column')} "
343
+ "whose signal may be lost during data distillation workflows")
344
+
313
345
  def apply(self, attributes: dict) -> None:
314
346
  """Apply the computed rare values preservation config to the FeatureAttributesBase object."""
315
347
  if not self._user_set_mdc:
@@ -420,6 +452,46 @@ class IFASuggestionCollector:
420
452
  """Get all suggestions that belong to this collector."""
421
453
  return self._suggestions
422
454
 
455
+ def summary_lines(self, *, attributes_name: str = "your_attributes_object") -> list[str]:
456
+ """
457
+ Render the summary block as lines of console markup.
458
+
459
+ The block is styled to sit beside the progress output from
460
+ :mod:`howso.utilities.progress`: a bold cyan header, the way a progress
461
+ label is styled, one dim-bulleted finding per suggestion, indented the
462
+ way progress lines are, and a dim footer pointing at the suggestions.
463
+
464
+ Parameters
465
+ ----------
466
+ attributes_name : str, default "your_attributes_object"
467
+ How to refer to the returned feature attributes object in the footer.
468
+
469
+ Returns
470
+ -------
471
+ list of str
472
+ One entry per line, or an empty list when there are no suggestions.
473
+ """
474
+ if not self._suggestions:
475
+ return []
476
+ lines = ["[bold cyan]Feature Attributes Summary[/bold cyan]"]
477
+ lines.extend(f" [dim]\u00b7[/dim] {suggestion.summary}" for suggestion in self._suggestions.values())
478
+ lines.append(f" [dim]Inspect `{attributes_name}.suggestions` for details and how to apply them.[/dim]")
479
+ return lines
480
+
481
+ def print_summary(self, *, console: Console | None = None) -> None:
482
+ """
483
+ Print the summary of collected suggestions, when there are any.
484
+
485
+ Parameters
486
+ ----------
487
+ console : Console, optional
488
+ Console to print to. Defaults to the one :func:`howso.utilities.progress.status_console`
489
+ selects for the current environment.
490
+ """
491
+ lines = self.summary_lines()
492
+ if lines:
493
+ print_status(*lines, console=console)
494
+
423
495
  def append(self, suggestion: IFASuggestion) -> None:
424
496
  """Append a new IFASuggestion to this collector."""
425
497
  if suggestion.name in self._suggestions:
@@ -761,6 +761,9 @@ class InferFeatureAttributesTimeSeries(ABC):
761
761
  time_invariant_features.append(id_feature)
762
762
 
763
763
  infer._time_invariant_features = time_invariant_features
764
+ # The summary is printed once, from this object's ``__call__``, after the
765
+ # inner suggestions have been propagated into this collector.
766
+ infer._summarize = False # pyright: ignore[reportPrivateUsage]
764
767
 
765
768
  features = infer(
766
769
  attempt_infer_extended_nominals=attempt_infer_extended_nominals,
@@ -901,6 +904,9 @@ class InferFeatureAttributesTimeSeries(ABC):
901
904
  """Process and return feature attributes."""
902
905
  feature_attributes = self._process(**kwargs)
903
906
  self.warnings_collector.emit_all()
907
+ # Print the summary once, from here, now that the inner inferrer's
908
+ # suggestions have been propagated into this collector.
909
+ self.suggestions_collector.print_summary()
904
910
  # Put the time_feature_name back into the kwargs dictionary.
905
911
  kwargs["time_feature_name"] = self.time_feature_name
906
912
  return SingleTableFeatureAttributes(
@@ -75,7 +75,9 @@ __all__ = [
75
75
  "disable_auto_progress",
76
76
  "enable_auto_progress",
77
77
  "engine_polling_supported",
78
+ "print_status",
78
79
  "reset_auto_progress",
80
+ "status_console",
79
81
  "with_progress",
80
82
  ]
81
83
 
@@ -306,21 +308,7 @@ class BaseProgressReporter:
306
308
  -------
307
309
  None
308
310
  """
309
- streams = (
310
- self._console.file,
311
- sys.stdout,
312
- sys.stderr,
313
- sys.__stdout__,
314
- sys.__stderr__,
315
- )
316
- seen: set[int] = set()
317
- for stream in streams:
318
- flush = getattr(stream, "flush", None)
319
- if flush is None or id(stream) in seen:
320
- continue
321
- seen.add(id(stream))
322
- with suppress(Exception):
323
- flush()
311
+ _flush_streams(self._console.file)
324
312
 
325
313
 
326
314
  class RichProgressReporter(BaseProgressReporter):
@@ -1768,6 +1756,109 @@ def auto_reporter(*, console: Console | None = None) -> ProgressReporter:
1768
1756
  return SimpleProgressReporter(console=console)
1769
1757
 
1770
1758
 
1759
+ def status_console(console: Console | None = None) -> Console:
1760
+ """
1761
+ Build the console a one-off status line should print to.
1762
+
1763
+ A status line — a summary printed once a call has finished — has to land
1764
+ where a progress bar would have, and look the way the bar's completion line
1765
+ looks, whichever front-end is reading. So this follows the same selection
1766
+ :func:`auto_reporter` makes, but stops at the console: a stock console for
1767
+ a tty, the kernel-safe console of :func:`_notebook_console` for a notebook
1768
+ that would get a rich reporter, and a stdout-only console for everything
1769
+ else — including a forced ``HOWSO_SIMPLE_PROGRESS``, which prints plain
1770
+ lines and so should have its summaries print plain too.
1771
+
1772
+ Parameters
1773
+ ----------
1774
+ console : Console, optional
1775
+ A console to use instead. Returned as-is, so a caller who already
1776
+ holds one — a reporter, a test — can route a status line through it.
1777
+
1778
+ Returns
1779
+ -------
1780
+ Console
1781
+ The console to print the status line to.
1782
+ """
1783
+ if console is not None:
1784
+ return console
1785
+ if _parse_tristate(os.environ.get("HOWSO_SIMPLE_PROGRESS")) is True:
1786
+ return Console(force_jupyter=False)
1787
+ if sys.stdout.isatty():
1788
+ return Console()
1789
+ if _in_notebook() and (_display_handle_available() or _interactive_frontend()):
1790
+ return _notebook_console()
1791
+ # ``force_jupyter=False`` keeps the output on stdout rather than in a
1792
+ # separate ``IPython.display`` block; see SimpleProgressReporter.
1793
+ return Console(force_jupyter=False)
1794
+
1795
+
1796
+ def print_status(*lines: str, console: Console | None = None) -> None:
1797
+ """
1798
+ Print one or more lines of console markup as a status block.
1799
+
1800
+ This is the static counterpart of a reporter's completion line: the same
1801
+ console selection as :func:`status_console`, and the same flush discipline
1802
+ as a progress session. Every stream is drained before the block so that
1803
+ anything already buffered — a warning on ``stderr``, a ``print`` from the
1804
+ call that just returned — lands *above* it rather than through it, and
1805
+ drained again afterwards so the block itself is out before whatever the
1806
+ caller does next.
1807
+
1808
+ Parameters
1809
+ ----------
1810
+ *lines : str
1811
+ Console markup, one entry per line. Empty entries print blank lines.
1812
+ Lines are never hard-wrapped by rich: a line longer than the console
1813
+ is left to the terminal, which wraps it without the bare, unindented
1814
+ continuation rich would otherwise produce.
1815
+ console : Console, optional
1816
+ Console to print to. Defaults to :func:`status_console`.
1817
+
1818
+ Returns
1819
+ -------
1820
+ None
1821
+ """
1822
+ target = status_console(console)
1823
+ _flush_streams(target.file)
1824
+ for line in lines:
1825
+ target.print(line, soft_wrap=True)
1826
+ _flush_streams(target.file)
1827
+
1828
+
1829
+ def _flush_streams(*streams: Any) -> None:
1830
+ """
1831
+ Drain the given streams along with every standard stream.
1832
+
1833
+ Streams are deduplicated by identity (a console's file is normally
1834
+ ``sys.stdout`` itself) and flushed newest-wrapper-first, so a stack such
1835
+ as ipykernel's ``OutStream`` over the real ``stdout`` drains in order.
1836
+ Every flush is individually guarded: a closed, detached, or replaced
1837
+ stream — pytest's captured ``stdout``, a torn-down notebook kernel — must
1838
+ not take down the caller.
1839
+
1840
+ Note that this reaches only Python-level buffers.
1841
+
1842
+ Parameters
1843
+ ----------
1844
+ *streams : Any
1845
+ Streams to drain ahead of ``sys.stdout``, ``sys.stderr`` and their
1846
+ originals.
1847
+
1848
+ Returns
1849
+ -------
1850
+ None
1851
+ """
1852
+ seen: set[int] = set()
1853
+ for stream in (*streams, sys.stdout, sys.stderr, sys.__stdout__, sys.__stderr__):
1854
+ flush = getattr(stream, "flush", None)
1855
+ if flush is None or id(stream) in seen:
1856
+ continue
1857
+ seen.add(id(stream))
1858
+ with suppress(Exception):
1859
+ flush()
1860
+
1861
+
1771
1862
  def _supports_param(bound_func: Callable[..., Any], name: str) -> bool:
1772
1863
  """
1773
1864
  Report whether ``bound_func`` accepts a parameter called ``name``.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: howso-engine
3
- Version: 65.2.1
3
+ Version: 65.3.0
4
4
  Summary: The Howso Engine™ is a natively and fully explainable ML engine, serving as an alternative to black box AI neural networks.
5
5
  Author: Howso Incorporated
6
6
  Author-email: support@howso.com
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes