Skip to content

Commit 1680497

Browse files
authored
Merge pull request #244 from oslokommune/csv-output
Add new output format option for printing CSV
2 parents 898b3cc + 8d05c5f commit 1680497

5 files changed

Lines changed: 57 additions & 23 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
* Streamlined output in the dataset listing commands.
1010
* New option `--verbose` for the `datasets ls` command which lists every
1111
relevant metadata field for the listed datasets.
12+
* New output format option for printing CSV: `--format=csv`.
1213

1314
## 4.4.0 - 2025-05-14
1415

doc/datasets.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ uploaded=false
208208
echo "Checking status for uploaded file"
209209
while ! $uploaded; do
210210
echo "\Checking upload status..."
211-
upload_status=`okdaata status $trace_id --format=json`
211+
upload_status=`okdata status $trace_id --format=json`
212212
uploaded=`echo $upload_status | jq -r '.done'`
213213
done
214214
echo "Uploaded file is processed and ready to be consumed"

okdata/cli/command.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,9 @@ def handle_input(self):
7979
return content
8080

8181
def print(self, str, payload=None):
82-
is_json = self.opt("format") == "json"
83-
if not is_json:
82+
is_structured_data = self.opt("format") in ("csv", "json")
83+
84+
if not is_structured_data:
8485
print(str)
8586
# Normally a return json value from the API
8687
if payload:

okdata/cli/commands/datasets/datasets.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class DatasetsCommand(BaseCommand):
3838
okdata datasets ls my-dataset --verbose
3939
okdata datasets ls my-dataset/1
4040
okdata datasets ls my-dataset/1/20240101T102030
41+
okdata datasets ls my-dataset/1/20240101T102030 --format=csv
4142
okdata datasets ls my-dataset/1/20240101T102030 --format=json
4243
okdata datasets create --file=dataset.json
4344
okdata datasets cp /tmp/file.csv ds:my-dataset-id

okdata/cli/output.py

Lines changed: 51 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import csv
2+
import io
13
import json
24
import logging
35
import os
@@ -9,25 +11,28 @@
911
log = logging.getLogger()
1012

1113

12-
def get_script_path():
14+
def _get_script_path():
1315
return os.path.dirname(__file__)
1416

1517

16-
# Create a output printer
17-
#
18-
# Formatting of output is defined in okdata/cli/data/ouput-format.
19-
# Each command defines what (from the API response) should be printed, and
20-
# what the human-readable name should be
21-
def create_output(format, configfile):
22-
datadir = f"{get_script_path()}/data/output-format"
23-
filename = f"{datadir}/{configfile}"
24-
log.info(f"Creating output format: {format}, from: {filename}")
25-
with open(filename) as config_file:
26-
config = json.load(config_file)
18+
def create_output(fmt, config_file):
19+
"""Create and return an output printer.
2720
28-
if format == "json":
29-
return JsonOutput(config)
30-
return TableOutput(config)
21+
Formatting of output is defined in the `okdata/cli/data/ouput-format`
22+
directory. Each command defines which fields from the API response should
23+
be printed, and what the human-readable name should be.
24+
"""
25+
datadir = f"{_get_script_path()}/data/output-format"
26+
filename = f"{datadir}/{config_file}"
27+
log.info(f"Creating output format: {fmt}, from: {filename}")
28+
29+
with open(filename) as f:
30+
config = json.load(f)
31+
32+
return {
33+
"json": JsonOutput(config),
34+
"csv": CSVOutput(config),
35+
}.get(fmt, TableOutput(config))
3136

3237

3338
class TableOutput(PrettyTable):
@@ -104,13 +109,9 @@ def format_cell_value(value, max_width):
104109
return value
105110

106111

107-
# TODO: merge many outputs to one array of elements when listing a version
108-
class JsonOutput:
112+
class StructuredDataOutput:
109113
def __init__(self, config):
110114
self.config = config
111-
# set to True it will return a single object from self.out when printed. This will make it
112-
# easier to pass output to jq without having to access out[0] from the CLI
113-
self.output_singular_object = False
114115
self.out = []
115116

116117
def field_values(self, row, row_key, key):
@@ -144,6 +145,36 @@ def add_rows(self, rows):
144145
for row in rows:
145146
self.add_row(row)
146147

148+
def __str__(self):
149+
raise NotImplementedError
150+
151+
152+
class CSVOutput(StructuredDataOutput):
153+
def __str__(self):
154+
out = io.StringIO()
155+
writer = csv.DictWriter(out, self.out[0].keys())
156+
writer.writeheader()
157+
writer.writerows(self.out)
158+
return out.getvalue()
159+
160+
def get_row_value(self, row, key):
161+
val = super().get_row_value(row, key)
162+
163+
if isinstance(val, str):
164+
return val.replace("\n", "")
165+
elif isinstance(val, list):
166+
return ";".join(val)
167+
return val
168+
169+
170+
# TODO: merge many outputs to one array of elements when listing a version
171+
class JsonOutput(StructuredDataOutput):
172+
def __init__(self, config):
173+
super().__init__(config)
174+
# set to True it will return a single object from self.out when printed. This will make it
175+
# easier to pass output to jq without having to access out[0] from the CLI
176+
self.output_singular_object = False
177+
147178
def __repr__(self):
148179
return json.dumps(self.out)
149180

0 commit comments

Comments
 (0)