datacontract-cli 0.10.23__py3-none-any.whl → 0.10.40__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. datacontract/__init__.py +13 -0
  2. datacontract/api.py +12 -5
  3. datacontract/catalog/catalog.py +5 -3
  4. datacontract/cli.py +119 -13
  5. datacontract/data_contract.py +145 -67
  6. datacontract/engines/data_contract_checks.py +366 -60
  7. datacontract/engines/data_contract_test.py +50 -4
  8. datacontract/engines/fastjsonschema/check_jsonschema.py +37 -19
  9. datacontract/engines/fastjsonschema/s3/s3_read_files.py +3 -2
  10. datacontract/engines/soda/check_soda_execute.py +27 -3
  11. datacontract/engines/soda/connections/athena.py +79 -0
  12. datacontract/engines/soda/connections/duckdb_connection.py +65 -6
  13. datacontract/engines/soda/connections/kafka.py +4 -2
  14. datacontract/engines/soda/connections/oracle.py +50 -0
  15. datacontract/export/avro_converter.py +20 -3
  16. datacontract/export/bigquery_converter.py +1 -1
  17. datacontract/export/dbt_converter.py +36 -7
  18. datacontract/export/dqx_converter.py +126 -0
  19. datacontract/export/duckdb_type_converter.py +57 -0
  20. datacontract/export/excel_exporter.py +923 -0
  21. datacontract/export/exporter.py +3 -0
  22. datacontract/export/exporter_factory.py +17 -1
  23. datacontract/export/great_expectations_converter.py +55 -5
  24. datacontract/export/{html_export.py → html_exporter.py} +31 -20
  25. datacontract/export/markdown_converter.py +134 -5
  26. datacontract/export/mermaid_exporter.py +110 -0
  27. datacontract/export/odcs_v3_exporter.py +193 -149
  28. datacontract/export/protobuf_converter.py +163 -69
  29. datacontract/export/rdf_converter.py +2 -2
  30. datacontract/export/sodacl_converter.py +9 -1
  31. datacontract/export/spark_converter.py +31 -4
  32. datacontract/export/sql_converter.py +6 -2
  33. datacontract/export/sql_type_converter.py +124 -8
  34. datacontract/imports/avro_importer.py +63 -12
  35. datacontract/imports/csv_importer.py +111 -57
  36. datacontract/imports/excel_importer.py +1112 -0
  37. datacontract/imports/importer.py +16 -3
  38. datacontract/imports/importer_factory.py +17 -0
  39. datacontract/imports/json_importer.py +325 -0
  40. datacontract/imports/odcs_importer.py +2 -2
  41. datacontract/imports/odcs_v3_importer.py +367 -151
  42. datacontract/imports/protobuf_importer.py +264 -0
  43. datacontract/imports/spark_importer.py +117 -13
  44. datacontract/imports/sql_importer.py +32 -16
  45. datacontract/imports/unity_importer.py +84 -38
  46. datacontract/init/init_template.py +1 -1
  47. datacontract/integration/entropy_data.py +126 -0
  48. datacontract/lint/resolve.py +112 -23
  49. datacontract/lint/schema.py +24 -15
  50. datacontract/lint/urls.py +17 -3
  51. datacontract/model/data_contract_specification/__init__.py +1 -0
  52. datacontract/model/odcs.py +13 -0
  53. datacontract/model/run.py +3 -0
  54. datacontract/output/junit_test_results.py +3 -3
  55. datacontract/schemas/datacontract-1.1.0.init.yaml +1 -1
  56. datacontract/schemas/datacontract-1.2.0.init.yaml +91 -0
  57. datacontract/schemas/datacontract-1.2.0.schema.json +2029 -0
  58. datacontract/schemas/datacontract-1.2.1.init.yaml +91 -0
  59. datacontract/schemas/datacontract-1.2.1.schema.json +2058 -0
  60. datacontract/schemas/odcs-3.0.2.schema.json +2382 -0
  61. datacontract/schemas/odcs-3.1.0.schema.json +2809 -0
  62. datacontract/templates/datacontract.html +54 -3
  63. datacontract/templates/datacontract_odcs.html +685 -0
  64. datacontract/templates/index.html +5 -2
  65. datacontract/templates/partials/server.html +2 -0
  66. datacontract/templates/style/output.css +319 -145
  67. {datacontract_cli-0.10.23.dist-info → datacontract_cli-0.10.40.dist-info}/METADATA +711 -433
  68. datacontract_cli-0.10.40.dist-info/RECORD +121 -0
  69. {datacontract_cli-0.10.23.dist-info → datacontract_cli-0.10.40.dist-info}/WHEEL +1 -1
  70. {datacontract_cli-0.10.23.dist-info → datacontract_cli-0.10.40.dist-info/licenses}/LICENSE +1 -1
  71. datacontract/export/csv_type_converter.py +0 -36
  72. datacontract/integration/datamesh_manager.py +0 -72
  73. datacontract/lint/lint.py +0 -142
  74. datacontract/lint/linters/description_linter.py +0 -35
  75. datacontract/lint/linters/field_pattern_linter.py +0 -34
  76. datacontract/lint/linters/field_reference_linter.py +0 -48
  77. datacontract/lint/linters/notice_period_linter.py +0 -55
  78. datacontract/lint/linters/quality_schema_linter.py +0 -52
  79. datacontract/lint/linters/valid_constraints_linter.py +0 -100
  80. datacontract/model/data_contract_specification.py +0 -327
  81. datacontract_cli-0.10.23.dist-info/RECORD +0 -113
  82. /datacontract/{lint/linters → output}/__init__.py +0 -0
  83. {datacontract_cli-0.10.23.dist-info → datacontract_cli-0.10.40.dist-info}/entry_points.txt +0 -0
  84. {datacontract_cli-0.10.23.dist-info → datacontract_cli-0.10.40.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,18 @@
1
- from typing import Dict
2
-
3
- import yaml
1
+ from typing import Any, Dict
2
+
3
+ from open_data_contract_standard.model import (
4
+ CustomProperty,
5
+ DataQuality,
6
+ Description,
7
+ OpenDataContractStandard,
8
+ Role,
9
+ SchemaObject,
10
+ SchemaProperty,
11
+ Server,
12
+ ServiceLevelAgreementProperty,
13
+ Support,
14
+ Team,
15
+ )
4
16
 
5
17
  from datacontract.export.exporter import Exporter
6
18
  from datacontract.model.data_contract_specification import DataContractSpecification, Field, Model
@@ -12,154 +24,149 @@ class OdcsV3Exporter(Exporter):
12
24
 
13
25
 
14
26
  def to_odcs_v3_yaml(data_contract_spec: DataContractSpecification) -> str:
15
- odcs = {
16
- "apiVersion": "v3.0.0",
17
- "kind": "DataContract",
18
- "id": data_contract_spec.id,
19
- "name": data_contract_spec.info.title,
20
- "version": data_contract_spec.info.version,
21
- "domain": data_contract_spec.info.owner,
22
- "status": to_status(data_contract_spec.info.status),
23
- }
27
+ result = to_odcs_v3(data_contract_spec)
28
+
29
+ return result.to_yaml()
30
+
24
31
 
32
+ def to_odcs_v3(data_contract_spec: DataContractSpecification) -> OpenDataContractStandard:
33
+ result = OpenDataContractStandard(
34
+ apiVersion="v3.1.0",
35
+ kind="DataContract",
36
+ id=data_contract_spec.id,
37
+ name=data_contract_spec.info.title,
38
+ version=data_contract_spec.info.version,
39
+ status=to_status(data_contract_spec.info.status),
40
+ )
25
41
  if data_contract_spec.terms is not None:
26
- odcs["description"] = {
27
- "purpose": data_contract_spec.terms.description.strip()
42
+ result.description = Description(
43
+ purpose=data_contract_spec.terms.description.strip()
28
44
  if data_contract_spec.terms.description is not None
29
45
  else None,
30
- "usage": data_contract_spec.terms.usage.strip() if data_contract_spec.terms.usage is not None else None,
31
- "limitations": data_contract_spec.terms.limitations.strip()
46
+ usage=data_contract_spec.terms.usage.strip() if data_contract_spec.terms.usage is not None else None,
47
+ limitations=data_contract_spec.terms.limitations.strip()
32
48
  if data_contract_spec.terms.limitations is not None
33
49
  else None,
34
- }
35
-
36
- odcs["schema"] = []
50
+ )
51
+ result.schema_ = []
37
52
  for model_key, model_value in data_contract_spec.models.items():
38
53
  odcs_schema = to_odcs_schema(model_key, model_value)
39
- odcs["schema"].append(odcs_schema)
40
-
54
+ result.schema_.append(odcs_schema)
41
55
  if data_contract_spec.servicelevels is not None:
42
56
  slas = []
43
57
  if data_contract_spec.servicelevels.availability is not None:
44
58
  slas.append(
45
- {
46
- "property": "generalAvailability",
47
- "value": data_contract_spec.servicelevels.availability.description,
48
- }
59
+ ServiceLevelAgreementProperty(
60
+ property="generalAvailability", value=data_contract_spec.servicelevels.availability.description
61
+ )
49
62
  )
50
63
  if data_contract_spec.servicelevels.retention is not None:
51
- slas.append({"property": "retention", "value": data_contract_spec.servicelevels.retention.period})
64
+ slas.append(
65
+ ServiceLevelAgreementProperty(
66
+ property="retention", value=data_contract_spec.servicelevels.retention.period
67
+ )
68
+ )
52
69
 
53
70
  if len(slas) > 0:
54
- odcs["slaProperties"] = slas
55
-
71
+ result.slaProperties = slas
56
72
  if data_contract_spec.info.contact is not None:
57
73
  support = []
58
74
  if data_contract_spec.info.contact.email is not None:
59
- support.append(
60
- {
61
- "channel": "email",
62
- "url": "mailto:" + data_contract_spec.info.contact.email,
63
- }
64
- )
75
+ support.append(Support(channel="email", url="mailto:" + data_contract_spec.info.contact.email))
65
76
  if data_contract_spec.info.contact.url is not None:
66
- support.append(
67
- {
68
- "channel": "other",
69
- "url": data_contract_spec.info.contact.url,
70
- }
71
- )
77
+ support.append(Support(channel="other", url=data_contract_spec.info.contact.url))
72
78
  if len(support) > 0:
73
- odcs["support"] = support
74
-
79
+ result.support = support
75
80
  if data_contract_spec.servers is not None and len(data_contract_spec.servers) > 0:
76
81
  servers = []
77
82
 
78
83
  for server_key, server_value in data_contract_spec.servers.items():
79
- server_dict = {}
80
- server_dict["server"] = server_key
81
- if server_value.type is not None:
82
- server_dict["type"] = server_value.type
84
+ server = Server(server=server_key, type=server_value.type or "")
85
+
86
+ # Set all the attributes that are not None
83
87
  if server_value.environment is not None:
84
- server_dict["environment"] = server_value.environment
88
+ server.environment = server_value.environment
85
89
  if server_value.account is not None:
86
- server_dict["account"] = server_value.account
90
+ server.account = server_value.account
87
91
  if server_value.database is not None:
88
- server_dict["database"] = server_value.database
92
+ if server.type == "oracle":
93
+ server.serviceName = server_value.database
94
+ else:
95
+ server.database = server_value.database
89
96
  if server_value.schema_ is not None:
90
- server_dict["schema"] = server_value.schema_
97
+ server.schema_ = server_value.schema_
91
98
  if server_value.format is not None:
92
- server_dict["format"] = server_value.format
99
+ server.format = server_value.format
93
100
  if server_value.project is not None:
94
- server_dict["project"] = server_value.project
101
+ server.project = server_value.project
95
102
  if server_value.dataset is not None:
96
- server_dict["dataset"] = server_value.dataset
103
+ server.dataset = server_value.dataset
97
104
  if server_value.path is not None:
98
- server_dict["path"] = server_value.path
105
+ server.path = server_value.path
99
106
  if server_value.delimiter is not None:
100
- server_dict["delimiter"] = server_value.delimiter
107
+ server.delimiter = server_value.delimiter
101
108
  if server_value.endpointUrl is not None:
102
- server_dict["endpointUrl"] = server_value.endpointUrl
109
+ server.endpointUrl = server_value.endpointUrl
103
110
  if server_value.location is not None:
104
- server_dict["location"] = server_value.location
111
+ server.location = server_value.location
105
112
  if server_value.host is not None:
106
- server_dict["host"] = server_value.host
113
+ server.host = server_value.host
107
114
  if server_value.port is not None:
108
- server_dict["port"] = server_value.port
115
+ server.port = server_value.port
109
116
  if server_value.catalog is not None:
110
- server_dict["catalog"] = server_value.catalog
117
+ server.catalog = server_value.catalog
111
118
  if server_value.topic is not None:
112
- server_dict["topic"] = server_value.topic
119
+ server.topic = server_value.topic
113
120
  if server_value.http_path is not None:
114
- server_dict["http_path"] = server_value.http_path
121
+ server.http_path = server_value.http_path
115
122
  if server_value.token is not None:
116
- server_dict["token"] = server_value.token
123
+ server.token = server_value.token
117
124
  if server_value.driver is not None:
118
- server_dict["driver"] = server_value.driver
125
+ server.driver = server_value.driver
126
+
119
127
  if server_value.roles is not None:
120
- server_dict["roles"] = [
121
- {"name": role.name, "description": role.description} for role in server_value.roles
122
- ]
123
- servers.append(server_dict)
128
+ server.roles = [Role(role=role.name, description=role.description) for role in server_value.roles]
124
129
 
125
- if len(servers) > 0:
126
- odcs["servers"] = servers
130
+ servers.append(server)
127
131
 
128
- odcs["customProperties"] = []
132
+ if len(servers) > 0:
133
+ result.servers = servers
134
+ if (data_contract_spec.info.owner is not None) and (data_contract_spec.info.owner != ""):
135
+ result.team = Team(name=data_contract_spec.info.owner)
136
+ custom_properties = []
129
137
  if data_contract_spec.info.model_extra is not None:
130
138
  for key, value in data_contract_spec.info.model_extra.items():
131
- odcs["customProperties"].append({"property": key, "value": value})
132
- if len(odcs["customProperties"]) == 0:
133
- del odcs["customProperties"]
139
+ custom_properties.append(CustomProperty(property=key, value=value))
140
+ if len(custom_properties) > 0:
141
+ result.customProperties = custom_properties
142
+ return result
134
143
 
135
- return yaml.dump(odcs, indent=2, sort_keys=False, allow_unicode=True)
136
144
 
145
+ def to_odcs_schema(model_key, model_value: Model) -> SchemaObject:
146
+ schema_obj = SchemaObject(
147
+ name=model_key, physicalName=model_key, logicalType="object", physicalType=model_value.type
148
+ )
137
149
 
138
- def to_odcs_schema(model_key, model_value: Model) -> dict:
139
- odcs_table = {
140
- "name": model_key,
141
- "physicalName": model_key,
142
- "logicalType": "object",
143
- "physicalType": model_value.type,
144
- }
145
150
  if model_value.description is not None:
146
- odcs_table["description"] = model_value.description
151
+ schema_obj.description = model_value.description
152
+
147
153
  properties = to_properties(model_value.fields)
148
154
  if properties:
149
- odcs_table["properties"] = properties
155
+ schema_obj.properties = properties
150
156
 
151
157
  model_quality = to_odcs_quality_list(model_value.quality)
152
158
  if len(model_quality) > 0:
153
- odcs_table["quality"] = model_quality
159
+ schema_obj.quality = model_quality
154
160
 
155
- odcs_table["customProperties"] = []
161
+ custom_properties = []
156
162
  if model_value.model_extra is not None:
157
163
  for key, value in model_value.model_extra.items():
158
- odcs_table["customProperties"].append({"property": key, "value": value})
159
- if len(odcs_table["customProperties"]) == 0:
160
- del odcs_table["customProperties"]
164
+ custom_properties.append(CustomProperty(property=key, value=value))
165
+
166
+ if len(custom_properties) > 0:
167
+ schema_obj.customProperties = custom_properties
161
168
 
162
- return odcs_table
169
+ return schema_obj
163
170
 
164
171
 
165
172
  def to_properties(fields: Dict[str, Field]) -> list:
@@ -191,88 +198,123 @@ def to_logical_type(type: str) -> str | None:
191
198
  return "integer"
192
199
  if type.lower() in ["boolean"]:
193
200
  return "boolean"
194
- if type.lower() in ["object", "record", "struct"]:
201
+ if type.lower() in ["object", "record", "struct", "map", "variant"]:
195
202
  return "object"
196
- if type.lower() in ["bytes"]:
197
- return "array"
198
- if type.lower() in ["array"]:
203
+ if type.lower() in ["bytes", "array"]:
199
204
  return "array"
200
205
  if type.lower() in ["null"]:
201
206
  return None
202
207
  return None
203
208
 
204
209
 
205
- def to_physical_type(type: str) -> str | None:
206
- # TODO: to we need to do a server mapping here?
207
- return type
210
+ def to_physical_type(config: Dict[str, Any]) -> str | None:
211
+ if config is None:
212
+ return None
213
+ if "postgresType" in config:
214
+ return config["postgresType"]
215
+ elif "bigqueryType" in config:
216
+ return config["bigqueryType"]
217
+ elif "snowflakeType" in config:
218
+ return config["snowflakeType"]
219
+ elif "redshiftType" in config:
220
+ return config["redshiftType"]
221
+ elif "sqlserverType" in config:
222
+ return config["sqlserverType"]
223
+ elif "databricksType" in config:
224
+ return config["databricksType"]
225
+ elif "physicalType" in config:
226
+ return config["physicalType"]
227
+ elif "oracleType" in config:
228
+ return config["oracleType"]
229
+ return None
230
+
231
+
232
+ def to_property(field_name: str, field: Field) -> SchemaProperty:
233
+ property = SchemaProperty(name=field_name)
208
234
 
235
+ if field.fields:
236
+ properties = []
237
+ for field_name_, field_ in field.fields.items():
238
+ property_ = to_property(field_name_, field_)
239
+ properties.append(property_)
240
+ property.properties = properties
241
+
242
+ if field.items:
243
+ items = to_property(field_name, field.items)
244
+ items.name = None # Clear the name for items
245
+ property.items = items
209
246
 
210
- def to_property(field_name: str, field: Field) -> dict:
211
- property = {"name": field_name}
212
247
  if field.title is not None:
213
- property["businessName"] = field.title
248
+ property.businessName = field.title
249
+
214
250
  if field.type is not None:
215
- property["logicalType"] = to_logical_type(field.type)
216
- property["physicalType"] = to_physical_type(field.type)
251
+ property.logicalType = to_logical_type(field.type)
252
+ property.physicalType = to_physical_type(field.config) or field.type
253
+
217
254
  if field.description is not None:
218
- property["description"] = field.description
255
+ property.description = field.description
256
+
219
257
  if field.required is not None:
220
- property["nullable"] = not field.required
258
+ property.required = field.required
259
+
221
260
  if field.unique is not None:
222
- property["unique"] = field.unique
261
+ property.unique = field.unique
262
+
223
263
  if field.classification is not None:
224
- property["classification"] = field.classification
264
+ property.classification = field.classification
265
+
225
266
  if field.examples is not None:
226
- property["examples"] = field.examples
267
+ property.examples = field.examples.copy()
268
+
227
269
  if field.example is not None:
228
- property["examples"] = [field.example]
270
+ property.examples = [field.example]
271
+
229
272
  if field.primaryKey is not None and field.primaryKey:
230
- property["primaryKey"] = field.primaryKey
231
- property["primaryKeyPosition"] = 1
273
+ property.primaryKey = field.primaryKey
274
+ property.primaryKeyPosition = 1
275
+
232
276
  if field.primary is not None and field.primary:
233
- property["primaryKey"] = field.primary
234
- property["primaryKeyPosition"] = 1
277
+ property.primaryKey = field.primary
278
+ property.primaryKeyPosition = 1
235
279
 
236
- property["customProperties"] = []
280
+ custom_properties = []
237
281
  if field.model_extra is not None:
238
282
  for key, value in field.model_extra.items():
239
- property["customProperties"].append({"property": key, "value": value})
283
+ custom_properties.append(CustomProperty(property=key, value=value))
284
+
240
285
  if field.pii is not None:
241
- property["customProperties"].append({"property": "pii", "value": field.pii})
242
- if property.get("customProperties") is not None and len(property["customProperties"]) == 0:
243
- del property["customProperties"]
286
+ custom_properties.append(CustomProperty(property="pii", value=field.pii))
287
+
288
+ if len(custom_properties) > 0:
289
+ property.customProperties = custom_properties
244
290
 
245
- property["tags"] = []
246
- if field.tags is not None:
247
- property["tags"].extend(field.tags)
248
- if not property["tags"]:
249
- del property["tags"]
291
+ if field.tags is not None and len(field.tags) > 0:
292
+ property.tags = field.tags
250
293
 
251
- property["logicalTypeOptions"] = {}
294
+ logical_type_options = {}
252
295
  if field.minLength is not None:
253
- property["logicalTypeOptions"]["minLength"] = field.minLength
296
+ logical_type_options["minLength"] = field.minLength
254
297
  if field.maxLength is not None:
255
- property["logicalTypeOptions"]["maxLength"] = field.maxLength
298
+ logical_type_options["maxLength"] = field.maxLength
256
299
  if field.pattern is not None:
257
- property["logicalTypeOptions"]["pattern"] = field.pattern
300
+ logical_type_options["pattern"] = field.pattern
258
301
  if field.minimum is not None:
259
- property["logicalTypeOptions"]["minimum"] = field.minimum
302
+ logical_type_options["minimum"] = field.minimum
260
303
  if field.maximum is not None:
261
- property["logicalTypeOptions"]["maximum"] = field.maximum
304
+ logical_type_options["maximum"] = field.maximum
262
305
  if field.exclusiveMinimum is not None:
263
- property["logicalTypeOptions"]["exclusiveMinimum"] = field.exclusiveMinimum
306
+ logical_type_options["exclusiveMinimum"] = field.exclusiveMinimum
264
307
  if field.exclusiveMaximum is not None:
265
- property["logicalTypeOptions"]["exclusiveMaximum"] = field.exclusiveMaximum
266
- if property["logicalTypeOptions"] == {}:
267
- del property["logicalTypeOptions"]
308
+ logical_type_options["exclusiveMaximum"] = field.exclusiveMaximum
309
+
310
+ if logical_type_options:
311
+ property.logicalTypeOptions = logical_type_options
268
312
 
269
313
  if field.quality is not None:
270
314
  quality_list = field.quality
271
315
  quality_property = to_odcs_quality_list(quality_list)
272
316
  if len(quality_property) > 0:
273
- property["quality"] = quality_property
274
-
275
- # todo enum
317
+ property.quality = quality_property
276
318
 
277
319
  return property
278
320
 
@@ -285,33 +327,35 @@ def to_odcs_quality_list(quality_list):
285
327
 
286
328
 
287
329
  def to_odcs_quality(quality):
288
- quality_dict = {"type": quality.type}
330
+ quality_obj = DataQuality(type=quality.type)
331
+
289
332
  if quality.description is not None:
290
- quality_dict["description"] = quality.description
333
+ quality_obj.description = quality.description
291
334
  if quality.query is not None:
292
- quality_dict["query"] = quality.query
335
+ quality_obj.query = quality.query
293
336
  # dialect is not supported in v3.0.0
294
337
  if quality.mustBe is not None:
295
- quality_dict["mustBe"] = quality.mustBe
338
+ quality_obj.mustBe = quality.mustBe
296
339
  if quality.mustNotBe is not None:
297
- quality_dict["mustNotBe"] = quality.mustNotBe
340
+ quality_obj.mustNotBe = quality.mustNotBe
298
341
  if quality.mustBeGreaterThan is not None:
299
- quality_dict["mustBeGreaterThan"] = quality.mustBeGreaterThan
342
+ quality_obj.mustBeGreaterThan = quality.mustBeGreaterThan
300
343
  if quality.mustBeGreaterThanOrEqualTo is not None:
301
- quality_dict["mustBeGreaterThanOrEqualTo"] = quality.mustBeGreaterThanOrEqualTo
344
+ quality_obj.mustBeGreaterOrEqualTo = quality.mustBeGreaterThanOrEqualTo
302
345
  if quality.mustBeLessThan is not None:
303
- quality_dict["mustBeLessThan"] = quality.mustBeLessThan
346
+ quality_obj.mustBeLessThan = quality.mustBeLessThan
304
347
  if quality.mustBeLessThanOrEqualTo is not None:
305
- quality_dict["mustBeLessThanOrEqualTo"] = quality.mustBeLessThanOrEqualTo
348
+ quality_obj.mustBeLessOrEqualTo = quality.mustBeLessThanOrEqualTo
306
349
  if quality.mustBeBetween is not None:
307
- quality_dict["mustBeBetween"] = quality.mustBeBetween
350
+ quality_obj.mustBeBetween = quality.mustBeBetween
308
351
  if quality.mustNotBeBetween is not None:
309
- quality_dict["mustNotBeBetween"] = quality.mustNotBeBetween
352
+ quality_obj.mustNotBeBetween = quality.mustNotBeBetween
310
353
  if quality.engine is not None:
311
- quality_dict["engine"] = quality.engine
354
+ quality_obj.engine = quality.engine
312
355
  if quality.implementation is not None:
313
- quality_dict["implementation"] = quality.implementation
314
- return quality_dict
356
+ quality_obj.implementation = quality.implementation
357
+
358
+ return quality_obj
315
359
 
316
360
 
317
361
  def to_status(status):