Skip to content

Commit e52f76f

Browse files
committed
Use instances instead of structs for object instantiations
1 parent 949af38 commit e52f76f

7 files changed

Lines changed: 153 additions & 107 deletions

File tree

pfdl_scheduler/model/instance.py

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"""Contains Instance class."""
88

99
# standard libraries
10-
import uuid
10+
import copy
1111
from numbers import Number
1212
from typing import Dict, Union
1313

@@ -16,6 +16,8 @@
1616

1717
# local sources
1818
## PFDL base sources
19+
from pfdl_scheduler.model.array import Array
20+
from pfdl_scheduler.pfdl_base_classes import PFDLBaseClasses
1921
from pfdl_scheduler.validation.error_handler import ErrorHandler
2022

2123

@@ -56,39 +58,61 @@ def __init__(
5658
self.context: ParserRuleContext = context
5759
self.attribute_contexts: Dict = {}
5860

61+
def __deepcopy__(self, memo):
62+
cls = self.__class__
63+
result = cls.__new__(cls)
64+
memo[id(self)] = result
65+
for attr, value in self.__dict__.items():
66+
try:
67+
setattr(result, attr, copy.deepcopy(value, memo))
68+
except Exception:
69+
setattr(result, attr, value)
70+
return result
71+
5972
@classmethod
6073
def from_json(
6174
cls,
6275
json_object: Dict,
6376
error_handler: ErrorHandler,
6477
struct_context: ParserRuleContext,
65-
instance_class="Instance",
78+
pfdl_base_classes=PFDLBaseClasses,
6679
):
67-
return parse_json(json_object, error_handler, struct_context, instance_class)
80+
return parse_json(json_object, error_handler, struct_context, pfdl_base_classes)
6881

6982

7083
def parse_json(
7184
json_object: Dict,
7285
error_handler: ErrorHandler,
7386
instance_context: ParserRuleContext,
74-
instance_class=Instance,
87+
pfdl_base_classes=PFDLBaseClasses,
7588
) -> Instance:
7689
"""Parses the JSON Struct initialization.
7790
7891
Returns:
7992
An Instance object representing the initialized instance.
8093
"""
81-
instance = instance_class()
94+
instance = pfdl_base_classes.get_class("Instance")()
8295
instance.context = instance_context
8396
for identifier, value in json_object.items():
8497
if isinstance(value, (int, str, bool)):
8598
instance.attributes[identifier] = value
8699
elif isinstance(value, list):
87-
if error_handler and instance_context:
88-
error_msg = "Array definition in JSON are not supported in the PFDL."
89-
error_handler.print_error(error_msg, context=instance_context)
100+
array = pfdl_base_classes.get_class("Array")()
101+
instance.attributes[identifier] = array
102+
for element in value:
103+
if isinstance(element, (int, float, str, bool)):
104+
if isinstance(element, bool):
105+
array.type_of_elements = "boolean"
106+
elif isinstance(element, (int, float)):
107+
array.type_of_elements = "number"
108+
else:
109+
array.type_of_elements = "string"
110+
array.append_value(element)
111+
elif isinstance(element, dict):
112+
inner_struct = parse_json(element, error_handler)
113+
array.append_value(inner_struct)
90114
elif isinstance(value, dict):
91-
inner_struct = parse_json(value, error_handler, instance_context, instance_class)
115+
inner_struct = parse_json(value, error_handler, instance_context, pfdl_base_classes)
92116
instance.attributes[identifier] = inner_struct
93117

94118
return instance

pfdl_scheduler/model/service.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from antlr4.ParserRuleContext import ParserRuleContext
1515

1616
# local sources
17+
from pfdl_scheduler.model.instance import Instance
1718
from pfdl_scheduler.model.struct import Struct
1819
from pfdl_scheduler.model.array import Array
1920

@@ -36,7 +37,7 @@ class Service:
3637
def __init__(
3738
self,
3839
name: str = "",
39-
input_parameters: List[Union[str, List[str], Struct]] = None,
40+
input_parameters: List[Union[str, List[str], Instance]] = None,
4041
output_parameters: Dict[str, Union[str, Array]] = None,
4142
context: ParserRuleContext = None,
4243
) -> None:
@@ -51,9 +52,9 @@ def __init__(
5152
self.name: str = name
5253

5354
if input_parameters:
54-
self.input_parameters: List[Union[str, List[str], Struct]] = input_parameters
55+
self.input_parameters: List[Union[str, List[str], Instance]] = input_parameters
5556
else:
56-
self.input_parameters: List[Union[str, List[str], Struct]] = []
57+
self.input_parameters: List[Union[str, List[str], Instance]] = []
5758

5859
if output_parameters:
5960
self.output_parameters: OrderedDict[str, Union[str, Array]] = output_parameters

pfdl_scheduler/model/struct.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
# standard libraries
1010
import copy
1111
from dataclasses import dataclass
12-
from typing import Dict, Union
12+
from typing import Dict, Type, Union
1313
import json
1414

1515
# 3rd party libraries
@@ -91,7 +91,11 @@ def __deepcopy__(self, memo):
9191

9292
@classmethod
9393
def from_json(
94-
cls, json_string: str, error_handler: ErrorHandler, struct_context: ParserRuleContext
94+
cls,
95+
json_string: str,
96+
error_handler: ErrorHandler,
97+
struct_context: ParserRuleContext,
98+
struct_class: Type,
9599
) -> "Struct":
96100
"""Creates a Struct instance out of the given JSON string.
97101
@@ -103,24 +107,28 @@ def from_json(
103107
The Struct which was created from the JSON string.
104108
"""
105109
json_object = json.loads(json_string)
106-
struct = parse_json(json_object, error_handler, struct_context)
110+
struct = parse_json(json_object, error_handler, struct_context, struct_class)
107111
return struct
108112

109113

110114
def parse_json(
111-
json_object: Dict, error_handler: ErrorHandler, struct_context: ParserRuleContext
115+
json_object: Dict,
116+
error_handler: ErrorHandler,
117+
struct_context: ParserRuleContext,
118+
struct_class: Type,
112119
) -> Struct:
113120
"""Parses the JSON Struct initialization.
114121
115122
Args:
116123
json_object: A JSON object describing the Struct.
117124
error_handler: An ErrorHandler instance used for printing errors.
118125
struct_context: The ANTLR struct context the struct corresponds to.
126+
struct_class: The class of the struct from which the struct object is created.
119127
120128
Returns:
121129
A Struct object representing the initialized Struct.
122130
"""
123-
struct = Struct()
131+
struct = struct_class()
124132
struct.context = struct_context
125133

126134
for identifier, value in json_object.items():
@@ -140,9 +148,9 @@ def parse_json(
140148
array.type_of_elements = "string"
141149
array.append_value(element)
142150
elif isinstance(element, dict):
143-
inner_struct = parse_json(element, error_handler, struct_context)
151+
inner_struct = parse_json(element, error_handler, struct_context, struct_class)
144152
array.append_value(inner_struct)
145153
elif isinstance(value, dict):
146-
inner_struct = parse_json(value, error_handler, struct_context)
154+
inner_struct = parse_json(value, error_handler, struct_context, struct_class)
147155
struct.attributes[identifier] = inner_struct
148156
return struct

pfdl_scheduler/parser/pfdl_tree_visitor.py

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"""Contains PFDLTreeVisitor class."""
88

99
# standard libraries
10+
import json
1011
from typing import Dict, List, OrderedDict, Tuple, Union
1112
from pfdl_scheduler.model.instance import Instance
1213
from pfdl_scheduler.pfdl_base_classes import PFDLBaseClasses
@@ -213,7 +214,7 @@ def visitInstance(self, ctx: PFDLParser.InstanceContext) -> Instance:
213214
attribute_value,
214215
self.error_handler,
215216
ctx,
216-
self.pfdl_base_classes.get_class("Instance"),
217+
self.pfdl_base_classes,
217218
)
218219
instance.attributes[attribute_name] = attribute_value
219220
instance.attribute_contexts[attribute_name] = attribute_assignment_ctx
@@ -294,12 +295,15 @@ def visitCall_input(
294295
self, ctx: PFDLParser.Call_inputContext
295296
) -> List[Union[str, List[str], Struct]]:
296297
input_params = []
297-
for child in ctx.parameter():
298-
parameter = self.visitParameter(child)
299-
input_params.append(parameter)
300-
for child in ctx.struct_initialization():
301-
struct = self.visitStruct_initialization(child)
302-
input_params.append(struct)
298+
for child in ctx.children:
299+
if isinstance(child, self.pfdl_base_classes.get_class("PFDLParser").ParameterContext):
300+
parameter = self.visitParameter(child)
301+
input_params.append(parameter)
302+
elif isinstance(
303+
child, self.pfdl_base_classes.get_class("PFDLParser").Struct_initializationContext
304+
):
305+
instance = self.visitStruct_initialization(child)
306+
input_params.append(instance)
303307
return input_params
304308

305309
def visitCall_output(self, ctx: PFDLParser.Call_outputContext) -> Dict[str, Union[str, Array]]:
@@ -320,15 +324,18 @@ def visitParameter(self, ctx: PFDLParser.ParameterContext) -> Union[str, List[st
320324
return ctx.STARTS_WITH_LOWER_C_STR().getText()
321325
return self.visitAttribute_access(ctx.attribute_access())
322326

323-
def visitStruct_initialization(self, ctx: PFDLParser.Struct_initializationContext) -> Struct:
327+
def visitStruct_initialization(self, ctx: PFDLParser.Struct_initializationContext) -> Instance:
324328
json_string = ctx.json_object().getText()
325329

326-
struct = self.pfdl_base_classes.get_class("Struct").from_json(
327-
json_string, self.error_handler, ctx.json_object()
330+
instance = self.pfdl_base_classes.get_class("Instance").from_json(
331+
json.loads(json_string),
332+
self.error_handler,
333+
ctx.json_object(),
334+
self.pfdl_base_classes,
328335
)
329-
struct.name = ctx.STARTS_WITH_UPPER_C_STR().getText()
330-
struct.context = ctx
331-
return struct
336+
instance.name = ctx.STARTS_WITH_UPPER_C_STR().getText()
337+
instance.context = ctx
338+
return instance
332339

333340
def visitTask_call(self, ctx: PFDLParser.Task_callContext) -> TaskCall:
334341
task_call = self.pfdl_base_classes.get_class("TaskCall")()

pfdl_scheduler/validation/semantic_error_checker.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -577,7 +577,7 @@ def check_call_input_parameters(
577577
valid = True
578578

579579
for input_parameter in called_entity.input_parameters:
580-
if isinstance(input_parameter, self.pfdl_base_classes.get_class("Struct")):
580+
if isinstance(input_parameter, self.pfdl_base_classes.get_class("Instance")):
581581
if not self.check_instantiated_struct_attributes(input_parameter):
582582
valid = False
583583
elif isinstance(input_parameter, list):
@@ -660,7 +660,7 @@ def check_call_output_parameters(self, called_entity: Union[Service, TaskCall])
660660
valid = False
661661
return valid
662662

663-
def check_instantiated_struct_attributes(self, struct_instance: Struct) -> bool:
663+
def check_instantiated_struct_attributes(self, instance: Instance) -> bool:
664664
"""Calls multiple check methods to validate an instantiated Struct.
665665
666666
Multiple Checks are done:
@@ -670,25 +670,26 @@ def check_instantiated_struct_attributes(self, struct_instance: Struct) -> bool:
670670
(4) Check if attributes in the instance do not match with attributes in the definition.
671671
672672
Args:
673-
struct_instance: The instantiated struct that is checked.
673+
instance: The instantiated struct that is checked.
674674
675675
Returns:
676676
True if the instantiated Struct is valid.
677677
"""
678678
valid = True
679-
if self.check_if_struct_exists(struct_instance):
680-
struct_definition = self.structs[struct_instance.name]
679+
if self.check_if_struct_exists(instance):
680+
struct_definition = self.structs[instance.name]
681681

682-
if not self.check_for_missing_attribute_in_struct(struct_instance, struct_definition):
682+
if not self.check_for_missing_attribute_in_struct(instance, struct_definition):
683683
valid = False
684684

685-
for identifier in struct_instance.attributes:
685+
# Create a copy of the struct instance attributes and remove default attributes
686+
for identifier in instance.attributes:
686687
if not (
687688
self.check_for_unknown_attribute_in_struct(
688-
struct_instance, identifier, struct_definition
689+
instance, identifier, struct_definition
689690
)
690-
and self.check_for_wrong_attribute_type_in_struct(
691-
struct_instance, identifier, struct_definition
691+
and self.check_for_wrong_attribute_type_in_instance(
692+
instance, identifier, struct_definition
692693
)
693694
):
694695
valid = False
@@ -732,7 +733,7 @@ def check_for_unknown_attribute_in_struct(
732733
return False
733734
return True
734735

735-
def check_for_wrong_attribute_type_in_struct(
736+
def check_for_wrong_attribute_type_in_instance(
736737
self, struct_instance: Struct, identifier: str, struct_definition: Struct
737738
) -> bool:
738739
"""Calls check methods for the attribute assignments in an instantiated Struct.
@@ -757,7 +758,7 @@ def check_for_wrong_attribute_type_in_struct(
757758
struct_def = self.structs[correct_attribute_type]
758759
struct_correct = True
759760
for identifier in attribute.attributes:
760-
if not self.check_for_wrong_attribute_type_in_struct(
761+
if not self.check_for_wrong_attribute_type_in_instance(
761762
attribute, identifier, struct_def
762763
):
763764
struct_correct = False

tests/unit_test/model/test_struct.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,9 @@ def test_struct_from_json(self):
7171
context = ParserRuleContext()
7272

7373
json_string = '{"attr1": "value1", "attr2": [1, 2, 3], "attr3": {"attr4": "value4"}}'
74-
struct = Struct.from_json(json_string, ErrorHandler("", False), context)
74+
struct = Struct.from_json(
75+
json_string, ErrorHandler("", False), context, struct_class=Struct
76+
)
7577
self.assertEqual(struct.name, "")
7678

7779
attributes = {
@@ -88,7 +90,7 @@ def test_parse_json(self):
8890
context = ParserRuleContext()
8991

9092
# empty
91-
struct = parse_json({}, ErrorHandler("", False), context)
93+
struct = parse_json({}, ErrorHandler("", False), context, Struct)
9294
self.assertEqual(struct.name, "")
9395
self.assertEqual(struct.attributes, {})
9496
self.assertEqual(struct.context, context)
@@ -97,7 +99,10 @@ def test_parse_json(self):
9799
# simple attributes
98100
attributes = {"attr1": "value1", "attr2": 123, "attr3": True}
99101
struct = parse_json(
100-
{"attr1": "value1", "attr2": 123, "attr3": True}, ErrorHandler("", False), context
102+
{"attr1": "value1", "attr2": 123, "attr3": True},
103+
ErrorHandler("", False),
104+
context,
105+
Struct,
101106
)
102107
self.assertEqual(struct.name, "")
103108

@@ -114,7 +119,7 @@ def test_parse_json(self):
114119
"attr7": {"attr8": {"attr9": True}},
115120
"attr10": [True, True, False],
116121
}
117-
struct = parse_json(struct_dict, ErrorHandler("", False), None)
122+
struct = parse_json(struct_dict, ErrorHandler("", False), None, Struct)
118123
self.assertEqual(struct.name, "")
119124
self.assertEqual(
120125
struct.attributes,
@@ -144,7 +149,7 @@ def test_parse_json(self):
144149
},
145150
]
146151
}
147-
struct = parse_json(struct_dict, ErrorHandler("", False), None)
152+
struct = parse_json(struct_dict, ErrorHandler("", False), None, Struct)
148153
self.assertEqual(struct.name, "")
149154
array = Array("", [Struct("", {"attr2": 5}), Struct("", {"attr3": "string"})])
150155
self.assertEqual(struct.attributes, {"attr1": array})

0 commit comments

Comments
 (0)