datacontract-cli 0.11.4__tar.gz → 0.11.5__tar.gz

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 (217) hide show
  1. {datacontract_cli-0.11.4/datacontract_cli.egg-info → datacontract_cli-0.11.5}/PKG-INFO +13 -11
  2. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/README.md +4 -2
  3. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/catalog/catalog.py +5 -1
  4. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/cli.py +5 -1
  5. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/engines/data_contract_checks.py +72 -63
  6. datacontract_cli-0.11.5/datacontract/engines/soda/connections/bigquery.py +24 -0
  7. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/export/markdown_exporter.py +6 -6
  8. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/export/sql_type_converter.py +12 -8
  9. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/imports/bigquery_importer.py +74 -27
  10. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/output/junit_test_results.py +9 -1
  11. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/templates/partials/model_field.html +1 -1
  12. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5/datacontract_cli.egg-info}/PKG-INFO +13 -11
  13. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract_cli.egg-info/SOURCES.txt +1 -0
  14. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract_cli.egg-info/requires.txt +8 -8
  15. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/pyproject.toml +9 -9
  16. datacontract_cli-0.11.5/tests/test_bigquery_soda_connection.py +61 -0
  17. datacontract_cli-0.11.5/tests/test_data_contract_checks.py +203 -0
  18. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_export_dbml.py +1 -1
  19. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_export_sodacl.py +52 -0
  20. datacontract_cli-0.11.5/tests/test_test_output_junit.py +59 -0
  21. datacontract_cli-0.11.4/datacontract/engines/soda/connections/bigquery.py +0 -27
  22. datacontract_cli-0.11.4/tests/test_data_contract_checks.py +0 -74
  23. datacontract_cli-0.11.4/tests/test_test_output_junit.py +0 -22
  24. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/LICENSE +0 -0
  25. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/MANIFEST.in +0 -0
  26. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/__init__.py +0 -0
  27. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/api.py +0 -0
  28. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/data_contract.py +0 -0
  29. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/engines/__init__.py +0 -0
  30. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/engines/data_contract_test.py +0 -0
  31. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/engines/datacontract/check_that_datacontract_contains_valid_servers_configuration.py +0 -0
  32. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/engines/datacontract/check_that_datacontract_file_exists.py +0 -0
  33. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/engines/fastjsonschema/check_jsonschema.py +0 -0
  34. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/engines/fastjsonschema/s3/s3_read_files.py +0 -0
  35. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/engines/soda/__init__.py +0 -0
  36. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/engines/soda/check_soda_execute.py +0 -0
  37. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/engines/soda/connections/athena.py +0 -0
  38. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/engines/soda/connections/databricks.py +0 -0
  39. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/engines/soda/connections/duckdb_connection.py +0 -0
  40. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/engines/soda/connections/impala.py +0 -0
  41. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/engines/soda/connections/kafka.py +0 -0
  42. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/engines/soda/connections/oracle.py +0 -0
  43. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/engines/soda/connections/postgres.py +0 -0
  44. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/engines/soda/connections/snowflake.py +0 -0
  45. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/engines/soda/connections/sqlserver.py +0 -0
  46. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/engines/soda/connections/trino.py +0 -0
  47. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/export/__init__.py +0 -0
  48. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/export/avro_exporter.py +0 -0
  49. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/export/avro_idl_exporter.py +0 -0
  50. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/export/bigquery_exporter.py +0 -0
  51. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/export/custom_exporter.py +0 -0
  52. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/export/data_caterer_exporter.py +0 -0
  53. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/export/dbml_exporter.py +0 -0
  54. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/export/dbt_exporter.py +0 -0
  55. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/export/dcs_exporter.py +0 -0
  56. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/export/dqx_exporter.py +0 -0
  57. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/export/duckdb_type_converter.py +0 -0
  58. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/export/excel_exporter.py +0 -0
  59. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/export/exporter.py +0 -0
  60. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/export/exporter_factory.py +0 -0
  61. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/export/go_exporter.py +0 -0
  62. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/export/great_expectations_exporter.py +0 -0
  63. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/export/html_exporter.py +0 -0
  64. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/export/iceberg_exporter.py +0 -0
  65. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/export/jsonschema_exporter.py +0 -0
  66. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/export/mermaid_exporter.py +0 -0
  67. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/export/odcs_export_helper.py +0 -0
  68. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/export/odcs_v3_exporter.py +0 -0
  69. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/export/pandas_type_converter.py +0 -0
  70. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/export/protobuf_exporter.py +0 -0
  71. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/export/pydantic_exporter.py +0 -0
  72. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/export/rdf_exporter.py +0 -0
  73. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/export/sodacl_exporter.py +0 -0
  74. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/export/spark_exporter.py +0 -0
  75. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/export/sql_exporter.py +0 -0
  76. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/export/sqlalchemy_exporter.py +0 -0
  77. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/imports/avro_importer.py +0 -0
  78. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/imports/csv_importer.py +0 -0
  79. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/imports/dbml_importer.py +0 -0
  80. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/imports/dbt_importer.py +0 -0
  81. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/imports/dcs_importer.py +0 -0
  82. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/imports/excel_importer.py +0 -0
  83. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/imports/glue_importer.py +0 -0
  84. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/imports/iceberg_importer.py +0 -0
  85. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/imports/importer.py +0 -0
  86. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/imports/importer_factory.py +0 -0
  87. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/imports/json_importer.py +0 -0
  88. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/imports/jsonschema_importer.py +0 -0
  89. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/imports/odcs_helper.py +0 -0
  90. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/imports/odcs_importer.py +0 -0
  91. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/imports/parquet_importer.py +0 -0
  92. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/imports/protobuf_importer.py +0 -0
  93. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/imports/spark_importer.py +0 -0
  94. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/imports/sql_importer.py +0 -0
  95. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/imports/unity_importer.py +0 -0
  96. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/init/init_template.py +0 -0
  97. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/integration/entropy_data.py +0 -0
  98. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/lint/files.py +0 -0
  99. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/lint/resolve.py +0 -0
  100. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/lint/resources.py +0 -0
  101. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/lint/schema.py +0 -0
  102. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/lint/urls.py +0 -0
  103. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/model/exceptions.py +0 -0
  104. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/model/odcs.py +0 -0
  105. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/model/run.py +0 -0
  106. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/output/__init__.py +0 -0
  107. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/output/output_format.py +0 -0
  108. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/output/test_results_writer.py +0 -0
  109. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/py.typed +0 -0
  110. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/schemas/datacontract-1.1.0.init.yaml +0 -0
  111. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/schemas/datacontract-1.1.0.schema.json +0 -0
  112. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/schemas/datacontract-1.2.0.init.yaml +0 -0
  113. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/schemas/datacontract-1.2.0.schema.json +0 -0
  114. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/schemas/datacontract-1.2.1.init.yaml +0 -0
  115. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/schemas/datacontract-1.2.1.schema.json +0 -0
  116. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/schemas/odcs-3.0.1.schema.json +0 -0
  117. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/schemas/odcs-3.0.2.schema.json +0 -0
  118. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/schemas/odcs-3.1.0.init.yaml +0 -0
  119. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/schemas/odcs-3.1.0.schema.json +0 -0
  120. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/templates/datacontract.html +0 -0
  121. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/templates/datacontract_odcs.html +0 -0
  122. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/templates/index.html +0 -0
  123. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/templates/partials/datacontract_information.html +0 -0
  124. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/templates/partials/datacontract_servicelevels.html +0 -0
  125. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/templates/partials/datacontract_terms.html +0 -0
  126. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/templates/partials/definition.html +0 -0
  127. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/templates/partials/example.html +0 -0
  128. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/templates/partials/quality.html +0 -0
  129. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/templates/partials/server.html +0 -0
  130. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract/templates/style/output.css +0 -0
  131. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract_cli.egg-info/dependency_links.txt +0 -0
  132. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract_cli.egg-info/entry_points.txt +0 -0
  133. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/datacontract_cli.egg-info/top_level.txt +0 -0
  134. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/setup.cfg +0 -0
  135. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_api.py +0 -0
  136. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_catalog.py +0 -0
  137. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_cli.py +0 -0
  138. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_data_contract_specification.py +0 -0
  139. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_description_linter.py +0 -0
  140. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_download_datacontract_file.py +0 -0
  141. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_duckdb_json.py +0 -0
  142. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_export_avro.py +0 -0
  143. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_export_avro_idl.py +0 -0
  144. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_export_bigquery.py +0 -0
  145. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_export_complex_data_contract.py +0 -0
  146. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_export_custom.py +0 -0
  147. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_export_custom_exporter.py +0 -0
  148. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_export_data_caterer.py +0 -0
  149. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_export_dbt_models.py +0 -0
  150. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_export_dbt_sources.py +0 -0
  151. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_export_dbt_staging_sql.py +0 -0
  152. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_export_dqx.py +0 -0
  153. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_export_excel.py +0 -0
  154. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_export_go.py +0 -0
  155. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_export_great_expectations.py +0 -0
  156. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_export_html.py +0 -0
  157. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_export_iceberg.py +0 -0
  158. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_export_jsonschema.py +0 -0
  159. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_export_markdown.py +0 -0
  160. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_export_mermaid.py +0 -0
  161. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_export_odcs_v3.py +0 -0
  162. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_export_protobuf.py +0 -0
  163. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_export_pydantic.py +0 -0
  164. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_export_rdf.py +0 -0
  165. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_export_spark.py +0 -0
  166. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_export_sql.py +0 -0
  167. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_export_sql_query.py +0 -0
  168. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_export_sqlalchemy.py +0 -0
  169. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_import_avro.py +0 -0
  170. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_import_bigquery.py +0 -0
  171. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_import_csv.py +0 -0
  172. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_import_dbml.py +0 -0
  173. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_import_dbt.py +0 -0
  174. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_import_excel.py +0 -0
  175. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_import_glue.py +0 -0
  176. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_import_iceberg.py +0 -0
  177. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_import_json.py +0 -0
  178. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_import_jsonschema.py +0 -0
  179. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_import_odcs_v3.py +0 -0
  180. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_import_parquet.py +0 -0
  181. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_import_protobuf.py +0 -0
  182. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_import_spark.py +0 -0
  183. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_import_sql_oracle.py +0 -0
  184. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_import_sql_postgres.py +0 -0
  185. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_import_sql_sqlserver.py +0 -0
  186. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_import_unity_file.py +0 -0
  187. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_integration_entropydata.py +0 -0
  188. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_lint.py +0 -0
  189. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_resolve.py +0 -0
  190. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_roundtrip_jsonschema.py +0 -0
  191. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_test_api.py +0 -0
  192. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_test_athena_iceberg.py +0 -0
  193. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_test_azure_remote.py +0 -0
  194. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_test_bigquery.py +0 -0
  195. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_test_databricks.py +0 -0
  196. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_test_dataframe.py +0 -0
  197. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_test_delta.py +0 -0
  198. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_test_gcs_csv_remote.py +0 -0
  199. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_test_gcs_json_remote.py +0 -0
  200. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_test_kafka.py +0 -0
  201. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_test_kafka_remote.py +0 -0
  202. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_test_local_json.py +0 -0
  203. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_test_local_json_nd.py +0 -0
  204. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_test_oracle.py +0 -0
  205. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_test_parquet.py +0 -0
  206. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_test_postgres.py +0 -0
  207. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_test_quality.py +0 -0
  208. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_test_s3_csv.py +0 -0
  209. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_test_s3_delta.py +0 -0
  210. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_test_s3_json.py +0 -0
  211. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_test_s3_json_complex.py +0 -0
  212. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_test_s3_json_multiple_models.py +0 -0
  213. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_test_s3_json_remote.py +0 -0
  214. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_test_schema_evolution.py +0 -0
  215. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_test_snowflake.py +0 -0
  216. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_test_sqlserver.py +0 -0
  217. {datacontract_cli-0.11.4 → datacontract_cli-0.11.5}/tests/test_test_trino.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datacontract-cli
3
- Version: 0.11.4
3
+ Version: 0.11.5
4
4
  Summary: The datacontract CLI is an open source command-line tool for working with Data Contracts. It uses data contract YAML files to lint the data contract, connect to data sources and execute schema and quality tests, detect breaking changes, and export to different formats. The tool is written in Python. It can be used as a standalone CLI tool, in a CI/CD pipeline, or directly as a Python library.
5
5
  Author-email: Jochen Christ <jochen.christ@innoq.com>, Stefan Negele <stefan.negele@innoq.com>, Simon Harrer <simon.harrer@innoq.com>
6
6
  License-Expression: MIT
@@ -39,7 +39,7 @@ Provides-Extra: databricks
39
39
  Requires-Dist: soda-core-spark-df<3.6.0,>=3.3.20; extra == "databricks"
40
40
  Requires-Dist: soda-core-spark[databricks]<3.6.0,>=3.3.20; extra == "databricks"
41
41
  Requires-Dist: databricks-sql-connector<4.3.0,>=3.7.0; extra == "databricks"
42
- Requires-Dist: databricks-sdk<0.78.0; extra == "databricks"
42
+ Requires-Dist: databricks-sdk<0.86.0; extra == "databricks"
43
43
  Requires-Dist: pyspark<5.0.0,>=3.5.0; extra == "databricks"
44
44
  Requires-Dist: numpy<2.0.0,>=1.26.4; extra == "databricks"
45
45
  Provides-Extra: iceberg
@@ -52,10 +52,10 @@ Requires-Dist: numpy<2.0.0,>=1.26.4; extra == "kafka"
52
52
  Provides-Extra: postgres
53
53
  Requires-Dist: soda-core-postgres<3.6.0,>=3.3.20; extra == "postgres"
54
54
  Provides-Extra: s3
55
- Requires-Dist: s3fs<2026.0.0,>=2025.2.0; extra == "s3"
55
+ Requires-Dist: s3fs<2027.0.0,>=2025.2.0; extra == "s3"
56
56
  Requires-Dist: aiobotocore<3.2.0,>=2.17.0; extra == "s3"
57
57
  Provides-Extra: snowflake
58
- Requires-Dist: snowflake-connector-python[pandas]<4.2,>=3.6; extra == "snowflake"
58
+ Requires-Dist: snowflake-connector-python[pandas]<4.3,>=3.6; extra == "snowflake"
59
59
  Requires-Dist: soda-core-snowflake<3.6.0,>=3.3.20; extra == "snowflake"
60
60
  Provides-Extra: sqlserver
61
61
  Requires-Dist: soda-core-sqlserver<3.6.0,>=3.3.20; extra == "sqlserver"
@@ -77,7 +77,7 @@ Requires-Dist: pyarrow>=18.1.0; extra == "parquet"
77
77
  Provides-Extra: rdf
78
78
  Requires-Dist: rdflib==7.5.0; extra == "rdf"
79
79
  Provides-Extra: api
80
- Requires-Dist: fastapi==0.121.2; extra == "api"
80
+ Requires-Dist: fastapi==0.128.0; extra == "api"
81
81
  Requires-Dist: uvicorn==0.40.0; extra == "api"
82
82
  Provides-Extra: protobuf
83
83
  Requires-Dist: grpcio-tools>=1.53; extra == "protobuf"
@@ -87,15 +87,15 @@ Provides-Extra: dev
87
87
  Requires-Dist: datacontract-cli[all]; extra == "dev"
88
88
  Requires-Dist: httpx==0.28.1; extra == "dev"
89
89
  Requires-Dist: kafka-python; extra == "dev"
90
- Requires-Dist: minio==7.2.17; extra == "dev"
91
- Requires-Dist: moto==5.1.18; extra == "dev"
90
+ Requires-Dist: minio==7.2.20; extra == "dev"
91
+ Requires-Dist: moto==5.1.21; extra == "dev"
92
92
  Requires-Dist: pandas>=2.1.0; extra == "dev"
93
93
  Requires-Dist: pre-commit<4.6.0,>=3.7.1; extra == "dev"
94
94
  Requires-Dist: pytest; extra == "dev"
95
95
  Requires-Dist: pytest-xdist; extra == "dev"
96
- Requires-Dist: pymssql==2.3.9; extra == "dev"
96
+ Requires-Dist: pymssql==2.3.13; extra == "dev"
97
97
  Requires-Dist: ruff; extra == "dev"
98
- Requires-Dist: testcontainers[kafka,minio,mssql,postgres]==4.13.3; extra == "dev"
98
+ Requires-Dist: testcontainers[kafka,minio,mssql,postgres]==4.14.1; extra == "dev"
99
99
  Requires-Dist: trino==0.336.0; extra == "dev"
100
100
  Dynamic: license-file
101
101
 
@@ -622,10 +622,11 @@ servers:
622
622
 
623
623
  #### BigQuery
624
624
 
625
- We support authentication to BigQuery using Service Account Key. The used Service Account should include the roles:
625
+ We support authentication to BigQuery using Service Account Key or Application Default Credentials (ADC). ADC supports Workload Identity Federation (WIF), GCE metadata server, and `gcloud auth application-default login`. The used Service Account should include the roles:
626
626
  * BigQuery Job User
627
627
  * BigQuery Data Viewer
628
628
 
629
+ When no `DATACONTRACT_BIGQUERY_ACCOUNT_INFO_JSON_PATH` is set, the CLI falls back to ADC/WIF automatically via Soda's `use_context_auth`.
629
630
 
630
631
  ##### Example
631
632
 
@@ -646,7 +647,8 @@ models:
646
647
 
647
648
  | Environment Variable | Example | Description |
648
649
  |----------------------------------------------|---------------------------|---------------------------------------------------------|
649
- | `DATACONTRACT_BIGQUERY_ACCOUNT_INFO_JSON_PATH` | `~/service-access-key.json` | Service Access key as saved on key creation by BigQuery. If this environment variable isn't set, the cli tries to use `GOOGLE_APPLICATION_CREDENTIALS` as a fallback, so if you have that set for using their Python library anyway, it should work seamlessly. |
650
+ | `DATACONTRACT_BIGQUERY_ACCOUNT_INFO_JSON_PATH` | `~/service-access-key.json` | Service Account key JSON file. If not set, ADC/WIF is used automatically. |
651
+ | `DATACONTRACT_BIGQUERY_IMPERSONATION_ACCOUNT` | `sa@project.iam.gserviceaccount.com` | Optional. Service account to impersonate. Works with both key file and ADC auth. |
650
652
 
651
653
 
652
654
  #### Azure
@@ -521,10 +521,11 @@ servers:
521
521
 
522
522
  #### BigQuery
523
523
 
524
- We support authentication to BigQuery using Service Account Key. The used Service Account should include the roles:
524
+ We support authentication to BigQuery using Service Account Key or Application Default Credentials (ADC). ADC supports Workload Identity Federation (WIF), GCE metadata server, and `gcloud auth application-default login`. The used Service Account should include the roles:
525
525
  * BigQuery Job User
526
526
  * BigQuery Data Viewer
527
527
 
528
+ When no `DATACONTRACT_BIGQUERY_ACCOUNT_INFO_JSON_PATH` is set, the CLI falls back to ADC/WIF automatically via Soda's `use_context_auth`.
528
529
 
529
530
  ##### Example
530
531
 
@@ -545,7 +546,8 @@ models:
545
546
 
546
547
  | Environment Variable | Example | Description |
547
548
  |----------------------------------------------|---------------------------|---------------------------------------------------------|
548
- | `DATACONTRACT_BIGQUERY_ACCOUNT_INFO_JSON_PATH` | `~/service-access-key.json` | Service Access key as saved on key creation by BigQuery. If this environment variable isn't set, the cli tries to use `GOOGLE_APPLICATION_CREDENTIALS` as a fallback, so if you have that set for using their Python library anyway, it should work seamlessly. |
549
+ | `DATACONTRACT_BIGQUERY_ACCOUNT_INFO_JSON_PATH` | `~/service-access-key.json` | Service Account key JSON file. If not set, ADC/WIF is used automatically. |
550
+ | `DATACONTRACT_BIGQUERY_IMPERSONATION_ACCOUNT` | `sa@project.iam.gserviceaccount.com` | Optional. Service account to impersonate. Works with both key file and ADC auth. |
549
551
 
550
552
 
551
553
  #### Azure
@@ -32,7 +32,11 @@ def create_data_contract_html(contracts, file: Path, path: Path, schema: str):
32
32
  odcs = data_contract.get_data_contract()
33
33
  file_without_suffix = file.with_suffix(".html")
34
34
  html_filepath = path / file_without_suffix
35
- html_filepath.parent.mkdir(parents=True, exist_ok=True)
35
+ try:
36
+ html_filepath.parent.mkdir(parents=True, exist_ok=True)
37
+ except FileExistsError:
38
+ if not html_filepath.parent.is_dir():
39
+ raise
36
40
  with open(html_filepath, "w", encoding="utf-8") as f:
37
41
  f.write(html)
38
42
  contracts.append(
@@ -431,7 +431,11 @@ def catalog(
431
431
  enable_debug_logging(debug)
432
432
 
433
433
  path = Path(output)
434
- path.mkdir(parents=True, exist_ok=True)
434
+ try:
435
+ path.mkdir(parents=True, exist_ok=True)
436
+ except FileExistsError:
437
+ if not path.is_dir():
438
+ raise
435
439
  console.print(f"Created {output}")
436
440
 
437
441
  contracts = []
@@ -20,10 +20,22 @@ from datacontract.model.run import Check
20
20
  @dataclass
21
21
  class QuotingConfig:
22
22
  quote_field_name: bool = False
23
+ quote_field_name_with_backticks: bool = False
23
24
  quote_model_name: bool = False
24
25
  quote_model_name_with_backticks: bool = False
25
26
 
26
27
 
28
+ def _quote_field_name(field_name: str, quoting_config: QuotingConfig) -> str:
29
+ """Quote a field name according to the quoting configuration."""
30
+ if field_name is None:
31
+ return field_name
32
+ if quoting_config.quote_field_name:
33
+ return f'"{field_name}"'
34
+ elif quoting_config.quote_field_name_with_backticks:
35
+ return f"`{field_name}`"
36
+ return field_name
37
+
38
+
27
39
  def _get_logical_type_option(prop: SchemaProperty, key: str):
28
40
  """Get a logical type option value."""
29
41
  if prop.logicalTypeOptions is None:
@@ -72,8 +84,9 @@ def to_schema_checks(schema_object: SchemaObject, server: Server) -> List[Check]
72
84
 
73
85
  type1 = server.type if server and server.type else None
74
86
  config = QuotingConfig(
75
- quote_field_name=type1 in ["postgres", "sqlserver"],
76
- quote_model_name=type1 in ["postgres", "sqlserver"],
87
+ quote_field_name=type1 in ["postgres", "sqlserver", "azure", "snowflake"],
88
+ quote_field_name_with_backticks=type1 in ["databricks"],
89
+ quote_model_name=type1 in ["postgres", "sqlserver", "snowflake"],
77
90
  quote_model_name_with_backticks=type1 == "bigquery",
78
91
  )
79
92
  quoting_config = config
@@ -229,10 +242,7 @@ def check_property_type(
229
242
 
230
243
 
231
244
  def check_property_required(model_name: str, field_name: str, quoting_config: QuotingConfig = QuotingConfig()):
232
- if quoting_config.quote_field_name:
233
- field_name_for_soda = f'"{field_name}"'
234
- else:
235
- field_name_for_soda = field_name
245
+ field_name_for_soda = _quote_field_name(field_name, quoting_config)
236
246
 
237
247
  check_type = "field_required"
238
248
  check_key = f"{model_name}__{field_name}__{check_type}"
@@ -260,10 +270,7 @@ def check_property_required(model_name: str, field_name: str, quoting_config: Qu
260
270
 
261
271
 
262
272
  def check_property_unique(model_name: str, field_name: str, quoting_config: QuotingConfig = QuotingConfig()):
263
- if quoting_config.quote_field_name:
264
- field_name_for_soda = f'"{field_name}"'
265
- else:
266
- field_name_for_soda = field_name
273
+ field_name_for_soda = _quote_field_name(field_name, quoting_config)
267
274
 
268
275
  check_type = "field_unique"
269
276
  check_key = f"{model_name}__{field_name}__{check_type}"
@@ -293,10 +300,7 @@ def check_property_unique(model_name: str, field_name: str, quoting_config: Quot
293
300
  def check_property_min_length(
294
301
  model_name: str, field_name: str, min_length: int, quoting_config: QuotingConfig = QuotingConfig()
295
302
  ):
296
- if quoting_config.quote_field_name:
297
- field_name_for_soda = f'"{field_name}"'
298
- else:
299
- field_name_for_soda = field_name
303
+ field_name_for_soda = _quote_field_name(field_name, quoting_config)
300
304
 
301
305
  check_type = "field_min_length"
302
306
  check_key = f"{model_name}__{field_name}__{check_type}"
@@ -327,10 +331,7 @@ def check_property_min_length(
327
331
  def check_property_max_length(
328
332
  model_name: str, field_name: str, max_length: int, quoting_config: QuotingConfig = QuotingConfig()
329
333
  ):
330
- if quoting_config.quote_field_name:
331
- field_name_for_soda = f'"{field_name}"'
332
- else:
333
- field_name_for_soda = field_name
334
+ field_name_for_soda = _quote_field_name(field_name, quoting_config)
334
335
 
335
336
  check_type = "field_max_length"
336
337
  check_key = f"{model_name}__{field_name}__{check_type}"
@@ -361,10 +362,7 @@ def check_property_max_length(
361
362
  def check_property_minimum(
362
363
  model_name: str, field_name: str, minimum: int, quoting_config: QuotingConfig = QuotingConfig()
363
364
  ):
364
- if quoting_config.quote_field_name:
365
- field_name_for_soda = f'"{field_name}"'
366
- else:
367
- field_name_for_soda = field_name
365
+ field_name_for_soda = _quote_field_name(field_name, quoting_config)
368
366
 
369
367
  check_type = "field_minimum"
370
368
  check_key = f"{model_name}__{field_name}__{check_type}"
@@ -395,10 +393,7 @@ def check_property_minimum(
395
393
  def check_property_maximum(
396
394
  model_name: str, field_name: str, maximum: int, quoting_config: QuotingConfig = QuotingConfig()
397
395
  ):
398
- if quoting_config.quote_field_name:
399
- field_name_for_soda = f'"{field_name}"'
400
- else:
401
- field_name_for_soda = field_name
396
+ field_name_for_soda = _quote_field_name(field_name, quoting_config)
402
397
 
403
398
  check_type = "field_maximum"
404
399
  check_key = f"{model_name}__{field_name}__{check_type}"
@@ -429,10 +424,7 @@ def check_property_maximum(
429
424
  def check_property_not_equal(
430
425
  model_name: str, field_name: str, value: int, quoting_config: QuotingConfig = QuotingConfig()
431
426
  ):
432
- if quoting_config.quote_field_name:
433
- field_name_for_soda = f'"{field_name}"'
434
- else:
435
- field_name_for_soda = field_name
427
+ field_name_for_soda = _quote_field_name(field_name, quoting_config)
436
428
 
437
429
  check_type = "field_not_equal"
438
430
  check_key = f"{model_name}__{field_name}__{check_type}"
@@ -461,10 +453,7 @@ def check_property_not_equal(
461
453
 
462
454
 
463
455
  def check_property_enum(model_name: str, field_name: str, enum: list, quoting_config: QuotingConfig = QuotingConfig()):
464
- if quoting_config.quote_field_name:
465
- field_name_for_soda = f'"{field_name}"'
466
- else:
467
- field_name_for_soda = field_name
456
+ field_name_for_soda = _quote_field_name(field_name, quoting_config)
468
457
 
469
458
  check_type = "field_enum"
470
459
  check_key = f"{model_name}__{field_name}__{check_type}"
@@ -493,10 +482,7 @@ def check_property_enum(model_name: str, field_name: str, enum: list, quoting_co
493
482
 
494
483
 
495
484
  def check_property_regex(model_name: str, field_name: str, pattern: str, quoting_config: QuotingConfig = QuotingConfig()):
496
- if quoting_config.quote_field_name:
497
- field_name_for_soda = f'"{field_name}"'
498
- else:
499
- field_name_for_soda = field_name
485
+ field_name_for_soda = _quote_field_name(field_name, quoting_config)
500
486
 
501
487
  check_type = "field_regex"
502
488
  check_key = f"{model_name}__{field_name}__{check_type}"
@@ -553,7 +539,7 @@ def check_model_duplicate_values(
553
539
  ):
554
540
  check_type = "model_duplicate_values"
555
541
  check_key = f"{model_name}__{check_type}"
556
- col_joined = ", ".join(cols)
542
+ col_joined = ", ".join(_quote_field_name(col, quoting_config) for col in cols)
557
543
  sodacl_check_dict = {
558
544
  checks_for(model_name, quoting_config, check_type): [
559
545
  {
@@ -578,10 +564,7 @@ def check_model_duplicate_values(
578
564
  def check_property_duplicate_values(
579
565
  model_name: str, field_name: str, threshold: str, quoting_config: QuotingConfig = QuotingConfig()
580
566
  ):
581
- if quoting_config.quote_field_name:
582
- field_name_for_soda = f'"{field_name}"'
583
- else:
584
- field_name_for_soda = field_name
567
+ field_name_for_soda = _quote_field_name(field_name, quoting_config)
585
568
 
586
569
  check_type = "field_duplicate_values"
587
570
  check_key = f"{model_name}__{field_name}__{check_type}"
@@ -611,10 +594,7 @@ def check_property_duplicate_values(
611
594
  def check_property_null_values(
612
595
  model_name: str, field_name: str, threshold: str, quoting_config: QuotingConfig = QuotingConfig()
613
596
  ):
614
- if quoting_config.quote_field_name:
615
- field_name_for_soda = f'"{field_name}"'
616
- else:
617
- field_name_for_soda = field_name
597
+ field_name_for_soda = _quote_field_name(field_name, quoting_config)
618
598
 
619
599
  check_type = "field_null_values"
620
600
  check_key = f"{model_name}__{field_name}__{check_type}"
@@ -648,10 +628,7 @@ def check_property_invalid_values(
648
628
  valid_values: list = None,
649
629
  quoting_config: QuotingConfig = QuotingConfig(),
650
630
  ):
651
- if quoting_config.quote_field_name:
652
- field_name_for_soda = f'"{field_name}"'
653
- else:
654
- field_name_for_soda = field_name
631
+ field_name_for_soda = _quote_field_name(field_name, quoting_config)
655
632
 
656
633
  check_type = "field_invalid_values"
657
634
  check_key = f"{model_name}__{field_name}__{check_type}"
@@ -691,10 +668,7 @@ def check_property_missing_values(
691
668
  missing_values: list = None,
692
669
  quoting_config: QuotingConfig = QuotingConfig(),
693
670
  ):
694
- if quoting_config.quote_field_name:
695
- field_name_for_soda = f'"{field_name}"'
696
- else:
697
- field_name_for_soda = field_name
671
+ field_name_for_soda = _quote_field_name(field_name, quoting_config)
698
672
 
699
673
  check_type = "field_missing_values"
700
674
  check_key = f"{model_name}__{field_name}__{check_type}"
@@ -863,10 +837,7 @@ def prepare_query(
863
837
 
864
838
  query = quality.query
865
839
 
866
- if quoting_config.quote_field_name:
867
- field_name_for_soda = f'"{field_name}"'
868
- else:
869
- field_name_for_soda = field_name
840
+ field_name_for_soda = _quote_field_name(field_name, quoting_config)
870
841
 
871
842
  if quoting_config.quote_model_name:
872
843
  model_name_for_soda = f'"{model_name}"'
@@ -1050,11 +1021,12 @@ def to_servicelevel_retention_check(data_contract: OpenDataContractStandard, sla
1050
1021
  logger.info(f"Model {model_name} not found in schema, skipping retention check")
1051
1022
  return None
1052
1023
 
1053
- # Parse ISO 8601 duration to seconds
1054
- retention_period = sla.value
1055
- seconds = _parse_iso8601_to_seconds(retention_period)
1024
+ # Convert retention value to seconds
1025
+ # Supports both numeric value + unit (ODCS style: value=3, unit=y)
1026
+ # and ISO 8601 duration strings (e.g., "P1Y")
1027
+ seconds = _retention_value_to_seconds(sla.value, sla.unit)
1056
1028
  if seconds is None:
1057
- logger.info(f"Could not parse retention period {retention_period}, skipping retention check")
1029
+ logger.info(f"Could not parse retention period (value={sla.value}, unit={sla.unit}), skipping retention check")
1058
1030
  return None
1059
1031
 
1060
1032
  check_type = "servicelevel_retention"
@@ -1083,6 +1055,43 @@ def to_servicelevel_retention_check(data_contract: OpenDataContractStandard, sla
1083
1055
  )
1084
1056
 
1085
1057
 
1058
+ def _retention_value_to_seconds(value, unit: str | None) -> int | None:
1059
+ """Convert a retention value to seconds.
1060
+
1061
+ Supports:
1062
+ - Numeric value with unit (ODCS style): value=3, unit="y"
1063
+ - ISO 8601 duration string: value="P1Y"
1064
+ """
1065
+ if value is None:
1066
+ return None
1067
+
1068
+ # If value is numeric, use the unit to convert to seconds
1069
+ if isinstance(value, (int, float)):
1070
+ numeric_value = int(value)
1071
+ unit_lower = unit.lower() if unit else "d"
1072
+ if unit_lower in ("y", "yr", "year", "years"):
1073
+ return numeric_value * 365 * 24 * 60 * 60
1074
+ elif unit_lower in ("m", "mo", "month", "months"):
1075
+ return numeric_value * 30 * 24 * 60 * 60
1076
+ elif unit_lower in ("d", "day", "days"):
1077
+ return numeric_value * 24 * 60 * 60
1078
+ elif unit_lower in ("h", "hr", "hour", "hours"):
1079
+ return numeric_value * 60 * 60
1080
+ elif unit_lower in ("min", "minute", "minutes"):
1081
+ return numeric_value * 60
1082
+ elif unit_lower in ("s", "sec", "second", "seconds"):
1083
+ return numeric_value
1084
+ else:
1085
+ logger.info(f"Unsupported retention unit: {unit}")
1086
+ return None
1087
+
1088
+ # If value is a string, try ISO 8601 parsing
1089
+ if isinstance(value, str):
1090
+ return _parse_iso8601_to_seconds(value)
1091
+
1092
+ return None
1093
+
1094
+
1086
1095
  def _parse_iso8601_to_seconds(duration: str) -> int | None:
1087
1096
  """Parse ISO 8601 duration to seconds."""
1088
1097
  if not duration:
@@ -0,0 +1,24 @@
1
+ import os
2
+
3
+ import yaml
4
+
5
+
6
+ # https://docs.soda.io/soda/connect-bigquery.html#authentication-methods
7
+ def to_bigquery_soda_configuration(server):
8
+ data_source = {
9
+ "type": "bigquery",
10
+ "project_id": server.project,
11
+ "dataset": server.dataset,
12
+ }
13
+
14
+ if "DATACONTRACT_BIGQUERY_ACCOUNT_INFO_JSON_PATH" in os.environ:
15
+ data_source["account_info_json_path"] = os.environ["DATACONTRACT_BIGQUERY_ACCOUNT_INFO_JSON_PATH"]
16
+ data_source["auth_scopes"] = ["https://www.googleapis.com/auth/bigquery"]
17
+ else:
18
+ data_source["use_context_auth"] = True
19
+
20
+ if "DATACONTRACT_BIGQUERY_IMPERSONATION_ACCOUNT" in os.environ:
21
+ data_source["impersonation_account"] = os.environ["DATACONTRACT_BIGQUERY_IMPERSONATION_ACCOUNT"]
22
+
23
+ soda_configuration_str = yaml.dump({f"data_source {server.type}": data_source})
24
+ return soda_configuration_str
@@ -105,7 +105,7 @@ def obj_attributes_to_markdown(obj: BaseModel, excluded_fields: set = set(), is_
105
105
  return ""
106
106
  if is_in_table_cell:
107
107
  bullet_char = "•"
108
- newline_char = "<br>"
108
+ newline_char = "<br />"
109
109
  else:
110
110
  bullet_char = "-"
111
111
  newline_char = "\n"
@@ -247,12 +247,12 @@ def description_to_markdown(description) -> str:
247
247
  if description is None:
248
248
  return "No description."
249
249
  if isinstance(description, str):
250
- return description.replace("\n", "<br>")
250
+ return description.replace("\n", "<br />")
251
251
  # Handle Description object - use purpose as the primary description
252
252
  if hasattr(description, "purpose") and description.purpose:
253
- return description.purpose.replace("\n", "<br>")
253
+ return description.purpose.replace("\n", "<br />")
254
254
  if hasattr(description, "usage") and description.usage:
255
- return description.usage.replace("\n", "<br>")
255
+ return description.usage.replace("\n", "<br />")
256
256
  return "No description."
257
257
 
258
258
 
@@ -285,7 +285,7 @@ def array_of_dict_to_markdown(array: List[Dict[str, str]]) -> str:
285
285
  markdown_parts.append(
286
286
  "| "
287
287
  + " | ".join(
288
- f"{str(element.get(header, ''))}".replace("\n", "<br>").replace("\t", TAB) for header in headers
288
+ f"{str(element.get(header, ''))}".replace("\n", "<br />").replace("\t", TAB) for header in headers
289
289
  )
290
290
  + " |"
291
291
  )
@@ -352,7 +352,7 @@ def extra_to_markdown(obj: BaseModel, is_in_table_cell: bool = False) -> str:
352
352
 
353
353
  bullet_char = "•"
354
354
  value_line_ending = "" if is_in_table_cell else "\n"
355
- row_suffix = "<br>" if is_in_table_cell else ""
355
+ row_suffix = "<br />" if is_in_table_cell else ""
356
356
 
357
357
  def render_header(key: str) -> str:
358
358
  return f"{bullet_char} **{key}:** " if is_in_table_cell else f"\n### {key.capitalize()}\n"
@@ -5,6 +5,7 @@ from open_data_contract_standard.model import SchemaProperty
5
5
 
6
6
  class FieldLike(Protocol):
7
7
  """Protocol for field-like objects (DCS Field or PropertyAdapter)."""
8
+
8
9
  type: Optional[str]
9
10
  config: Optional[Dict[str, Any]]
10
11
  precision: Optional[int]
@@ -270,11 +271,7 @@ def convert_to_dataframe(field: Union[SchemaProperty, FieldLike]) -> None | str:
270
271
  def convert_to_databricks(field: Union[SchemaProperty, FieldLike]) -> None | str:
271
272
  type = _get_type(field)
272
273
  databricks_type = _get_config_value(field, "databricksType")
273
- if (
274
- databricks_type
275
- and type
276
- and type.lower() not in ["array", "object", "record", "struct"]
277
- ):
274
+ if databricks_type and type and type.lower() not in ["array", "object", "record", "struct"]:
278
275
  return databricks_type
279
276
  if type is None:
280
277
  return None
@@ -343,6 +340,7 @@ def convert_to_duckdb(field: Union[SchemaProperty, FieldLike]) -> None | str:
343
340
 
344
341
  # Prepare
345
342
  type_mapping = {
343
+ "nvarchar": "VARCHAR",
346
344
  "varchar": "VARCHAR",
347
345
  "string": "VARCHAR",
348
346
  "text": "VARCHAR",
@@ -370,10 +368,15 @@ def convert_to_duckdb(field: Union[SchemaProperty, FieldLike]) -> None | str:
370
368
  return type_mapping[type_lower]
371
369
 
372
370
  # convert decimal numbers with precision and scale
373
- if type_lower == "decimal" or type_lower == "number" or type_lower == "numeric":
371
+ if "decimal" in type_lower or "number" in type_lower or "numeric" in type_lower:
372
+ # try CustomProperties
374
373
  precision = _get_precision(field)
375
374
  scale = _get_scale(field)
376
- return f"DECIMAL({precision},{scale})"
375
+ if precision and scale:
376
+ return f"DECIMAL({precision},{scale})"
377
+ else:
378
+ # force physicalType as is
379
+ return type
377
380
 
378
381
  # Check list and map
379
382
  if type_lower == "list" or type_lower == "array":
@@ -576,6 +579,7 @@ def convert_type_to_impala(field: Union[SchemaProperty, FieldLike]) -> None | st
576
579
  # (returning None means no "has type" check is generated)
577
580
  return None
578
581
 
582
+
579
583
  def convert_type_to_oracle(schema_property: SchemaProperty) -> None | str:
580
584
  """Convert ODCS logical types to Oracle types.
581
585
 
@@ -605,4 +609,4 @@ def convert_type_to_oracle(schema_property: SchemaProperty) -> None | str:
605
609
  "array": "CLOB",
606
610
  }
607
611
 
608
- return mapping.get(logical_type)
612
+ return mapping.get(logical_type)
@@ -143,27 +143,58 @@ def import_table_fields(table_fields) -> List[SchemaProperty]:
143
143
  required = field.get("mode") == "REQUIRED"
144
144
  description = field.get("description")
145
145
  field_type = field.get("type")
146
+ repeated = field.get("mode") == "REPEATED"
146
147
 
147
148
  if field_type == "RECORD":
148
149
  nested_properties = import_table_fields(field.get("fields", []))
149
- prop = create_property(
150
- name=field_name,
151
- logical_type="object",
152
- physical_type="RECORD",
153
- description=description,
154
- required=required if required else None,
155
- properties=nested_properties,
156
- )
150
+ if repeated:
151
+ items_prop = create_property(
152
+ name="items",
153
+ logical_type="object",
154
+ physical_type="RECORD",
155
+ properties=nested_properties,
156
+ )
157
+ prop = create_property(
158
+ name=field_name,
159
+ logical_type="array",
160
+ description=description,
161
+ required=None,
162
+ items=items_prop,
163
+ )
164
+ else:
165
+ prop = create_property(
166
+ name=field_name,
167
+ logical_type="object",
168
+ physical_type="RECORD",
169
+ description=description,
170
+ required=required if required else None,
171
+ properties=nested_properties,
172
+ )
157
173
  elif field_type == "STRUCT":
158
174
  nested_properties = import_table_fields(field.get("fields", []))
159
- prop = create_property(
160
- name=field_name,
161
- logical_type="object",
162
- physical_type="STRUCT",
163
- description=description,
164
- required=required if required else None,
165
- properties=nested_properties,
166
- )
175
+ if repeated:
176
+ items_prop = create_property(
177
+ name="items",
178
+ logical_type="object",
179
+ physical_type="STRUCT",
180
+ properties=nested_properties,
181
+ )
182
+ prop = create_property(
183
+ name=field_name,
184
+ logical_type="array",
185
+ description=description,
186
+ required=None,
187
+ items=items_prop,
188
+ )
189
+ else:
190
+ prop = create_property(
191
+ name=field_name,
192
+ logical_type="object",
193
+ physical_type="STRUCT",
194
+ description=description,
195
+ required=required if required else None,
196
+ properties=nested_properties,
197
+ )
167
198
  elif field_type == "RANGE":
168
199
  # Range of date/datetime/timestamp - multiple values, map to array
169
200
  items_prop = create_property(
@@ -194,17 +225,33 @@ def import_table_fields(table_fields) -> List[SchemaProperty]:
194
225
  if field.get("scale") is not None:
195
226
  scale = int(field.get("scale"))
196
227
 
197
- prop = create_property(
198
- name=field_name,
199
- logical_type=logical_type,
200
- physical_type=field_type,
201
- description=description,
202
- required=required if required else None,
203
- max_length=max_length,
204
- precision=precision,
205
- scale=scale,
206
- )
207
-
228
+ if repeated:
229
+ items_prop = create_property(
230
+ name="items",
231
+ logical_type=logical_type,
232
+ physical_type=field_type,
233
+ max_length=max_length,
234
+ precision=precision,
235
+ scale=scale,
236
+ )
237
+ prop = create_property(
238
+ name=field_name,
239
+ logical_type="array",
240
+ description=description,
241
+ required=None,
242
+ items=items_prop,
243
+ )
244
+ else:
245
+ prop = create_property(
246
+ name=field_name,
247
+ logical_type=logical_type,
248
+ physical_type=field_type,
249
+ description=description,
250
+ required=required if required else None,
251
+ max_length=max_length,
252
+ precision=precision,
253
+ scale=scale,
254
+ )
208
255
  properties.append(prop)
209
256
 
210
257
  return properties