Skip to content

Commit

Permalink
Allow Cylc Config to output a metadata JSON.
Browse files Browse the repository at this point in the history
  • Loading branch information
wxtim committed Aug 23, 2024
1 parent 68a7b37 commit 5480b12
Show file tree
Hide file tree
Showing 3 changed files with 147 additions and 4 deletions.
39 changes: 37 additions & 2 deletions cylc/flow/parsec/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.

from copy import deepcopy
import json
import re
import sys
from textwrap import dedent
from typing import TYPE_CHECKING, Callable, Iterable, List, Optional

Expand All @@ -33,10 +35,12 @@

if TYPE_CHECKING:
from optparse import Values
from typing_extensions import Literal


class ParsecConfig:
"""Object wrapper for parsec functions."""
META: "Literal['meta']" = 'meta'

def __init__(
self,
Expand Down Expand Up @@ -162,7 +166,7 @@ def get(self, keys: Optional[Iterable[str]] = None, sparse: bool = False):
return cfg

def idump(self, items=None, sparse=False, prefix='',
oneline=False, none_str='', handle=None):
oneline=False, none_str='', handle=None, json=False):
"""
items is a list of --item style inputs:
'[runtime][foo]script'.
Expand All @@ -178,7 +182,38 @@ def idump(self, items=None, sparse=False, prefix='',
mkeys.append(j)
if null:
mkeys = [[]]
self.mdump(mkeys, sparse, prefix, oneline, none_str, handle=handle)
if json:
self.jdump(mkeys, sparse, prefix, oneline, none_str, handle=handle)

Check warning on line 186 in cylc/flow/parsec/config.py

View check run for this annotation

Codecov / codecov/patch

cylc/flow/parsec/config.py#L186

Added line #L186 was not covered by tests
else:
self.mdump(mkeys, sparse, prefix, oneline, none_str, handle=handle)

def jdump(
self,
mkeys=None,
sparse=False,
prefix='',
oneline=False,
none_str='',
handle=None
):
"""Dump a config to JSON format.
"""
# When we call json.dumps we use indent to
# control whether output is multi-line:
indent = None

Check warning on line 203 in cylc/flow/parsec/config.py

View check run for this annotation

Codecov / codecov/patch

cylc/flow/parsec/config.py#L203

Added line #L203 was not covered by tests
if not oneline:
indent = 4

Check warning on line 205 in cylc/flow/parsec/config.py

View check run for this annotation

Codecov / codecov/patch

cylc/flow/parsec/config.py#L205

Added line #L205 was not covered by tests

for keys in mkeys:
if not keys:
keys = []
cfg = self.get(keys, sparse)
data = json.dumps(cfg, indent=indent)

Check warning on line 211 in cylc/flow/parsec/config.py

View check run for this annotation

Codecov / codecov/patch

cylc/flow/parsec/config.py#L209-L211

Added lines #L209 - L211 were not covered by tests

# We can replace null values with our none_str:
data = data.replace('null', f'"{none_str}"')

Check warning on line 214 in cylc/flow/parsec/config.py

View check run for this annotation

Codecov / codecov/patch

cylc/flow/parsec/config.py#L214

Added line #L214 was not covered by tests

print(data, file=handle or sys.stdout)

Check warning on line 216 in cylc/flow/parsec/config.py

View check run for this annotation

Codecov / codecov/patch

cylc/flow/parsec/config.py#L216

Added line #L216 was not covered by tests

def mdump(self, mkeys=None, sparse=False, prefix='',
oneline=False, none_str='', handle=None):
Expand Down
16 changes: 14 additions & 2 deletions cylc/flow/scripts/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,16 @@ def get_option_parser() -> COP:
"overrides any settings it shares with those higher up."),
action="store_true", default=False, dest="print_hierarchy")

parser.add_option(
'--json',
help=(
'Print metadata from a Cylc configuration.'
),
default=False,
action='store_true',
dest='json'
)

parser.add_option(icp_option)

platform_listing_options_group = parser.add_option_group(
Expand Down Expand Up @@ -188,7 +198,8 @@ async def _main(
options.item,
not options.defaults,
oneline=options.oneline,
none_str=options.none_str
none_str=options.none_str,
json=options.json,
)
return

Expand All @@ -213,5 +224,6 @@ async def _main(
options.item,
not options.defaults,
oneline=options.oneline,
none_str=options.none_str
none_str=options.none_str,
json=options.json
)
96 changes: 96 additions & 0 deletions tests/functional/cylc-config/11-json-dump.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#!/usr/bin/env bash
# THIS FILE IS PART OF THE CYLC WORKFLOW ENGINE.
# Copyright (C) NIWA & British Crown (Met Office) & Contributors.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#-------------------------------------------------------------------------------
# Test cylc config can dump json files.
# n.b. not heavily tested because most of this functionality
# is from Standard library json.
. "$(dirname "$0")/test_header"
#-------------------------------------------------------------------------------
set_test_number 6
#-------------------------------------------------------------------------------

# Test the global.cylc
TEST_NAME="${TEST_NAME_BASE}-global"

cat > "global.cylc" <<__HEREDOC__
[platforms]
[[golders_green]]
[[[meta]]]
can = "Test lots of things"
because = metadata, is, not, fussy
number = 99
__HEREDOC__

export CYLC_CONF_PATH="${PWD}"
run_ok "${TEST_NAME}" cylc config --json --one-line
cmp_ok "${TEST_NAME}.stdout" <<__HERE__
{"platforms": {"golders_green": {"meta": {"can": "Test lots of things", "because": "metadata, is, not, fussy", "number": "99"}}}}
__HERE__

# Test a flow.cylc
TEST_NAME="${TEST_NAME_BASE}-workflow"

cat > "flow.cylc" <<__HERE__
[scheduling]
[[graph]]
P1D = foo
[runtime]
[[foo]]
__HERE__

run_ok "${TEST_NAME}" cylc config . --json --icp 1000
cmp_ok "${TEST_NAME}.stdout" <<__HERE__
{
"scheduling": {
"graph": {
"P1D": "foo"
},
"initial cycle point": "1000"
},
"runtime": {
"root": {},
"foo": {
"completion": "succeeded"
}
}
}
__HERE__

# Test an empty global.cylc to check:
# * item selection
# * null value setting
# * showing defaults
TEST_NAME="${TEST_NAME_BASE}-defaults-item-null-value"
echo "" > global.cylc
export CYLC_CONF_PATH="${PWD}"

run_ok "${TEST_NAME}" cylc config \
-i '[scheduler][mail]' \
--json \
--defaults \
--null-value='zilch'

cmp_ok "${TEST_NAME}.stdout" <<__HERE__
{
"from": "zilch",
"smtp": "zilch",
"to": "zilch",
"footer": "zilch",
"task event batch interval": 300.0
}
__HERE__

0 comments on commit 5480b12

Please sign in to comment.