Skip to content

feat(emitter):add structured data output with JSON and table formatting#380

Open
dharapandya85 wants to merge 5 commits intocanonical:mainfrom
dharapandya85:feat/emitter-data-output
Open

feat(emitter):add structured data output with JSON and table formatting#380
dharapandya85 wants to merge 5 commits intocanonical:mainfrom
dharapandya85:feat/emitter-data-output

Conversation

@dharapandya85
Copy link

@dharapandya85 dharapandya85 commented Aug 25, 2025

Fixes : #348

  • Have you followed the guidelines for contributing?
  • Have you signed the CLA?
  • Have you successfully run make lint && make test?

This PR extends the Emitter class to support structured data output in multiple formats(JSON,table), with a registry for adding formats.
Implementation Details

  • A method on the emitter to write structured data to stdout
  • Output methods: TableFormatter for human-readable tables,JSONFormatterfor machine-readable JSON
  • Implemented usage in examples.py
  • Unit tests covering all formatters and edge cases
  • Added Emitter.data() method which accepts structured data(list[dict]) and uses registered formatter based on the --format flag.
  • Implemented JSONFormatter to fetch JSON outputs with json.dumps
  • Implemented TableFormatter for printing rows and columns
  • Emitter.register_formate(name,formatter) allows registering new formats
  • Default registry includes JSON and table
  • Updated CLI (dispatcher.py) to add -- format{json,table}
  • Added example_36 in examples.py to demo usage.
  • Added unit tests in test_messages_emitter.py
  • TableFomattter, handles list of dicts, empty list and dict input
  • JSONFormatter serializes list of data

Example Run

python examples.py 36 table
>>> Running example_33 with args: ('table',)
TABLE output:
name  | status   | version
--------------------------
App A | active   | 1.0.1
App B | inactive | 2.3.0
python examples.py 36 json
>>> Running example_33 with args: ('json',)
JSON output:
[
  {
    "name": "App A",
    "version": "1.0.1",
    "status": "active"
  },
  {
    "name": "App B",
    "version": "2.3.0",
    "status": "inactive"
  }
]

@dharapandya85 dharapandya85 requested a review from tigarmo as a code owner August 25, 2025 04:24
Copy link
Contributor

@lengau lengau left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, please fix the broken tests and lint errors here.

@dharapandya85 dharapandya85 requested review from a team and medubelko as code owners October 1, 2025 11:49
@dharapandya85 dharapandya85 force-pushed the feat/emitter-data-output branch from f33ec01 to 7a4f4b5 Compare October 23, 2025 13:45
@dharapandya85 dharapandya85 force-pushed the feat/emitter-data-output branch from ae4958a to 00bd742 Compare November 27, 2025 09:05
Comment on lines 65 to 66
# messages.emit._initiated = False
# messages.emit._stopped = True
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should it be here?

examples.py Outdated
Comment on lines 607 to 610
# emit.message("Table output:")
# emit.data(sample_data,format="table")

# emit.structured(sample_data,fmt=fmt)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

@medubelko medubelko dismissed their stale review November 27, 2025 23:20

Problem solved, and no doc changes that require my review.

pyproject.toml Outdated
"FBT002", # Boolean default value in function definition (preserving for backwards-comp)
"S101", # Use of `assert` detected
]

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

something looks wrong with this file. Lot's of unnecessary changes

@dharapandya85 dharapandya85 force-pushed the feat/emitter-data-output branch 3 times, most recently from e64a8dc to 2afdf38 Compare December 4, 2025 18:40
@lengau lengau requested a review from Copilot December 5, 2025 20:46
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds structured data output capabilities to the Emitter class, supporting JSON and table formatting for machine and human-readable output. The implementation includes a formatter registry pattern, CLI integration, and comprehensive test coverage.

Key changes:

  • Added BaseFormatter, JSONFormatter, and TableFormatter classes for flexible output formatting
  • Implemented Emitter.data(), Emitter.table(), and Emitter.json() methods for structured data output
  • Added --format CLI argument with json/table choices (filtered from help output)

Reviewed changes

Copilot reviewed 13 out of 14 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
craft_cli/messages.py Core formatter classes and Emitter methods for structured data output
craft_cli/dispatcher.py Added --format CLI argument and filtered it from command help
craft_cli/helptexts.py Fixed help text generation when options list is empty
craft_cli/printer.py Added raw_output parameter support to show() method
examples.py Added example_36 demonstrating structured output formats
tests/unit/test_messages_emitter.py Added unit tests for formatters and updated double init test
tests/unit/test_help.py Updated tests to reflect filtered global options from help
tests/integration/test_messages_integration.py Adjusted timing thresholds for test stability
craft_cli/init.py Exposed Emitter and complete in public API
craft_cli/pytest_plugin.py Added type ignore comments for pytest imports
craft_cli/completion/completion.py Added explicit type annotation for template render
docs/conf.py Added type ignore comment for sphinx import
pyproject.toml File deleted (full file removal)
Comments suppressed due to low confidence (1)

pyproject.toml:1

  • The entire pyproject.toml file has been deleted. This removes critical project metadata, dependencies, and build configuration. This change should be reverted unless there's a deliberate migration to a different build system.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


Example:
-------
>>> formatter = JsonFormatter()
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Class name in docstring example should be JSONFormatter (matching the actual class name) instead of JsonFormatter.

Suggested change
>>> formatter = JsonFormatter()
>>> formatter = JSONFormatter()

Copilot uses AI. Check for mistakes.
Example:
-------
>>> formatter = JsonFormatter()
>>> formatter.format([{"name":"Alice","age":30})
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The example code is missing a closing bracket ] and has unbalanced parentheses. Should be formatter.format([{\"name\":\"Alice\",\"age\":30}]).

Suggested change
>>> formatter.format([{"name":"Alice","age":30})
>>> formatter.format([{"name":"Alice","age":30}])

Copilot uses AI. Check for mistakes.
messages, "_get_log_filepath", lambda appname: tmp_path / FAKE_LOG_NAME
)

monkeypatch.setattr(messages, "_get_log_filepath", None)
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting _get_log_filepath to None will cause an error when it's called as a function. This should be a lambda or mock function that returns a path.

Copilot uses AI. Check for mistakes.

class _CustomArgumentParser(argparse.ArgumentParser):
"""ArgumentParser with custom error manager."""

Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] This class attribute appears to be added without context. If this is documenting an existing attribute for type checking, consider adding a comment explaining its purpose.

Suggested change
# Type annotation for instance attribute set in __init__; aids type checking.

Copilot uses AI. Check for mistakes.
@mr-cal
Copy link
Contributor

mr-cal commented Jan 21, 2026

Hi @dharapandya85,

Are you still actively working on this? I haven't seen any activity or responses to the open comments since 2025-Dec-05.

@dharapandya85
Copy link
Author

Hi @dharapandya85,

Are you still actively working on this? I haven't seen any activity or responses to the open comments since 2025-Dec-05.

@mr-cal, working on this project.

@dharapandya85 dharapandya85 force-pushed the feat/emitter-data-output branch 2 times, most recently from 000a63a to 1887069 Compare January 22, 2026 13:29
@dharapandya85 dharapandya85 force-pushed the feat/emitter-data-output branch from 1887069 to 1bcd53d Compare January 22, 2026 13:42
@dharapandya85 dharapandya85 force-pushed the feat/emitter-data-output branch from 1bcd53d to 19bdba0 Compare January 23, 2026 17:59
@mr-cal mr-cal requested a review from lengau January 23, 2026 18:14
class _CustomArgumentParser(argparse.ArgumentParser):
"""ArgumentParser with custom error manager."""

_help_builder: HelpBuilder
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why this is here?

Comment on lines 182 to 187
parser.add_argument(
"--format",
choices=["json", "table"],
default="table",
help="Format for structured output",
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not be included by default on commands, as in many cases there is no structured output.


# produce the complete help message for the command
command_options = self._get_global_options()
command_options: list[tuple[str, str]] = []
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this cause the global options to disappear?

Comment on lines 1566 to 1568
out = fmt.format({"foo": "bar"})
assert "foo" in out
assert "bar" in out
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please test the full tabular output, not just that there are parts in it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Output formats for complex types

5 participants