tabalyst 0.4.2__tar.gz → 0.4.4__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.
- {tabalyst-0.4.2/src/tabalyst.egg-info → tabalyst-0.4.4}/PKG-INFO +53 -11
- {tabalyst-0.4.2 → tabalyst-0.4.4}/README.md +49 -9
- {tabalyst-0.4.2 → tabalyst-0.4.4}/examples/README.md +4 -2
- {tabalyst-0.4.2 → tabalyst-0.4.4}/pyproject.toml +4 -2
- tabalyst-0.4.4/src/tabalyst/cache_service.py +114 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/cli/app.py +2 -0
- tabalyst-0.4.4/src/tabalyst/cli/cache.py +64 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/cli/report.py +10 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/cli/scan.py +3 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/models.py +41 -3
- tabalyst-0.4.4/src/tabalyst/project_open_service.py +179 -0
- tabalyst-0.4.4/src/tabalyst/project_scan_service.py +204 -0
- tabalyst-0.4.4/src/tabalyst/projects/__init__.py +48 -0
- tabalyst-0.4.4/src/tabalyst/projects/_bounded_values.py +449 -0
- tabalyst-0.4.4/src/tabalyst/projects/_cache.py +370 -0
- tabalyst-0.4.4/src/tabalyst/projects/_codec.py +116 -0
- tabalyst-0.4.4/src/tabalyst/projects/_generation.py +118 -0
- tabalyst-0.4.4/src/tabalyst/projects/_locks.py +144 -0
- tabalyst-0.4.4/src/tabalyst/projects/_publication.py +278 -0
- tabalyst-0.4.4/src/tabalyst/projects/_queries.py +481 -0
- tabalyst-0.4.4/src/tabalyst/projects/_query_budget.py +83 -0
- tabalyst-0.4.4/src/tabalyst/projects/_recovery.py +59 -0
- tabalyst-0.4.4/src/tabalyst/projects/_retention.py +70 -0
- tabalyst-0.4.4/src/tabalyst/projects/_schema.py +115 -0
- tabalyst-0.4.4/src/tabalyst/projects/_session.py +226 -0
- tabalyst-0.4.4/src/tabalyst/projects/_staging.py +439 -0
- tabalyst-0.4.4/src/tabalyst/projects/_sync.py +42 -0
- tabalyst-0.4.4/src/tabalyst/projects/_validation.py +459 -0
- tabalyst-0.4.4/src/tabalyst/projects/catalog.py +75 -0
- tabalyst-0.4.4/src/tabalyst/projects/freshness.py +54 -0
- tabalyst-0.4.4/src/tabalyst/projects/identity.py +42 -0
- tabalyst-0.4.4/src/tabalyst/projects/index.py +175 -0
- tabalyst-0.4.4/src/tabalyst/projects/location.py +125 -0
- tabalyst-0.4.4/src/tabalyst/projects/models.py +110 -0
- tabalyst-0.4.4/src/tabalyst/projects/store.py +117 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/report_profile.py +34 -2
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/report_service.py +17 -2
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/reporting.py +52 -2
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/scan_reuse.py +51 -16
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/scan_service.py +65 -25
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/scanner/records.py +20 -13
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/service.py +105 -6
- tabalyst-0.4.4/src/tabalyst/shared_scan_service.py +79 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/static/report.js +35 -24
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/static/theme.css +23 -1
- tabalyst-0.4.4/src/tabalyst/templates/column.html +66 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/templates/report.html +13 -7
- {tabalyst-0.4.2 → tabalyst-0.4.4/src/tabalyst.egg-info}/PKG-INFO +53 -11
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst.egg-info/SOURCES.txt +44 -1
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst.egg-info/requires.txt +2 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/tests/test_cli.py +7 -2
- tabalyst-0.4.4/tests/test_project_cache.py +439 -0
- tabalyst-0.4.4/tests/test_project_database.py +525 -0
- tabalyst-0.4.4/tests/test_project_generations.py +481 -0
- tabalyst-0.4.4/tests/test_project_identity.py +55 -0
- tabalyst-0.4.4/tests/test_project_index.py +172 -0
- tabalyst-0.4.4/tests/test_project_location.py +89 -0
- tabalyst-0.4.4/tests/test_project_open_service.py +300 -0
- tabalyst-0.4.4/tests/test_project_queries.py +718 -0
- tabalyst-0.4.4/tests/test_project_rescan.py +536 -0
- tabalyst-0.4.4/tests/test_project_scan.py +238 -0
- tabalyst-0.4.4/tests/test_project_sessions.py +441 -0
- tabalyst-0.4.4/tests/test_project_store.py +238 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/tests/test_public_api.py +34 -15
- tabalyst-0.4.4/tests/test_reporting.py +296 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/tests/test_scan_command.py +36 -17
- tabalyst-0.4.4/tests/test_shared_scan_cli.py +139 -0
- tabalyst-0.4.4/tests/test_source_freshness.py +79 -0
- tabalyst-0.4.2/tests/test_reporting.py +0 -137
- {tabalyst-0.4.2 → tabalyst-0.4.4}/LICENSE +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/MANIFEST.in +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/examples/config.json +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/examples/input/basic.csv +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/examples/input/insurance-customers.csv +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/setup.cfg +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/__init__.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/__main__.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/_version.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/batch.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/cli/__init__.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/cli/sample.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/cli/terminal.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/config.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/errors.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/execution_log.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/progress.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/report_config.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/sampling.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/sampling_service.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/scanner/__init__.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/scanner/api.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/scanner/config.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/scanner/detectors/__init__.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/scanner/detectors/amount.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/scanner/detectors/base.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/scanner/detectors/boolean.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/scanner/detectors/currency.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/scanner/detectors/email.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/scanner/detectors/enumeration.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/scanner/detectors/ip.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/scanner/detectors/names.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/scanner/detectors/number.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/scanner/detectors/pattern.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/scanner/detectors/percentage.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/scanner/detectors/phone.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/scanner/detectors/postal.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/scanner/detectors/quantity.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/scanner/detectors/registry.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/scanner/detectors/shape.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/scanner/detectors/temporal.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/scanner/detectors/url.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/scanner/detectors/uuid.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/scanner/diagnostics.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/scanner/engine.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/scanner/exposure.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/scanner/field.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/scanner/measures.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/scanner/models.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/scanner/normalization.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/scanner/observations.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/scanner/paths.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/scanner/readers/__init__.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/scanner/readers/base.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/scanner/readers/csv_reader.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/scanner/readers/json_reader.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/scanner/structure.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/scanner/technical.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/scanner/values.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst/scanner/workers.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst.egg-info/dependency_links.txt +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst.egg-info/entry_points.txt +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/src/tabalyst.egg-info/top_level.txt +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/tests/test_sampling.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/tests/test_scanner_amounts.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/tests/test_scanner_batches.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/tests/test_scanner_config.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/tests/test_scanner_csv.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/tests/test_scanner_email_url.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/tests/test_scanner_exposure.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/tests/test_scanner_paths.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/tests/test_scanner_phone.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/tests/test_scanner_postal.py +0 -0
- {tabalyst-0.4.2 → tabalyst-0.4.4}/tests/test_scanner_uuid_ip.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tabalyst
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.4
|
|
4
4
|
Summary: Open-source toolkit for understanding and working with unfamiliar data.
|
|
5
5
|
Author: Gregory Borelli
|
|
6
6
|
License-Expression: MIT
|
|
@@ -8,7 +8,7 @@ Project-URL: Homepage, https://github.com/loribel-labs/tabalyst
|
|
|
8
8
|
Project-URL: Repository, https://github.com/loribel-labs/tabalyst.git
|
|
9
9
|
Project-URL: Issues, https://github.com/loribel-labs/tabalyst/issues
|
|
10
10
|
Keywords: csv,data-analysis,data-profiling,data-quality,eda,html-report,json
|
|
11
|
-
Classifier: Development Status ::
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
12
|
Classifier: Intended Audience :: Developers
|
|
13
13
|
Classifier: Intended Audience :: Science/Research
|
|
14
14
|
Classifier: Operating System :: OS Independent
|
|
@@ -21,8 +21,10 @@ Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
|
21
21
|
Requires-Python: >=3.11
|
|
22
22
|
Description-Content-Type: text/markdown
|
|
23
23
|
License-File: LICENSE
|
|
24
|
+
Requires-Dist: duckdb==1.5.5
|
|
24
25
|
Requires-Dist: ijson>=3.3
|
|
25
26
|
Requires-Dist: jinja2>=3.1
|
|
27
|
+
Requires-Dist: platformdirs>=4
|
|
26
28
|
Requires-Dist: pydantic>=2.11
|
|
27
29
|
Requires-Dist: typer>=0.12
|
|
28
30
|
Provides-Extra: dev
|
|
@@ -42,8 +44,10 @@ with structured data.
|
|
|
42
44
|
Its first tool is **Tabalyst Report**. The current CSV implementation,
|
|
43
45
|
**Tabalyst CSV Report**, analyzes a CSV file and produces both a structured JSON
|
|
44
46
|
profile and a self-contained interactive HTML report.
|
|
47
|
+
**Tabalyst Scan** describes CSV and JSON files in a scan document, and
|
|
48
|
+
**Tabalyst Sample** creates smaller CSV files.
|
|
45
49
|
|
|
46
|
-
Tabalyst is in
|
|
50
|
+
Tabalyst is in beta. Its interfaces may still change while
|
|
47
51
|
the shared toolkit architecture is being established.
|
|
48
52
|
|
|
49
53
|
## Install
|
|
@@ -54,7 +58,7 @@ Tabalyst supports Python 3.11, 3.12, 3.13, and 3.14.
|
|
|
54
58
|
pip install --upgrade tabalyst
|
|
55
59
|
```
|
|
56
60
|
|
|
57
|
-
The same command installs Tabalyst or updates it. Tabalyst is in
|
|
61
|
+
The same command installs Tabalyst or updates it. Tabalyst is in beta and
|
|
58
62
|
changes often: update it before each new test.
|
|
59
63
|
|
|
60
64
|
## Tabalyst Report
|
|
@@ -83,6 +87,20 @@ customers.json
|
|
|
83
87
|
executions.json
|
|
84
88
|
```
|
|
85
89
|
|
|
90
|
+
Add `--details` to generate one self-contained HTML page per column:
|
|
91
|
+
|
|
92
|
+
```console
|
|
93
|
+
tabalyst report customers.csv --details
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The **Columns** table then links to those pages. Each page has its own sidebar
|
|
97
|
+
and shows values, counts, detected formats and other analysis directly. The
|
|
98
|
+
subfolder takes the HTML filename without `.html`; for `-o report.html`, pages
|
|
99
|
+
are in `report/`. Names use `col-01-` and a shortened slug of the column name,
|
|
100
|
+
in source order. Details are off by default. `--no-details` states that choice
|
|
101
|
+
explicitly; with `--force`, it removes prior generated column pages while
|
|
102
|
+
preserving other files in the folder.
|
|
103
|
+
|
|
86
104
|
Use `-o` to choose a different HTML filename for one source:
|
|
87
105
|
|
|
88
106
|
```console
|
|
@@ -153,8 +171,10 @@ instead.
|
|
|
153
171
|
|
|
154
172
|
### Safe batch behavior
|
|
155
173
|
|
|
156
|
-
Before processing begins, Tabalyst resolves every input and planned
|
|
157
|
-
stops the entire batch if
|
|
174
|
+
Before processing begins, Tabalyst resolves every input and planned main HTML
|
|
175
|
+
and JSON output. It stops the entire batch if those names collide or if an
|
|
176
|
+
output already exists. With `--details`, each column page is checked after its
|
|
177
|
+
profile has been built, before that report's files are written.
|
|
158
178
|
Use `--force` only when replacing all matching report artifacts is intentional:
|
|
159
179
|
|
|
160
180
|
```console
|
|
@@ -225,8 +245,10 @@ tabalyst scan customers.csv
|
|
|
225
245
|
tabalyst scan orders.json --collection "$.customers[]"
|
|
226
246
|
```
|
|
227
247
|
|
|
228
|
-
|
|
229
|
-
|
|
248
|
+
For a CSV, the default scan is a reusable `scan.json` under Tabalyst's local
|
|
249
|
+
storage directory (or `TABALYST_HOME`). The default workflow does not build a
|
|
250
|
+
DuckDB database. `-o` or `-d` writes
|
|
251
|
+
a standalone `<stem>.scan.json` export instead. Tabalyst Scan reads the file once as
|
|
230
252
|
a stream, with memory bounded by configurable limits, and records for each
|
|
231
253
|
field its presence, native types, missing values, frequencies, exact
|
|
232
254
|
statistics, normalization variants, technical type and the result of every
|
|
@@ -238,7 +260,13 @@ interrupted scan never leaves a partial file. Files of 16 MiB or more are
|
|
|
238
260
|
analyzed by several worker processes, with the same result; `--workers`
|
|
239
261
|
chooses their number, `--workers 1` keeps one process.
|
|
240
262
|
|
|
241
|
-
|
|
263
|
+
`tabalyst report customers.csv` reuses the verified stored scan when the CSV
|
|
264
|
+
content and requested scan settings are current. It creates a scan when none
|
|
265
|
+
exists and atomically replaces a stale one. The source check uses size,
|
|
266
|
+
modification time and SHA-256. Existing DuckDB projects from 0.4.3 are left
|
|
267
|
+
untouched.
|
|
268
|
+
|
|
269
|
+
Build the report from a standalone scan document without reading the source again:
|
|
242
270
|
|
|
243
271
|
```console
|
|
244
272
|
tabalyst report --scan customers.scan.json
|
|
@@ -247,6 +275,19 @@ tabalyst report --scan customers.scan.json
|
|
|
247
275
|
Tabalyst refuses a scan whose source changed since it was written, or whose
|
|
248
276
|
settings differ from the `scan` settings of `--config`.
|
|
249
277
|
|
|
278
|
+
Inspect or clean disposable query caches of existing DuckDB projects, for every
|
|
279
|
+
project or for one CSV file:
|
|
280
|
+
|
|
281
|
+
```console
|
|
282
|
+
tabalyst cache info
|
|
283
|
+
tabalyst cache info customers.csv
|
|
284
|
+
tabalyst cache clean
|
|
285
|
+
tabalyst cache clean customers.csv
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
`cache clean` leaves project scans and databases in place. Ordinary scans and
|
|
289
|
+
reports do not create these query caches.
|
|
290
|
+
|
|
250
291
|
See [Scan CSV and JSON files](https://github.com/loribel-labs/tabalyst/blob/main/docs/en/how-to/scan-files.md)
|
|
251
292
|
and the [scan format](https://github.com/loribel-labs/tabalyst/blob/main/docs/en/reference/scan-format.md).
|
|
252
293
|
|
|
@@ -301,8 +342,9 @@ reports = tabalyst.generate_reports(["scans/*.scan.json"], from_scan=True)
|
|
|
301
342
|
`analyze()` returns the JSON-serializable profile for one report.
|
|
302
343
|
`generate_reports()` returns the complete batch plan, successes, and failures.
|
|
303
344
|
`scan()` returns a scan result without writing anything; `generate_scans()`
|
|
304
|
-
writes one `.scan.json` document per source
|
|
305
|
-
|
|
345
|
+
writes one standalone `.scan.json` document per source by default. Pass
|
|
346
|
+
`project_storage=True` to use the command's scan-only storage for CSV files. `from_scan=True`
|
|
347
|
+
builds reports from standalone scan documents.
|
|
306
348
|
Expected failures derive from `tabalyst.TabalystError`.
|
|
307
349
|
|
|
308
350
|
Existing artifacts are never replaced silently. Pass `force=True` when
|
|
@@ -8,8 +8,10 @@ with structured data.
|
|
|
8
8
|
Its first tool is **Tabalyst Report**. The current CSV implementation,
|
|
9
9
|
**Tabalyst CSV Report**, analyzes a CSV file and produces both a structured JSON
|
|
10
10
|
profile and a self-contained interactive HTML report.
|
|
11
|
+
**Tabalyst Scan** describes CSV and JSON files in a scan document, and
|
|
12
|
+
**Tabalyst Sample** creates smaller CSV files.
|
|
11
13
|
|
|
12
|
-
Tabalyst is in
|
|
14
|
+
Tabalyst is in beta. Its interfaces may still change while
|
|
13
15
|
the shared toolkit architecture is being established.
|
|
14
16
|
|
|
15
17
|
## Install
|
|
@@ -20,7 +22,7 @@ Tabalyst supports Python 3.11, 3.12, 3.13, and 3.14.
|
|
|
20
22
|
pip install --upgrade tabalyst
|
|
21
23
|
```
|
|
22
24
|
|
|
23
|
-
The same command installs Tabalyst or updates it. Tabalyst is in
|
|
25
|
+
The same command installs Tabalyst or updates it. Tabalyst is in beta and
|
|
24
26
|
changes often: update it before each new test.
|
|
25
27
|
|
|
26
28
|
## Tabalyst Report
|
|
@@ -49,6 +51,20 @@ customers.json
|
|
|
49
51
|
executions.json
|
|
50
52
|
```
|
|
51
53
|
|
|
54
|
+
Add `--details` to generate one self-contained HTML page per column:
|
|
55
|
+
|
|
56
|
+
```console
|
|
57
|
+
tabalyst report customers.csv --details
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The **Columns** table then links to those pages. Each page has its own sidebar
|
|
61
|
+
and shows values, counts, detected formats and other analysis directly. The
|
|
62
|
+
subfolder takes the HTML filename without `.html`; for `-o report.html`, pages
|
|
63
|
+
are in `report/`. Names use `col-01-` and a shortened slug of the column name,
|
|
64
|
+
in source order. Details are off by default. `--no-details` states that choice
|
|
65
|
+
explicitly; with `--force`, it removes prior generated column pages while
|
|
66
|
+
preserving other files in the folder.
|
|
67
|
+
|
|
52
68
|
Use `-o` to choose a different HTML filename for one source:
|
|
53
69
|
|
|
54
70
|
```console
|
|
@@ -119,8 +135,10 @@ instead.
|
|
|
119
135
|
|
|
120
136
|
### Safe batch behavior
|
|
121
137
|
|
|
122
|
-
Before processing begins, Tabalyst resolves every input and planned
|
|
123
|
-
stops the entire batch if
|
|
138
|
+
Before processing begins, Tabalyst resolves every input and planned main HTML
|
|
139
|
+
and JSON output. It stops the entire batch if those names collide or if an
|
|
140
|
+
output already exists. With `--details`, each column page is checked after its
|
|
141
|
+
profile has been built, before that report's files are written.
|
|
124
142
|
Use `--force` only when replacing all matching report artifacts is intentional:
|
|
125
143
|
|
|
126
144
|
```console
|
|
@@ -191,8 +209,10 @@ tabalyst scan customers.csv
|
|
|
191
209
|
tabalyst scan orders.json --collection "$.customers[]"
|
|
192
210
|
```
|
|
193
211
|
|
|
194
|
-
|
|
195
|
-
|
|
212
|
+
For a CSV, the default scan is a reusable `scan.json` under Tabalyst's local
|
|
213
|
+
storage directory (or `TABALYST_HOME`). The default workflow does not build a
|
|
214
|
+
DuckDB database. `-o` or `-d` writes
|
|
215
|
+
a standalone `<stem>.scan.json` export instead. Tabalyst Scan reads the file once as
|
|
196
216
|
a stream, with memory bounded by configurable limits, and records for each
|
|
197
217
|
field its presence, native types, missing values, frequencies, exact
|
|
198
218
|
statistics, normalization variants, technical type and the result of every
|
|
@@ -204,7 +224,13 @@ interrupted scan never leaves a partial file. Files of 16 MiB or more are
|
|
|
204
224
|
analyzed by several worker processes, with the same result; `--workers`
|
|
205
225
|
chooses their number, `--workers 1` keeps one process.
|
|
206
226
|
|
|
207
|
-
|
|
227
|
+
`tabalyst report customers.csv` reuses the verified stored scan when the CSV
|
|
228
|
+
content and requested scan settings are current. It creates a scan when none
|
|
229
|
+
exists and atomically replaces a stale one. The source check uses size,
|
|
230
|
+
modification time and SHA-256. Existing DuckDB projects from 0.4.3 are left
|
|
231
|
+
untouched.
|
|
232
|
+
|
|
233
|
+
Build the report from a standalone scan document without reading the source again:
|
|
208
234
|
|
|
209
235
|
```console
|
|
210
236
|
tabalyst report --scan customers.scan.json
|
|
@@ -213,6 +239,19 @@ tabalyst report --scan customers.scan.json
|
|
|
213
239
|
Tabalyst refuses a scan whose source changed since it was written, or whose
|
|
214
240
|
settings differ from the `scan` settings of `--config`.
|
|
215
241
|
|
|
242
|
+
Inspect or clean disposable query caches of existing DuckDB projects, for every
|
|
243
|
+
project or for one CSV file:
|
|
244
|
+
|
|
245
|
+
```console
|
|
246
|
+
tabalyst cache info
|
|
247
|
+
tabalyst cache info customers.csv
|
|
248
|
+
tabalyst cache clean
|
|
249
|
+
tabalyst cache clean customers.csv
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
`cache clean` leaves project scans and databases in place. Ordinary scans and
|
|
253
|
+
reports do not create these query caches.
|
|
254
|
+
|
|
216
255
|
See [Scan CSV and JSON files](https://github.com/loribel-labs/tabalyst/blob/main/docs/en/how-to/scan-files.md)
|
|
217
256
|
and the [scan format](https://github.com/loribel-labs/tabalyst/blob/main/docs/en/reference/scan-format.md).
|
|
218
257
|
|
|
@@ -267,8 +306,9 @@ reports = tabalyst.generate_reports(["scans/*.scan.json"], from_scan=True)
|
|
|
267
306
|
`analyze()` returns the JSON-serializable profile for one report.
|
|
268
307
|
`generate_reports()` returns the complete batch plan, successes, and failures.
|
|
269
308
|
`scan()` returns a scan result without writing anything; `generate_scans()`
|
|
270
|
-
writes one `.scan.json` document per source
|
|
271
|
-
|
|
309
|
+
writes one standalone `.scan.json` document per source by default. Pass
|
|
310
|
+
`project_storage=True` to use the command's scan-only storage for CSV files. `from_scan=True`
|
|
311
|
+
builds reports from standalone scan documents.
|
|
272
312
|
Expected failures derive from `tabalyst.TabalystError`.
|
|
273
313
|
|
|
274
314
|
Existing artifacts are never replaced silently. Pass `force=True` when
|
|
@@ -18,6 +18,7 @@ examples/
|
|
|
18
18
|
|-- insurance-customers/
|
|
19
19
|
| |-- report.html
|
|
20
20
|
| |-- report.json
|
|
21
|
+
| |-- report/ (34 standalone column pages with --details)
|
|
21
22
|
| `-- executions.json
|
|
22
23
|
`-- orders/
|
|
23
24
|
|-- report.html
|
|
@@ -36,12 +37,13 @@ From the repository root, regenerate the outputs with:
|
|
|
36
37
|
|
|
37
38
|
```console
|
|
38
39
|
tabalyst report examples/input/basic.csv -o examples/output/basic/report.html --config examples/config.json --force
|
|
39
|
-
tabalyst report examples/input/insurance-customers.csv -o examples/output/insurance-customers/report.html --config examples/config.json --force
|
|
40
|
+
tabalyst report examples/input/insurance-customers.csv -o examples/output/insurance-customers/report.html --config examples/config.json --details --force
|
|
40
41
|
tabalyst report examples/input/orders.json -o examples/output/orders/report.html --config examples/config.json --force
|
|
41
42
|
```
|
|
42
43
|
|
|
43
44
|
Every output folder is independent and contains its own HTML report, canonical
|
|
44
|
-
JSON profile
|
|
45
|
+
JSON profile and cumulative `executions.json` history. The insurance example
|
|
46
|
+
also has standalone HTML pages under `report/`. Delete an output folder
|
|
45
47
|
before regenerating when a fresh one-entry execution history is required.
|
|
46
48
|
|
|
47
49
|
The input datasets and generated output are public repository examples. The wheel
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "tabalyst"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.4"
|
|
8
8
|
description = "Open-source toolkit for understanding and working with unfamiliar data."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -22,7 +22,7 @@ keywords = [
|
|
|
22
22
|
"json",
|
|
23
23
|
]
|
|
24
24
|
classifiers = [
|
|
25
|
-
"Development Status ::
|
|
25
|
+
"Development Status :: 4 - Beta",
|
|
26
26
|
"Intended Audience :: Developers",
|
|
27
27
|
"Intended Audience :: Science/Research",
|
|
28
28
|
"Operating System :: OS Independent",
|
|
@@ -34,8 +34,10 @@ classifiers = [
|
|
|
34
34
|
"Topic :: Scientific/Engineering :: Information Analysis",
|
|
35
35
|
]
|
|
36
36
|
dependencies = [
|
|
37
|
+
"duckdb==1.5.5",
|
|
37
38
|
"ijson>=3.3",
|
|
38
39
|
"jinja2>=3.1",
|
|
40
|
+
"platformdirs>=4",
|
|
39
41
|
"pydantic>=2.11",
|
|
40
42
|
"typer>=0.12",
|
|
41
43
|
]
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""Public cleanup of disposable project query caches."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from tabalyst.errors import InputError
|
|
7
|
+
from tabalyst.projects._cache import clean_query_caches, inventory_query_caches
|
|
8
|
+
from tabalyst.projects._locks import ProjectBusyError
|
|
9
|
+
from tabalyst.projects.identity import is_project_id
|
|
10
|
+
from tabalyst.projects.index import (
|
|
11
|
+
document_key,
|
|
12
|
+
find_project,
|
|
13
|
+
read_index,
|
|
14
|
+
source_key,
|
|
15
|
+
)
|
|
16
|
+
from tabalyst.projects.location import StorageLocation
|
|
17
|
+
from tabalyst.projects.store import read_project
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True)
|
|
21
|
+
class CacheCleanResult:
|
|
22
|
+
projects: int
|
|
23
|
+
removed: int
|
|
24
|
+
removed_bytes: int
|
|
25
|
+
skipped: tuple[str, ...]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True)
|
|
29
|
+
class CacheInfo:
|
|
30
|
+
projects: int
|
|
31
|
+
directories: int
|
|
32
|
+
logical_bytes: int
|
|
33
|
+
skipped: tuple[str, ...]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _selected_projects(
|
|
37
|
+
source: str | Path | None = None,
|
|
38
|
+
*,
|
|
39
|
+
location: StorageLocation | None = None,
|
|
40
|
+
) -> tuple[StorageLocation, list]:
|
|
41
|
+
location = location or StorageLocation.local()
|
|
42
|
+
if source is None:
|
|
43
|
+
try:
|
|
44
|
+
directories = sorted(location.projects_dir.iterdir())
|
|
45
|
+
except FileNotFoundError:
|
|
46
|
+
directories = []
|
|
47
|
+
projects = []
|
|
48
|
+
for directory in directories:
|
|
49
|
+
if is_project_id(directory.name):
|
|
50
|
+
try:
|
|
51
|
+
projects.append(read_project(location, directory.name))
|
|
52
|
+
except InputError:
|
|
53
|
+
continue
|
|
54
|
+
else:
|
|
55
|
+
key = source_key(Path(source))
|
|
56
|
+
index = read_index(location)
|
|
57
|
+
project_id = index.projects.get(key) if index else None
|
|
58
|
+
try:
|
|
59
|
+
selected = read_project(location, project_id) if project_id else None
|
|
60
|
+
except InputError:
|
|
61
|
+
selected = None
|
|
62
|
+
if selected is None or document_key(selected) != key:
|
|
63
|
+
selected = find_project(location, Path(source))
|
|
64
|
+
if selected is None:
|
|
65
|
+
raise InputError(f"No Tabalyst project for source: {source}")
|
|
66
|
+
projects = [selected]
|
|
67
|
+
return location, projects
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def cache_info(
|
|
71
|
+
source: str | Path | None = None,
|
|
72
|
+
*,
|
|
73
|
+
location: StorageLocation | None = None,
|
|
74
|
+
) -> CacheInfo:
|
|
75
|
+
"""Summarize owned disposable cache files without modifying them."""
|
|
76
|
+
location, projects = _selected_projects(source, location=location)
|
|
77
|
+
directories = 0
|
|
78
|
+
logical_bytes = 0
|
|
79
|
+
skipped = []
|
|
80
|
+
for project in projects:
|
|
81
|
+
inventory = inventory_query_caches(location, project.project_id)
|
|
82
|
+
for entry in inventory.entries:
|
|
83
|
+
if entry.status == "candidate":
|
|
84
|
+
directories += 1
|
|
85
|
+
logical_bytes += entry.logical_bytes
|
|
86
|
+
else:
|
|
87
|
+
skipped.append(f"{project.project_id}/{entry.path}: {entry.reason}")
|
|
88
|
+
return CacheInfo(len(projects), directories, logical_bytes, tuple(skipped))
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def clean_cache(
|
|
92
|
+
source: str | Path | None = None,
|
|
93
|
+
*,
|
|
94
|
+
location: StorageLocation | None = None,
|
|
95
|
+
) -> CacheCleanResult:
|
|
96
|
+
"""Clean owned disposable cache files for one source or all projects."""
|
|
97
|
+
location, projects = _selected_projects(source, location=location)
|
|
98
|
+
removed = 0
|
|
99
|
+
removed_bytes = 0
|
|
100
|
+
skipped = []
|
|
101
|
+
for project in projects:
|
|
102
|
+
plan = inventory_query_caches(location, project.project_id)
|
|
103
|
+
try:
|
|
104
|
+
result = clean_query_caches(location, project.project_id, plan=plan)
|
|
105
|
+
except ProjectBusyError as exc:
|
|
106
|
+
skipped.append(f"{project.project_id}/cache: {exc}")
|
|
107
|
+
continue
|
|
108
|
+
for entry in result.entries:
|
|
109
|
+
if entry.status == "removed":
|
|
110
|
+
removed += 1
|
|
111
|
+
removed_bytes += entry.removed_bytes
|
|
112
|
+
elif entry.status in ("skipped", "error"):
|
|
113
|
+
skipped.append(f"{project.project_id}/{entry.path}: {entry.reason}")
|
|
114
|
+
return CacheCleanResult(len(projects), removed, removed_bytes, tuple(skipped))
|
|
@@ -5,6 +5,7 @@ from typing import Annotated
|
|
|
5
5
|
import typer
|
|
6
6
|
|
|
7
7
|
from tabalyst import __version__
|
|
8
|
+
from tabalyst.cli.cache import cache_app
|
|
8
9
|
from tabalyst.cli.report import report_command
|
|
9
10
|
from tabalyst.cli.sample import sample_command
|
|
10
11
|
from tabalyst.cli.scan import scan_command
|
|
@@ -41,6 +42,7 @@ def root(
|
|
|
41
42
|
app.command("report")(report_command)
|
|
42
43
|
app.command("sample")(sample_command)
|
|
43
44
|
app.command("scan")(scan_command)
|
|
45
|
+
app.add_typer(cache_app, name="cache")
|
|
44
46
|
|
|
45
47
|
|
|
46
48
|
def main() -> None:
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""CLI adapter for disposable project cache cleanup."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Annotated
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from tabalyst.cache_service import cache_info, clean_cache
|
|
9
|
+
from tabalyst.cli.terminal import error_exit_code
|
|
10
|
+
from tabalyst.errors import TabalystError
|
|
11
|
+
|
|
12
|
+
cache_app = typer.Typer(help="Manage disposable project caches.")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _count(value: int, singular: str) -> str:
|
|
16
|
+
if value == 1:
|
|
17
|
+
return f"{value} {singular}"
|
|
18
|
+
plural = singular.removesuffix("y") + "ies" if singular.endswith("y") else singular + "s"
|
|
19
|
+
return f"{value} {plural}"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@cache_app.command("info")
|
|
23
|
+
def info_command(
|
|
24
|
+
source: Annotated[
|
|
25
|
+
Path | None,
|
|
26
|
+
typer.Argument(help="CSV file whose project cache should be inspected."),
|
|
27
|
+
] = None,
|
|
28
|
+
) -> None:
|
|
29
|
+
"""Show the size and directory count of all caches or one source cache."""
|
|
30
|
+
try:
|
|
31
|
+
result = cache_info(source)
|
|
32
|
+
except TabalystError as exc:
|
|
33
|
+
typer.echo(f"Error: {exc}", err=True)
|
|
34
|
+
raise typer.Exit(error_exit_code(exc)) from exc
|
|
35
|
+
typer.echo(
|
|
36
|
+
f"Cache: {_count(result.directories, 'directory')} in "
|
|
37
|
+
f"{_count(result.projects, 'project')} "
|
|
38
|
+
f"({result.logical_bytes:,} bytes)."
|
|
39
|
+
)
|
|
40
|
+
for warning in result.skipped:
|
|
41
|
+
typer.echo(f"Retained: {warning}")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@cache_app.command("clean")
|
|
45
|
+
def clean_command(
|
|
46
|
+
source: Annotated[
|
|
47
|
+
Path | None,
|
|
48
|
+
typer.Argument(help="CSV file whose project cache should be cleaned."),
|
|
49
|
+
] = None,
|
|
50
|
+
) -> None:
|
|
51
|
+
"""Clean all project caches, or the cache for one source file."""
|
|
52
|
+
try:
|
|
53
|
+
result = clean_cache(source)
|
|
54
|
+
except TabalystError as exc:
|
|
55
|
+
typer.echo(f"Error: {exc}", err=True)
|
|
56
|
+
raise typer.Exit(error_exit_code(exc)) from exc
|
|
57
|
+
typer.echo(
|
|
58
|
+
f"Cleaned {_count(result.removed, 'cache directory')} in "
|
|
59
|
+
f"{_count(result.projects, 'project')} "
|
|
60
|
+
f"({result.removed_bytes:,} bytes).",
|
|
61
|
+
err=True,
|
|
62
|
+
)
|
|
63
|
+
for warning in result.skipped:
|
|
64
|
+
typer.echo(f"Warning: retained {warning}", err=True)
|
|
@@ -58,6 +58,8 @@ def _print_batch_result(
|
|
|
58
58
|
typer.echo(" " + " | ".join(details), err=True)
|
|
59
59
|
|
|
60
60
|
for success in batch.successes:
|
|
61
|
+
for warning in success.warnings:
|
|
62
|
+
typer.echo(f"Warning [{success.job.source}]: {warning}", err=True)
|
|
61
63
|
if success.source_checked is False:
|
|
62
64
|
typer.echo(
|
|
63
65
|
f"Warning [{success.job.source}]: source "
|
|
@@ -128,6 +130,13 @@ def report_command(
|
|
|
128
130
|
bool,
|
|
129
131
|
typer.Option("--force", "-f", help="Replace all existing report artifacts."),
|
|
130
132
|
] = False,
|
|
133
|
+
details: Annotated[
|
|
134
|
+
bool,
|
|
135
|
+
typer.Option(
|
|
136
|
+
"--details/--no-details",
|
|
137
|
+
help="Generate standalone HTML pages for every column (off by default).",
|
|
138
|
+
),
|
|
139
|
+
] = False,
|
|
131
140
|
quiet: Annotated[
|
|
132
141
|
bool,
|
|
133
142
|
typer.Option("--quiet", "-q", help="Suppress success messages."),
|
|
@@ -171,6 +180,7 @@ def report_command(
|
|
|
171
180
|
encoding=encoding,
|
|
172
181
|
config_path=config,
|
|
173
182
|
force=force,
|
|
183
|
+
details=details,
|
|
174
184
|
on_progress=progress,
|
|
175
185
|
from_scan=from_scan,
|
|
176
186
|
workers=workers,
|
|
@@ -76,6 +76,8 @@ def _print_batch_result(
|
|
|
76
76
|
for success in batch.successes:
|
|
77
77
|
if success.result.status == "partial":
|
|
78
78
|
_print_partial(success)
|
|
79
|
+
for warning in success.warnings:
|
|
80
|
+
typer.echo(f"Warning [{success.job.source}]: {warning}", err=True)
|
|
79
81
|
for failure in batch.failures:
|
|
80
82
|
typer.echo(f"Error [{failure.job.source}]: {failure.error}", err=True)
|
|
81
83
|
|
|
@@ -180,6 +182,7 @@ def scan_command(
|
|
|
180
182
|
force=force,
|
|
181
183
|
on_progress=progress,
|
|
182
184
|
workers=workers,
|
|
185
|
+
project_storage=True,
|
|
183
186
|
)
|
|
184
187
|
except TabalystError as exc:
|
|
185
188
|
typer.echo(f"Error: {exc}", err=True)
|
|
@@ -1,14 +1,28 @@
|
|
|
1
|
-
"""Serializable report profile (revision
|
|
1
|
+
"""Serializable report profile (revision 9), independent from presentation.
|
|
2
2
|
|
|
3
3
|
Built from a Tabalyst Scan result by ``report_profile.py``.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
from datetime import datetime
|
|
7
|
-
from typing import Literal
|
|
7
|
+
from typing import Any, Literal
|
|
8
8
|
|
|
9
9
|
from pydantic import BaseModel, ConfigDict, FiniteFloat
|
|
10
10
|
|
|
11
11
|
from tabalyst.report_config import ReportConfig
|
|
12
|
+
from tabalyst.scanner.models import (
|
|
13
|
+
Adaptive,
|
|
14
|
+
BooleanCounts,
|
|
15
|
+
Coverage,
|
|
16
|
+
Evidence,
|
|
17
|
+
Missing,
|
|
18
|
+
Presence,
|
|
19
|
+
StringCategories,
|
|
20
|
+
StringCharacteristics,
|
|
21
|
+
StringLengths,
|
|
22
|
+
TemporalStats,
|
|
23
|
+
ValueAt,
|
|
24
|
+
)
|
|
25
|
+
from tabalyst.scanner.models import NumericStats as ScanNumericStats
|
|
12
26
|
|
|
13
27
|
|
|
14
28
|
class ResultModel(BaseModel):
|
|
@@ -176,6 +190,29 @@ class DetectorProfile(ResultModel):
|
|
|
176
190
|
ambiguous_count: int
|
|
177
191
|
invalid_count: int
|
|
178
192
|
formats: list[DetectorFormat]
|
|
193
|
+
coverage: Coverage | None = None
|
|
194
|
+
evidence: Evidence | None = None
|
|
195
|
+
details: dict[str, Any] = {}
|
|
196
|
+
adaptive: Adaptive | None = None
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class ScanFieldDetails(ResultModel):
|
|
200
|
+
"""Useful bounded field evidence retained from the exposed Scan document."""
|
|
201
|
+
|
|
202
|
+
first_record: int | None
|
|
203
|
+
occurrences: int
|
|
204
|
+
value_count: int
|
|
205
|
+
presence: Presence
|
|
206
|
+
native_types: dict[str, int]
|
|
207
|
+
strings: StringCategories
|
|
208
|
+
missing: Missing
|
|
209
|
+
first: ValueAt | None
|
|
210
|
+
last: ValueAt | None
|
|
211
|
+
string_characteristics: StringCharacteristics
|
|
212
|
+
string_lengths: StringLengths | None
|
|
213
|
+
numeric: ScanNumericStats | None
|
|
214
|
+
booleans: BooleanCounts | None
|
|
215
|
+
temporal: TemporalStats | None
|
|
179
216
|
|
|
180
217
|
|
|
181
218
|
class ColumnProfile(ResultModel):
|
|
@@ -208,6 +245,7 @@ class ColumnProfile(ResultModel):
|
|
|
208
245
|
numeric: NumericStats | None = None
|
|
209
246
|
# Detectors that recognized values, in scan order; failed ones included.
|
|
210
247
|
detectors: list[DetectorProfile] = []
|
|
248
|
+
scan_details: ScanFieldDetails | None = None
|
|
211
249
|
|
|
212
250
|
|
|
213
251
|
class DatasetSummary(ResultModel):
|
|
@@ -378,7 +416,7 @@ class DatasetProfile(ResultModel):
|
|
|
378
416
|
|
|
379
417
|
class ReportProfile(ResultModel):
|
|
380
418
|
format_version: Literal["0.1.0a"] = "0.1.0a"
|
|
381
|
-
format_revision: Literal[
|
|
419
|
+
format_revision: Literal[9] = 9
|
|
382
420
|
generated_at: datetime
|
|
383
421
|
processing_seconds: FiniteFloat
|
|
384
422
|
source: SourceInfo
|