checkup 0.2.7__tar.gz → 0.2.9__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 (23) hide show
  1. {checkup-0.2.7 → checkup-0.2.9}/PKG-INFO +2 -4
  2. {checkup-0.2.7 → checkup-0.2.9}/README.md +1 -3
  3. {checkup-0.2.7 → checkup-0.2.9}/pyproject.toml +1 -1
  4. {checkup-0.2.7 → checkup-0.2.9}/src/checkup/executor.py +89 -11
  5. {checkup-0.2.7 → checkup-0.2.9}/src/checkup/hub.py +7 -3
  6. {checkup-0.2.7 → checkup-0.2.9}/src/checkup/__init__.py +0 -0
  7. {checkup-0.2.7 → checkup-0.2.9}/src/checkup/config.py +0 -0
  8. {checkup-0.2.7 → checkup-0.2.9}/src/checkup/errors.py +0 -0
  9. {checkup-0.2.7 → checkup-0.2.9}/src/checkup/graph.py +0 -0
  10. {checkup-0.2.7 → checkup-0.2.9}/src/checkup/materializers/__init__.py +0 -0
  11. {checkup-0.2.7 → checkup-0.2.9}/src/checkup/materializers/base.py +0 -0
  12. {checkup-0.2.7 → checkup-0.2.9}/src/checkup/materializers/console.py +0 -0
  13. {checkup-0.2.7 → checkup-0.2.9}/src/checkup/materializers/csv_file.py +0 -0
  14. {checkup-0.2.7 → checkup-0.2.9}/src/checkup/materializers/database.py +0 -0
  15. {checkup-0.2.7 → checkup-0.2.9}/src/checkup/materializers/html_report.py +0 -0
  16. {checkup-0.2.7 → checkup-0.2.9}/src/checkup/metric.py +0 -0
  17. {checkup-0.2.7 → checkup-0.2.9}/src/checkup/provider.py +0 -0
  18. {checkup-0.2.7 → checkup-0.2.9}/src/checkup/providers/__init__.py +0 -0
  19. {checkup-0.2.7 → checkup-0.2.9}/src/checkup/providers/tags.py +0 -0
  20. {checkup-0.2.7 → checkup-0.2.9}/src/checkup/templates/metrics_report.html +0 -0
  21. {checkup-0.2.7 → checkup-0.2.9}/src/checkup/types.py +0 -0
  22. {checkup-0.2.7 → checkup-0.2.9}/src/checkup/utils.py +0 -0
  23. {checkup-0.2.7 → checkup-0.2.9}/src/checkup/validators.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: checkup
3
- Version: 0.2.7
3
+ Version: 0.2.9
4
4
  Summary: Add your description here
5
5
  Author: Jan Vanbuel
6
6
  Author-email: Jan Vanbuel <jan.vanbuel@ond.vlaanderen.be>
@@ -14,12 +14,10 @@ Description-Content-Type: text/markdown
14
14
 
15
15
  ![CheckUp](images/horseshoe.png)
16
16
 
17
- Computational governance framework for measuring data product health.
17
+ Computational governance framework for measuring data product health.
18
18
 
19
19
  ### Key Concepts
20
20
 
21
21
  - **Metrics** - Calculate values from context
22
22
  - **Providers** - Functions that enrich context (shared across metrics)
23
23
  - **Materializers** - Output formats (Console, HTML, CSV, etc.)
24
-
25
-
@@ -1,11 +1,9 @@
1
1
  ![CheckUp](images/horseshoe.png)
2
2
 
3
- Computational governance framework for measuring data product health.
3
+ Computational governance framework for measuring data product health.
4
4
 
5
5
  ### Key Concepts
6
6
 
7
7
  - **Metrics** - Calculate values from context
8
8
  - **Providers** - Functions that enrich context (shared across metrics)
9
9
  - **Materializers** - Output formats (Console, HTML, CSV, etc.)
10
-
11
-
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "checkup"
3
- version = "0.2.7"
3
+ version = "0.2.9"
4
4
  description = "Add your description here"
5
5
  readme = "README.md"
6
6
  authors = [{ name = "Jan Vanbuel", email = "jan.vanbuel@ond.vlaanderen.be" }]
@@ -54,7 +54,7 @@ class ProviderExecutor:
54
54
  """
55
55
 
56
56
  def execute(
57
- self, provider_set: list[Provider], fail_fast: bool = True
57
+ self, provider_set: list[Provider]
58
58
  ) -> tuple[Context, dict[str, Any], list[ProviderError]]:
59
59
  """Execute all providers and build namespaced context.
60
60
 
@@ -64,14 +64,9 @@ class ProviderExecutor:
64
64
 
65
65
  Args:
66
66
  provider_set: List of provider instances
67
- fail_fast: If True, raise on first provider error.
68
- If False, collect errors and continue.
69
67
 
70
68
  Returns:
71
69
  Tuple of (context dict, tags dict, list of errors)
72
-
73
- Raises:
74
- ProviderError: If fail_fast=True and a provider fails
75
70
  """
76
71
  context: Context = {}
77
72
  tags: dict[str, Any] = {}
@@ -89,8 +84,6 @@ class ProviderExecutor:
89
84
  except Exception as e:
90
85
  error = ProviderError(provider, e)
91
86
  logger.error("Provider %s failed: %s", provider.name, e)
92
- if fail_fast:
93
- raise error from e
94
87
  errors.append(error)
95
88
 
96
89
  return context, tags, errors
@@ -113,6 +106,7 @@ class MetricCalculator:
113
106
  context: Context,
114
107
  tags: dict[str, Any],
115
108
  provided_classes: set[type[Provider]],
109
+ failed_providers: dict[type[Provider], ProviderError] | None = None,
116
110
  ) -> list[Metric]:
117
111
  """Calculate all metrics in execution order.
118
112
 
@@ -124,13 +118,16 @@ class MetricCalculator:
124
118
  context: Context dict from providers
125
119
  tags: Tags dict to merge into metrics
126
120
  provided_classes: Set of provider classes available
121
+ failed_providers: Dict mapping failed provider classes to their errors
127
122
 
128
123
  Returns:
129
124
  List of calculated metrics
130
125
  """
126
+ failed_providers = failed_providers or {}
131
127
  logger.debug("Starting metric calculation for %d metrics", len(execution_order))
132
128
  calculated: dict[type[Metric], Metric] = {}
133
129
  skipped: set[type[Metric]] = set()
130
+ failed: set[type[Metric]] = set()
134
131
  result_metrics: list[Metric] = []
135
132
 
136
133
  i = 0
@@ -138,12 +135,24 @@ class MetricCalculator:
138
135
  while i < len(execution_order):
139
136
  metric_cls = execution_order[i]
140
137
 
141
- # Check for skip conditions
138
+ # Check for skip conditions (missing providers)
142
139
  if self._should_skip(metric_cls, provided_classes, skipped):
143
140
  skipped.add(metric_cls)
144
141
  i += 1
145
142
  continue
146
143
 
144
+ # Check for failed provider dependencies
145
+ failed_deps = self._get_failed_providers(
146
+ metric_cls, failed_providers, failed
147
+ )
148
+ if failed_deps:
149
+ metric = self._create_failed_metric(metric_cls, tags, failed_deps)
150
+ calculated[metric_cls] = metric
151
+ result_metrics.append(metric)
152
+ failed.add(metric_cls)
153
+ i += 1
154
+ continue
155
+
147
156
  # Start a new batch with this executor type
148
157
  current_executor = metric_cls.executor
149
158
  batch: list[type[Metric]] = [metric_cls]
@@ -160,6 +169,17 @@ class MetricCalculator:
160
169
  i += 1
161
170
  continue
162
171
 
172
+ failed_deps = self._get_failed_providers(
173
+ next_cls, failed_providers, failed
174
+ )
175
+ if failed_deps:
176
+ metric = self._create_failed_metric(next_cls, tags, failed_deps)
177
+ calculated[next_cls] = metric
178
+ result_metrics.append(metric)
179
+ failed.add(next_cls)
180
+ i += 1
181
+ continue
182
+
163
183
  if next_cls.executor != current_executor:
164
184
  break # Different executor, start new batch
165
185
 
@@ -192,9 +212,10 @@ class MetricCalculator:
192
212
  )
193
213
 
194
214
  logger.info(
195
- "Metric calculation complete: %d calculated, %d skipped",
196
- len(result_metrics),
215
+ "Metric calculation complete: %d calculated, %d skipped, %d failed",
216
+ len(result_metrics) - len(failed),
197
217
  len(skipped),
218
+ len(failed),
198
219
  )
199
220
  return result_metrics
200
221
 
@@ -234,6 +255,63 @@ class MetricCalculator:
234
255
 
235
256
  return False
236
257
 
258
+ def _get_failed_providers(
259
+ self,
260
+ metric_cls: type[Metric],
261
+ failed_providers: dict[type[Provider], ProviderError],
262
+ failed_metrics: set[type[Metric]],
263
+ ) -> list[str]:
264
+ """Get list of failed provider/metric names that this metric depends on.
265
+
266
+ Args:
267
+ metric_cls: Metric class to check
268
+ failed_providers: Dict of failed provider classes to errors
269
+ failed_metrics: Set of metrics that failed due to provider errors
270
+
271
+ Returns:
272
+ List of failed provider/metric names, empty if none
273
+ """
274
+ failed_names = []
275
+
276
+ # Check direct provider dependencies
277
+ for provider_cls in metric_cls.providers():
278
+ if provider_cls in failed_providers:
279
+ failed_names.append(f"provider '{provider_cls.name}'")
280
+
281
+ # Check metric dependencies that failed
282
+ for dep_cls in metric_cls.depends_on():
283
+ if dep_cls in failed_metrics:
284
+ failed_names.append(f"metric '{dep_cls.name}'")
285
+
286
+ return failed_names
287
+
288
+ def _create_failed_metric(
289
+ self,
290
+ metric_cls: type[Metric],
291
+ tags: dict[str, Any],
292
+ failed_deps: list[str],
293
+ ) -> Metric:
294
+ """Create a metric instance with null value due to failed dependencies.
295
+
296
+ Args:
297
+ metric_cls: Metric class to instantiate
298
+ tags: Tags to merge into the metric
299
+ failed_deps: List of failed dependency names
300
+
301
+ Returns:
302
+ Metric instance with value=None and diagnostic explaining failure
303
+ """
304
+ metric = metric_cls(**self._configs.get(metric_cls.name, {}))
305
+ metric.tags.update(tags)
306
+ metric.value = None
307
+ metric.diagnostic = f"Failed: {', '.join(failed_deps)} failed"
308
+ logger.debug(
309
+ "Metric %s marked as failed: %s",
310
+ metric_cls.name,
311
+ metric.diagnostic,
312
+ )
313
+ return metric
314
+
237
315
  def _execute_batch(
238
316
  self,
239
317
  batch: list[type[Metric]],
@@ -73,10 +73,14 @@ def _measure_single_provider_set(
73
73
  Raises:
74
74
  ProviderError: If a provider fails during execution
75
75
  """
76
- context, tags, errors = ProviderExecutor().execute(provider_set, fail_fast=True)
77
- # errors list will be empty if fail_fast=True since it raises on error
76
+ context, tags, errors = ProviderExecutor().execute(provider_set)
77
+ failed_providers = {type(e.provider): e for e in errors}
78
78
  return MetricCalculator(metric_configs).calculate(
79
- execution_order, context, tags, {type(p) for p in provider_set}
79
+ execution_order,
80
+ context,
81
+ tags,
82
+ {type(p) for p in provider_set},
83
+ failed_providers,
80
84
  )
81
85
 
82
86
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes