Utilities & Helpers¶
taters.helpers.feature_gather ¶
AggregationPlan
dataclass
¶
AggregationPlan(
group_by,
per_file=True,
stats=("mean", "std"),
exclude_cols=(),
include_regex=None,
exclude_regex=None,
dropna=False,
)
Plan describing how numeric feature columns should be aggregated.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
group_by
|
Sequence[str]
|
One or more column names used as grouping keys (e.g., |
required |
per_file
|
bool
|
If True, include |
True
|
stats
|
Sequence[str]
|
Statistical reductions to compute for each numeric feature column.
Values are passed to |
("mean", "std")
|
exclude_cols
|
Sequence[str]
|
Columns to drop before filtering/selecting numeric features (e.g., timestamps or free text). |
()
|
include_regex
|
str or None
|
Optional regex; if provided, only columns matching this pattern are kept
(after excluding |
None
|
exclude_regex
|
str or None
|
Optional regex; if provided, columns matching this pattern are removed
(after applying |
None
|
dropna
|
bool
|
Whether to drop rows with NA in any of the group-by keys before grouping. The default keeps them, so rows with a missing key land in their own clearly-labeled group instead of vanishing from the output. |
False
|
Notes
This plan is consumed by :func:aggregate_features. Column filtering happens
before numeric selection; only columns that remain and can be coerced to numeric
will be aggregated.
aggregate_features ¶
aggregate_features(
*,
root_dir,
pattern="*.csv",
recursive=True,
delimiter=",",
encoding="utf-8-sig",
add_source_path=False,
plan,
out_csv=None,
overwrite_existing=False,
verbose=True,
on_progress=None
)
Discover files, read, concatenate, and aggregate numeric columns per plan.
This function consolidates CSVs from a single folder, filters columns,
coerces candidate features to numeric, groups by the specified keys,
and computes the requested statistics. Output columns for aggregated
features are flattened with the pattern "{column}__{stat}".
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root_dir
|
PathLike
|
Folder containing per-item CSVs, or a single CSV file. |
required |
pattern
|
str
|
Glob pattern for selecting files. |
"*.csv"
|
recursive
|
bool
|
Recurse into subdirectories when True. |
True
|
delimiter
|
str
|
CSV delimiter. |
","
|
encoding
|
str
|
CSV encoding for read/write. |
"utf-8-sig"
|
add_source_path
|
bool
|
If True, include absolute path in |
False
|
plan
|
AggregationPlan
|
Aggregation configuration (group keys, stats, filters, NA handling). |
required |
out_csv
|
PathLike or None
|
Output path. If None, defaults to
|
None
|
overwrite_existing
|
bool
|
If False and |
False
|
Returns:
| Type | Description |
|---|---|
Path
|
Path to the written CSV of aggregated features. |
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If no files match the pattern under |
RuntimeError
|
If files were found but none could be read successfully. |
ValueError
|
If required group-by columns are missing,
or if no numeric columns remain after filtering,
or if per-file grouping is requested but the |
Notes
Group keys are preserved as leading columns in the output. The output places
"source" (and optionally "source_path") first when present.
Source code in src\taters\helpers\feature_gather.py
649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 | |
feature_gather ¶
feature_gather(
*,
root_dir,
pattern="*.csv",
recursive=True,
delimiter=",",
encoding="utf-8-sig",
add_source_path=False,
aggregate=False,
plan=None,
group_by=None,
per_file=True,
stats=("mean", "std"),
exclude_cols=(),
include_regex=None,
exclude_regex=None,
dropna=False,
out_csv=None,
overwrite_existing=False,
verbose=True,
on_progress=None
)
Single entry point to concatenate or aggregate feature CSVs from one folder.
If aggregate=False, CSVs are concatenated with origin metadata
(see :func:gather_csvs_to_one). If aggregate=True, numeric feature
columns are aggregated per the provided or constructed plan
(see :func:aggregate_features).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root_dir
|
PathLike
|
Folder containing per-item CSVs (or a single CSV file). |
required |
pattern
|
str
|
Glob pattern for selecting CSV files. |
"*.csv"
|
recursive
|
bool
|
Recurse into subdirectories when True. |
True
|
delimiter
|
str
|
CSV delimiter. |
","
|
encoding
|
str
|
CSV encoding. |
"utf-8-sig"
|
add_source_path
|
bool
|
If True, include a |
False
|
aggregate
|
bool
|
Toggle aggregation mode. If False, files are concatenated. |
False
|
plan
|
AggregationPlan or None
|
Explicit plan for aggregation. Required if |
None
|
group_by
|
Sequence[str] or None
|
Quick-plan keys. Used only when |
None
|
per_file
|
bool
|
Quick-plan flag; include |
True
|
stats
|
Sequence[str]
|
Quick-plan statistics to compute per numeric column. |
("mean", "std")
|
exclude_cols
|
Sequence[str]
|
Quick-plan columns to drop before numeric selection. |
()
|
include_regex
|
str or None
|
Quick-plan regex to include feature columns by name. |
None
|
exclude_regex
|
str or None
|
Quick-plan regex to exclude feature columns by name. |
None
|
dropna
|
bool
|
Quick-plan NA handling for group keys. When True, rows whose group key is missing are dropped before aggregating. |
False
|
out_csv
|
PathLike or None
|
Output CSV path. If None, defaults to
|
None
|
overwrite_existing
|
bool
|
If False and |
False
|
Returns:
| Type | Description |
|---|---|
Path
|
Path to the resulting CSV. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
See Also
gather_csvs_to_one : Concatenate CSVs with origin metadata. aggregate_features : Aggregate numeric columns according to a plan.
Source code in src\taters\helpers\feature_gather.py
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 | |
gather_csvs_to_one ¶
gather_csvs_to_one(
*,
root_dir,
pattern="*.csv",
recursive=True,
delimiter=",",
encoding="utf-8-sig",
add_source_path=False,
out_csv=None,
overwrite_existing=False,
verbose=True,
on_progress=None
)
Concatenate many CSVs into a single CSV with origin metadata.
Each input CSV is loaded (all columns as object dtype), a leading
"source" column is inserted (and optionally "source_path"), and
rows are appended. The final CSV ensures "source" (and, if present,
"source_path") lead the column order.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root_dir
|
PathLike
|
Folder containing CSVs, or a single CSV file. |
required |
pattern
|
str
|
Glob pattern for selecting files. |
"*.csv"
|
recursive
|
bool
|
Recurse into subdirectories when True. |
True
|
delimiter
|
str
|
CSV delimiter. |
","
|
encoding
|
str
|
CSV encoding for read/write. |
"utf-8-sig"
|
add_source_path
|
bool
|
If True, include absolute path in |
False
|
out_csv
|
PathLike or None
|
Output path. If None, defaults to
|
None
|
overwrite_existing
|
bool
|
If False and |
False
|
Returns:
| Type | Description |
|---|---|
Path
|
Path to the written CSV. |
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If no files match the pattern under |
RuntimeError
|
If files were found but none could be read successfully. |
Notes
Input rows are not type-coerced beyond object dtype. Column order from inputs is preserved after the leading origin columns.
Source code in src\taters\helpers\feature_gather.py
406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 | |
make_plan ¶
make_plan(
*,
group_by,
per_file=True,
stats=("mean", "std"),
exclude_cols=(),
include_regex=None,
exclude_regex=None,
dropna=False
)
Create an :class:AggregationPlan from simple arguments.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
group_by
|
Sequence[str]
|
Grouping key(s) to use (e.g., |
required |
per_file
|
bool
|
If True, group within files by including |
True
|
stats
|
Sequence[str]
|
Statistical reductions to compute per numeric column. |
("mean", "std")
|
exclude_cols
|
Sequence[str]
|
Columns to drop prior to feature selection. |
()
|
include_regex
|
str or None
|
Regex to include feature columns by name. |
None
|
exclude_regex
|
str or None
|
Regex to exclude feature columns by name. |
None
|
dropna
|
bool
|
Drop rows with NA in any group key. Off by default so rows with a missing key are still reported rather than silently discarded. |
False
|
Returns:
| Type | Description |
|---|---|
AggregationPlan
|
A configured plan instance for :func: |
Source code in src\taters\helpers\feature_gather.py
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 | |
taters.helpers.find_files ¶
find_files ¶
find_files(
root_dir,
*,
file_type="video",
extensions=None,
recursive=True,
follow_symlinks=False,
include_hidden=False,
include_globs=None,
exclude_globs=None,
absolute=True,
sort=True,
ffprobe_verify=False
)
Discover media files under a folder using smart, FFmpeg-friendly filters.
You can either (a) choose a built-in group of extensions via file_type
("audio"|"video"|"image"|"subtitle"|"archive"|"any") or (b) pass an explicit
list of extensions to match. Matching is case-insensitive; dots are optional
(e.g., ".wav" and "wav" are equivalent). Hidden files and directories are
excluded by default.
For audio/video, ffprobe_verify=True additionally checks that at least one
corresponding stream is present (e.g., exclude MP4s with no audio when
file_type="audio"). This is slower but robust when your dataset contains
“container only” files.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root_dir
|
str | PathLike
|
Folder to scan. |
required |
file_type
|
str
|
Built-in group selector. Ignored if |
'video'
|
extensions
|
Optional[Sequence[str]]
|
Explicit extensions to include (e.g., |
None
|
recursive
|
bool
|
Recurse into subfolders. Default: |
True
|
follow_symlinks
|
bool
|
Follow directory symlinks during traversal. Default: |
False
|
include_hidden
|
bool
|
Include dot-files and dot-dirs. Default: |
False
|
include_globs
|
Optional[Sequence[str]]
|
Additional glob filters applied after extension filtering; |
None
|
absolute
|
bool
|
Return absolute paths when |
True
|
sort
|
bool
|
Sort lexicographically (case-insensitive). Default: |
True
|
ffprobe_verify
|
bool
|
For |
False
|
Returns:
| Type | Description |
|---|---|
list[Path]
|
The matched files. |
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If |
ValueError
|
If |
Examples:
Find all videos (recursive), as absolute paths:
>>> find_files("dataset", file_type="video")
Use explicit extensions and keep paths relative:
>>> find_files("dataset", extensions=[".wav",".flac"], absolute=False)
Only include files matching a glob and exclude temp folders:
>>> find_files("dataset", file_type="audio",
... include_globs=["**/*session*"], exclude_globs=["**/tmp/**"])
Verify playable audio streams exist:
>>> find_files("dataset", file_type="audio", ffprobe_verify=True)
Source code in src\taters\helpers\find_files.py
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 | |
taters.helpers.text_gather ¶
csv_to_analysis_ready_csv ¶
csv_to_analysis_ready_csv(
*,
csv_path,
out_csv=None,
overwrite_existing=False,
text_cols=None,
id_cols=None,
mode="concat",
group_by=None,
delimiter=None,
encoding=DEFAULT_ENCODING,
joiner=DEFAULT_JOINER,
num_buckets=1024,
max_open_bucket_files=64,
tmp_root=None,
include_id_cols=True,
carry_cols=None,
agg_cols=None,
row_filters=None,
verbose=True,
on_progress=None
)
Stream a (possibly huge) CSV into a compact analysis-ready CSV with a stable schema and optional external grouping.
Output schema
Always writes a header and enforces a consistent column order:
• No grouping:
text_id,text (plus source_col if mode="separate")
• With grouping:
text_id,text,group_count (plus source_col if mode="separate")
carry_cols inserts the named source columns between the identifiers and
text in both shapes.
Where:
- text_id is either the composed ID from id_cols or row_<n> when
id_cols=None.
- mode="concat" joins all text_cols using joiner per row or group.
- mode="separate" emits one row per (row_or_group, text_col) and
fills source_col with the contributing column name.
Grouping at scale
If group_by is provided, the function performs a two-pass external
grouping that does not require presorting:
1) Hash-partition rows to on-disk “bucket” CSVs (bounded writers with LRU).
2) Aggregate each bucket into final rows (concat or separate mode), writing
group_count to record how many pieces contributed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
csv_path
|
PathLike
|
Source CSV with at least the columns in |
required |
out_csv
|
PathLike | None
|
Destination CSV. If |
None
|
overwrite_existing
|
bool
|
If |
False
|
text_cols
|
Sequence[str] | None
|
Text fields to concatenate or emit separately. May be empty or None:
the spreadsheet is then wrangled without any text -- rows are still
combined, counted, summarized and carried -- and the output has no
|
None
|
id_cols
|
Sequence[str] | None
|
Optional columns to compose |
None
|
carry_cols
|
Sequence[str] | None
|
Source columns to copy through to the output untouched. Distinct from
When grouping, a carried column is written only where its value is the same for every row in the group; where rows disagree the cell is blank, because there is no honest single answer and picking the first row's is the kind of quiet wrong answer that survives into a published table. Names in |
None
|
agg_cols
|
Mapping[str, str] | Sequence[str] | None
|
Number columns to summarize per group, e.g. a per-post score becoming
the average score of everything a user wrote. A sequence of names
means "the mean of each"; a mapping picks the statistic per column
from Values that are blank or not parseable as numbers are skipped rather than treated as zero -- a missing score is missing, not 0 -- and a group with no numeric values at all gets a blank cell, for the same NA-is-not-zero reason a disagreeing carried column does. |
None
|
row_filters
|
Sequence[Sequence[object]] | None
|
Which rows to keep, as Only the spreadsheet's own columns, because nothing has been measured yet: a word count is not available here and does not belong here either. What somebody knows at this point is what they collected -- a screener somebody failed, a condition they are not analyzing, an age below the one they meant to study. It matters most when rows are being combined, since a row inside somebody else's joined text cannot be taken out again afterwards; that is why it is applied before the rows are bucketed rather than filtered later. Summaries run over every row of the group, including rows whose
text cells are empty -- Requires |
None
|
mode
|
str
|
|
'concat'
|
group_by
|
Sequence[str] | None
|
Optional list of columns to aggregate by; works on unsorted CSVs. |
None
|
delimiter
|
str | None
|
Parsing/formatting options. If |
None
|
encoding
|
str | None
|
Parsing/formatting options. If |
None
|
joiner
|
str | None
|
Parsing/formatting options. If |
None
|
num_buckets
|
int
|
External grouping controls (partition count, LRU limit, temp root). |
1024
|
max_open_bucket_files
|
int
|
External grouping controls (partition count, LRU limit, temp root). |
1024
|
tmp_root
|
int
|
External grouping controls (partition count, LRU limit, temp root). |
1024
|
include_id_cols
|
bool
|
When not grouping, write the id columns beside |
True
|
on_progress
|
Optional |
None
|
Returns:
| Type | Description |
|---|---|
Path
|
Path to the analysis-ready CSV. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If required columns are missing or |
Examples:
Concatenate two text fields per row:
>>> csv_to_analysis_ready_csv(
... csv_path="transcripts.csv",
... text_cols=["prompt","response"],
... id_cols=["speaker"],
... )
Group by speaker and join rows:
>>> csv_to_analysis_ready_csv(
... csv_path="transcripts.csv",
... text_cols=["text"],
... group_by=["speaker"],
... )
Source code in src\taters\helpers\text_gather.py
299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 | |
resolve_analysis_ready ¶
resolve_analysis_ready(
*,
csv_path=None,
txt_dir=None,
analysis_csv=None,
gathered_csv=None,
text_cols=("text",),
id_cols=None,
mode="concat",
group_by=None,
delimiter=",",
encoding="utf-8-sig",
joiner=" ",
num_buckets=64,
max_open_bucket_files=64,
tmp_root=None,
recursive=True,
pattern="*.txt",
id_from="stem",
include_source_path=False,
overwrite_existing=False,
on_progress=None,
workers=0,
carry_cols=None,
verbose=None
)
Accept an analysis-ready table, or gather one -- the analyzers' front door.
Every text analyzer takes its input three ways: a spreadsheet to gather
from, a folder of documents to gather from, or a table already gathered.
The forty lines that told those apart, announced the gather and called
one of the two gatherers with a dozen forwarded settings were copied into
eleven modules, and had begun to drift (one forwarded verbose and
carry_cols, the others did not). This is that block, once. Settings
recording is unaffected: the provenance decorator reads the analyzer's
own bound arguments, not this function's.
carry_cols and verbose are forwarded to the gatherers only when
given, so the analyzers that never passed them behave exactly as before.
Source code in src\taters\helpers\text_gather.py
999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 | |
txt_folder_to_analysis_ready_csv ¶
txt_folder_to_analysis_ready_csv(
*,
root_dir,
out_csv=None,
recursive=False,
pattern=DOCUMENT_PATTERN,
encoding="utf-8",
id_from="stem",
include_source_path=True,
overwrite_existing=False,
verbose=True,
on_progress=None,
workers=0
)
Stream a folder of documents into an analysis-ready CSV with predictable, reproducible IDs.
Documents are .txt, .docx, .doc and .pdf -- text is
extracted per type by :func:taters.helpers.doc_text.read_document_text
(no OCR: a PDF without a machine-readable text layer has zero text). A
document that cannot be read -- corrupt, password-protected, a legacy
.doc with no way to convert it -- is skipped with a warning naming it,
never allowed to take the whole gather down.
For each readable file matching pattern, the emitted row contains:
- text_id: the basename (stem), full filename, or relative path (see
id_from), and
- text: the extracted text.
- source_path: optional column with path relative to root_dir.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root_dir
|
PathLike
|
Folder containing documents. |
required |
out_csv
|
PathLike | None
|
Destination CSV. If |
None
|
recursive
|
bool
|
Recurse into subfolders. Default: |
False
|
pattern
|
str
|
Glob(s) for matching files; several can be joined with |
DOCUMENT_PATTERN
|
encoding
|
str
|
Decoding for plain-text files. Default: |
'utf-8'
|
workers
|
int
|
Parallel reader processes -- PDF and Word parsing is CPU-bound, and a
big folder reads several times faster in parallel. |
0
|
id_from
|
str
|
How to derive |
'stem'
|
include_source_path
|
bool
|
If |
True
|
overwrite_existing
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
Path
|
Path to the analysis-ready CSV. |
Examples:
>>> txt_folder_to_analysis_ready_csv(root_dir="notes", recursive=True, id_from="path")
Source code in src\taters\helpers\text_gather.py
829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 | |
taters.helpers.library ¶
The library: user-imported assets that outlive any one project.
A "dictionary" here is the general term -- a content-coding dictionary and an
archetype dictionary are different kinds, with different formats, consumed by
different modules. The library stores each kind in its own folder under the
user's home, so importing a dictionary once makes it available to every
pipeline on the machine, wherever it is run from. That replaces the old
convention of a dictionaries/ folder relative to the working directory,
which silently failed for anyone who did not happen to have one.
No UI imports (the :mod:taters.helpers.gpu pattern): the wizard is one
consumer, but the runner or a future GUI can read the same library. All the
operations are deliberately file-level and boring -- an entry is its file,
its name is the filename stem -- so a user can also just manage the folder by
hand and nothing here will be surprised.
Adding a new kind -- pretrained classifier models are the expected next one --
is a :data:KINDS entry plus a library= line on the recipe that consumes
it; the manager, the picker, and the empty-library contingency in the wizard
all key off those two declarations.
LibraryCollision ¶
LibraryCollision(existing)
Bases: Exception
An import would overwrite an entry that already exists.
Raised instead of overwriting so the UI can ask replace-or-rename; the
existing path rides along as .existing.
Source code in src\taters\helpers\library.py
351 352 353 | |
LibraryKind
dataclass
¶
LibraryKind(
id,
label,
help,
suffixes,
deep_check=None,
describe_entry=None,
)
One category of importable asset.
Attributes:
| Name | Type | Description |
|---|---|---|
id |
str
|
Stable identifier; also the folder name under the library. |
label, help |
str
|
What menus call it, and one sentence including the accepted formats. |
suffixes |
tuple of str
|
File extensions this kind accepts, lowercase with the dot. |
asset_problem ¶
asset_problem(path, kind=None)
Why this file cannot work as a library asset, or "" when it can.
The canonical check, shared by import (refuse the file while the user is holding it and can act) and by the analyzers (a file can still arrive by CLI path without ever passing through the library). Two layers:
- cheap shape heuristics, always: an empty file is not a dictionary, and a
.dicwith no%...%category header is a bare word list -- the mistake that made contentcoder die with a bare "list index out of range" mid-run, an hour after it was made; - with a
kind, that kind's :attr:~LibraryKind.deep_check-- the real parser. Import passes the kind; the analyzers do not, because they construct the real parser on the very next line and asking it twice buys nothing.
Source code in src\taters\helpers\library.py
770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 | |
delete ¶
delete(kind, name)
Remove an entry and the weights it carries. Permanent, so the UI confirms before calling this.
Source code in src\taters\helpers\library.py
920 921 922 923 924 925 926 927 | |
display_name ¶
display_name(path)
How an entry is named in menus: the filename stem.
Source code in src\taters\helpers\library.py
551 552 553 | |
entries ¶
entries(kind)
Every entry of this kind, sorted by name. Only the kind's own formats count -- a stray .txt dropped into the folder by hand is ignored, not an error.
Source code in src\taters\helpers\library.py
537 538 539 540 541 542 543 544 545 546 547 548 | |
expand ¶
expand(kind, values)
Resolve a mixed list of files and folders to this kind's files.
A folder means "everything of this kind inside it, recursively" -- the reading the analyzers give a folder path, and what the wizard's default writes into a preset (the kind's whole library folder). Any screen seeded through this shows exactly what a run would use; seeding from the raw values made the picker intersect a folder path against entry file paths and open with everything unticked while the settings row said "all 10".
Source code in src\taters\helpers\library.py
556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 | |
export_to ¶
export_to(kind, name, dest_dir, *, replace=False)
Copy an entry out, keeping its filename. Returns the new path.
Raises:
| Type | Description |
|---|---|
LibraryCollision
|
When the destination file already exists and |
Source code in src\taters\helpers\library.py
859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 | |
find_payload ¶
find_payload(kind, name, digests)
A payload in the library whose files match the digests a manifest records.
A model carried into another run travels as its manifest text only (see
pipelines.run_pipeline._materialize_assets); the weights stay in the
library. The manifest records payload_digests -- sha256 per file --
so the loader can find them here and prove they are the same bytes. The
match is by content, not by name: a renamed library entry still counts.
name is the payload's suffix-bearing name, used to skip payloads of
the wrong shape quickly.
Source code in src\taters\helpers\library.py
727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 | |
import_into ¶
import_into(kind, src, *, replace=False)
Copy a file into the library.
Raises:
| Type | Description |
|---|---|
ValueError
|
For a format the kind does not accept -- with the formats it does,
because "wrong extension" without the right ones is a dead end -- or
for a file the kind's own parser cannot load (see
:func: |
LibraryCollision
|
When an entry with this name exists and |
Source code in src\taters\helpers\library.py
805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 | |
kind_by_id ¶
kind_by_id(kind_id)
One kind, or a KeyError that names the valid ids (it is always a typo).
Source code in src\taters\helpers\library.py
356 357 358 359 360 361 362 363 | |
kind_dir ¶
kind_dir(kind)
This kind's folder, created and kept level with what the package ships.
Seeding used to happen only when the folder did not exist, which meant a
release that added a dictionary reached new users and nobody else. So it
now reconciles file by file against a ledger of what we have installed
before (see :func:_seed), and an upgrade brings its new and corrected
built-ins to a library that has been in use for years.
What it will never do is undo a decision: a built-in the user deleted stays deleted, and a copy they edited stays edited. An empty library is a valid state the UI explains, not one this quietly "repairs."
Source code in src\taters\helpers\library.py
377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 | |
library_home ¶
library_home()
Where the library lives: $TATERS_HOME/library, or ~/.taters/library.
The environment override exists so tests -- and anyone who wants their library on a different drive -- can move the whole thing without patching.
Source code in src\taters\helpers\library.py
366 367 368 369 370 371 372 373 374 | |
model_files ¶
model_files(folder)
The .json manifests under a folder, without the ones inside a payload.
What a step handed "a folder" resolves to, and what the finish screen
scans a whole run folder with. Only .json files are walked -- a run
over thousands of recordings holds tens of thousands of WAVs and CSVs,
and listing every one of them to find a handful of manifests made the
finish screen wait on a network drive. A payload declared by any
manifest found is pruned, wherever in the tree it sits.
Source code in src\taters\helpers\library.py
651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 | |
payload_dirs ¶
payload_dirs(folder)
Every payload path declared by the manifests directly in folder.
Source code in src\taters\helpers\library.py
618 619 620 621 622 623 624 625 626 | |
payload_of ¶
payload_of(entry)
The sibling files and folders a .json entry declares as its payload.
Empty for anything that is not a JSON manifest with a payload list.
Names are taken as siblings only: a name with a path separator, or a
dot entry, is ignored rather than allowed to point outside the folder.
Source code in src\taters\helpers\library.py
591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 | |
payload_size ¶
payload_size(entry)
Bytes of an entry and everything it carries.
Source code in src\taters\helpers\library.py
713 714 715 716 717 718 719 720 721 722 723 724 | |
rename ¶
rename(kind, old, new)
Rename an entry, keeping its suffix.
The suffix carries the format, which renaming must not be able to lie about -- so a new name arriving with an extension has it stripped rather than honored.
Source code in src\taters\helpers\library.py
886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 | |
taters.helpers.model_spec ¶
One description of a saved model, whatever kind it is.
Taters writes several kinds of reusable instrument -- a MEM topic model, a ridge regression, a classifier -- and they were each self-describing to their own scoring function and to nothing else. That was fine while each kind had its own menu entry, and stops being fine the moment a screen wants to offer models as a category: to list them it has to say what each one is, to score with one it has to know whether the thing needs text or features, and to write the results it has to know what columns will appear.
So each kind registers those three answers here, and every screen asks this module instead of sniffing JSON keys for itself. The alternative -- a per-kind branch at each call site -- is the shape where adding a fourth kind means finding all of them.
Adding a kind of model
Everything a screen or a scoring run needs to know about a model comes from this registry, so a new kind -- a fine-tuned transformer, a gradient-boosted tree, whatever comes next -- is added in one place:
- Give it a
kindtag in the file it writes ("taters-<x>-model") and aformatinteger. -
Add a :class:
ModelTypeto :data:MODEL_TYPESstating: -
needs--"text"if it reads text and re-derives its own instrument (the MEM pattern),"features"if it reads feature columns someone else measured. That one word decides whether the settings-provenance gate applies to it, and nothing else has to know. read-- doc ->(outputs, columns, inputs), so listings can say what it is and what it will add.score--"module:function"for applying it. Omit this and scoring refuses by name instead of guessing.- a
"payload"list in the model file -- the sibling weights the library moves with it (seehelpers.library.payload_of) write_outputs-- if its output columns can be renamed at import.classes-- doc ->{outcome: [class, ...]}when it predicts categories, so a class can be relabeled per model ("0"->"control") and every applier writes the label through :func:class_label.-
apply_settings-- the defaults that govern how it is applied to new data (batch size, weighting, whether to emit probabilities). They live in the model file'sapplyblock, editable per model in Settings, and an applier reads them through :func:apply_defaultsso a pipeline gets the model's own settings unless a call overrides them. -
Add a
_load_model-style gate in the module that owns it, and register it in :func:taters.helpers.library._model_problemso the import screen is exactly as strict as run time.
Nothing else needs editing. In particular the scoring entry point, the library folder, the naming flow, the wizard row and the provenance gate are all written against this registry rather than against the three kinds that happen to exist today.
Naming
A model file is named by whoever exported it, which is nearly always the
step's own output name (ridge__all__age.json), and that says nothing about
what it predicts. So a model carries a name the researcher chose, and
output names for the columns it will write: a ridge fitted to predict
age on a blog corpus should be able to land in a new dataset as
pred_age_blogs, not as pred_age colliding with the age already
there. Both are stored in the model file, so they travel with it.
ApplySetting
dataclass
¶
ApplySetting(
default, help, kind="str", choices=None, validate=None
)
One default that governs how a kind of model is applied to new data.
Stored per model in the file's apply block and read back through
:func:apply_defaults, so a model carries its own settings wherever it
goes -- a word-vector model that should weight by types, a predictor
that fits a small card at batch 8 -- rather than every pipeline having
to know. kind says how a typed-in value is read: "int",
"float", "bool", "str", or "text" for free text that is
not one of a fixed set. choices restricts a str to a list.
coerce ¶
coerce(value, name)
The value as this setting stores it, or a refusal naming both.
Source code in src\taters\helpers\model_spec.py
230 231 232 233 234 235 | |
FeaturePlan
dataclass
¶
FeaturePlan(
model,
slug,
tables=(),
problems=(),
path="",
controls=(),
)
Everything a run needs in order to score with one model, as plain data.
Deliberately free of paths, recipes and pipeline templates: it is built from the model file alone, so the composer stays pure and a test can hand a synthetic plan straight in.
problems is the honest half. A plan with problems cannot be replayed,
and each entry is a sentence a researcher can act on -- which is better
than a partial replay, because a partial replay is a wrong answer.
ModelInfo
dataclass
¶
ModelInfo(
path,
type_id,
type_label,
name,
outputs,
columns,
inputs,
needs,
needs_tables=(),
provenance=dict(),
library_kind=LIBRARY_KIND,
bulk_outputs=False,
controls=(),
zero_when_absent=(),
classes=dict(),
class_names=dict(),
apply=dict(),
modality="text",
)
What one saved model is, in the terms a screen needs.
Attributes:
| Name | Type | Description |
|---|---|---|
path |
Path
|
The file itself. |
type_id |
str
|
Short kind key: |
type_label |
str
|
What to show a person: |
name |
str
|
The researcher's name for the model, defaulting to the file stem. |
outputs |
tuple of str
|
The output labels, after any renaming -- one per predicted outcome, or one per theme. |
columns |
tuple of str
|
The column names those outputs will actually produce, which is not the same list: a classifier writes a predicted class and a probability per class for one outcome. |
inputs |
tuple of str
|
Feature columns the model needs by name. Empty when the model works from text. |
needs |
str
|
|
needs_tables |
tuple of str
|
Which feature tables, named by the step that writes them. A model records its predictors by name, which is enough to score a table that already has them and no help at all in getting there: someone who fitted on cohesion features and came back a week later had 165 column names and nothing saying a cohesion step was needed. |
library_kind |
str
|
The :data: |
bulk_outputs |
bool
|
True when the outputs are a numbered family (MEM themes) rather than a handful of named quantities, so renaming them means choosing one prefix rather than editing a hundred labels. |
n_outputs |
int
|
How many outputs there are, for a listing that does not want to print a hundred theme names. |
display ¶
display()
age_blogs [ridge] -- the one-line form every menu uses.
Source code in src\taters\helpers\model_spec.py
203 204 205 | |
ModelType
dataclass
¶
ModelType(
id,
label,
kind_tag,
needs,
read,
score=None,
write_outputs=None,
bulk_outputs=False,
classes=None,
apply_settings=dict(),
modality="text",
)
One kind of model, and how to read and rewrite its names.
TablePlan
dataclass
¶
TablePlan(
stem,
target,
instrument,
assets,
digest,
grain,
replay=None,
)
One feature table a model needs, and exactly how to measure it.
Attributes:
| Name | Type | Description |
|---|---|---|
stem |
str
|
The table's name at fit time. Load-bearing: the stem is what fixed the model's predictor names, so a replayed table has to be written under it or the names stop matching. |
target |
str
|
|
instrument |
dict
|
The measuring settings, as literal values. Complete, because they were recorded after defaults were applied -- which is the whole reason a replay is constructible rather than a guess. |
assets |
dict
|
|
digest |
str
|
The instrument digest. Belongs in the private output directory: two models needing identical settings then share one extraction for free, and a re-fit under the same model name cannot short-circuit onto the previous private table. |
grain |
dict
|
What one row was at fit time. Informational only -- never compared. |
replay |
dict or None
|
|
UnknownModel ¶
UnknownModel(message, kind=None)
Bases: Exception
The file is not a saved Taters model, or is a kind this build cannot
describe. Carries the kind tag it did have, when it had one.
Source code in src\taters\helpers\model_spec.py
102 103 104 | |
apply_defaults ¶
apply_defaults(doc)
How a model wants to be applied: registry defaults under its own.
Only the settings its kind registers are read, each coerced to its
type; a key the file carries from a newer Taters is ignored rather than
passed on. An applier takes apply_defaults(doc)[key] wherever its
caller left the argument as None, so the model's own settings win over
the function's signature but never over an explicit call.
Source code in src\taters\helpers\model_spec.py
332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 | |
class_label ¶
class_label(doc, outcome, cls)
The label written for one predicted class: its relabel, or itself.
A model fitted on a condition column coded 0/1 predicts "0" and
"1" -- correct, and unreadable in a results table a month later.
The relabel lives in the model file (class_names), keyed by the
class as fitted, and every applier writes predictions and the
p_<outcome>_<class> columns through this one function, so a model
relabeled in Settings is relabeled however it is invoked.
Source code in src\taters\helpers\model_spec.py
316 317 318 319 320 321 322 323 324 325 326 327 328 329 | |
describe ¶
describe(model_json)
Describe one saved model, or say why it cannot be described.
Deliberately cheap and structural: it reads the file's own declaration of what it is and does not vet the matrices. Vetting is the scoring loader's job (and the library import gate delegates to it), because a listing that had to fully validate every model would be slow and would hide a damaged model behind a blank row instead of naming it.
Source code in src\taters\helpers\model_spec.py
670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 | |
describe_all ¶
describe_all(paths)
Describe every model that can be described, skipping the rest silently.
Used by listings, where one unreadable file in a library folder must not take the whole menu down -- the import gate already refused anything broken, so a file that fails here arrived some other way.
Source code in src\taters\helpers\model_spec.py
714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 | |
describe_encoder ¶
describe_encoder(model_json)
One encoder's row: its name and where it came from, then what training did.
Source code in src\taters\helpers\model_spec.py
1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 | |
edit_model ¶
edit_model(
model_json,
*,
name=None,
outputs=None,
prefix=None,
class_names=None,
apply=None
)
Change what a model is called, what it writes, and how it is applied.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model_json
|
PathLike
|
The model file, rewritten in place (atomically). |
required |
name
|
Optional[str]
|
The researcher's name for the model. Shown in every menu. |
None
|
outputs
|
Optional[Sequence[str]]
|
One new label per output, in the order :func: |
None
|
prefix
|
Optional[str]
|
For a model whose outputs are a numbered family (MEM themes), the
stem to number from: |
None
|
class_names
|
Optional[Mapping[str, Mapping[str, str]]]
|
|
None
|
apply
|
Optional[Mapping[str, object]]
|
Values for the settings its kind registers (:attr: |
None
|
Returns:
| Type | Description |
|---|---|
ModelInfo
|
The model as it now reads. |
Notes
Every edit rewrites the file rather than a sidecar, so a model that is copied, zipped or emailed keeps the names and settings it was given -- a sidecar would be left behind by every one of those, and the columns would quietly revert.
Source code in src\taters\helpers\model_spec.py
731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 | |
encoder_problem ¶
encoder_problem(model_json)
Why this file is not a usable text encoder, or "" when it is.
Structural and torch-free: the manifest's kind and format, and a payload folder beside it holding a config, weights and a tokenizer. Loading the weights is the extractor's job, minutes later, on the device it chose.
Source code in src\taters\helpers\model_spec.py
1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 | |
feature_plan ¶
feature_plan(info)
Read a model's own account of the features it was fitted on.
Returns a :class:FeaturePlan. A model that reads text needs no features
and gets an empty plan with no problems -- it re-derives its own
instrument, so there is nothing for a run to arrange.
Source code in src\taters\helpers\model_spec.py
974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 | |
library_kind_for ¶
library_kind_for(model_json)
Which library kind a model file belongs in: encoders for an
adapted encoder, models for anything that scores.
Source code in src\taters\helpers\model_spec.py
1185 1186 1187 1188 1189 1190 1191 1192 | |
models_produced ¶
models_produced(folder)
The model files a run left behind, each with a label for a menu.
Scoring models of every registered kind and adapted encoders alike, found by their manifests (never inside a payload folder), so the finish screen and the training task can offer to add them to the library.
Source code in src\taters\helpers\model_spec.py
1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 | |
one_model_path ¶
one_model_path(model_json)
Exactly one model file, or an actionable refusal.
The library hands steps a folder (or a list) meaning "everything of this
kind", which is right for dictionaries and wrong for a model: scoring
with an unspecified one of three is not a thing anyone means. A single
file passes through, a folder or a list resolves to its .json files,
and anything other than exactly one refuses with the fix named.
Owned here because it is about model files, not about any one kind of model: ridge and the topic model each carried a copy, with different refusal wording and one of them letting a folder through unresolved.
Source code in src\taters\helpers\model_spec.py
1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 | |
output_label ¶
output_label(doc, outcome)
The column-name stem for one outcome: its rename, or the outcome itself.
Read by the scoring functions rather than by the wizard, so a renamed model produces renamed columns however it is invoked -- through the app, through the API, or from the command line.
Source code in src\taters\helpers\model_spec.py
303 304 305 306 307 308 309 310 311 312 313 | |
rename_model ¶
rename_model(
model_json, *, name=None, outputs=None, prefix=None
)
Name a model and its output columns -- :func:edit_model without
the class labels and apply settings. Kept for the callers that only
ever name things.
Source code in src\taters\helpers\model_spec.py
859 860 861 862 863 864 865 | |
scorer ¶
scorer(type_id)
The function that scores a new table with this kind of model.
Resolved from the registry rather than chosen at the call site, so that
adding a kind of model is adding a registry entry -- and forgetting the
entry is this refusal, rather than the model being quietly scored by
whichever applier happened to be the else branch.
Source code in src\taters\helpers\model_spec.py
635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 | |
slug ¶
slug(name, fallback='model')
Filesystem- and template-safe: artifact references split on . and
:, and a save_as built from this must not contain either.
One spelling for every model-shaped file name. Three private copies had grown -- here, in ridge (fallback "set") and in score_model -- and two of them kept the dot the third refused, so the same set name could produce two different file names depending on which module wrote it.
Source code in src\taters\helpers\model_spec.py
1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 | |
taters.helpers.settings ¶
Persistent user settings, and the one that matters today: where downloaded models are kept.
Taters has a home folder ($TATERS_HOME or ~/.taters) for the
library; this module puts a small settings.json beside it for choices
that should outlive a session and are not about any one pipeline. The first
such choice is the model cache: every transformer, sentence-transformers
and Whisper model is downloaded by the Hugging Face hub library into its
cache, which lives under the user's home by default. On a shared server
that is the wrong place -- one copy per user of a 500 MB encoder, on a home
partition that is small on purpose -- and the fix has been an environment
variable that has to be set in every shell. Here it is a setting, chosen
once in Settings and applied every time Taters starts, before any of those
libraries is imported.
Resolution order for the model cache, most explicit first:
TATERS_MODEL_CACHEin the environment (an administrator's or a test's word, and the one thing that beats a saved setting);model_cacheinsettings.json(the user's choice in Settings);HF_HUB_CACHEorHF_HOMEfrom the environment (the hub library's own conventions, honored as they always were);- the hub library's default,
~/.cache/huggingface/hub.
Whichever wins is exported as HF_HUB_CACHE at import (see
:func:apply_model_cache), so the hub library, transformers,
sentence-transformers and faster-whisper all download to and read from the
same place -- and it is passed explicitly to the transformer steps too, in
case those libraries were imported before Taters was.
apply_model_cache ¶
apply_model_cache()
Export the chosen cache as HF_HUB_CACHE so every downloading library
agrees with it. Called when Taters is imported. Returns the path when a
Taters-level choice (environment or setting) was applied, else None --
the hub library's own environment is left exactly as found.
Source code in src\taters\helpers\settings.py
164 165 166 167 168 169 170 171 172 173 174 175 176 177 | |
clear_setting ¶
clear_setting(key)
Forget one setting; nothing happens when it was not set.
Source code in src\taters\helpers\settings.py
116 117 118 119 120 121 122 123 124 125 126 127 | |
describe_model_cache ¶
describe_model_cache()
One line for the setup check: the folder, how it was chosen, and how much is in it.
Source code in src\taters\helpers\settings.py
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 | |
inspect_row_limit ¶
inspect_row_limit()
How many rows to read when looking a spreadsheet over. 0 means all.
All of them by default, because the questions the wizard builds from a spreadsheet are only as true as what it read: on a real file, ten columns looked constant within a group across the first two hundred rows and were not across the other seven hundred, so they were offered as controls that would have come out empty. Reading everything also says at the moment the file is chosen whether its rows match its header, which is much cheaper to learn then than after an hour of extraction.
Lowered by somebody whose files are big enough that a full pass is worth skipping, in Settings. The environment variable wins, for scripts and tests.
Source code in src\taters\helpers\settings.py
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | |
load_settings ¶
load_settings()
The saved settings, or {}; a damaged file reads as empty rather
than stopping Taters from starting.
Source code in src\taters\helpers\settings.py
92 93 94 95 96 97 98 99 100 | |
model_cache_dir ¶
model_cache_dir()
Where downloaded models live; see the module docstring for the order.
Source code in src\taters\helpers\settings.py
159 160 161 | |
model_cache_source ¶
model_cache_source()
The model cache and which rule chose it: "environment"
(TATERS_MODEL_CACHE), "setting" (chosen in Settings),
"hub environment" (HF_HUB_CACHE/HF_HOME) or "default".
Source code in src\taters\helpers\settings.py
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 | |
save_setting ¶
save_setting(key, value)
Write one setting, keeping the others, atomically.
Source code in src\taters\helpers\settings.py
103 104 105 106 107 108 109 110 111 112 113 | |
settings_path ¶
settings_path()
$TATERS_HOME/settings.json (or ~/.taters/settings.json).
Source code in src\taters\helpers\settings.py
87 88 89 | |
taters.helpers.update_check ¶
"A newer version is out" -- said once, quietly, and never in the way.
The rules this is built around, in order of importance:
- The menu never waits for the network. The note is read from a cached answer, which is instant; the refresh that produces that answer runs in a background thread and its result is for next launch. A slow network, a proxy that blackholes the request, no network at all -- none of it can delay the first screen or stop Taters starting.
- It can be turned off, and turning it off is honored everywhere. Some
people run this on data that cannot leave the building, and an unexplained
outbound connection is a conversation with IT nobody wants. Setting
TATERS_NO_UPDATE_CHECK=1, or the saved setting, stops it dead -- no thread, no request. - It cannot break anything. Every path here is wrapped. A failure leaves no note and no complaint; there is nothing here worth interrupting a run for.
- It says one thing. "Newer version available: v0.7.3" under the banner, in dim text. Not an exhortation, not something to dismiss.
note ¶
note()
The line to print under the banner, or "" -- read from cache, instantly.
Returns "" when: the check is off, Taters is running from a source tree with no version metadata, nothing has been cached yet (the first launch), the cached answer is not newer, or anything at all goes wrong.
Source code in src\taters\helpers\update_check.py
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | |
refresh_in_background ¶
refresh_in_background()
Start the refresh, or don't. Returns the thread for tests to join.
A daemon thread: if somebody quits Taters two seconds after opening it, the interpreter exits without waiting on a socket nobody is reading.
Source code in src\taters\helpers\update_check.py
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | |
taters.helpers.feature_columns ¶
What every feature table calls its columns, declared once and checked at build time.
The problem this solves only shows up when two measures meet. Feature tables get
joined side by side into one analysis table, and if two of them use the same
column name, something has to give. stats.assemble already handles that: it
refuses two tables with the same stem, and renames any column used by two tables
to <stem>__<column> in every table that has it, so the outcome does not
depend on file order.
That is a good safety net and it stays. But it is reactive, and reactive
renaming has a property that is poison for research: the name depends on what
else ran. A column is Topic_1 in a pipeline with one topic model and
lda_topics__Topic_1 in a pipeline with two. The same instrument, the same
corpus, two different column names -- so a script written against one study
silently fails against the next, and two results tables cannot be compared
without knowing what else was in each run.
So the names we choose have to be disjoint up front. Each feature module
declares what it writes; :func:overlaps finds any two declarations that could
produce the same name; and tests/test_feature_columns.py fails the build if
any do, naming both offenders. Collisions among shipped measures therefore never
happen, the reactive rename never fires for them, and Topic_1 is Topic_1
in every pipeline forever.
Three kinds of column name exist, and only two of them can be policed here:
- Fixed -- names we choose and always write (
flesch_reading_ease). - Patterned -- names we choose whose tail is a number or a setting
(
Theme_{n},msttr_{n}). Declared as a pattern, matched as one. - Dynamic -- names that come out of the user's own data: the categories in
their dictionary, their archetype names, however many dimensions their encoder
has. We cannot know these and do not pretend to. They are declared with a
sentence saying where they come from, and they are exactly what the reactive
rename in
assembleis for.
Adding a measure means adding a FEATURE_COLUMNS to its module. The test finds
it through the recipe catalog rather than through a list kept here, so forgetting
is a failure rather than a silence.
ColumnSpec
dataclass
¶
ColumnSpec(
label,
names=(),
patterns=(),
reduces_to="Component",
dynamic="",
_module="",
)
One module's claim about the columns it writes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
label
|
str
|
What to call this measure in a collision message. A person reads it. |
required |
names
|
tuple of str
|
Column names always written, spelled exactly. |
()
|
patterns
|
tuple of str
|
Names whose tail varies with a setting, with a placeholder where the
varying part goes. The distinction earns its keep: sentence embeddings write |
()
|
reduces_to
|
str
|
What a PCA over these columns produces, as a singular noun. Reducing a
topic model's topics does not give you "components", it gives you
something worth naming -- so the topic models say |
'Component'
|
dynamic
|
str
|
Non-empty when this module also writes columns whose names come from
the user's data rather than from us. The text says where they come from.
It is documentation, not an exemption: whatever is declared in |
''
|
Notes
Bookkeeping columns are left out on purpose. Several modules write
token_count beside their measures, and declaring it would report a
collision on a column no analysis ever treats as a feature -- assemble
keeps those aside from the feature sets and renames them harmlessly if two
tables carry one. Declare what somebody would analyze.
pattern_regex ¶
pattern_regex(pattern)
A pattern as a regex: everything literal but the one placeholder.
{n} becomes \d+ and {*} becomes .+. Use {n} whenever
the varying part really is a number, because it is what tells e{n} and
e_{n} apart -- two real patterns in this codebase that would otherwise
read as the same one.
Source code in src\taters\helpers\feature_columns.py
129 130 131 132 133 134 135 136 137 138 139 | |
overlaps ¶
overlaps(specs)
Every pair of declarations that could write the same column name.
Returns:
| Type | Description |
|---|---|
list of (label, label, names)
|
One entry per colliding pair, with the names they collide on. Empty when the declarations are disjoint, which is the only acceptable state for the measures Taters ships. |
Source code in src\taters\helpers\feature_columns.py
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 | |
by_module ¶
by_module(specs)
Declarations keyed by the module that made them, for error messages.
Source code in src\taters\helpers\feature_columns.py
195 196 197 | |
registry ¶
registry()
Every declaration, keyed by module. Built once, and never raises.
A module that cannot be imported (an optional dependency is missing) is skipped rather than fatal: the registry is used to name things nicely, and a missing encoder should not stop a run that never wanted one.
Source code in src\taters\helpers\feature_columns.py
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 | |
reduced_name ¶
reduced_name(columns, *, set_name='')
What to call a component built from these columns: "Supertopic", or "Component" when nothing more specific is known.
Matched on the column names themselves rather than threaded down from the pipeline, because by the time the statistics stage reduces a feature set, the set is just a list of column names in a table -- whatever produced it is long out of scope.
Source code in src\taters\helpers\feature_columns.py
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 | |