feat(emitter):add structured data output with JSON and table formatting#380
feat(emitter):add structured data output with JSON and table formatting#380dharapandya85 wants to merge 6 commits intocanonical:mainfrom
Conversation
lengau
left a comment
There was a problem hiding this comment.
Hi, please fix the broken tests and lint errors here.
f33ec01 to
7a4f4b5
Compare
ae4958a to
00bd742
Compare
Problem solved, and no doc changes that require my review.
e64a8dc to
2afdf38
Compare
There was a problem hiding this comment.
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, andTableFormatterclasses for flexible output formatting - Implemented
Emitter.data(),Emitter.table(), andEmitter.json()methods for structured data output - Added
--formatCLI 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.
craft_cli/messages.py
Outdated
| Example: | ||
| ------- | ||
| >>> formatter = JsonFormatter() | ||
| >>> formatter.format([{"name":"Alice","age":30}) |
There was a problem hiding this comment.
The example code is missing a closing bracket ] and has unbalanced parentheses. Should be formatter.format([{\"name\":\"Alice\",\"age\":30}]).
| >>> formatter.format([{"name":"Alice","age":30}) | |
| >>> formatter.format([{"name":"Alice","age":30}]) |
tests/unit/test_messages_emitter.py
Outdated
| messages, "_get_log_filepath", lambda appname: tmp_path / FAKE_LOG_NAME | ||
| ) | ||
|
|
||
| monkeypatch.setattr(messages, "_get_log_filepath", None) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Agreed, this unit test is unrelated to the PR, so these changes should be reverted.
|
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. |
1bcd53d to
19bdba0
Compare
7d7ccf1 to
ec07206
Compare
craft_cli/completion/completion.py
Outdated
| result: str = template.render( | ||
| shell_cmd=shell_cmd, | ||
| commands=command_map, | ||
| global_opts=global_opts, | ||
| ) | ||
| return result |
There was a problem hiding this comment.
This can be reverted, the type checkers are able to deduce the type.
craft_cli/messages.py
Outdated
| from typing import TYPE_CHECKING, Any, Literal, TextIO, TypeVar, cast | ||
|
|
||
| import platformdirs | ||
| import platformdirs # type: ignore [import-not-found] |
There was a problem hiding this comment.
Please revert this, the import should be found.
craft_cli/messages.py
Outdated
| Example: | ||
| ------- | ||
| >>> formatter = JsonFormatter() | ||
| >>> formatter.format([{"name":"Alice","age":30}) |
craft_cli/messages.py
Outdated
| """Output structured data to the terminal in a specific format. | ||
|
|
||
| :param data: The structured data to output( list of dicts or a single dict). | ||
| :param format: The format( defaults to 'table') |
There was a problem hiding this comment.
| :param format: The format( defaults to 'table') | |
| :param format: The format (defaults to 'table') |
craft_cli/messages.py
Outdated
| """ | ||
|
|
||
| def format(self, data: TabularData, headers: dict[str, str] | None = None) -> str: | ||
| _ = headers |
There was a problem hiding this comment.
I don't see this getting used.
tests/unit/test_messages_emitter.py
Outdated
| assert out.strip() == expected.strip() | ||
|
|
||
|
|
||
| def test_table_formatter_empty_list(): |
There was a problem hiding this comment.
Can you test?
- empty list
- empty dict
Both as json and a table?
tests/unit/test_messages_emitter.py
Outdated
|
|
||
|
|
||
| def test_table_formatter_simple_dict_list(): | ||
| data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}] |
There was a problem hiding this comment.
Can you exercise printing the following data types as json and as a table?
- str
- int
- float
- bool
- None
There was a problem hiding this comment.
Can you test a single column table?
There was a problem hiding this comment.
Please revert the changes here.
craft_cli/messages.py
Outdated
| rows = [[str(row.get(h, "")) for h in table_headers] for row in data] | ||
| else: | ||
| table_headers = ["Key", "Value"] | ||
| rows = [[str(k), str(v)] for k, v in data.items()] |
There was a problem hiding this comment.
This is one reason I want to change the TabularData type in https://github.com/canonical/craft-cli/pull/380/changes#r2921516309.
fix JSON formatter
ec07206 to
06fb779
Compare
|
Thank you @mr-cal for detailed review. Please check now for further improvements. Some tests and linting checks are failing for now. In messages.py, I used Mapping, to avoid strict and here, it is failing |
Fixes : #348
make lint && make test?This PR extends the
Emitterclass to support structured data output in multiple formats(JSON,table), with a registry for adding formats.Implementation Details
stdoutTableFormatterfor human-readable tables,JSONFormatterfor machine-readable JSONEmitter.data()method which accepts structured data(list[dict]) and uses registered formatter based on the--formatflag.JSONFormatterto fetch JSON outputs withjson.dumpsTableFormatterfor printing rows and columnsEmitter.register_formate(name,formatter)allows registering new formatsjson,table}example_36in examples.py to demo usage.test_messages_emitter.pyTableFomattter, handles list ofdicts, empty list anddictinputJSONFormatterserializes list of dataExample Run