datacontract-cli 0.10.23__py3-none-any.whl → 0.10.37__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.
- datacontract/__init__.py +13 -0
- datacontract/api.py +12 -5
- datacontract/catalog/catalog.py +5 -3
- datacontract/cli.py +116 -10
- datacontract/data_contract.py +143 -65
- datacontract/engines/data_contract_checks.py +366 -60
- datacontract/engines/data_contract_test.py +50 -4
- datacontract/engines/fastjsonschema/check_jsonschema.py +37 -19
- datacontract/engines/fastjsonschema/s3/s3_read_files.py +3 -2
- datacontract/engines/soda/check_soda_execute.py +22 -3
- datacontract/engines/soda/connections/athena.py +79 -0
- datacontract/engines/soda/connections/duckdb_connection.py +65 -6
- datacontract/engines/soda/connections/kafka.py +4 -2
- datacontract/export/avro_converter.py +20 -3
- datacontract/export/bigquery_converter.py +1 -1
- datacontract/export/dbt_converter.py +36 -7
- datacontract/export/dqx_converter.py +126 -0
- datacontract/export/duckdb_type_converter.py +57 -0
- datacontract/export/excel_exporter.py +923 -0
- datacontract/export/exporter.py +3 -0
- datacontract/export/exporter_factory.py +17 -1
- datacontract/export/great_expectations_converter.py +55 -5
- datacontract/export/{html_export.py → html_exporter.py} +31 -20
- datacontract/export/markdown_converter.py +134 -5
- datacontract/export/mermaid_exporter.py +110 -0
- datacontract/export/odcs_v3_exporter.py +187 -145
- datacontract/export/protobuf_converter.py +163 -69
- datacontract/export/rdf_converter.py +2 -2
- datacontract/export/sodacl_converter.py +9 -1
- datacontract/export/spark_converter.py +31 -4
- datacontract/export/sql_converter.py +6 -2
- datacontract/export/sql_type_converter.py +20 -8
- datacontract/imports/avro_importer.py +63 -12
- datacontract/imports/csv_importer.py +111 -57
- datacontract/imports/excel_importer.py +1111 -0
- datacontract/imports/importer.py +16 -3
- datacontract/imports/importer_factory.py +17 -0
- datacontract/imports/json_importer.py +325 -0
- datacontract/imports/odcs_importer.py +2 -2
- datacontract/imports/odcs_v3_importer.py +351 -151
- datacontract/imports/protobuf_importer.py +264 -0
- datacontract/imports/spark_importer.py +117 -13
- datacontract/imports/sql_importer.py +32 -16
- datacontract/imports/unity_importer.py +84 -38
- datacontract/init/init_template.py +1 -1
- datacontract/integration/datamesh_manager.py +16 -2
- datacontract/lint/resolve.py +112 -23
- datacontract/lint/schema.py +24 -15
- datacontract/model/data_contract_specification/__init__.py +1 -0
- datacontract/model/odcs.py +13 -0
- datacontract/model/run.py +3 -0
- datacontract/output/junit_test_results.py +3 -3
- datacontract/schemas/datacontract-1.1.0.init.yaml +1 -1
- datacontract/schemas/datacontract-1.2.0.init.yaml +91 -0
- datacontract/schemas/datacontract-1.2.0.schema.json +2029 -0
- datacontract/schemas/datacontract-1.2.1.init.yaml +91 -0
- datacontract/schemas/datacontract-1.2.1.schema.json +2058 -0
- datacontract/schemas/odcs-3.0.2.schema.json +2382 -0
- datacontract/templates/datacontract.html +54 -3
- datacontract/templates/datacontract_odcs.html +685 -0
- datacontract/templates/index.html +5 -2
- datacontract/templates/partials/server.html +2 -0
- datacontract/templates/style/output.css +319 -145
- {datacontract_cli-0.10.23.dist-info → datacontract_cli-0.10.37.dist-info}/METADATA +656 -431
- datacontract_cli-0.10.37.dist-info/RECORD +119 -0
- {datacontract_cli-0.10.23.dist-info → datacontract_cli-0.10.37.dist-info}/WHEEL +1 -1
- {datacontract_cli-0.10.23.dist-info → datacontract_cli-0.10.37.dist-info/licenses}/LICENSE +1 -1
- datacontract/export/csv_type_converter.py +0 -36
- datacontract/lint/lint.py +0 -142
- datacontract/lint/linters/description_linter.py +0 -35
- datacontract/lint/linters/field_pattern_linter.py +0 -34
- datacontract/lint/linters/field_reference_linter.py +0 -48
- datacontract/lint/linters/notice_period_linter.py +0 -55
- datacontract/lint/linters/quality_schema_linter.py +0 -52
- datacontract/lint/linters/valid_constraints_linter.py +0 -100
- datacontract/model/data_contract_specification.py +0 -327
- datacontract_cli-0.10.23.dist-info/RECORD +0 -113
- /datacontract/{lint/linters → output}/__init__.py +0 -0
- {datacontract_cli-0.10.23.dist-info → datacontract_cli-0.10.37.dist-info}/entry_points.txt +0 -0
- {datacontract_cli-0.10.23.dist-info → datacontract_cli-0.10.37.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
datacontract/__init__.py,sha256=ThDdxDJsd7qNErLoh628nK5M7RzhJNYCmN-C6BAJFoo,405
|
|
2
|
+
datacontract/api.py,sha256=nFmrJOhC5AygY9YS1VXsbvKNtW92B8AF-lXdhuCvcPE,8578
|
|
3
|
+
datacontract/cli.py,sha256=ix1iwdo8bGfUW_tf1qiSoLhGBKCLcIgpVgn6diV9MB0,20742
|
|
4
|
+
datacontract/data_contract.py,sha256=24QE2ym5dfwTP6vJ0OmW37GNfGCCV6y4x4-J5Ouvfjk,13248
|
|
5
|
+
datacontract/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
datacontract/breaking/breaking.py,sha256=DnqgxUjD-EAZcg5RBizOP9a2WxsFTaQBik0AB_m3K00,20431
|
|
7
|
+
datacontract/breaking/breaking_change.py,sha256=BIDEUo1U2CQLVT2-I5PyFttxAj6zQPI1UUkEoOOQXMY,2249
|
|
8
|
+
datacontract/breaking/breaking_rules.py,sha256=M9IdzVJSA7oOr1fvLQl0y9MoBKeItPz42Db2U2cjH2Y,4063
|
|
9
|
+
datacontract/catalog/catalog.py,sha256=U5TpDyT9kcF086DoDSS3bWBE4Q8uj6HVuCSFaxN5kMw,2830
|
|
10
|
+
datacontract/engines/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
datacontract/engines/data_contract_checks.py,sha256=LNI0OZPrzrf7sn6lPgTL4uAHSqMH2VBrmaBo8etuSE0,37453
|
|
12
|
+
datacontract/engines/data_contract_test.py,sha256=8qg0SkwtTmayfzNL2U_0xgx5Hi_DUePaMt2q_JiCqX8,4543
|
|
13
|
+
datacontract/engines/datacontract/check_that_datacontract_contains_valid_servers_configuration.py,sha256=zrDn-_EJJ5kv0kZWAA-toeEPuBd3YQ0-U7Jb8euNUS8,1558
|
|
14
|
+
datacontract/engines/datacontract/check_that_datacontract_file_exists.py,sha256=Vw-7U0GmQT2127tybxggZfpRFiZVgoIh6ndkTGM0FP4,665
|
|
15
|
+
datacontract/engines/fastjsonschema/check_jsonschema.py,sha256=EKPkFM8iGyiWsHw8peErhQvbt9gu_zJ2S-NDQnupfeM,10921
|
|
16
|
+
datacontract/engines/fastjsonschema/s3/s3_read_files.py,sha256=0sTDWvuu0AzSgn7fKWJxGaTmPww00TFYyDK-X0s5T3c,1193
|
|
17
|
+
datacontract/engines/soda/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
|
+
datacontract/engines/soda/check_soda_execute.py,sha256=qc56ZNKyHIoSFgoXzBRioOhnhgsFSJ6L-nyBU7d0fW8,8902
|
|
19
|
+
datacontract/engines/soda/connections/athena.py,sha256=wjrJA9CHhl6FbSW0HulWcYlkT2_nY1s19Y2MFe4lbCU,3028
|
|
20
|
+
datacontract/engines/soda/connections/bigquery.py,sha256=C-8kxmzpYe88bJp80ObHFLMh4rpnIjnUQ7XOj0Ke7lk,903
|
|
21
|
+
datacontract/engines/soda/connections/databricks.py,sha256=cMRasuO0MrSKVgHPB-9uFTGTZPFg6z9Kpk3tJ0SdR0s,943
|
|
22
|
+
datacontract/engines/soda/connections/duckdb_connection.py,sha256=wGiB6EKr-OZosEFvT2gkutFgAzAxFMKicfpjbIJUZwQ,9332
|
|
23
|
+
datacontract/engines/soda/connections/kafka.py,sha256=lnj_-3-CnJ6stetGqm6HOzN1Qatlw7xoCQU2zKBIXxU,8725
|
|
24
|
+
datacontract/engines/soda/connections/postgres.py,sha256=9GTF4Es3M5vb7ocSGqAxXmslvkS5CjsPQGIuo020CFc,626
|
|
25
|
+
datacontract/engines/soda/connections/snowflake.py,sha256=rfG2ysuqNM6TkvyqQKcGHFsTGJ6AROmud5VleUDRrb0,749
|
|
26
|
+
datacontract/engines/soda/connections/sqlserver.py,sha256=RzGLbCUdRyfmDcqtM_AB9WZ-Xk-XYX91nkXpVNpYbvc,1440
|
|
27
|
+
datacontract/engines/soda/connections/trino.py,sha256=JvKUP9aFg_n095oWE0-bGmfbETSWEOURGEZdQuG8txA,718
|
|
28
|
+
datacontract/export/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
29
|
+
datacontract/export/avro_converter.py,sha256=MnfeW2x-Eko9dK6_fpdQYWtEzLkFWXfKABAUSJqiDpo,5381
|
|
30
|
+
datacontract/export/avro_idl_converter.py,sha256=SGO7JfI9UGXLYFR5wMGNUH1qf6kt9lF6dUU9roVqnFo,9878
|
|
31
|
+
datacontract/export/bigquery_converter.py,sha256=9mm-XP3klu1C5J87L9EL5ZyMCJhLBwsixo3aAw9QmRI,4738
|
|
32
|
+
datacontract/export/custom_converter.py,sha256=xb8KbkRRgHmT4ewwC7XxtnKpe_ZMSJWBjYOaKjmO_KQ,1216
|
|
33
|
+
datacontract/export/data_caterer_converter.py,sha256=eSEuy3TbqUIG_lHYEBOydAgp_CJNoGArXrcJvh81wcw,5984
|
|
34
|
+
datacontract/export/dbml_converter.py,sha256=f_OZEFwRUyL-Kg2yn_G58I8iz1VfFrZh8Nbw3Wq0JDo,4777
|
|
35
|
+
datacontract/export/dbt_converter.py,sha256=58bub8n22dfL8w6bKdFe28BLF0e4PHbrxO_H3rZ9wfk,11840
|
|
36
|
+
datacontract/export/dcs_exporter.py,sha256=RALQ7bLAjak7EsoFFL2GFX2Oju7pnCDPCdRN_wo9wHM,210
|
|
37
|
+
datacontract/export/dqx_converter.py,sha256=5UevFPE8RdFIeu4CgeVnXMNDfWU7DhR34DW7O1aVIFs,4105
|
|
38
|
+
datacontract/export/duckdb_type_converter.py,sha256=hUAAbImhJUMJOXEG-UoOKQqYGrJM6UILpn2YjUuAUOw,2216
|
|
39
|
+
datacontract/export/excel_exporter.py,sha256=qDVcwO38aWPMTYyXo2qJd1HJLkmj_Izhi1hdVOQ8o_w,38424
|
|
40
|
+
datacontract/export/exporter.py,sha256=DfvMHDWmdqhJswLkQ5oMNojgYDblXDuRgFJRHuFSawM,3085
|
|
41
|
+
datacontract/export/exporter_factory.py,sha256=UvP3_U7xj-GEjaifi__Jri6eYKx9SFXtmSrnkSbWuP0,6318
|
|
42
|
+
datacontract/export/go_converter.py,sha256=Ttvbfu3YU-3GBwRD6nwCsFyZuc_hiIvJD-Jg2sT5WLw,3331
|
|
43
|
+
datacontract/export/great_expectations_converter.py,sha256=Wx0mESRy4xAf8y7HjESsGsQaaei8k9xOVu3RbC6BlQM,12257
|
|
44
|
+
datacontract/export/html_exporter.py,sha256=EyTMj25_Df3irZiYw1hxVZeLYWp6YSG6z3IuFUviP14,3066
|
|
45
|
+
datacontract/export/iceberg_converter.py,sha256=ArcQ_Y3z_W4_kGDU_8jPRx2-pHpP3Nhx1zYoETOL3c4,6804
|
|
46
|
+
datacontract/export/jsonschema_converter.py,sha256=2MT82MurcQQbrVDRj1kFsxnmFd9scNSfYI1upQSecl4,5631
|
|
47
|
+
datacontract/export/markdown_converter.py,sha256=J6QEGuopR9AUyEhux1GpjmJaQa1iihsbNMAmGRQ63BQ,10430
|
|
48
|
+
datacontract/export/mermaid_exporter.py,sha256=Hg2yc5DYDTEZ7etoIhB1LU6rob_sGlouDtkPxUtf6kQ,4008
|
|
49
|
+
datacontract/export/odcs_v3_exporter.py,sha256=b__AiPAnCUuFQE5DPHsvXBrMeEl1t_mJ1vzTx84TMlI,13931
|
|
50
|
+
datacontract/export/pandas_type_converter.py,sha256=464pQ3JQKFQa1TO0HBNcEoZvQye_yUbY6jQtiBaphSc,1117
|
|
51
|
+
datacontract/export/protobuf_converter.py,sha256=DHLl8BW26xqltBsd7Qhz0RhTl9YZQKCbkmjNpECgubg,7928
|
|
52
|
+
datacontract/export/pydantic_converter.py,sha256=1Lt9F8i6zyQYb44MyQtsXwCWWXYxZ47SmzArr_uPqsU,5579
|
|
53
|
+
datacontract/export/rdf_converter.py,sha256=zY2BZrRxM0J6C2cgf5zA8c7FxDRImFjZUrJ4ksmvSTw,6435
|
|
54
|
+
datacontract/export/sodacl_converter.py,sha256=75vQ2TnoLfjiDtWT2x8opumvotXVRs1YaIu1NLYz05M,1473
|
|
55
|
+
datacontract/export/spark_converter.py,sha256=aol9ygEq29mjrZNiaK3Vdm8kEZhCgFFphuFiFDX-pOE,7953
|
|
56
|
+
datacontract/export/sql_converter.py,sha256=vyLbDqzt_J3LRXpPv2W2HqUIyAtQx_S-jviBiSxh14A,5087
|
|
57
|
+
datacontract/export/sql_type_converter.py,sha256=eWHRHJNeg6oOT2uUPjmcVjEf6H_qXZvDhvSCk-_iBAM,13890
|
|
58
|
+
datacontract/export/sqlalchemy_converter.py,sha256=0DMncvA811lTtd5q4ZORREQ9YH1vQm1lJeqMWsFvloE,6463
|
|
59
|
+
datacontract/export/terraform_converter.py,sha256=ExFoEvErVk-gBnWJiqC38SxDUmUEydpACWc917l5RyM,2163
|
|
60
|
+
datacontract/imports/avro_importer.py,sha256=isfAnMq9bk-Yo5zSyTnqMegu7JIujn_sTGSTOYAc8-0,11847
|
|
61
|
+
datacontract/imports/bigquery_importer.py,sha256=7TcP9FDsIas5LwJZ-HrOPXZ-NuR056sxLfDDh3vjo8E,8419
|
|
62
|
+
datacontract/imports/csv_importer.py,sha256=mBsmyTvfB8q64Z3NYqv4zTDUOvoXG896hZvp3oLt5YM,5330
|
|
63
|
+
datacontract/imports/dbml_importer.py,sha256=o0IOgvXN34lU1FICDHm_QUTv0DKsgwbHPHUDxQhIapE,3872
|
|
64
|
+
datacontract/imports/dbt_importer.py,sha256=hQwqD9vbvwLLc6Yj3tQbar5ldI0pV-ynSiz7CZZ0JCc,8290
|
|
65
|
+
datacontract/imports/excel_importer.py,sha256=eBLc9VS9OYVFYFcHFHq9HYOStAPBDfVHwmgnBHjxOmc,46415
|
|
66
|
+
datacontract/imports/glue_importer.py,sha256=fiJPkvfwOCsaKKCGW19-JM5CCGXZ2mkNrVtUzp2iw6g,8370
|
|
67
|
+
datacontract/imports/iceberg_importer.py,sha256=vadGJVqQKgG-j8swUytZALFB8QjbGRqZPCcPcCy0vco,5923
|
|
68
|
+
datacontract/imports/importer.py,sha256=NRhR_9AWPWDNq2ac_DVUHGoJuvkVpwwaao8nDfJG_l0,1257
|
|
69
|
+
datacontract/imports/importer_factory.py,sha256=RS7uwkkT7rIKGeMKgPmZhE3GVC9IfZxZhm8XN0ooa3U,4124
|
|
70
|
+
datacontract/imports/json_importer.py,sha256=JeGbqAC_wAO0u8HeMA5H-KJBfs6gpp1oGIpxt6nxSZI,12641
|
|
71
|
+
datacontract/imports/jsonschema_importer.py,sha256=67H__XLugV4vguHrIqzW02dtx27zYTWnOms4D1ma3bk,4961
|
|
72
|
+
datacontract/imports/odcs_importer.py,sha256=ZP2u3kJsgULANTbbqkP3joOlU9cUneZOPy6Ak3oTMgs,2140
|
|
73
|
+
datacontract/imports/odcs_v3_importer.py,sha256=8mWFn4Ntf0jk0DKqmrp_DN7pKlSCO88Ol_UBWzHkY20,20467
|
|
74
|
+
datacontract/imports/parquet_importer.py,sha256=W_0_16mX4stwDUt4GM2L7dnGmTpAySab5k13-OlTCCc,3095
|
|
75
|
+
datacontract/imports/protobuf_importer.py,sha256=rlUIskv9PNi5rFQ4Hobt9zlnKpahGsb4dy5G5UJoVAw,10840
|
|
76
|
+
datacontract/imports/spark_importer.py,sha256=OxX9hJhi8e1o1pZGOKh5zWsK96SX13r0WV04kKDD61M,8964
|
|
77
|
+
datacontract/imports/sql_importer.py,sha256=AdbBe7RrOEDMwdDt4huF5XmOV2EDpOP-k_m8kFQRlJg,10130
|
|
78
|
+
datacontract/imports/unity_importer.py,sha256=ZoWVMPffYNAXxPa0E8d6gRBtx3l-KSx0fPqnQx81DX0,9067
|
|
79
|
+
datacontract/init/init_template.py,sha256=sLCxvXHqoeW-Qes9W8GSVPfDmmu7pfnVOm-puI1-wsQ,721
|
|
80
|
+
datacontract/integration/datamesh_manager.py,sha256=FT9eadzFz181lg54b49_c_x2caGJT7mR3drlZBSBJLo,3375
|
|
81
|
+
datacontract/lint/files.py,sha256=tg0vq_w4LQsEr_8A5qr4hUJmHeGalUpsXJXC1t-OGC0,471
|
|
82
|
+
datacontract/lint/resolve.py,sha256=TjCS0wX4OIkQsV1fXpgGwfnDRyBLrFwePyLsEVO8Qs0,15339
|
|
83
|
+
datacontract/lint/resources.py,sha256=nfeZmORh1aP7EKpMKCmfbS04Te8pQ0nz64vJVkHOq3c,647
|
|
84
|
+
datacontract/lint/schema.py,sha256=wijp3Ix7WNqA2gnIQ6_IxbjB6fe35nYvMNM16dtAEA4,2220
|
|
85
|
+
datacontract/lint/urls.py,sha256=giac0eAYa6hha8exleL3KsiPtiFlOq8l53axtAmCilw,2529
|
|
86
|
+
datacontract/model/exceptions.py,sha256=5BMuEH2qWuckNP4FTfpUEeEu6rjgGcLOD0GQugKRQ1U,1242
|
|
87
|
+
datacontract/model/odcs.py,sha256=Ku-n2xLC0_5EX7KxxLWrQ5ei5kugQiJy7AChKMmRWTc,782
|
|
88
|
+
datacontract/model/run.py,sha256=4UdEUaJl5RxEpN9S3swSu1vGJUVyNhOpRkdfbBZhh90,3146
|
|
89
|
+
datacontract/model/data_contract_specification/__init__.py,sha256=lO7ywraknlDwJNUaSd2B9FWFsWhE8v5S-kob_shW_lg,47
|
|
90
|
+
datacontract/output/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
91
|
+
datacontract/output/junit_test_results.py,sha256=ZjevRMTxNSiR0HMr3bEvqv4olozPw2zEutbuLloInww,4822
|
|
92
|
+
datacontract/output/output_format.py,sha256=_ZokDBo7-HXBs6czUv7kLLf9cYft_q5QaKzthsVnc58,212
|
|
93
|
+
datacontract/output/test_results_writer.py,sha256=PWNLs3R_LQMH4xp5WDxLkQgY3xvj8Eyzw1jnfgkQxlc,2713
|
|
94
|
+
datacontract/schemas/datacontract-1.1.0.init.yaml,sha256=ij_-ZEJP4A7ekeJfoqGpRbiCys7_YjkClNluVVo4C6E,1828
|
|
95
|
+
datacontract/schemas/datacontract-1.1.0.schema.json,sha256=3Bu2rxEjkF6dNLcqi1GF4KoXBnEIopaJ87Qb8S4zUvg,62872
|
|
96
|
+
datacontract/schemas/datacontract-1.2.0.init.yaml,sha256=ij_-ZEJP4A7ekeJfoqGpRbiCys7_YjkClNluVVo4C6E,1828
|
|
97
|
+
datacontract/schemas/datacontract-1.2.0.schema.json,sha256=sk7oL06cug9-WozCrLH8v8MuR3a8MaV1Ztkm1P-7UFk,64226
|
|
98
|
+
datacontract/schemas/datacontract-1.2.1.init.yaml,sha256=ij_-ZEJP4A7ekeJfoqGpRbiCys7_YjkClNluVVo4C6E,1828
|
|
99
|
+
datacontract/schemas/datacontract-1.2.1.schema.json,sha256=Ha6F8i2jaL3BKOV5kjWgaxzykAiaSLqjIq-OEOojnx4,65233
|
|
100
|
+
datacontract/schemas/odcs-3.0.1.schema.json,sha256=bRZsSXA0fV0EmV_8f1K68PlXu1m4K7JcuHpLnY3ESwQ,72933
|
|
101
|
+
datacontract/schemas/odcs-3.0.2.schema.json,sha256=_J13Tqc9E7RzpSho645meE86AxeU0dIt2U12-MnAfHk,69968
|
|
102
|
+
datacontract/templates/datacontract.html,sha256=dksPEnY3c66jaaVS5r5vWfG6LzyXPjA4nO_yLUirJWg,17394
|
|
103
|
+
datacontract/templates/datacontract_odcs.html,sha256=u4bpcgQVqwGmR0QjijJqecOClV2ZhpDvnNMAMzj4Ezc,32659
|
|
104
|
+
datacontract/templates/index.html,sha256=EY2LYSTFCwMMcezsDbYsJwM5A7As7oOEiqTBpK_MXe8,12683
|
|
105
|
+
datacontract/templates/partials/datacontract_information.html,sha256=7ZBxgEgi2XndKBypeOpe03oCSRPOujC6NVlN7zexGNM,6221
|
|
106
|
+
datacontract/templates/partials/datacontract_servicelevels.html,sha256=ed3QgB11B0Qq2h_NwaroGZ4pQMBPEhfeQaoS-qEipqY,11401
|
|
107
|
+
datacontract/templates/partials/datacontract_terms.html,sha256=1cnJcOTpxwot2BCuZmkLF_SPfiVloLs3c8mj9WfE4sc,1865
|
|
108
|
+
datacontract/templates/partials/definition.html,sha256=gZEmNvwNGGxA_Fnzx_0L6tXlAMk_EAPWr5ziRIThb_o,1005
|
|
109
|
+
datacontract/templates/partials/example.html,sha256=F1dWbHDIXQScgfs4OVgqM1lR4uV4xX5j6suasXHNM88,1204
|
|
110
|
+
datacontract/templates/partials/model_field.html,sha256=2YBF95ypNCPFYuYKoeilRnDG-H_FuW4JK1znkCaYCac,7625
|
|
111
|
+
datacontract/templates/partials/quality.html,sha256=ynEDWRn8I90Uje-xhGYgFcfwOgKI1R-CDki-EvTsauQ,1785
|
|
112
|
+
datacontract/templates/partials/server.html,sha256=dHFJtonMjhiUHtT69RUgTpkoRwmNdTRzkCdH0LtGg_4,6279
|
|
113
|
+
datacontract/templates/style/output.css,sha256=ioIo1f96VW7LHhDifj6QI8QbRChJl-LlQ59EwM8MEmA,28692
|
|
114
|
+
datacontract_cli-0.10.37.dist-info/licenses/LICENSE,sha256=0hcS8X51AL0UvEsx1ZM6WQcxiy9d0j5iOfzdPYM6ONU,2205
|
|
115
|
+
datacontract_cli-0.10.37.dist-info/METADATA,sha256=xG1Rilzh_eBYa-gBTG4b9AreKjV5-lcZ41sVqkHtHoc,115131
|
|
116
|
+
datacontract_cli-0.10.37.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
117
|
+
datacontract_cli-0.10.37.dist-info/entry_points.txt,sha256=D3Eqy4q_Z6bHauGd4ppIyQglwbrm1AJnLau4Ppbw9Is,54
|
|
118
|
+
datacontract_cli-0.10.37.dist-info/top_level.txt,sha256=VIRjd8EIUrBYWjEXJJjtdUgc0UAJdPZjmLiOR8BRBYM,13
|
|
119
|
+
datacontract_cli-0.10.37.dist-info/RECORD,,
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
# https://duckdb.org/docs/data/csv/overview.html
|
|
2
|
-
# ['SQLNULL', 'BOOLEAN', 'BIGINT', 'DOUBLE', 'TIME', 'DATE', 'TIMESTAMP', 'VARCHAR']
|
|
3
|
-
def convert_to_duckdb_csv_type(field) -> None | str:
|
|
4
|
-
type = field.type
|
|
5
|
-
if type is None:
|
|
6
|
-
return "VARCHAR"
|
|
7
|
-
if type.lower() in ["string", "varchar", "text"]:
|
|
8
|
-
return "VARCHAR"
|
|
9
|
-
if type.lower() in ["timestamp", "timestamp_tz"]:
|
|
10
|
-
return "TIMESTAMP"
|
|
11
|
-
if type.lower() in ["timestamp_ntz"]:
|
|
12
|
-
return "TIMESTAMP"
|
|
13
|
-
if type.lower() in ["date"]:
|
|
14
|
-
return "DATE"
|
|
15
|
-
if type.lower() in ["time"]:
|
|
16
|
-
return "TIME"
|
|
17
|
-
if type.lower() in ["number", "decimal", "numeric"]:
|
|
18
|
-
# precision and scale not supported by data contract
|
|
19
|
-
return "VARCHAR"
|
|
20
|
-
if type.lower() in ["float", "double"]:
|
|
21
|
-
return "DOUBLE"
|
|
22
|
-
if type.lower() in ["integer", "int", "long", "bigint"]:
|
|
23
|
-
return "BIGINT"
|
|
24
|
-
if type.lower() in ["boolean"]:
|
|
25
|
-
return "BOOLEAN"
|
|
26
|
-
if type.lower() in ["object", "record", "struct"]:
|
|
27
|
-
# not supported in CSV
|
|
28
|
-
return "VARCHAR"
|
|
29
|
-
if type.lower() in ["bytes"]:
|
|
30
|
-
# not supported in CSV
|
|
31
|
-
return "VARCHAR"
|
|
32
|
-
if type.lower() in ["array"]:
|
|
33
|
-
return "VARCHAR"
|
|
34
|
-
if type.lower() in ["null"]:
|
|
35
|
-
return "SQLNULL"
|
|
36
|
-
return "VARCHAR"
|
datacontract/lint/lint.py
DELETED
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
import abc
|
|
2
|
-
from dataclasses import dataclass, field
|
|
3
|
-
from enum import Enum
|
|
4
|
-
from typing import Any, Sequence, cast
|
|
5
|
-
|
|
6
|
-
from datacontract.model.run import Check
|
|
7
|
-
|
|
8
|
-
from ..model.data_contract_specification import DataContractSpecification
|
|
9
|
-
|
|
10
|
-
"""This module contains linter definitions for linting a data contract.
|
|
11
|
-
|
|
12
|
-
Lints are quality checks that can succeed, fail, or warn. They are
|
|
13
|
-
distinct from checks such as "valid yaml" or "file not found", which
|
|
14
|
-
will cause the processing of the data contract to stop. Lints can be
|
|
15
|
-
ignored, and are high-level requirements on the format of a data
|
|
16
|
-
contract."""
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class LintSeverity(Enum):
|
|
20
|
-
"""The severity of a lint message. Generally, lint messages should be
|
|
21
|
-
emitted with a severity of ERROR. WARNING should be used when the linter
|
|
22
|
-
cannot determine a lint result, for example, when an unsupported model
|
|
23
|
-
type is used.
|
|
24
|
-
"""
|
|
25
|
-
|
|
26
|
-
ERROR = 2
|
|
27
|
-
WARNING = 1
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
@dataclass
|
|
31
|
-
class LinterMessage:
|
|
32
|
-
"""A single linter message with attached severity and optional "model" that
|
|
33
|
-
caused the message.
|
|
34
|
-
|
|
35
|
-
Attributes:
|
|
36
|
-
outcome: The outcome of the linting, either ERROR or WARNING. Linting outcomes with level WARNING are discarded for now.
|
|
37
|
-
message: A message describing the error or warning in more detail.
|
|
38
|
-
model: The model that caused the lint to fail. Is optional.
|
|
39
|
-
|
|
40
|
-
"""
|
|
41
|
-
|
|
42
|
-
outcome: LintSeverity
|
|
43
|
-
message: str
|
|
44
|
-
model: Any = None
|
|
45
|
-
|
|
46
|
-
@classmethod
|
|
47
|
-
def error(cls, message: str, model=None):
|
|
48
|
-
return LinterMessage(LintSeverity.ERROR, message, model)
|
|
49
|
-
|
|
50
|
-
@classmethod
|
|
51
|
-
def warning(cls, message: str, model=None):
|
|
52
|
-
return LinterMessage(LintSeverity.WARNING, message, model)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
@dataclass
|
|
56
|
-
class LinterResult:
|
|
57
|
-
"""Result of linting a contract. Contains multiple LinterResults from
|
|
58
|
-
the same linter or lint phase.
|
|
59
|
-
|
|
60
|
-
Attributes:
|
|
61
|
-
linter: The linter that produced these results
|
|
62
|
-
results: A list of linting results. Multiple identical linting
|
|
63
|
-
results can be present in the list. An empty list means that
|
|
64
|
-
the linter ran without producing warnings or errors.
|
|
65
|
-
"""
|
|
66
|
-
|
|
67
|
-
results: Sequence[LinterMessage] = field(default_factory=list)
|
|
68
|
-
|
|
69
|
-
@classmethod
|
|
70
|
-
def erroneous(cls, message, model=None):
|
|
71
|
-
return cls([LinterMessage.error(message, model)])
|
|
72
|
-
|
|
73
|
-
@classmethod
|
|
74
|
-
def cautious(cls, message, model=None):
|
|
75
|
-
return cls([LinterMessage.warning(message, model)])
|
|
76
|
-
|
|
77
|
-
def with_warning(self, message, model=None):
|
|
78
|
-
result = LinterMessage.warning(message, model)
|
|
79
|
-
return LinterResult(cast(list[LinterMessage], self.results) + [result])
|
|
80
|
-
|
|
81
|
-
def with_error(self, message, model=None):
|
|
82
|
-
result = LinterMessage.error(message, model)
|
|
83
|
-
return LinterResult(cast(list[LinterMessage], self.results) + [result])
|
|
84
|
-
|
|
85
|
-
def has_errors(self) -> bool:
|
|
86
|
-
return any(map(lambda result: result.outcome == LintSeverity.ERROR, self.results))
|
|
87
|
-
|
|
88
|
-
def has_warnings(self) -> bool:
|
|
89
|
-
return any(map(lambda result: result.outcome == LintSeverity.WARNING, self.results))
|
|
90
|
-
|
|
91
|
-
def error_results(self) -> Sequence[LinterMessage]:
|
|
92
|
-
return [result for result in self.results if result.outcome == LintSeverity.ERROR]
|
|
93
|
-
|
|
94
|
-
def warning_results(self) -> Sequence[LinterMessage]:
|
|
95
|
-
return [result for result in self.results if result.outcome == LintSeverity.WARNING]
|
|
96
|
-
|
|
97
|
-
def no_errors_or_warnings(self) -> bool:
|
|
98
|
-
return len(self.results) == 0
|
|
99
|
-
|
|
100
|
-
def combine(self, other: "LinterResult") -> "LinterResult":
|
|
101
|
-
return LinterResult(cast(list[Any], self.results) + cast(list[Any], other.results))
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
class Linter(abc.ABC):
|
|
105
|
-
@property
|
|
106
|
-
@abc.abstractmethod
|
|
107
|
-
def name(self) -> str:
|
|
108
|
-
"""Human-readable name of the linter."""
|
|
109
|
-
pass
|
|
110
|
-
|
|
111
|
-
@property
|
|
112
|
-
@abc.abstractmethod
|
|
113
|
-
def id(self) -> str:
|
|
114
|
-
"""A linter ID for configuration (i.e. enabling and disabling)."""
|
|
115
|
-
pass
|
|
116
|
-
|
|
117
|
-
@abc.abstractmethod
|
|
118
|
-
def lint_implementation(self, contract: DataContractSpecification) -> LinterResult:
|
|
119
|
-
pass
|
|
120
|
-
|
|
121
|
-
def lint(self, contract: DataContractSpecification) -> list[Check]:
|
|
122
|
-
"""Call with a data contract to get a list of check results from the linter."""
|
|
123
|
-
result = self.lint_implementation(contract)
|
|
124
|
-
checks = []
|
|
125
|
-
if not result.error_results():
|
|
126
|
-
checks.append(Check(type="lint", name=f"Linter '{self.name}'", result="passed", engine="datacontract"))
|
|
127
|
-
else:
|
|
128
|
-
# All linter messages are treated as warnings. Severity is
|
|
129
|
-
# currently ignored, but could be used in filtering in the future
|
|
130
|
-
# Linter messages with level WARNING are currently ignored, but might
|
|
131
|
-
# be logged or printed in the future.
|
|
132
|
-
for lint_error in result.error_results():
|
|
133
|
-
checks.append(
|
|
134
|
-
Check(
|
|
135
|
-
type="lint",
|
|
136
|
-
name=f"Linter '{self.name}'",
|
|
137
|
-
result="warning",
|
|
138
|
-
engine="datacontract",
|
|
139
|
-
reason=lint_error.message,
|
|
140
|
-
)
|
|
141
|
-
)
|
|
142
|
-
return checks
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
from datacontract.model.data_contract_specification import DataContractSpecification
|
|
2
|
-
|
|
3
|
-
from ..lint import Linter, LinterResult
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class DescriptionLinter(Linter):
|
|
7
|
-
"""Check for a description on contracts, models, model fields, definitions and examples."""
|
|
8
|
-
|
|
9
|
-
@property
|
|
10
|
-
def name(self) -> str:
|
|
11
|
-
return "Objects have descriptions"
|
|
12
|
-
|
|
13
|
-
@property
|
|
14
|
-
def id(self) -> str:
|
|
15
|
-
return "description"
|
|
16
|
-
|
|
17
|
-
def lint_implementation(self, contract: DataContractSpecification) -> LinterResult:
|
|
18
|
-
result = LinterResult()
|
|
19
|
-
if not contract.info or not contract.info.description:
|
|
20
|
-
result = result.with_error("Contract has empty description.")
|
|
21
|
-
for model_name, model in contract.models.items():
|
|
22
|
-
if not model.description:
|
|
23
|
-
result = result.with_error(f"Model '{model_name}' has empty description.")
|
|
24
|
-
for field_name, field in model.fields.items():
|
|
25
|
-
if not field.description:
|
|
26
|
-
result = result.with_error(
|
|
27
|
-
f"Field '{field_name}' in model '{model_name}'" f" has empty description."
|
|
28
|
-
)
|
|
29
|
-
for definition_name, definition in contract.definitions.items():
|
|
30
|
-
if not definition.description:
|
|
31
|
-
result = result.with_error(f"Definition '{definition_name}' has empty description.")
|
|
32
|
-
for index, example in enumerate(contract.examples):
|
|
33
|
-
if not example.description:
|
|
34
|
-
result = result.with_error(f"Example {index + 1} has empty description.")
|
|
35
|
-
return result
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import re
|
|
2
|
-
|
|
3
|
-
from datacontract.model.data_contract_specification import DataContractSpecification
|
|
4
|
-
|
|
5
|
-
from ..lint import Linter, LinterResult
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class FieldPatternLinter(Linter):
|
|
9
|
-
"""Checks that all patterns defined for fields are correct Python regex
|
|
10
|
-
syntax.
|
|
11
|
-
|
|
12
|
-
"""
|
|
13
|
-
|
|
14
|
-
@property
|
|
15
|
-
def name(self):
|
|
16
|
-
return "Field pattern is correct regex"
|
|
17
|
-
|
|
18
|
-
@property
|
|
19
|
-
def id(self) -> str:
|
|
20
|
-
return "field-pattern"
|
|
21
|
-
|
|
22
|
-
def lint_implementation(self, contract: DataContractSpecification) -> LinterResult:
|
|
23
|
-
result = LinterResult()
|
|
24
|
-
for model_name, model in contract.models.items():
|
|
25
|
-
for field_name, field in model.fields.items():
|
|
26
|
-
if field.pattern:
|
|
27
|
-
try:
|
|
28
|
-
re.compile(field.pattern)
|
|
29
|
-
except re.error as e:
|
|
30
|
-
result = result.with_error(
|
|
31
|
-
f"Failed to compile pattern regex '{field.pattern}' for "
|
|
32
|
-
f"field '{field_name}' in model '{model_name}': {e.msg}"
|
|
33
|
-
)
|
|
34
|
-
return result
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
from datacontract.model.data_contract_specification import DataContractSpecification
|
|
2
|
-
|
|
3
|
-
from ..lint import Linter, LinterResult
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class FieldReferenceLinter(Linter):
|
|
7
|
-
"""Checks that all references definitions in fields refer to existing
|
|
8
|
-
fields.
|
|
9
|
-
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
@property
|
|
13
|
-
def name(self):
|
|
14
|
-
return "Field references existing field"
|
|
15
|
-
|
|
16
|
-
@property
|
|
17
|
-
def id(self) -> str:
|
|
18
|
-
return "field-reference"
|
|
19
|
-
|
|
20
|
-
def lint_implementation(self, contract: DataContractSpecification) -> LinterResult:
|
|
21
|
-
result = LinterResult()
|
|
22
|
-
for model_name, model in contract.models.items():
|
|
23
|
-
for field_name, field in model.fields.items():
|
|
24
|
-
if field.references:
|
|
25
|
-
reference_hierarchy = field.references.split(".")
|
|
26
|
-
if len(reference_hierarchy) != 2:
|
|
27
|
-
result = result.with_error(
|
|
28
|
-
f"Field '{field_name}' in model '{model_name}'"
|
|
29
|
-
f" references must follow the model.field syntax and refer to a field in a model in this data contract."
|
|
30
|
-
)
|
|
31
|
-
continue
|
|
32
|
-
ref_model = reference_hierarchy[0]
|
|
33
|
-
ref_field = reference_hierarchy[1]
|
|
34
|
-
|
|
35
|
-
if ref_model not in contract.models:
|
|
36
|
-
result = result.with_error(
|
|
37
|
-
f"Field '{field_name}' in model '{model_name}'"
|
|
38
|
-
f" references non-existing model '{ref_model}'."
|
|
39
|
-
)
|
|
40
|
-
else:
|
|
41
|
-
ref_model_obj = contract.models[ref_model]
|
|
42
|
-
if ref_field not in ref_model_obj.fields:
|
|
43
|
-
result = result.with_error(
|
|
44
|
-
f"Field '{field_name}' in model '{model_name}'"
|
|
45
|
-
f" references non-existing field '{ref_field}'"
|
|
46
|
-
f" in model '{ref_model}'."
|
|
47
|
-
)
|
|
48
|
-
return result
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import re
|
|
2
|
-
|
|
3
|
-
from datacontract.model.data_contract_specification import DataContractSpecification
|
|
4
|
-
|
|
5
|
-
from ..lint import Linter, LinterResult
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class NoticePeriodLinter(Linter):
|
|
9
|
-
@property
|
|
10
|
-
def name(self) -> str:
|
|
11
|
-
return "noticePeriod in ISO8601 format"
|
|
12
|
-
|
|
13
|
-
@property
|
|
14
|
-
def id(self) -> str:
|
|
15
|
-
return "notice-period"
|
|
16
|
-
|
|
17
|
-
# Regex matching the "simple" ISO8601 duration format
|
|
18
|
-
simple = re.compile(
|
|
19
|
-
r"""P # Introduces period
|
|
20
|
-
(:?[0-9\.,]+Y)? # Number of years
|
|
21
|
-
(:?[0-9\.,]+M)? # Number of months
|
|
22
|
-
(:?[0-9\.,]+W)? # Number of weeks
|
|
23
|
-
(:?[0-9\.,]+D)? # Number of days
|
|
24
|
-
(:? # Time part (optional)
|
|
25
|
-
T # Always starts with T
|
|
26
|
-
(:?[0-9\.,]+H)? # Number of hours
|
|
27
|
-
(:?[0-9\.,]+M)? # Number of minutes
|
|
28
|
-
(:?[0-9\.,]+S)? # Number of seconds
|
|
29
|
-
)?
|
|
30
|
-
""",
|
|
31
|
-
re.VERBOSE,
|
|
32
|
-
)
|
|
33
|
-
datetime_basic = re.compile(r"P\d{8}T\d{6}")
|
|
34
|
-
datetime_extended = re.compile(r"P\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}")
|
|
35
|
-
|
|
36
|
-
def lint_implementation(self, contract: DataContractSpecification) -> LinterResult:
|
|
37
|
-
"""Check whether the notice period is specified using ISO8601 duration syntax."""
|
|
38
|
-
if not contract.terms:
|
|
39
|
-
return LinterResult.cautious("No terms defined.")
|
|
40
|
-
period = contract.terms.noticePeriod
|
|
41
|
-
if not period:
|
|
42
|
-
return LinterResult.cautious("No notice period defined.")
|
|
43
|
-
if not period.startswith("P"):
|
|
44
|
-
return LinterResult.erroneous(f"Notice period '{period}' is not a valid" "ISO8601 duration.")
|
|
45
|
-
if period == "P":
|
|
46
|
-
return LinterResult.erroneous(
|
|
47
|
-
"Notice period 'P' is not a valid" "ISO8601 duration, requires at least one" "duration to be specified."
|
|
48
|
-
)
|
|
49
|
-
if (
|
|
50
|
-
not self.simple.fullmatch(period)
|
|
51
|
-
and not self.datetime_basic.fullmatch(period)
|
|
52
|
-
and not self.datetime_extended.fullmatch(period)
|
|
53
|
-
):
|
|
54
|
-
return LinterResult.erroneous(f"Notice period '{period}' is not a valid ISO8601 duration.")
|
|
55
|
-
return LinterResult()
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import yaml
|
|
2
|
-
|
|
3
|
-
from datacontract.model.data_contract_specification import DataContractSpecification, Model
|
|
4
|
-
|
|
5
|
-
from ..lint import Linter, LinterResult
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class QualityUsesSchemaLinter(Linter):
|
|
9
|
-
@property
|
|
10
|
-
def name(self) -> str:
|
|
11
|
-
return "Quality check(s) use model"
|
|
12
|
-
|
|
13
|
-
@property
|
|
14
|
-
def id(self) -> str:
|
|
15
|
-
return "quality-schema"
|
|
16
|
-
|
|
17
|
-
def lint_sodacl(self, check, models: dict[str, Model]) -> LinterResult:
|
|
18
|
-
result = LinterResult()
|
|
19
|
-
for sodacl_check in check.keys():
|
|
20
|
-
table_name = sodacl_check[len("checks for ") :]
|
|
21
|
-
if table_name not in models:
|
|
22
|
-
result = result.with_error(f"Quality check on unknown model '{table_name}'")
|
|
23
|
-
return result
|
|
24
|
-
|
|
25
|
-
def lint_montecarlo(self, check, models: dict[str, Model]) -> LinterResult:
|
|
26
|
-
return LinterResult().with_warning("Linting montecarlo checks is not currently implemented")
|
|
27
|
-
|
|
28
|
-
def lint_great_expectations(self, check, models: dict[str, Model]) -> LinterResult:
|
|
29
|
-
return LinterResult().with_warning("Linting great expectations checks is not currently implemented")
|
|
30
|
-
|
|
31
|
-
def lint_implementation(self, contract: DataContractSpecification) -> LinterResult:
|
|
32
|
-
result = LinterResult()
|
|
33
|
-
models = contract.models
|
|
34
|
-
check = contract.quality
|
|
35
|
-
if not check:
|
|
36
|
-
return LinterResult()
|
|
37
|
-
if not check.specification:
|
|
38
|
-
return LinterResult.cautious("Quality check without specification.")
|
|
39
|
-
if isinstance(check.specification, str):
|
|
40
|
-
check_specification = yaml.safe_load(check.specification)
|
|
41
|
-
else:
|
|
42
|
-
check_specification = check.specification
|
|
43
|
-
match check.type:
|
|
44
|
-
case "SodaCL":
|
|
45
|
-
result = result.combine(self.lint_sodacl(check_specification, models))
|
|
46
|
-
case "montecarlo":
|
|
47
|
-
result = result.combine(self.lint_montecarlo(check_specification, models))
|
|
48
|
-
case "great-expectations":
|
|
49
|
-
result = result.combine(self.lint_great_expectations(check_specification, models))
|
|
50
|
-
case _:
|
|
51
|
-
result = result.with_warning("Can't lint quality check " f"with type '{check.type}'")
|
|
52
|
-
return result
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
from datacontract.model.data_contract_specification import DataContractSpecification, Field
|
|
2
|
-
|
|
3
|
-
from ..lint import Linter, LinterResult
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class ValidFieldConstraintsLinter(Linter):
|
|
7
|
-
"""Check validity of field constraints.
|
|
8
|
-
|
|
9
|
-
More precisely, check that only numeric constraints are specified on
|
|
10
|
-
fields of numeric type and string constraints on fields of string type.
|
|
11
|
-
Additionally, the linter checks that defined constraints make sense.
|
|
12
|
-
Minimum values should not be greater than maximum values, exclusive and
|
|
13
|
-
non-exclusive minimum and maximum should not be combined and string
|
|
14
|
-
pattern and format should not be combined.
|
|
15
|
-
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
valid_types_for_constraint = {
|
|
19
|
-
"pattern": set(["string", "text", "varchar"]),
|
|
20
|
-
"format": set(["string", "text", "varchar"]),
|
|
21
|
-
"minLength": set(["string", "text", "varchar"]),
|
|
22
|
-
"maxLength": set(["string", "text", "varchar"]),
|
|
23
|
-
"minimum": set(["int", "integer", "number", "decimal", "numeric", "long", "bigint", "float", "double"]),
|
|
24
|
-
"exclusiveMinimum": set(
|
|
25
|
-
["int", "integer", "number", "decimal", "numeric", "long", "bigint", "float", "double"]
|
|
26
|
-
),
|
|
27
|
-
"maximum": set(["int", "integer", "number", "decimal", "numeric", "long", "bigint", "float", "double"]),
|
|
28
|
-
"exclusiveMaximum": set(
|
|
29
|
-
["int", "integer", "number", "decimal", "numeric", "long", "bigint", "float", "double"]
|
|
30
|
-
),
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
def check_minimum_maximum(self, field: Field, field_name: str, model_name: str) -> LinterResult:
|
|
34
|
-
(min, max, xmin, xmax) = (field.minimum, field.maximum, field.exclusiveMinimum, field.exclusiveMaximum)
|
|
35
|
-
match (
|
|
36
|
-
"minimum" in field.model_fields_set,
|
|
37
|
-
"maximum" in field.model_fields_set,
|
|
38
|
-
"exclusiveMinimum" in field.model_fields_set,
|
|
39
|
-
"exclusiveMaximum" in field.model_fields_set,
|
|
40
|
-
):
|
|
41
|
-
case (True, True, _, _) if min > max:
|
|
42
|
-
return LinterResult.erroneous(
|
|
43
|
-
f"Minimum {min} is greater than maximum {max} on " f"field '{field_name}' in model '{model_name}'."
|
|
44
|
-
)
|
|
45
|
-
case (_, _, True, True) if xmin >= xmax:
|
|
46
|
-
return LinterResult.erroneous(
|
|
47
|
-
f"Exclusive minimum {xmin} is greater than exclusive"
|
|
48
|
-
f" maximum {xmax} on field '{field_name}' in model '{model_name}'."
|
|
49
|
-
)
|
|
50
|
-
case (True, True, True, True):
|
|
51
|
-
return LinterResult.erroneous(
|
|
52
|
-
f"Both exclusive and non-exclusive minimum and maximum are "
|
|
53
|
-
f"defined on field '{field_name}' in model '{model_name}'."
|
|
54
|
-
)
|
|
55
|
-
case (True, _, True, _):
|
|
56
|
-
return LinterResult.erroneous(
|
|
57
|
-
f"Both exclusive and non-exclusive minimum are "
|
|
58
|
-
f"defined on field '{field_name}' in model '{model_name}'."
|
|
59
|
-
)
|
|
60
|
-
case (_, True, _, True):
|
|
61
|
-
return LinterResult.erroneous(
|
|
62
|
-
f"Both exclusive and non-exclusive maximum are "
|
|
63
|
-
f"defined on field '{field_name}' in model '{model_name}'."
|
|
64
|
-
)
|
|
65
|
-
return LinterResult()
|
|
66
|
-
|
|
67
|
-
def check_string_constraints(self, field: Field, field_name: str, model_name: str) -> LinterResult:
|
|
68
|
-
result = LinterResult()
|
|
69
|
-
if field.minLength and field.maxLength and field.minLength > field.maxLength:
|
|
70
|
-
result = result.with_error(
|
|
71
|
-
f"Minimum length is greater that maximum length on" f" field '{field_name}' in model '{model_name}'."
|
|
72
|
-
)
|
|
73
|
-
if field.pattern and field.format:
|
|
74
|
-
result = result.with_error(
|
|
75
|
-
f"Both a pattern and a format are defined for field" f" '{field_name}' in model '{model_name}'."
|
|
76
|
-
)
|
|
77
|
-
return result
|
|
78
|
-
|
|
79
|
-
@property
|
|
80
|
-
def name(self):
|
|
81
|
-
return "Fields use valid constraints"
|
|
82
|
-
|
|
83
|
-
@property
|
|
84
|
-
def id(self):
|
|
85
|
-
return "field-constraints"
|
|
86
|
-
|
|
87
|
-
def lint_implementation(self, contract: DataContractSpecification) -> LinterResult:
|
|
88
|
-
result = LinterResult()
|
|
89
|
-
for model_name, model in contract.models.items():
|
|
90
|
-
for field_name, field in model.fields.items():
|
|
91
|
-
for _property, allowed_types in self.valid_types_for_constraint.items():
|
|
92
|
-
if _property in field.model_fields_set and field.type not in allowed_types:
|
|
93
|
-
result = result.with_error(
|
|
94
|
-
f"Forbidden constraint '{_property}' defined on field "
|
|
95
|
-
f"'{field_name}' in model '{model_name}'. Field type "
|
|
96
|
-
f"is '{field.type}'."
|
|
97
|
-
)
|
|
98
|
-
result = result.combine(self.check_minimum_maximum(field, field_name, model_name))
|
|
99
|
-
result = result.combine(self.check_string_constraints(field, field_name, model_name))
|
|
100
|
-
return result
|