Skip to content

Commit 0bff654

Browse files
committed
Merge branch 'fix_conversion_io' into develop
2 parents e35ac34 + ca0470f commit 0bff654

9 files changed

Lines changed: 222 additions & 125 deletions

File tree

‎ymmsl/command_line.py‎

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import click
22
import os
33
from shutil import copyfile
4-
from typing import Optional
4+
from typing import Optional, TextIO, Type, Union
5+
import warnings
56

67
from ymmsl.conversion.converter import DowngradeError
78
from ymmsl.document import Document
@@ -10,10 +11,13 @@
1011
import ymmsl.v0_2 as v0_2
1112

1213

13-
_version_tag_to_type = {
14-
'v0.1': v0_1.PartialConfiguration,
15-
'v0.2': v0_2.Configuration,
16-
}
14+
def showwarning(
15+
message: Union[Warning, str], category: Type[Warning], filename: str,
16+
lineno: int, file: Optional[TextIO] = None, line: Optional[str] = None) -> None:
17+
print(f'WARNING: {message}', file=file)
18+
19+
20+
warnings.showwarning = showwarning
1721

1822

1923
@click.group()
@@ -27,6 +31,12 @@ def ymmsl() -> None:
2731
pass
2832

2933

34+
_version_tag_to_type = {
35+
'v0.1': v0_1.PartialConfiguration,
36+
'v0.2': v0_2.Configuration,
37+
}
38+
39+
3040
@ymmsl.command(short_help='Convert a yMMSL file to a newer version')
3141
@click.argument(
3242
'input_file', default='-', type=click.Path(
@@ -87,6 +97,9 @@ def convert(
8797
won't work, because the shell will open the file to do the redirect, empty it, and
8898
then run ymmsl convert, which then fails because the input is empty.
8999
"""
100+
if input_file != '-':
101+
print(f'Converting {input_file}')
102+
90103
if output_file is None:
91104
output_file = input_file
92105

@@ -113,7 +126,11 @@ def convert(
113126
if output_file != '-' and os.path.exists(output_file):
114127
backup_file = output_file + '.bak'
115128
if not os.path.exists(backup_file):
116-
copyfile(output_file, output_file + '.bak')
129+
copyfile(output_file, backup_file)
130+
print(f'Wrote backup file {backup_file}')
117131

118132
with click.open_file(output_file, 'w') as output_stream:
119133
save(document, output_stream)
134+
135+
if input_file != '-':
136+
print('Conversion complete')

‎ymmsl/conversion/convert_v0_1_to_v0_2.py‎

Lines changed: 60 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from copy import deepcopy
2-
from typing import Dict, Optional
2+
from typing import Dict, List, Optional
33
import warnings
44

55
import ymmsl.v0_1 as v0_1
@@ -15,11 +15,21 @@ def convert_v0_1_to_v0_2(config: v0_1.PartialConfiguration) -> v0_2.Configuratio
1515
Returns:
1616
The corresponding configuration expressed in yMMSL v0.2.
1717
"""
18-
description = '' if config.description is None else config.description
18+
description = 'Please add a description'
19+
if config.description:
20+
description = config.description
1921
models = [convert_model(config.model)] if config.model is not None else None
2022
settings = deepcopy(config.settings)
2123
programs = [
2224
convert_implementation(impl) for impl in config.implementations.values()]
25+
if programs:
26+
warnings.warn(
27+
'In yMMSL v0.2 implementations have become programs, and you can now'
28+
' specify the ports of a program in the yMMSL description. If your'
29+
' program has fixed ports then you should do this, because it will make'
30+
' incorrect wiring easier to debug. While there, add a description too!'
31+
)
32+
2333
resources = deepcopy(config.resources)
2434
checkpoints = deepcopy(config.checkpoints)
2535
resume = deepcopy(config.resume)
@@ -32,20 +42,57 @@ def convert_v0_1_to_v0_2(config: v0_1.PartialConfiguration) -> v0_2.Configuratio
3242
def convert_component(component: v0_1.Component) -> v0_2.Component:
3343
"""Convert a v0.1 Component object to a v0.2 Component."""
3444
ports = component.ports if component.ports else v0_1.Ports()
45+
description = 'Please add a description'
3546
implementation: Optional[str] = None
3647
if component.implementation is not None:
3748
implementation = str(component.implementation)
3849

3950
return v0_2.Component(
40-
str(component.name), convert_ports(ports), '', False, implementation,
41-
component.multiplicity)
51+
str(component.name), convert_ports(ports), description, False,
52+
implementation, component.multiplicity)
4253

4354

4455
def convert_conduit(conduit: v0_1.Conduit) -> v0_2.Conduit:
4556
"""Convert a v0.1 Conduit to a v0.2 Conduit."""
4657
return v0_2.Conduit(str(conduit.sender), str(conduit.receiver))
4758

4859

60+
def infer_ports(components: List[v0_2.Component], conduits: List[v0_2.Conduit]) -> None:
61+
"""Infer component ports from conduits where absent.
62+
63+
This can create an incorrect result, because we have to guess at the operators. We
64+
assume that they're F_INIT for receiving ports and O_F for sending ports, but this
65+
can be wrong. Only the user knows, so we'll warn them that they have to check.
66+
"""
67+
changed_components = list()
68+
for component in components:
69+
if len(component.ports) == 0:
70+
for conduit in conduits:
71+
if conduit.sending_component() == component.name:
72+
component.ports[conduit.sending_port()] = v0_2.Port(
73+
conduit.sending_port(), v0_2.Operator.O_F,
74+
v0_2.Timeline(''))
75+
if component.name not in changed_components:
76+
changed_components.append(component.name)
77+
78+
if conduit.receiving_component() == component.name:
79+
component.ports[conduit.receiving_port()] = v0_2.Port(
80+
conduit.receiving_port(), v0_2.Operator.F_INIT,
81+
v0_2.Timeline(''))
82+
if component.name not in changed_components:
83+
changed_components.append(component.name)
84+
85+
if changed_components:
86+
ch_comp_list = '\n - ' + '\n - '.join(map(str, changed_components))
87+
warnings.warn(
88+
'In yMMSL v0.2 components are required to declare their ports. The'
89+
' following components did not have a ports declaration, so one has'
90+
' been added based on the connected conduits. THIS MAY BE WRONG,'
91+
' because the operators have all been set to F_INIT and O_F, while they'
92+
' may really be O_I or S. Please check these components and adjust'
93+
f' as needed: {ch_comp_list}')
94+
95+
4996
def convert_model(model: v0_1.ModelReference) -> v0_2.Model:
5097
"""Convert a v0.1 ModelReference object to a v0.2 Model.
5198
@@ -55,13 +102,16 @@ def convert_model(model: v0_1.ModelReference) -> v0_2.Model:
55102
Returns:
56103
The corresponding configuration expressed in yMMSL v0.2.
57104
"""
105+
description = 'Please add a description'
106+
58107
if isinstance(model, v0_1.Model):
108+
components = list(map(convert_component, model.components))
109+
conduits = list(map(convert_conduit, model.conduits))
110+
infer_ports(components, conduits)
59111
return v0_2.Model(
60-
str(model.name), None, '', None,
61-
list(map(convert_component, model.components)),
62-
list(map(convert_conduit, model.conduits)))
112+
str(model.name), None, description, None, components, conduits)
63113
else:
64-
return v0_2.Model(str(model.name), None, '', None, [], [])
114+
return v0_2.Model(str(model.name), None, description, None, [], [])
65115

66116

67117
def convert_implementation(impl: v0_1.Implementation) -> v0_2.Program:
@@ -76,12 +126,7 @@ def convert_implementation(impl: v0_1.Implementation) -> v0_2.Program:
76126
Returns:
77127
The corresponding program expressed in yMMSL v0.2.
78128
"""
79-
warnings.warn(
80-
'In yMMSL v0.2 implementations have become programs, and you can now'
81-
' specify the ports of a program in the yMMSL description. If your program'
82-
' has fixed ports then you should do this, because it will make incorrect'
83-
' wiring easier to debug. While there, add a description too!')
84-
129+
description = 'Please add a description'
85130
base_env: Optional[v0_1.BaseEnv] = impl.base_env
86131
env: Optional[Dict[str, str]] = impl.env
87132

@@ -94,7 +139,7 @@ def convert_implementation(impl: v0_1.Implementation) -> v0_2.Program:
94139
env = None
95140

96141
return v0_2.Program(
97-
str(impl.name), None, '', None, base_env, impl.modules,
142+
str(impl.name), None, description, None, base_env, impl.modules,
98143
impl.virtual_env, env, execution_model, impl.executable, impl.args,
99144
impl.script, impl.can_share_resources, impl.keeps_state_for_next_use)
100145

‎ymmsl/conversion/tests/test_convert_v0_1_to_v0_2.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
def test_convert_simple_config(empty_config: v0_1.PartialConfiguration) -> None:
1818
v2 = convert_v0_1_to_v0_2(empty_config)
19-
assert v2.description == ''
19+
assert v2.description == 'Please add a description'
2020
assert isinstance(v2.settings, v0_2.Settings)
2121
assert len(v2.settings) == 0
2222
assert v2.resources == {}

‎ymmsl/io.py‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,11 @@
2727
v0_2.BaseEnv, v0_2.CheckpointRangeRule, v0_2.CheckpointAtRule,
2828
v0_2.CheckpointRule, v0_2.Checkpoints, v0_2.Component, v0_2.Conduit,
2929
v0_2.Configuration, v0_2.Document, v0_2.ExecutionModel, v0_2.Identifier,
30-
v0_2.ImportKind, v0_2.ImportStatement, v0_2.KeepsStateForNextUse, v0_2.Model,
31-
v0_2.MPICoresResReq, v0_2.MPINodesResReq, v0_2_MulticastConduit, v0_2.Ports,
32-
v0_2.Program, v0_2.Reference, v0_2.ResourceRequirements, v0_2.SettingType,
33-
v0_2.Settings, v0_2.SupportedSettings, v0_2.ThreadedResReq)
30+
v0_2.Implementation, v0_2.ImportKind, v0_2.ImportStatement,
31+
v0_2.KeepsStateForNextUse, v0_2.Model, v0_2.MPICoresResReq, v0_2.MPINodesResReq,
32+
v0_2_MulticastConduit, v0_2.Ports, v0_2.Program, v0_2.Reference,
33+
v0_2.ResourceRequirements, v0_2.SettingType, v0_2.Settings,
34+
v0_2.SupportedSettings, v0_2.ThreadedResReq)
3435

3536

3637
_load = yatiml.load_function(*_classes)

‎ymmsl/v0_2/configuration.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ def check_duplicate_impl_names(
9595
self.models: MutableMapping[Reference, Model] = dict()
9696
elif isinstance(models, abc.Sequence):
9797
check_duplicate_impl_names('models', models)
98-
self.models = {model.name: model for model in models}
98+
self.models = {copy(model.name): model for model in models}
9999
elif isinstance(models, Model):
100100
self.models = {models.name: models}
101101
else:
@@ -531,6 +531,7 @@ def _yatiml_sweeten(cls, node: yatiml.Node) -> None:
531531
models = node.get_attribute('models')
532532
if (models.is_scalar(type(None)) or models.is_mapping() and models.is_empty()):
533533
node.remove_attribute('models')
534+
node.index_attribute_to_map('models', 'name')
534535

535536
if node.get_attribute('custom_implementations').is_scalar(type(None)):
536537
node.remove_attribute('custom_implementations')

‎ymmsl/v0_2/implementation.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,8 @@ def _yatiml_sweeten(cls, node: yatiml.Node) -> None:
5858
ynode.style = '|'
5959
if not ynode.value.endswith('\n'):
6060
ynode.value += '\n'
61+
62+
if len(node.get_attribute('supported_settings').yaml_node.value) == 0:
63+
node.remove_attribute('supported_settings')
64+
65+
node.remove_attributes_with_default_values(cls)

0 commit comments

Comments
 (0)