Skip to content

Commit 762000c

Browse files
committed
Release version 1.0.17
- Improved CLI documentation with detailed help text using Typer Annotated types - Refactored analyzer output writing into separate function for better maintainability - Fixed analyzer output file handling with proper context managers - Updated CHANGELOG.md with release notes
1 parent c4e5ad5 commit 762000c

5 files changed

Lines changed: 331 additions & 353 deletions

File tree

CHANGELOG.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [1.0.17] - 2025-12-12
11+
12+
### Changed
13+
- **Improved CLI documentation**: Enhanced all command-line interface functions with detailed help text using Typer's `Annotated` types
14+
- **Code refactoring**: Refactored analyzer output writing into separate `_write_analysis_output()` function for better maintainability
15+
- **Better file handling**: Improved file output handling in analyzer command with proper context managers
16+
17+
### Fixed
18+
- Fixed analyzer output not writing to files correctly when `--output` option was used
19+
- Improved consistency between stdout and file output formatting
20+
1021
## [1.0.16] - 2025-12-12
1122

1223
### Added
@@ -116,7 +127,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
116127
### Added
117128
- First public release on PyPI and updated github code
118129

119-
[Unreleased]: https://github.com/datacoon/undatum/compare/v1.0.16...HEAD
130+
[Unreleased]: https://github.com/datacoon/undatum/compare/v1.0.17...HEAD
131+
[1.0.17]: https://github.com/datacoon/undatum/compare/v1.0.16...v1.0.17
120132
[1.0.16]: https://github.com/datacoon/undatum/compare/v1.0.15...v1.0.16
121133
[1.0.15]: https://github.com/datacoon/undatum/compare/v1.0.14...v1.0.15
122134
[1.0.14]: https://github.com/datacoon/undatum/compare/v1.0.13...v1.0.14

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "undatum"
7-
version = "1.0.16"
7+
version = "1.0.17"
88
description = "A powerful command-line tool for data processing and analysis"
99
readme = "README.md"
1010
requires-python = ">=3.8"

undatum/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@
44
55
"""
66

7-
__version__ = "1.0.16"
7+
__version__ = "1.0.17"
88
__author__ = 'Ivan Begtin'
99
__licence__ = 'MIT'

undatum/cmds/analyzer.py

Lines changed: 93 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import io
1212
import json
1313
import os
14+
import sys
1415
import tempfile
1516
from collections import OrderedDict
1617
from typing import Optional
@@ -586,6 +587,88 @@ def _format_number(num):
586587
return f"{num:,}"
587588

588589

590+
def _write_analysis_output(report, options, output_stream):
591+
"""Write analysis report to output stream in the specified format."""
592+
from tabulate import tabulate
593+
594+
if options['outtype'] == 'json':
595+
json_output = json.dumps(report.model_dump(), indent=4, ensure_ascii=False)
596+
output_stream.write(json_output)
597+
output_stream.write('\n')
598+
elif options['outtype'] == 'yaml':
599+
yaml_output = yaml.dump(report.model_dump(), Dumper=yaml.Dumper)
600+
output_stream.write(yaml_output)
601+
elif options['outtype'] == 'markdown':
602+
raise NotImplementedError("Markdown output not implemented")
603+
else:
604+
# Text output format
605+
# Print header
606+
print("=" * 70, file=output_stream)
607+
print("ANALYSIS REPORT", file=output_stream)
608+
print("=" * 70, file=output_stream)
609+
print(file=output_stream)
610+
611+
# File information section
612+
print("File Information", file=output_stream)
613+
print("-" * 70, file=output_stream)
614+
headers = ['Attribute', 'Value']
615+
reptable = []
616+
reptable.append(['Filename', str(report.filename)])
617+
reptable.append(['File size', _format_file_size(report.file_size)])
618+
reptable.append(['File type', report.file_type or 'N/A'])
619+
reptable.append(['Compression', str(report.compression) if report.compression else 'None'])
620+
reptable.append(['Total tables', _format_number(report.total_tables)])
621+
reptable.append(['Total records', _format_number(report.total_records)])
622+
for k, v in report.metadata.items():
623+
reptable.append([k.replace('_', ' ').title(), str(v)])
624+
print(tabulate(reptable, headers=headers, tablefmt='grid'), file=output_stream)
625+
print(file=output_stream)
626+
627+
# Tables section
628+
if report.tables:
629+
print("=" * 70, file=output_stream)
630+
print("TABLE STRUCTURES", file=output_stream)
631+
print("=" * 70, file=output_stream)
632+
print(file=output_stream)
633+
634+
tabheaders = ['Field Name', 'Type', 'Is Array', 'Description']
635+
for idx, rtable in enumerate(report.tables, 1):
636+
if len(report.tables) > 1:
637+
print(f"Table {idx}: {rtable.id}", file=output_stream)
638+
else:
639+
print(f"Table: {rtable.id}", file=output_stream)
640+
print("-" * 70, file=output_stream)
641+
print(f" Records: {_format_number(rtable.num_records)}", file=output_stream)
642+
print(f" Columns: {_format_number(rtable.num_cols)}", file=output_stream)
643+
print(f" Structure: {'Flat' if rtable.is_flat else 'Nested'}", file=output_stream)
644+
print(file=output_stream)
645+
646+
table = []
647+
for field in rtable.fields:
648+
desc = field.description if field.description else '-'
649+
table.append([
650+
field.name,
651+
field.ftype,
652+
'Yes' if field.is_array else 'No',
653+
desc
654+
])
655+
print(tabulate(table, headers=tabheaders, tablefmt='grid'), file=output_stream)
656+
657+
if rtable.description:
658+
print(file=output_stream)
659+
print("Summary:", file=output_stream)
660+
print("-" * 70, file=output_stream)
661+
# Wrap description text for better readability
662+
desc_lines = rtable.description.split('\n')
663+
for line in desc_lines:
664+
if line.strip():
665+
print(f" {line.strip()}", file=output_stream)
666+
667+
if idx < len(report.tables):
668+
print(file=output_stream)
669+
print(file=output_stream)
670+
671+
589672
class Analyzer:
590673
"""Data analysis handler."""
591674
def __init__(self):
@@ -594,94 +677,21 @@ def __init__(self):
594677

595678
def analyze(self, filename, options):
596679
"""Analyzes given data file and returns it's parameters"""
597-
from tabulate import tabulate
598-
599-
table = None
600680
encoding = options.get('encoding')
601681
report = analyze(filename, encoding=encoding,
602682
engine=options['engine'],
603683
use_pandas=options['use_pandas'],
604684
autodoc=options['autodoc'], lang=options['lang'],
605685
ai_provider=options.get('ai_provider'),
606686
ai_config=options.get('ai_config'))
607-
if options['outtype'] == 'json':
608-
if options['output'] is not None:
609-
with open(options['output'], 'w', encoding='utf8') as f:
610-
f.write(json.dumps(report.model_dump()))
611-
else:
612-
print(json.dumps(report.model_dump(), indent=4, ensure_ascii=False))
613-
if options['outtype'] == 'yaml':
614-
if options['output'] is not None:
615-
with open(options['output'], 'w', encoding='utf8') as f:
616-
f.write(yaml.dump(report.model_dump(), Dumper=yaml.Dumper))
617-
else:
618-
print(yaml.dump(report.model_dump(), Dumper=yaml.Dumper))
619-
620-
elif options['outtype'] == 'markdown':
621-
raise NotImplementedError("Markdown output not implemented")
687+
688+
# Determine output destination
689+
output_file = options.get('output')
690+
691+
if output_file:
692+
# Use context manager for file output
693+
with open(output_file, 'w', encoding='utf8') as output_stream:
694+
_write_analysis_output(report, options, output_stream)
622695
else:
623-
# Print header
624-
print("=" * 70)
625-
print("ANALYSIS REPORT")
626-
print("=" * 70)
627-
print()
628-
629-
# File information section
630-
print("File Information")
631-
print("-" * 70)
632-
headers = ['Attribute', 'Value']
633-
reptable = []
634-
reptable.append(['Filename', str(report.filename)])
635-
reptable.append(['File size', _format_file_size(report.file_size)])
636-
reptable.append(['File type', report.file_type or 'N/A'])
637-
reptable.append(['Compression', str(report.compression) if report.compression else 'None'])
638-
reptable.append(['Total tables', _format_number(report.total_tables)])
639-
reptable.append(['Total records', _format_number(report.total_records)])
640-
for k, v in report.metadata.items():
641-
reptable.append([k.replace('_', ' ').title(), str(v)])
642-
print(tabulate(reptable, headers=headers, tablefmt='grid'))
643-
print()
644-
645-
# Tables section
646-
if report.tables:
647-
print("=" * 70)
648-
print("TABLE STRUCTURES")
649-
print("=" * 70)
650-
print()
651-
652-
tabheaders = ['Field Name', 'Type', 'Is Array', 'Description']
653-
for idx, rtable in enumerate(report.tables, 1):
654-
if len(report.tables) > 1:
655-
print(f"Table {idx}: {rtable.id}")
656-
else:
657-
print(f"Table: {rtable.id}")
658-
print("-" * 70)
659-
print(f" Records: {_format_number(rtable.num_records)}")
660-
print(f" Columns: {_format_number(rtable.num_cols)}")
661-
print(f" Structure: {'Flat' if rtable.is_flat else 'Nested'}")
662-
print()
663-
664-
table = []
665-
for field in rtable.fields:
666-
desc = field.description if field.description else '-'
667-
table.append([
668-
field.name,
669-
field.ftype,
670-
'Yes' if field.is_array else 'No',
671-
desc
672-
])
673-
print(tabulate(table, headers=tabheaders, tablefmt='grid'))
674-
675-
if rtable.description:
676-
print()
677-
print("Summary:")
678-
print("-" * 70)
679-
# Wrap description text for better readability
680-
desc_lines = rtable.description.split('\n')
681-
for line in desc_lines:
682-
if line.strip():
683-
print(f" {line.strip()}")
684-
685-
if idx < len(report.tables):
686-
print()
687-
print()
696+
# Write to stdout
697+
_write_analysis_output(report, options, sys.stdout)

0 commit comments

Comments
 (0)