|
| 1 | +import os |
| 2 | +from textwrap import dedent |
| 3 | + |
| 4 | +import requests |
| 5 | + |
| 6 | +from .run_pipeline import list_pipelines |
| 7 | + |
| 8 | + |
| 9 | +IGNORE_FIELD_TYPE_ERRORS = { |
| 10 | + 'KNS_BillHistoryInitiator': [ |
| 11 | + 'StartDate', # field type is string instead of datetime |
| 12 | + ] |
| 13 | +} |
| 14 | + |
| 15 | + |
| 16 | +def get_table_res_field(field): |
| 17 | + field_types = { |
| 18 | + 'Edm.Int64': 'integer', # 123 |
| 19 | + 'Edm.Int32': 'integer', # 123 |
| 20 | + 'Edm.Int16': 'integer', # 123 |
| 21 | + 'Edm.Byte': 'integer', # 123 |
| 22 | + 'Edm.DateTimeOffset': 'datetime', # "2016-02-28T10:22:10.843+02:00" |
| 23 | + None: 'string', # "string", |
| 24 | + 'Edm.Boolean': 'boolean', |
| 25 | + } |
| 26 | + assert field.get("$Type") in field_types, f"unknown field type {field.get('$Type')}" |
| 27 | + return { |
| 28 | + 'type': field_types[field.get("$Type")] |
| 29 | + } |
| 30 | + |
| 31 | + |
| 32 | +def get_parliamentinfo_tables(): |
| 33 | + data = requests.get('https://knesset.gov.il/OdataV4/ParliamentInfo/$metadata?$format=json').json() |
| 34 | + res = {} |
| 35 | + for table_name, fields in data['OdataService.DAL.ParliamentInfo'].items(): |
| 36 | + assert fields.pop("$Kind") == 'EntityType' |
| 37 | + keys = fields.pop("$Key") or [] |
| 38 | + assert len(keys) == 1, f"unexpected number of keys in table {table_name}: {keys}" |
| 39 | + res[table_name] = { |
| 40 | + 'primary_key': keys[0], |
| 41 | + 'fields': { |
| 42 | + field_name: get_table_res_field(field) for field_name, field in fields.items() if field.get('$Kind') is None |
| 43 | + } |
| 44 | + } |
| 45 | + return res |
| 46 | + |
| 47 | + |
| 48 | +def get_pipelines_res_field(field): |
| 49 | + assert field['source'] == '{name}', f'unexpected source {field["source"]}' |
| 50 | + return { |
| 51 | + 'type': field['type'], |
| 52 | + 'primary_key': bool(field.get('primaryKey')), |
| 53 | + } |
| 54 | + |
| 55 | + |
| 56 | +def get_parliamentinfo_pipelines(): |
| 57 | + res = {} |
| 58 | + for pipeline in list_pipelines(full=True): |
| 59 | + if pipeline.get('dataservice_params') and pipeline['dataservice_params'].get('service-name') == 'api': |
| 60 | + res[pipeline['dataservice_params']['method-name']] = {} |
| 61 | + res[pipeline['dataservice_params']['method-name']]['fields'] = { |
| 62 | + name: get_pipelines_res_field(field) for name, field in pipeline['dataservice_params']['fields'].items() |
| 63 | + } |
| 64 | + primary_keys = [name for name, field in res[pipeline['dataservice_params']['method-name']]['fields'].items() if field.get('primary_key')] |
| 65 | + assert len(primary_keys) <= 1, f"unexpected number of primary keys in pipeline {pipeline['pipeline_id']}: {primary_keys}" |
| 66 | + res[pipeline['dataservice_params']['method-name']]['primary_key'] = primary_keys[0] if primary_keys else None |
| 67 | + return res |
| 68 | + |
| 69 | + |
| 70 | +class MissingPipeline: |
| 71 | + |
| 72 | + def __init__(self, table_name, table): |
| 73 | + self.table_name = table_name |
| 74 | + self.table = table |
| 75 | + |
| 76 | + def __str__(self): |
| 77 | + res = f'table {self.table_name} is missing from pipelines\n' |
| 78 | + filename = f'knesset/{self.table_name.lower()}.yaml' |
| 79 | + filecontent = dedent(f''' |
| 80 | + pipeline-type: knesset dataservice |
| 81 | + dataservice-parameters: |
| 82 | + service-name: api |
| 83 | + method-name: "{self.table_name}" |
| 84 | + fields: |
| 85 | + ''') |
| 86 | + for field_name, field in self.table['fields'].items(): |
| 87 | + filecontent += f' {field_name}:\n' |
| 88 | + filecontent += f' source: "{{name}}"\n' |
| 89 | + filecontent += f' type: "{field["type"]}"\n' |
| 90 | + if field_name == self.table['primary_key']: |
| 91 | + filecontent += f' primaryKey: true\n' |
| 92 | + with open(os.path.join(os.path.dirname(__file__), '..', 'pipelines', filename), 'w') as f: |
| 93 | + f.write(filecontent) |
| 94 | + return res |
| 95 | + |
| 96 | + |
| 97 | +class IncompletePipeline: |
| 98 | + |
| 99 | + def __init__(self, table_name, table, pipeline): |
| 100 | + self.table_name = table_name |
| 101 | + self.table = table |
| 102 | + self.pipeline = pipeline |
| 103 | + self.missing_fields = [] |
| 104 | + self.wrong_type_fields = [] |
| 105 | + |
| 106 | + def __str__(self): |
| 107 | + res = f'pipeline for table {self.table_name} is incomplete:\n' |
| 108 | + if len(self.missing_fields) > 0: |
| 109 | + res += ' - missing fields:\n' |
| 110 | + for field_name in self.missing_fields: |
| 111 | + res += f'{field_name}: {{source: "{{name}}", type: "{self.table["fields"][field_name]["type"]}"}}\n' |
| 112 | + if len(self.wrong_type_fields) > 0: |
| 113 | + res += ' - wrong type fields:\n' |
| 114 | + for field_name in self.wrong_type_fields: |
| 115 | + res += f'{field_name}: {{source: "{{name}}", type: "{self.table["fields"][field_name]["type"]}"}}\n' |
| 116 | + return res |
| 117 | + |
| 118 | + |
| 119 | +def compare_parliamentinfo_tables_pipelines(): |
| 120 | + fix_objects = [] |
| 121 | + tables = get_parliamentinfo_tables() |
| 122 | + pipelines = get_parliamentinfo_pipelines() |
| 123 | + for table_name, table in tables.items(): |
| 124 | + if table_name not in pipelines: |
| 125 | + fix_objects.append(MissingPipeline(table_name, table)) |
| 126 | + continue |
| 127 | + pipeline = pipelines[table_name] |
| 128 | + incomplete_pipeline = IncompletePipeline(table_name, table, pipeline) |
| 129 | + for field_name, field in table['fields'].items(): |
| 130 | + if field_name not in pipeline['fields']: |
| 131 | + if table['primary_key'] == field_name and pipeline['primary_key']: |
| 132 | + field_name = pipeline['primary_key'] |
| 133 | + else: |
| 134 | + incomplete_pipeline.missing_fields.append(field_name) |
| 135 | + continue |
| 136 | + pipeline_field = pipeline['fields'][field_name] |
| 137 | + if field['type'] != pipeline_field['type'] and field_name not in IGNORE_FIELD_TYPE_ERRORS.get(table_name, []): |
| 138 | + incomplete_pipeline.wrong_type_fields.append(field_name) |
| 139 | + if len(incomplete_pipeline.missing_fields) > 0 or len(incomplete_pipeline.wrong_type_fields) > 0: |
| 140 | + fix_objects.append(incomplete_pipeline) |
| 141 | + if len(fix_objects) > 0: |
| 142 | + for fix_object in fix_objects: |
| 143 | + print(fix_object) |
| 144 | + raise Exception('need to fix the pipelines') |
| 145 | + else: |
| 146 | + print('all pipelines are complete and correct') |
| 147 | + |
| 148 | + |
| 149 | +if __name__ == '__main__': |
| 150 | + compare_parliamentinfo_tables_pipelines() |
0 commit comments