API reference

The entire public API is re-exported from the top-level asyoulikeit package, so from asyoulikeit import Report, Reports, report_output is the intended import path. The sub-module documentation below is for readers who want the full picture.

Top-level package

Utilities for enriching CLI tools with structured report output.

exception asyoulikeit.AsyoulikeitError[source]

Base class for all exceptions raised by the asyoulikeit package.

class asyoulikeit.Column(key: str, label: str, header: bool = False, importance: Importance = Importance.ESSENTIAL)[source]

Column definition for tabular data.

Parameters:
  • key – Internal identifier for the column (used in row dictionaries)

  • label – Display name for the column (shown in output)

  • header – Whether this column serves as a row header/label column

  • importance – Column importance level (ESSENTIAL or DETAIL)

class asyoulikeit.DetailLevel(*values)[source]

Control which columns to include in formatted output.

AUTO: Formatter decides based on its own default behavior (TSV excludes, table includes) DETAILED: Include all columns (ESSENTIAL + DETAIL) ESSENTIAL: Include only ESSENTIAL columns

class asyoulikeit.Extension(name: str, **kwargs)[source]
classmethod describe(*, single_line: bool = False) str[source]

A description of the extension.

By default, this is the docstring of the extension class. Override it in the extension if you want something different.

Parameters:

single_line – If True, return only the first non-empty line of the description. Defaults to False for full description.

Returns:

A string describing the extension. If single_line is True, returns only the first non-empty line; otherwise returns the complete description.

classmethod dirpath() Path[source]

The directory path to the extension package.

classmethod entry_point_name() str[source]

Get the entry point name (key) for this extension class.

This performs a reverse lookup from class to entry point name by searching through all extensions in the appropriate namespace. The result is cached indefinitely since entry point names are immutable.

Returns:

The entry point name for this extension class.

Raises:

ExtensionError – If this class is not registered as an extension.

classmethod kind() str[source]

The kind of extension.

Used to distinguish extension points.

classmethod version() str[source]

The extension version.

exception asyoulikeit.ExtensionError[source]
class asyoulikeit.Formatter(name: str, **kwargs)[source]

Base class for formatters.

Formatters convert a Reports collection into text suitable for a particular output channel (terminal table, TSV, JSON, etc.).

abstractmethod format(reports: Reports) str[source]

Format reports as string output.

Parameters:

reports – A Reports object containing one or more named reports. Each report has its own data, optional styles, and formatting preferences (detail_level, header) that may have been overridden by CLI flags.

Returns:

Formatted string representation suitable for the chosen output channel.

exception asyoulikeit.FormatterExtensionError[source]

Exception raised when a formatter extension cannot be loaded.

class asyoulikeit.Importance(*values)[source]

Importance level for columns and/or rows in tabular data.

ESSENTIAL columns and rows are always included in output (suitable for machine parsing). DETAIL columns provide additional information for human-friendly output but may be omitted in machine-readable formats like TSV/CSV. Moreover, information about deprecated entities can be marked as DETAIL to allow users to focus on current data.

class asyoulikeit.Node(owner: TreeContent, values: dict[str, Any], importance: Importance)[source]

A single node in a TreeContent.

A node owns its values (a dict keyed by the tree’s column schema), its importance (ESSENTIAL / DETAIL), and a list of child Node objects in insertion order.

add_child(*, _importance: Importance = Importance.ESSENTIAL, **values: Any) Node[source]

Append a child to this node and return the new child.

Parameters:
  • _importance – The child’s Importance. Underscore-prefixed so it cannot clash with a column key.

  • **values – The child’s column values. Must match the owning TreeContent’s column schema exactly — missing keys and extra keys both raise ValueError.

Returns:

The newly-added child. Retain it to chain more descendants; to add siblings, call add_child on the parent again.

property children: tuple[Node, ...]

Immutable tuple of this node’s children in insertion order.

property importance: Importance

This node’s Importance tag.

property values: Mapping[str, Any]

Immutable view of this node’s column values.

class asyoulikeit.Report(data: ReportContent, styles: TableContent | None = None, title: str | None = None, description: str | None = None, detail_level: DetailLevel = DetailLevel.AUTO, header: bool | None = None)[source]

A self-contained report with data and formatting preferences.

Reports are named via dictionary keys when returned from commands. Each report contains its data, optional styling, and formatting preferences.

Report names must be valid Python identifiers (alphanumeric + underscore, not starting with digit) to ensure compatibility with JSON/JavaScript.

data

The content of this report (a ReportContent subclass).

Type:

asyoulikeit.content.ReportContent

styles

Optional styling information with same structure as data.

Type:

asyoulikeit.tabular_data.TableContent | None

title

Optional title for the report (may differ from TableContent title).

Type:

str | None

description

Optional description (may differ from TableContent description).

Type:

str | None

detail_level

Default detail level for this report (can be overridden by CLI).

Type:

asyoulikeit.tabular_data.DetailLevel

header

Tri-state preference for whether headers/titles/chrome are emitted. True or False are honoured as explicit requests; None (the default) defers to the formatter, which picks a sensible default per content type. Resolution order, highest first: CLI --header/--no-header → this field → formatter default.

Type:

bool | None

class asyoulikeit.ReportContent[source]

Marker base class for the content carried by a Report.

Every concrete content kind (currently TableContent) inherits from this class and declares a stable short identifier via kind(). Formatters use the identifier — or an isinstance check — to pick an appropriate rendering path.

abstractmethod classmethod kind() str[source]

A short, stable identifier for this content kind.

Returns:

A token like "table" or (in future) "tree". Used by formatters and tooling to dispatch on content type.

exception asyoulikeit.ReportDeclarationError[source]

Raised when a @report_output command violates its reports= declaration.

Thrown at two distinct points:

  • Decoration time — when the reports= mapping is ill-formed (non-identifier keys, non-Ellipsis non-string keys, or a default_reports entry naming a report that wasn’t declared).

  • Runtime — when the handler returns a Reports containing a name that wasn’t declared, and the declaration has no Ellipsis slot to admit dynamic names.

class asyoulikeit.Reports(*args, **kwargs)[source]

An immutable collection of named reports.

Reports is a value object representing a mapping from report names to Report objects. Report names are validated to be valid Python identifiers on construction, ensuring compatibility with JSON/JavaScript object keys.

This class is constructable like a regular dictionary and can be used anywhere a Mapping is expected. It provides a clean abstraction for collections of reports and leaves room for future metadata additions.

Examples

Dict-style construction:

reports = Reports({"masters": master_report, "themes": theme_report})

Keyword arguments:

reports = Reports(masters=master_report, themes=theme_report)

From iterable of tuples:

reports = Reports([("masters", master_report)])

Access like a dict:

master_report = reports["masters"]
for name, report in reports.items():
    print(name, report.data.title)
Raises:

ValueError – If any report name is not a valid Python identifier

class asyoulikeit.ScalarContent(value: Any, title: str | None = None, description: str | None = None)[source]

A single value as a ReportContent.

No columns, no rows, no children — just one opaque value plus optional title and description metadata. The value’s Python type is unconstrained; the formatters will coerce via str() for text rendering and pass primitives through directly for JSON.

property description: str | None

The scalar’s description, if set.

classmethod kind() str[source]

Return the ReportContent kind identifier: "scalar".

property title: str | None

The scalar’s title / label, if set.

property value: Any

The single value carried by this report.

class asyoulikeit.TableContent(title: str | None = None, description: str | None = None, present_transposed: bool = False)[source]

Container for tabular data with schema validation.

TableContent enforces that all rows match a defined column schema, providing a builder-style API for constructing tables programmatically.

Example

data = TableContent(title="User List", description="Active users in the system")
data.add_column("name", "Name")
data.add_column("age", "Age")
data.add_row(name="Alice", age=30)
data.add_row(name="Bob", age=25)

# Access immutable views
for row in data.rows:
    print(row["name"], row["age"])
add_column(key: str, label: str, header: bool = False, importance: Importance = Importance.ESSENTIAL) TableContent[source]

Add a column definition to the schema.

Columns must be added before any rows. This method uses the builder pattern and returns self for method chaining.

Parameters:
  • key – Internal identifier for the column (must be a valid Python identifier)

  • label – Display name for the column

  • header – Whether this column serves as a row header/label column (default: False). Can only be True for the first column. Header columns must be ESSENTIAL.

  • importance – Column importance level (default: ESSENTIAL). Header columns must always be ESSENTIAL. DETAIL columns provide supplementary information, and may be omitted in machine-readable output formats to reduce clutter and ease parsing.

Returns:

Self for method chaining

Raises:

ValueError – If rows have already been added, if column key already exists, if key is not a valid Python identifier, if header=True is set on a non-first column, or if header=True is combined with DETAIL importance

add_row(*, _importance: Importance = Importance.ESSENTIAL, **values: Any) TableContent[source]

Add a data row with strict validation.

All defined columns must be present in the row, and no extra columns are allowed. This enforces schema consistency across all rows.

Parameters:
  • _importance – Row importance level (default: ESSENTIAL). Underscore prefix reserves this parameter name to avoid conflict with column keys.

  • **values – Column key-value pairs for the row

Returns:

Self for method chaining

Raises:

ValueError – If no columns defined, if required columns are missing, or if extra unexpected columns are present

property columns: tuple[Column, ...]

Get an immutable view of the column definitions.

Columns are returned in the order they were added.

Returns:

Tuple of Column objects

property description: str | None

Get the table description.

Returns:

The table description, or None if not set

property detailed_columns: tuple[Column, ...]

Get columns marked as DETAIL.

Detail columns provide supplementary information for human-friendly output but may be omitted in machine-readable formats.

Returns:

Tuple of Column objects with importance=DETAIL

property essential_columns: tuple[Column, ...]

Get columns marked as ESSENTIAL.

Essential columns are suitable for machine-readable output formats and provide core identifying information.

Returns:

Tuple of Column objects with importance=ESSENTIAL

property essential_rows: tuple[dict[str, Any], ...]

Get rows marked as ESSENTIAL.

Essential rows are always included in output.

Returns:

Tuple of row dictionaries with importance=ESSENTIAL

classmethod from_mappings(mappings: Iterable[Mapping[str, Any]], title: str | None = None, description: str | None = None, present_transposed: bool = False) TableContent[source]

Create TableContent from an iterable of mappings (e.g., list of dicts).

Columns are inferred from the union of all keys across all mappings. Column keys and labels will be identical. Missing keys in individual mappings will be filled with None.

Column order is determined by the order of first appearance across all mappings (not sorted).

Parameters:
  • mappings – Iterable of mappings (e.g., list of dictionaries)

  • title – Optional title for the table

  • description – Optional description

  • present_transposed – Whether to present transposed

Returns:

A new TableContent instance

Raises:

ValueError – If any key is not a valid Python identifier

Example

data = TableContent.from_mappings([
    {"name": "Alice", "age": 30},
    {"name": "Bob", "age": 25, "city": "NYC"}
])
# Results in columns: name, age, city
# Row 1: Alice, 30, None
# Row 2: Bob, 25, NYC
property header_column: Column | None

Get the header column if one exists.

Returns:

The header column, or None if no column is marked as header

is_compatible(other: TableContent) bool[source]

Check if another TableContent has compatible structure.

Compatibility requires: - Same number of columns - Same column keys in same order - Same number of rows

Note: Column labels are NOT checked (styles may use different labels)

Parameters:

other – Another TableContent instance to compare

Returns:

True if structures are compatible, False otherwise

classmethod kind() str[source]

Return the ReportContent kind identifier: "table".

property present_transposed: bool

Get whether the table should be presented transposed.

Returns:

True if the table should be presented with rows and columns swapped

property row_importances: tuple[Importance, ...]

Get row importance levels.

Returns:

Tuple of ColumnImportance values, one per row, in same order as rows

property rows: tuple[dict[str, Any], ...]

Get an immutable view of the data rows.

Each row is a dictionary mapping column keys to values.

Returns:

Tuple of row dictionaries

rows_for_detail_level(detail_level: DetailLevel) tuple[dict[str, Any], ...][source]

Get rows appropriate for the specified detail level.

This method mirrors the column filtering pattern used in formatters: - DETAILED: Returns all rows (ESSENTIAL + DETAIL) - ESSENTIAL: Returns only ESSENTIAL rows - AUTO: Treated as ESSENTIAL (should be resolved to DETAILED or ESSENTIAL by caller)

Parameters:

detail_level – DetailLevel.DETAILED or DetailLevel.ESSENTIAL. AUTO is treated as ESSENTIAL (should be resolved by caller).

Returns:

Tuple of row dictionaries filtered by importance

Example

>>> # In a formatter:
>>> if detail_level == DetailLevel.AUTO:
...     detail_level = DetailLevel.ESSENTIAL  # or DETAILED
>>> rows = data.rows_for_detail_level(detail_level)
property title: str | None

Get the table title.

Returns:

The table title, or None if not set

transpose(value_column_importance: Importance = Importance.ESSENTIAL) TableContent[source]

Transpose rows and columns.

Returns a new TableContent with rows and columns swapped. The header column (if present) becomes the column labels of the transposed data, and the original column labels become the first column of the transposed data (marked as header).

If no header column exists, row indices are used as column labels.

Note: Original column importance metadata is not directly preserved. Original row data does not carry importance metadata, so all value columns receive the same importance level specified by value_column_importance.

Parameters:

value_column_importance – Importance level for all value columns in the transposed table. Defaults to ESSENTIAL (safe for machine-readable output).

Returns:

A new transposed TableContent with present_transposed=False

class asyoulikeit.TreeContent(title: str | None = None, description: str | None = None)[source]

Hierarchical content with a uniform column schema across nodes.

Every node — roots and descendants alike — holds a set of column values keyed by the same schema declared on the TreeContent itself. Most trees have a single root; TreeContent also supports a forest via repeated calls to add_root().

Construction rules:

  • All columns must be declared before the first add_root() call.

  • Exactly one column must be marked header=True; it is the label column displayed in the tree’s first column by the display formatter.

  • Header columns must be Importance.ESSENTIAL (the default).

  • Every node’s values must match the column schema exactly.

Filtering on Importance prunes a DETAIL node and its entire subtree when the resolved detail level is ESSENTIAL — a detail node’s descendants cannot out-rank their ancestor.

add_column(key: str, label: str, header: bool = False, importance: Importance = Importance.ESSENTIAL) TreeContent[source]

Add a column to the tree’s schema.

Parameters:
  • key – Internal identifier for the column; must be a valid Python identifier and not start with an underscore.

  • label – Display name shown in formatted output.

  • header – Whether this column is the tree’s label column. Exactly one column on a tree must be marked header.

  • importanceESSENTIAL (default) or DETAIL. Header columns must be ESSENTIAL.

Returns:

self, for method chaining.

Raises:

ValueError – If columns are added after roots exist, the key clashes with an existing column, the key is not a valid identifier (or starts with underscore), or a header column is tagged DETAIL.

add_root(*, _importance: Importance = Importance.ESSENTIAL, **values: Any) Node[source]

Append a top-level node and return it.

May be called multiple times to build a forest (a TreeContent with more than one root). The common single-tree case is a single add_root() call, after which Node.add_child() builds the rest.

Parameters:
  • _importance – The root’s Importance. Underscore-prefixed to avoid clashing with a column key.

  • **values – The root’s column values. Must match the schema exactly.

Returns:

The newly-added root. Retain it to attach children.

Raises:

ValueError – If no columns are declared yet, if exactly one column is not marked header, or if the values do not match the column schema.

property columns: tuple[Column, ...]

Immutable tuple of the tree’s columns in declared order.

property description: str | None

The tree’s description / caption, if any.

property detailed_columns: tuple[Column, ...]

Columns marked Importance.DETAIL.

property essential_columns: tuple[Column, ...]

Columns marked Importance.ESSENTIAL.

property header_column: Column | None

The single column marked header=True, if declared.

classmethod kind() str[source]

Return the ReportContent kind identifier: "tree".

property roots: tuple[Node, ...]

Immutable tuple of top-level nodes in insertion order.

property title: str | None

The tree’s title, if any.

asyoulikeit.create_formatter(formatter_name: str) Formatter[source]

Create a formatter instance by name.

Parameters:

formatter_name – The name of the formatter to create (e.g., “tsv”, “json”)

Returns:

A Formatter instance

Raises:

FormatterExtensionError – If the formatter cannot be loaded

asyoulikeit.describe_formatter(formatter_name: str, *, single_line: bool = False) str[source]

Get the description of a formatter.

Parameters:
  • formatter_name – The name of the formatter

  • single_line – If True, return only the first non-empty line of the description.

Returns:

Description string from the formatter’s docstring

Raises:

FormatterExtensionError – If the formatter cannot be loaded

asyoulikeit.describe_formatter_command() Command[source]

Return a Click command that prints one formatter’s full description.

The returned command is decorated with report_output() and takes a single positional NAME argument restricted (via click.Choice) to the set of currently-registered formatters. The payload is a ScalarContent whose value is the full cleaned docstring and whose title is the formatter name — so --as tsv yields the bare description (pipe-friendly), --as json yields {"metadata": {"title": "<name>"}, "value": "<desc>"}, and --as display yields <name>: <desc>.

The host adds it under whatever name suits its CLI:

cli.add_command(describe_formatter_command(), name="describe-formatter")
asyoulikeit.describe_report_command() Command[source]

Return a Click command that prints one declared report’s description.

Takes two positionals — <command> and <report> — and surfaces the description the command declared for that report via reports={...}. Payload is a ScalarContent whose title is command.report and whose value is the declared description, so --as tsv yields the bare text (pipe- friendly), --as json gives metadata.title + value, and --as display yields command.report: <description>.

An <dynamic> report name works: it surfaces the description attached to the Ellipsis slot of the command’s declaration.

The host CLI adds it under whatever name it prefers:

cli.add_command(describe_report_command(), name="describe-report")
asyoulikeit.format_as(reports: Reports, format_name: str) str[source]

Format reports using the specified formatter.

This is the primary dispatcher function for formatters.

Parameters:
  • reports – A Reports object containing one or more named reports. Each report contains its own data, styles, and formatting preferences that may have been adjusted by CLI flags.

  • format_name – Name of the format (e.g., “tsv”, “json”, “display”)

Returns:

Formatted string

Raises:

FormatterExtensionError – If format_name is not recognized

asyoulikeit.formatter_names() list[str][source]

Get the names of all available formatters.

Returns:

List of formatter names (e.g., [“display”, “json”, “tsv”])

asyoulikeit.list_formatters_command() Command[source]

Return a Click command that lists the available report-output formatters.

The returned command is decorated with report_output() — so it inherits --as / --report / --header / --detailed and renders identically to any other asyoulikeit command. Its payload is a two-column TableContent (Name, Description) with one row per registered formatter. The description is the formatter class’s one-line summary (first non-empty line of its docstring).

The host CLI adds it to its group under whatever name it prefers:

cli.add_command(list_formatters_command(), name="list-formatters")

Because formatter_names() is evaluated at factory-call time (not at import time), any formatter that was registered via entry point before the factory is called will appear in the listing and in --as’s choices.

asyoulikeit.list_reports_command() Command[source]

Return a Click command that lists declared reports across the CLI.

Mirrors list_formatters_command(): the returned command is itself decorated with @report_output, so it inherits --as / --report / --header / --detailed and renders in any format. The payload is a TreeContent with one root node per @report_output command discovered under the root Click group and children for each declared report (name + description). Because reports= is required on every @report_output command, every discovered report-output command shows its declared names; a group may also contain plain @click.command commands that aren’t asyoulikeit-aware, and those surface with a single <not a report-output command> marker child. Action commands declared with reports={} show <no reports>.

The host CLI adds it under whatever name suits it:

cli.add_command(list_reports_command(), name="list-reports")

Usage:

mytool list-reports                # every command + its reports
mytool list-reports video-audit    # one command only

The command walks click.get_current_context().find_root() at invoke time, so it sees every command the host has added before invocation — no per-command wiring required.

asyoulikeit.report_output(func: Callable = None, /, *, default_reports: None | Iterable[str] | _UniversalContainer = ALL_REPORTS, reports: Mapping[str | EllipsisType, str] | None = None) Callable[source]

Decorator factory for commands that return tabular output.

reports= is required: every @report_output command must declare which report names it produces, as a mapping of {name: description}. Use Ellipsis as a key to declare “this command also produces dynamically-named reports” (e.g. one report per input file), or pass an empty dict for an action command that returns None. There is no back-compat fallback; the declaration lets the library validate --report values, generate help, run drift detection, and power the sibling introspection commands.

The decorated function should return:

  • Reports — one or more named reports (each key must be declared in reports= or admitted by an Ellipsis slot).

  • None — silent success (nothing displayed).

Parameters:
  • func – The function to decorate (when used without parentheses a reports= kwarg must be supplied — otherwise the decoration raises ReportDeclarationError).

  • default_reports – Which reports to show by default when no --report flags are specified. ALL_REPORTS (the default) shows every report; None shows nothing (silent action command); an iterable of names restricts the default to those names. Entries that aren’t in reports= raise at decoration time.

  • reports – Mapping of {name: description} declaring the report names this command produces. Keys must be valid Python identifiers; Ellipsis as a key declares the “dynamic slot” (names known only at runtime). The declaration drives decoration-time validation, --help generation, parse-time click.Choice on --report when fully static, and runtime drift detection at the return boundary — undeclared names (and no Ellipsis slot admitting them) raise ReportDeclarationError.

Adds these CLI options:

  • --as — output format (display / tsv / json). Defaults to display for terminals, tsv for pipes.

  • --detailed / --essential — column inclusion override.

  • --header / --no-header — header override.

  • --report — filter which reports to display. Hyphens normalise to underscores (--report monthly-sales resolves to monthly_sales).

  • --no-reports — suppress all report rendering while still running the handler (useful for action commands whose reports are incidental confirmation).

  • --all-reports — render every report the handler returns, regardless of default_reports (useful for opting into the full picture on a command that’s normally silent or narrow).

The three selection flags (--report, --no-reports, --all-reports) are mutually exclusive — pick at most one.

Header behaviour is format-specific: TSV prefixes the first header cell with #, display omits title/caption when headers are off, JSON ignores the flag.

Examples

@report_output(reports={"users": "The system's users."}) — one static report.

@report_output(reports={"overall": "…", Ellipsis: "…"}) — mixed static + dynamic.

@report_output(reports={Ellipsis: "One per input file."}) — purely dynamic.

@report_output(reports={}, default_reports=None) — silent action command.

Report content: the base

Abstract base for report content.

A Report holds one ReportContent value (currently always a TableContent — trees and other kinds are planned). Formatters iterate reports and dispatch on the concrete content type to render each appropriately.

The protocol is intentionally minimal in this first cut: only kind() is required. Common concerns that might eventually be lifted up (title / description hooks, detail-level filtering protocol, …) are deliberately left to each concrete subclass until a second content kind exists to triangulate what’s genuinely shared.

class asyoulikeit.content.ReportContent[source]

Bases: ABC

Marker base class for the content carried by a Report.

Every concrete content kind (currently TableContent) inherits from this class and declares a stable short identifier via kind(). Formatters use the identifier — or an isinstance check — to pick an appropriate rendering path.

abstractmethod classmethod kind() str[source]

A short, stable identifier for this content kind.

Returns:

A token like "table" or (in future) "tree". Used by formatters and tooling to dispatch on content type.

Table content

Tabular data representation for CLI output formatting.

This module provides a structured way to build and represent tabular data with a defined schema, suitable for formatting into various output formats like TSV, JSON, or rich console tables.

class asyoulikeit.tabular_data.Column(key: str, label: str, header: bool = False, importance: Importance = Importance.ESSENTIAL)[source]

Bases: object

Column definition for tabular data.

Parameters:
  • key – Internal identifier for the column (used in row dictionaries)

  • label – Display name for the column (shown in output)

  • header – Whether this column serves as a row header/label column

  • importance – Column importance level (ESSENTIAL or DETAIL)

class asyoulikeit.tabular_data.DetailLevel(*values)[source]

Bases: Enum

Control which columns to include in formatted output.

AUTO: Formatter decides based on its own default behavior (TSV excludes, table includes) DETAILED: Include all columns (ESSENTIAL + DETAIL) ESSENTIAL: Include only ESSENTIAL columns

class asyoulikeit.tabular_data.Importance(*values)[source]

Bases: Enum

Importance level for columns and/or rows in tabular data.

ESSENTIAL columns and rows are always included in output (suitable for machine parsing). DETAIL columns provide additional information for human-friendly output but may be omitted in machine-readable formats like TSV/CSV. Moreover, information about deprecated entities can be marked as DETAIL to allow users to focus on current data.

class asyoulikeit.tabular_data.Report(data: ReportContent, styles: TableContent | None = None, title: str | None = None, description: str | None = None, detail_level: DetailLevel = DetailLevel.AUTO, header: bool | None = None)[source]

Bases: object

A self-contained report with data and formatting preferences.

Reports are named via dictionary keys when returned from commands. Each report contains its data, optional styling, and formatting preferences.

Report names must be valid Python identifiers (alphanumeric + underscore, not starting with digit) to ensure compatibility with JSON/JavaScript.

data

The content of this report (a ReportContent subclass).

Type:

asyoulikeit.content.ReportContent

styles

Optional styling information with same structure as data.

Type:

asyoulikeit.tabular_data.TableContent | None

title

Optional title for the report (may differ from TableContent title).

Type:

str | None

description

Optional description (may differ from TableContent description).

Type:

str | None

detail_level

Default detail level for this report (can be overridden by CLI).

Type:

asyoulikeit.tabular_data.DetailLevel

header

Tri-state preference for whether headers/titles/chrome are emitted. True or False are honoured as explicit requests; None (the default) defers to the formatter, which picks a sensible default per content type. Resolution order, highest first: CLI --header/--no-header → this field → formatter default.

Type:

bool | None

class asyoulikeit.tabular_data.Reports(*args, **kwargs)[source]

Bases: Mapping[str, Report]

An immutable collection of named reports.

Reports is a value object representing a mapping from report names to Report objects. Report names are validated to be valid Python identifiers on construction, ensuring compatibility with JSON/JavaScript object keys.

This class is constructable like a regular dictionary and can be used anywhere a Mapping is expected. It provides a clean abstraction for collections of reports and leaves room for future metadata additions.

Examples

Dict-style construction:

reports = Reports({"masters": master_report, "themes": theme_report})

Keyword arguments:

reports = Reports(masters=master_report, themes=theme_report)

From iterable of tuples:

reports = Reports([("masters", master_report)])

Access like a dict:

master_report = reports["masters"]
for name, report in reports.items():
    print(name, report.data.title)
Raises:

ValueError – If any report name is not a valid Python identifier

class asyoulikeit.tabular_data.TableContent(title: str | None = None, description: str | None = None, present_transposed: bool = False)[source]

Bases: ReportContent

Container for tabular data with schema validation.

TableContent enforces that all rows match a defined column schema, providing a builder-style API for constructing tables programmatically.

Example

data = TableContent(title="User List", description="Active users in the system")
data.add_column("name", "Name")
data.add_column("age", "Age")
data.add_row(name="Alice", age=30)
data.add_row(name="Bob", age=25)

# Access immutable views
for row in data.rows:
    print(row["name"], row["age"])
add_column(key: str, label: str, header: bool = False, importance: Importance = Importance.ESSENTIAL) TableContent[source]

Add a column definition to the schema.

Columns must be added before any rows. This method uses the builder pattern and returns self for method chaining.

Parameters:
  • key – Internal identifier for the column (must be a valid Python identifier)

  • label – Display name for the column

  • header – Whether this column serves as a row header/label column (default: False). Can only be True for the first column. Header columns must be ESSENTIAL.

  • importance – Column importance level (default: ESSENTIAL). Header columns must always be ESSENTIAL. DETAIL columns provide supplementary information, and may be omitted in machine-readable output formats to reduce clutter and ease parsing.

Returns:

Self for method chaining

Raises:

ValueError – If rows have already been added, if column key already exists, if key is not a valid Python identifier, if header=True is set on a non-first column, or if header=True is combined with DETAIL importance

add_row(*, _importance: Importance = Importance.ESSENTIAL, **values: Any) TableContent[source]

Add a data row with strict validation.

All defined columns must be present in the row, and no extra columns are allowed. This enforces schema consistency across all rows.

Parameters:
  • _importance – Row importance level (default: ESSENTIAL). Underscore prefix reserves this parameter name to avoid conflict with column keys.

  • **values – Column key-value pairs for the row

Returns:

Self for method chaining

Raises:

ValueError – If no columns defined, if required columns are missing, or if extra unexpected columns are present

property columns: tuple[Column, ...]

Get an immutable view of the column definitions.

Columns are returned in the order they were added.

Returns:

Tuple of Column objects

property description: str | None

Get the table description.

Returns:

The table description, or None if not set

property detailed_columns: tuple[Column, ...]

Get columns marked as DETAIL.

Detail columns provide supplementary information for human-friendly output but may be omitted in machine-readable formats.

Returns:

Tuple of Column objects with importance=DETAIL

property essential_columns: tuple[Column, ...]

Get columns marked as ESSENTIAL.

Essential columns are suitable for machine-readable output formats and provide core identifying information.

Returns:

Tuple of Column objects with importance=ESSENTIAL

property essential_rows: tuple[dict[str, Any], ...]

Get rows marked as ESSENTIAL.

Essential rows are always included in output.

Returns:

Tuple of row dictionaries with importance=ESSENTIAL

classmethod from_mappings(mappings: Iterable[Mapping[str, Any]], title: str | None = None, description: str | None = None, present_transposed: bool = False) TableContent[source]

Create TableContent from an iterable of mappings (e.g., list of dicts).

Columns are inferred from the union of all keys across all mappings. Column keys and labels will be identical. Missing keys in individual mappings will be filled with None.

Column order is determined by the order of first appearance across all mappings (not sorted).

Parameters:
  • mappings – Iterable of mappings (e.g., list of dictionaries)

  • title – Optional title for the table

  • description – Optional description

  • present_transposed – Whether to present transposed

Returns:

A new TableContent instance

Raises:

ValueError – If any key is not a valid Python identifier

Example

data = TableContent.from_mappings([
    {"name": "Alice", "age": 30},
    {"name": "Bob", "age": 25, "city": "NYC"}
])
# Results in columns: name, age, city
# Row 1: Alice, 30, None
# Row 2: Bob, 25, NYC
property header_column: Column | None

Get the header column if one exists.

Returns:

The header column, or None if no column is marked as header

is_compatible(other: TableContent) bool[source]

Check if another TableContent has compatible structure.

Compatibility requires: - Same number of columns - Same column keys in same order - Same number of rows

Note: Column labels are NOT checked (styles may use different labels)

Parameters:

other – Another TableContent instance to compare

Returns:

True if structures are compatible, False otherwise

classmethod kind() str[source]

Return the ReportContent kind identifier: "table".

property present_transposed: bool

Get whether the table should be presented transposed.

Returns:

True if the table should be presented with rows and columns swapped

property row_importances: tuple[Importance, ...]

Get row importance levels.

Returns:

Tuple of ColumnImportance values, one per row, in same order as rows

property rows: tuple[dict[str, Any], ...]

Get an immutable view of the data rows.

Each row is a dictionary mapping column keys to values.

Returns:

Tuple of row dictionaries

rows_for_detail_level(detail_level: DetailLevel) tuple[dict[str, Any], ...][source]

Get rows appropriate for the specified detail level.

This method mirrors the column filtering pattern used in formatters: - DETAILED: Returns all rows (ESSENTIAL + DETAIL) - ESSENTIAL: Returns only ESSENTIAL rows - AUTO: Treated as ESSENTIAL (should be resolved to DETAILED or ESSENTIAL by caller)

Parameters:

detail_level – DetailLevel.DETAILED or DetailLevel.ESSENTIAL. AUTO is treated as ESSENTIAL (should be resolved by caller).

Returns:

Tuple of row dictionaries filtered by importance

Example

>>> # In a formatter:
>>> if detail_level == DetailLevel.AUTO:
...     detail_level = DetailLevel.ESSENTIAL  # or DETAILED
>>> rows = data.rows_for_detail_level(detail_level)
property title: str | None

Get the table title.

Returns:

The table title, or None if not set

transpose(value_column_importance: Importance = Importance.ESSENTIAL) TableContent[source]

Transpose rows and columns.

Returns a new TableContent with rows and columns swapped. The header column (if present) becomes the column labels of the transposed data, and the original column labels become the first column of the transposed data (marked as header).

If no header column exists, row indices are used as column labels.

Note: Original column importance metadata is not directly preserved. Original row data does not carry importance metadata, so all value columns receive the same importance level specified by value_column_importance.

Parameters:

value_column_importance – Importance level for all value columns in the transposed table. Defaults to ESSENTIAL (safe for machine-readable output).

Returns:

A new transposed TableContent with present_transposed=False

asyoulikeit.tabular_data.validate_styles_compatibility(data: TableContent, styles: TableContent) None[source]

Verify styles table is compatible with data table.

Parameters:
  • data – The data table

  • styles – The styles table

Raises:

ValueError – If tables are not compatible with detailed message

Tree content

Hierarchical report content with a uniform column schema across nodes.

Where TableContent carries a flat table of rows against a column schema, TreeContent carries a hierarchical forest: one or more root nodes, each optionally with children, where every node holds values keyed by the same column schema. Think file trees, syntax trees, project organisation hierarchies — anywhere the shape is hierarchical but the per-node data is homogeneous.

Construction mirrors TableContent’s builder pattern; TreeContent.add_root() starts a top-level node and Node.add_child() grows the tree downward. Both return the newly-added node so the caller can retain a handle for adding its own children or siblings.

Example:

tree = (
    TreeContent(title="/usr")
    .add_column("name", "Name", header=True)
    .add_column("size", "Size")
)
root = tree.add_root(name="/usr", size=0)
bin_dir = root.add_child(name="bin", size=4096)
bin_dir.add_child(name="ls", size=150_296)
bin_dir.add_child(name="cat", size=52_024)

TreeContent supports a forest rather than strictly a tree: call add_root() more than once to add multiple top-level nodes.

class asyoulikeit.tree_data.Node(owner: TreeContent, values: dict[str, Any], importance: Importance)[source]

Bases: object

A single node in a TreeContent.

A node owns its values (a dict keyed by the tree’s column schema), its importance (ESSENTIAL / DETAIL), and a list of child Node objects in insertion order.

add_child(*, _importance: Importance = Importance.ESSENTIAL, **values: Any) Node[source]

Append a child to this node and return the new child.

Parameters:
  • _importance – The child’s Importance. Underscore-prefixed so it cannot clash with a column key.

  • **values – The child’s column values. Must match the owning TreeContent’s column schema exactly — missing keys and extra keys both raise ValueError.

Returns:

The newly-added child. Retain it to chain more descendants; to add siblings, call add_child on the parent again.

property children: tuple[Node, ...]

Immutable tuple of this node’s children in insertion order.

property importance: Importance

This node’s Importance tag.

property values: Mapping[str, Any]

Immutable view of this node’s column values.

class asyoulikeit.tree_data.TreeContent(title: str | None = None, description: str | None = None)[source]

Bases: ReportContent

Hierarchical content with a uniform column schema across nodes.

Every node — roots and descendants alike — holds a set of column values keyed by the same schema declared on the TreeContent itself. Most trees have a single root; TreeContent also supports a forest via repeated calls to add_root().

Construction rules:

  • All columns must be declared before the first add_root() call.

  • Exactly one column must be marked header=True; it is the label column displayed in the tree’s first column by the display formatter.

  • Header columns must be Importance.ESSENTIAL (the default).

  • Every node’s values must match the column schema exactly.

Filtering on Importance prunes a DETAIL node and its entire subtree when the resolved detail level is ESSENTIAL — a detail node’s descendants cannot out-rank their ancestor.

add_column(key: str, label: str, header: bool = False, importance: Importance = Importance.ESSENTIAL) TreeContent[source]

Add a column to the tree’s schema.

Parameters:
  • key – Internal identifier for the column; must be a valid Python identifier and not start with an underscore.

  • label – Display name shown in formatted output.

  • header – Whether this column is the tree’s label column. Exactly one column on a tree must be marked header.

  • importanceESSENTIAL (default) or DETAIL. Header columns must be ESSENTIAL.

Returns:

self, for method chaining.

Raises:

ValueError – If columns are added after roots exist, the key clashes with an existing column, the key is not a valid identifier (or starts with underscore), or a header column is tagged DETAIL.

add_root(*, _importance: Importance = Importance.ESSENTIAL, **values: Any) Node[source]

Append a top-level node and return it.

May be called multiple times to build a forest (a TreeContent with more than one root). The common single-tree case is a single add_root() call, after which Node.add_child() builds the rest.

Parameters:
  • _importance – The root’s Importance. Underscore-prefixed to avoid clashing with a column key.

  • **values – The root’s column values. Must match the schema exactly.

Returns:

The newly-added root. Retain it to attach children.

Raises:

ValueError – If no columns are declared yet, if exactly one column is not marked header, or if the values do not match the column schema.

property columns: tuple[Column, ...]

Immutable tuple of the tree’s columns in declared order.

property description: str | None

The tree’s description / caption, if any.

property detailed_columns: tuple[Column, ...]

Columns marked Importance.DETAIL.

property essential_columns: tuple[Column, ...]

Columns marked Importance.ESSENTIAL.

property header_column: Column | None

The single column marked header=True, if declared.

classmethod kind() str[source]

Return the ReportContent kind identifier: "tree".

property roots: tuple[Node, ...]

Immutable tuple of top-level nodes in insertion order.

property title: str | None

The tree’s title, if any.

Scalar content

A single-value ReportContent kind.

Where TableContent carries a table of rows and TreeContent carries a hierarchy, ScalarContent carries one value: a title, a number, a status string, an address — the kind of thing a command like disc title image produces. It exists so that such commands can flow through @report_output with the same --as / --report / --header machinery as the other content kinds, rather than skipping the decorator (and losing those affordances) or wrapping the scalar in a 1×1 transposed table (which reads as ceremony around a single cell and clutters JSON output with empty tabular shape).

Example:

from asyoulikeit import (
    Report, Reports, ScalarContent, report_output,
)

@click.command()
@report_output(reports={"title": "The disc image's title."})
def disc_title(image):
    return Reports(title=Report(data=ScalarContent(
        value=_read_title(image),
        title="Disc title",
    )))

In a terminal the user sees Disc title: My Disc Image; piped to another tool with | pbcopy they get just My Disc Image (the TSV formatter’s per-content default for scalars is “no header line, just the value”). JSON output is always self-describing — consumers parse .reports.title.value via jq.

class asyoulikeit.scalar_data.ScalarContent(value: Any, title: str | None = None, description: str | None = None)[source]

Bases: ReportContent

A single value as a ReportContent.

No columns, no rows, no children — just one opaque value plus optional title and description metadata. The value’s Python type is unconstrained; the formatters will coerce via str() for text rendering and pass primitives through directly for JSON.

property description: str | None

The scalar’s description, if set.

classmethod kind() str[source]

Return the ReportContent kind identifier: "scalar".

property title: str | None

The scalar’s title / label, if set.

property value: Any

The single value carried by this report.

The report-output decorator

Report output decorator for Click commands.

Provides the report_output() decorator for commands that produce formatted report output with support for multiple reports, format selection (display/TSV/JSON), and column filtering.

asyoulikeit.cli.describe_formatter_command() Command[source]

Return a Click command that prints one formatter’s full description.

The returned command is decorated with report_output() and takes a single positional NAME argument restricted (via click.Choice) to the set of currently-registered formatters. The payload is a ScalarContent whose value is the full cleaned docstring and whose title is the formatter name — so --as tsv yields the bare description (pipe-friendly), --as json yields {"metadata": {"title": "<name>"}, "value": "<desc>"}, and --as display yields <name>: <desc>.

The host adds it under whatever name suits its CLI:

cli.add_command(describe_formatter_command(), name="describe-formatter")
asyoulikeit.cli.describe_report_command() Command[source]

Return a Click command that prints one declared report’s description.

Takes two positionals — <command> and <report> — and surfaces the description the command declared for that report via reports={...}. Payload is a ScalarContent whose title is command.report and whose value is the declared description, so --as tsv yields the bare text (pipe- friendly), --as json gives metadata.title + value, and --as display yields command.report: <description>.

An <dynamic> report name works: it surfaces the description attached to the Ellipsis slot of the command’s declaration.

The host CLI adds it under whatever name it prefers:

cli.add_command(describe_report_command(), name="describe-report")
asyoulikeit.cli.list_formatters_command() Command[source]

Return a Click command that lists the available report-output formatters.

The returned command is decorated with report_output() — so it inherits --as / --report / --header / --detailed and renders identically to any other asyoulikeit command. Its payload is a two-column TableContent (Name, Description) with one row per registered formatter. The description is the formatter class’s one-line summary (first non-empty line of its docstring).

The host CLI adds it to its group under whatever name it prefers:

cli.add_command(list_formatters_command(), name="list-formatters")

Because formatter_names() is evaluated at factory-call time (not at import time), any formatter that was registered via entry point before the factory is called will appear in the listing and in --as’s choices.

asyoulikeit.cli.list_reports_command() Command[source]

Return a Click command that lists declared reports across the CLI.

Mirrors list_formatters_command(): the returned command is itself decorated with @report_output, so it inherits --as / --report / --header / --detailed and renders in any format. The payload is a TreeContent with one root node per @report_output command discovered under the root Click group and children for each declared report (name + description). Because reports= is required on every @report_output command, every discovered report-output command shows its declared names; a group may also contain plain @click.command commands that aren’t asyoulikeit-aware, and those surface with a single <not a report-output command> marker child. Action commands declared with reports={} show <no reports>.

The host CLI adds it under whatever name suits it:

cli.add_command(list_reports_command(), name="list-reports")

Usage:

mytool list-reports                # every command + its reports
mytool list-reports video-audit    # one command only

The command walks click.get_current_context().find_root() at invoke time, so it sees every command the host has added before invocation — no per-command wiring required.

asyoulikeit.cli.report_output(func: Callable = None, /, *, default_reports: None | Iterable[str] | _UniversalContainer = ALL_REPORTS, reports: Mapping[str | EllipsisType, str] | None = None) Callable[source]

Decorator factory for commands that return tabular output.

reports= is required: every @report_output command must declare which report names it produces, as a mapping of {name: description}. Use Ellipsis as a key to declare “this command also produces dynamically-named reports” (e.g. one report per input file), or pass an empty dict for an action command that returns None. There is no back-compat fallback; the declaration lets the library validate --report values, generate help, run drift detection, and power the sibling introspection commands.

The decorated function should return:

  • Reports — one or more named reports (each key must be declared in reports= or admitted by an Ellipsis slot).

  • None — silent success (nothing displayed).

Parameters:
  • func – The function to decorate (when used without parentheses a reports= kwarg must be supplied — otherwise the decoration raises ReportDeclarationError).

  • default_reports – Which reports to show by default when no --report flags are specified. ALL_REPORTS (the default) shows every report; None shows nothing (silent action command); an iterable of names restricts the default to those names. Entries that aren’t in reports= raise at decoration time.

  • reports – Mapping of {name: description} declaring the report names this command produces. Keys must be valid Python identifiers; Ellipsis as a key declares the “dynamic slot” (names known only at runtime). The declaration drives decoration-time validation, --help generation, parse-time click.Choice on --report when fully static, and runtime drift detection at the return boundary — undeclared names (and no Ellipsis slot admitting them) raise ReportDeclarationError.

Adds these CLI options:

  • --as — output format (display / tsv / json). Defaults to display for terminals, tsv for pipes.

  • --detailed / --essential — column inclusion override.

  • --header / --no-header — header override.

  • --report — filter which reports to display. Hyphens normalise to underscores (--report monthly-sales resolves to monthly_sales).

  • --no-reports — suppress all report rendering while still running the handler (useful for action commands whose reports are incidental confirmation).

  • --all-reports — render every report the handler returns, regardless of default_reports (useful for opting into the full picture on a command that’s normally silent or narrow).

The three selection flags (--report, --no-reports, --all-reports) are mutually exclusive — pick at most one.

Header behaviour is format-specific: TSV prefixes the first header cell with #, display omits title/caption when headers are off, JSON ignores the flag.

Examples

@report_output(reports={"users": "The system's users."}) — one static report.

@report_output(reports={"overall": "…", Ellipsis: "…"}) — mixed static + dynamic.

@report_output(reports={Ellipsis: "One per input file."}) — purely dynamic.

@report_output(reports={}, default_reports=None) — silent action command.

Formatters

Formatter extension point for tabular data output.

class asyoulikeit.formatter.Formatter(name: str, **kwargs)[source]

Bases: Extension

Base class for formatters.

Formatters convert a Reports collection into text suitable for a particular output channel (terminal table, TSV, JSON, etc.).

abstractmethod format(reports: Reports) str[source]

Format reports as string output.

Parameters:

reports – A Reports object containing one or more named reports. Each report has its own data, optional styles, and formatting preferences (detail_level, header) that may have been overridden by CLI flags.

Returns:

Formatted string representation suitable for the chosen output channel.

exception asyoulikeit.formatter.FormatterExtensionError[source]

Bases: ExtensionError

Exception raised when a formatter extension cannot be loaded.

asyoulikeit.formatter.create_formatter(formatter_name: str) Formatter[source]

Create a formatter instance by name.

Parameters:

formatter_name – The name of the formatter to create (e.g., “tsv”, “json”)

Returns:

A Formatter instance

Raises:

FormatterExtensionError – If the formatter cannot be loaded

asyoulikeit.formatter.describe_formatter(formatter_name: str, *, single_line: bool = False) str[source]

Get the description of a formatter.

Parameters:
  • formatter_name – The name of the formatter

  • single_line – If True, return only the first non-empty line of the description.

Returns:

Description string from the formatter’s docstring

Raises:

FormatterExtensionError – If the formatter cannot be loaded

asyoulikeit.formatter.format_as(reports: Reports, format_name: str) str[source]

Format reports using the specified formatter.

This is the primary dispatcher function for formatters.

Parameters:
  • reports – A Reports object containing one or more named reports. Each report contains its own data, styles, and formatting preferences that may have been adjusted by CLI flags.

  • format_name – Name of the format (e.g., “tsv”, “json”, “display”)

Returns:

Formatted string

Raises:

FormatterExtensionError – If format_name is not recognized

asyoulikeit.formatter.formatter_names() list[str][source]

Get the names of all available formatters.

Returns:

List of formatter names (e.g., [“display”, “json”, “tsv”])

asyoulikeit.formatter.formatter_type(formatter_name: str) Type[Formatter][source]

Obtain the type of a formatter by name.

Parameters:

formatter_name – The name of a formatter. Available formatter names can be obtained from formatter_names().

Returns:

The type (i.e. class) of the requested formatter.

Raises:

FormatterExtensionError – If the requested formatter could not be found.

Extension machinery

Generic extension / plug-in machinery built on top of stevedore.

Extension classes are loaded via entry points. Each Extension subclass declares a kind (a short identifier such as "formatter") and is registered under a namespace of the form "<prefix>.<kind>" in pyproject.toml. Consumers then use create_extension(), extension(), or list_extensions() to load them.

class asyoulikeit.extension.Extension(name: str, **kwargs)[source]

Bases: ABC

classmethod describe(*, single_line: bool = False) str[source]

A description of the extension.

By default, this is the docstring of the extension class. Override it in the extension if you want something different.

Parameters:

single_line – If True, return only the first non-empty line of the description. Defaults to False for full description.

Returns:

A string describing the extension. If single_line is True, returns only the first non-empty line; otherwise returns the complete description.

classmethod dirpath() Path[source]

The directory path to the extension package.

classmethod entry_point_name() str[source]

Get the entry point name (key) for this extension class.

This performs a reverse lookup from class to entry point name by searching through all extensions in the appropriate namespace. The result is cached indefinitely since entry point names are immutable.

Returns:

The entry point name for this extension class.

Raises:

ExtensionError – If this class is not registered as an extension.

classmethod kind() str[source]

The kind of extension.

Used to distinguish extension points.

classmethod version() str[source]

The extension version.

exception asyoulikeit.extension.ExtensionError[source]

Bases: AsyoulikeitError

asyoulikeit.extension.create_extension(kind, namespace, name, exception_type=None, **kwargs) Extension[source]

Instantiate an extension.

Parameters:
  • kind – The kind of extension.

  • namespace – The namespace for the extension.

  • name – The name of the extension to be loaded.

  • exception_type – The exception type to be raised if the extension couldn’t be loaded.

  • **kwargs – Keyword arguments forwarded to the extension constructor.

Returns:

An extension.

asyoulikeit.extension.describe_extension(kind, namespace, name, exception_type=None, *, single_line: bool = False) str[source]

Describe an extension by name.

Parameters:
  • kind – The kind of extension.

  • namespace – The namespace for the extension.

  • name – The name of the extension.

  • exception_type – The exception type to be raised if the extension couldn’t be loaded.

  • single_line – If True, return only the first non-empty line of the description.

Returns:

A string describing the extension.

asyoulikeit.extension.extension(kind: str, namespace: str, name: str, exception_type: BaseException) Type[Extension][source]

Get the extension class without instantiating it.

Parameters:
  • kind – The kind of extension.

  • namespace – The namespace for the extension.

  • name – The name of the extension to be loaded.

  • exception_type – The exception type to be raised if the extension couldn’t be loaded.

Returns:

The type (i.e. class) of an extension.

asyoulikeit.extension.extension_name_from_class(namespace: str, extension_class: Type[Extension]) str[source]

Get the entry point name for an extension class.

This performs a reverse lookup from class to entry point name by iterating through all extensions in the namespace until finding one whose plugin matches the given class.

Parameters:
  • namespace – The namespace to search (e.g., ‘asyoulikeit.formatter’)

  • extension_class – The extension class to find

Returns:

The entry point name (key) for the extension

Raises:

ExtensionError – If the extension class is not found in the namespace

asyoulikeit.extension.list_dirpaths(namespace)[source]

A mapping of extension names to extension package paths.

asyoulikeit.extension.list_extensions(namespace) list[str][source]

List the names of the extensions available in a given namespace.

Exceptions

Exception hierarchy for the asyoulikeit package.

exception asyoulikeit.exceptions.AsyoulikeitError[source]

Bases: Exception

Base class for all exceptions raised by the asyoulikeit package.

exception asyoulikeit.exceptions.ReportDeclarationError[source]

Bases: AsyoulikeitError

Raised when a @report_output command violates its reports= declaration.

Thrown at two distinct points:

  • Decoration time — when the reports= mapping is ill-formed (non-identifier keys, non-Ellipsis non-string keys, or a default_reports entry naming a report that wasn’t declared).

  • Runtime — when the handler returns a Reports containing a name that wasn’t declared, and the declaration has no Ellipsis slot to admit dynamic names.