datacontract-cli 1.1.1__tar.gz → 1.1.3__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.
- datacontract_cli-1.1.3/LICENSE +21 -0
- {datacontract_cli-1.1.1/datacontract_cli.egg-info → datacontract_cli-1.1.3}/PKG-INFO +11 -7
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/README.md +3 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/api.py +313 -13
- datacontract_cli-1.1.3/datacontract/breaking/detector.py +74 -0
- datacontract_cli-1.1.3/datacontract/breaking/rules.py +196 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/catalog/catalog.py +1 -1
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/cli.py +24 -3
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/command_api.py +10 -3
- datacontract_cli-1.1.3/datacontract/command_breaking.py +39 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/command_ci.py +6 -1
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/command_dbt.py +15 -6
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/command_export.py +7 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/command_test.py +18 -2
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/config/settings.py +34 -2
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/data_contract.py +26 -4
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/engines/checks/check_spec.py +10 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/engines/checks/create_checks.py +126 -31
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/engines/checks/dimensions.py +3 -0
- datacontract_cli-1.1.3/datacontract/engines/checks/sql_guard.py +87 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/engines/data_contract_test.py +107 -20
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/engines/datacontract/check_azure_blob_file.py +10 -5
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/engines/datacontract/check_that_datacontract_contains_valid_servers_configuration.py +3 -3
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/engines/fastjsonschema/check_jsonschema.py +31 -5
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/engines/fastjsonschema/s3/s3_read_files.py +1 -1
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/engines/ibis/connections/connect.py +94 -26
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/engines/ibis/connections/duckdb_connection.py +87 -11
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/engines/ibis/connections/kafka.py +12 -11
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/engines/ibis/connections/redshift_credentials.py +4 -4
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/engines/ibis/ibis_check_execute.py +79 -16
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/export/avro_exporter.py +1 -1
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/export/dbml_exporter.py +2 -2
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/export/dcs_exporter.py +26 -1
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/export/duckdb_type_converter.py +1 -1
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/export/exporter.py +4 -2
- datacontract_cli-1.1.3/datacontract/export/great_expectations_exporter.py +732 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/export/html_exporter.py +5 -3
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/export/jsonschema_exporter.py +3 -3
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/export/mermaid_exporter.py +11 -2
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/export/protobuf_exporter.py +1 -1
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/export/pydantic_exporter.py +4 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/export/sodacl_check_builder.py +22 -6
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/export/sql_type_converter.py +2 -2
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/imports/athena_importer.py +3 -3
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/imports/avro_importer.py +4 -4
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/imports/bigquery_importer.py +5 -5
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/imports/dbml_importer.py +1 -1
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/imports/dcs_importer.py +2 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/imports/excel_importer.py +2 -2
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/imports/iceberg_importer.py +1 -1
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/imports/jsonschema_importer.py +3 -3
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/imports/mysql_importer.py +6 -6
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/imports/object_storage_importer.py +4 -4
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/imports/odcs_importer.py +4 -4
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/imports/oracle_importer.py +7 -7
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/imports/parquet_importer.py +1 -1
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/imports/postgres_importer.py +5 -5
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/imports/powerbi_importer.py +7 -7
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/imports/protobuf_importer.py +2 -2
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/imports/pydantic_importer.py +32 -6
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/imports/redshift_importer.py +6 -6
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/imports/snowflake_importer.py +5 -5
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/imports/sql_importer.py +81 -21
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/imports/sqlserver_importer.py +6 -6
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/imports/trino_importer.py +7 -7
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/imports/unity_importer.py +4 -4
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/integration/dbt_sync.py +38 -28
- datacontract_cli-1.1.3/datacontract/integration/entropy_data.py +248 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/lint/files.py +1 -1
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/lint/resolve.py +287 -39
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/lint/s3.py +1 -1
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/lint/schema.py +1 -1
- datacontract_cli-1.1.3/datacontract/lint/urls.py +48 -0
- datacontract_cli-1.1.3/datacontract/model/breaking.py +33 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/model/exceptions.py +16 -1
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/model/run.py +41 -5
- datacontract_cli-1.1.3/datacontract/model/server.py +112 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/output/test_results_writer.py +5 -0
- datacontract_cli-1.1.3/datacontract/output/text_breaking_results.py +81 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/templates/partials_odcs/diagram.html +1 -1
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3/datacontract_cli.egg-info}/PKG-INFO +11 -7
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract_cli.egg-info/SOURCES.txt +29 -2
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract_cli.egg-info/requires.txt +7 -6
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/pyproject.toml +27 -7
- datacontract_cli-1.1.3/tests/test_api.py +677 -0
- datacontract_cli-1.1.3/tests/test_breaking.py +337 -0
- datacontract_cli-1.1.3/tests/test_check_quality_provenance.py +77 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_cli.py +30 -0
- datacontract_cli-1.1.3/tests/test_command_api.py +32 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_config.py +4 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_config_file.py +14 -2
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_connect_bigquery.py +52 -11
- datacontract_cli-1.1.3/tests/test_create_checks_array_items.py +71 -0
- datacontract_cli-1.1.3/tests/test_create_checks_retention_period.py +57 -0
- datacontract_cli-1.1.3/tests/test_create_checks_servicelevel.py +112 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_data_contract_checks.py +64 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_dbt_sync.py +108 -0
- datacontract_cli-1.1.3/tests/test_description_linter.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_duckdb_json.py +48 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_export_avro.py +17 -0
- datacontract_cli-1.1.3/tests/test_export_dcs.py +131 -0
- datacontract_cli-1.1.3/tests/test_export_great_expectations.py +1508 -0
- datacontract_cli-1.1.3/tests/test_export_html.py +155 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_export_jsonschema.py +19 -1
- datacontract_cli-1.1.3/tests/test_export_large_contract.py +57 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_export_mermaid.py +36 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_export_sodacl.py +41 -3
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_import_sql_oracle.py +1 -1
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_import_sql_postgres.py +24 -1
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_import_sql_snowflake.py +42 -1
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_import_sql_sqlserver.py +1 -1
- datacontract_cli-1.1.3/tests/test_lint_urls.py +85 -0
- datacontract_cli-1.1.3/tests/test_publish_test_results.py +142 -0
- datacontract_cli-1.1.3/tests/test_publish_test_results_payload.py +141 -0
- datacontract_cli-1.1.3/tests/test_quality_sql_dialects.py +132 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_resolve.py +37 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_resolve_definitions.py +45 -5
- datacontract_cli-1.1.3/tests/test_resolve_local_definitions.py +615 -0
- datacontract_cli-1.1.3/tests/test_roundtrip_pydantic_model.py +108 -0
- datacontract_cli-1.1.3/tests/test_run_check_engine.py +40 -0
- datacontract_cli-1.1.3/tests/test_server_overrides.py +104 -0
- datacontract_cli-1.1.3/tests/test_server_type_synonyms.py +85 -0
- datacontract_cli-1.1.3/tests/test_test_array_items.py +109 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_test_azure_blob_file.py +3 -3
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_test_diagnostics.py +3 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_test_dimension_filter.py +12 -0
- datacontract_cli-1.1.3/tests/test_test_dry_run.py +175 -0
- datacontract_cli-1.1.3/tests/test_test_duckdb.py +194 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_test_kafka_decoding.py +20 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_test_postgres.py +26 -1
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_test_quality.py +1 -1
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_test_redshift.py +1 -1
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_test_soda_connection_error.py +1 -1
- datacontract_cli-1.1.3/tests/test_untrusted_contract.py +324 -0
- datacontract_cli-1.1.1/LICENSE +0 -48
- datacontract_cli-1.1.1/datacontract/export/great_expectations_exporter.py +0 -357
- datacontract_cli-1.1.1/datacontract/export/odcs_export_helper.py +0 -314
- datacontract_cli-1.1.1/datacontract/integration/entropy_data.py +0 -143
- datacontract_cli-1.1.1/datacontract/lint/urls.py +0 -76
- datacontract_cli-1.1.1/datacontract/model/server.py +0 -50
- datacontract_cli-1.1.1/tests/test_api.py +0 -337
- datacontract_cli-1.1.1/tests/test_export_great_expectations.py +0 -621
- datacontract_cli-1.1.1/tests/test_export_html.py +0 -40
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/MANIFEST.in +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/__init__.py +0 -0
- {datacontract_cli-1.1.1/datacontract/engines → datacontract_cli-1.1.3/datacontract/breaking}/__init__.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/changelog/__init__.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/changelog/changelog.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/changelog/normalize.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/command_catalog.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/command_changelog.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/command_edit.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/command_import.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/command_init.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/command_lint.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/command_publish.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/config/__init__.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/config/cli_context.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/VERSION +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/abap-Bs8kvjsq.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/apex-Dspwrl5m.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/assets/editor.worker-4.7.0.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/assets/yaml.worker-4.7.0.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/azcli-k1BiOau1.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/bat-BIP72KWc.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/bicep-VhQeYhS3.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/cameligo-DnuAXtHg.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/clojure-BAuDPsal.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/coffee-BV06qSnj.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/cpp-Cz8Jeyrf.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/csharp-F6hhlXFb.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/csp-KzlEHCyI.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/css-e4FKntZ3.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/cssMode-BNkTa2xp.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/cypher-CpvDGKRr.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/dart-DdHeFWR0.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/datacontract-editor.css +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/datacontract-editor.es.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/dockerfile-aoNXDcio.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/ecl-C4Q0DYuA.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/elixir-C9X42b0T.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/flow9-Bff42pfB.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/freemarker2-Dyfzmb5A.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/fsharp-C6vPAsHG.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/go-DEr8zx9f.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/graphql-BiNBBRFh.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/handlebars-BtDNI-uC.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/hcl-2T9lKuFg.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/html-Cn4TGw_o.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/htmlMode-Bf84vbo2.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/index.html +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/ini-CZGtssYM.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/java-B260NeH3.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/javascript-B86Kac07.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/jsonMode-Ckgg5lAE.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/julia-kuNLM9c5.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/kotlin-Bz6kAefA.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/less-AZUtS89I.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/lexon-BcmgnyRb.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/liquid-D0NPwuu_.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/logo_fuchsia_v2.svg +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/lua-Dz1RbATg.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/m3-CIMooLFn.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/markdown-C1BsCK9P.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/mdx-QFRSBtVQ.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/mips-BovsnfLQ.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/monaco-4.7.0.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/msdax-CiRB2ZB-.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/mysql-BwdpPE19.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/objective-c-D4nfHfso.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/odcs.svg +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/pascal--j5-PYtz.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/pascaligo-C7TcJxNL.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/perl-BzEWqUb5.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/pgsql-B_KZ0mGX.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/php-A19QHzQV.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/pla-COeKY1hA.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/postiats-BfMp7zJL.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/powerquery-B0TS_5Gb.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/powershell-5jSbKdYB.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/protobuf-YgnOutn3.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/pug-DLej6kn5.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/python-C8RW9B6A.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/qsharp-AhjqtSHB.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/r-D-F4P80w.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/razor-CLZqdY_r.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/redis-hwwIlMnV.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/redshift-Bcm_kjEG.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/restructuredtext-CrdzfPP-.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/ruby-QC2UNm1s.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/rust-5aqiQ3L5.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/sb-B1g1ZfLJ.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/scala-C2U4R5Gb.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/scheme-DeFRbslr.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/scss-RSTCiCmI.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/shell-BpF-iNu8.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/solidity-C00r2mbQ.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/sophia-DVW5ZKDB.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/sparql-B0nmpxeX.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/sql-CJwKPERn.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/st-DPlKG5DT.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/swift-YZBAbVHV.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/systemverilog-CDQSaH63.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/tcl-Dlu8sp6Q.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/tsMode-yleMwhPq.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/twig-C5TO7-kw.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/typescript-CJBeebKU.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/typespec-BsmjDEu7.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/vb-ChHvIDrk.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/wgsl-XEp1ju8c.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/xml-vC-NMVX1.js +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/editor_assets/yaml-Dlf2VF-T.js +0 -0
- {datacontract_cli-1.1.1/datacontract/engines/checks → datacontract_cli-1.1.3/datacontract/engines}/__init__.py +0 -0
- {datacontract_cli-1.1.1/datacontract/engines/ibis → datacontract_cli-1.1.3/datacontract/engines/checks}/__init__.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/engines/checks/check_filter.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/engines/checks/physical_type_match.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/engines/checks/type_normalize.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/engines/datacontract/check_that_datacontract_file_exists.py +0 -0
- {datacontract_cli-1.1.1/datacontract/engines/ibis/connections → datacontract_cli-1.1.3/datacontract/engines/ibis}/__init__.py +0 -0
- {datacontract_cli-1.1.1/datacontract/export → datacontract_cli-1.1.3/datacontract/engines/ibis/connections}/__init__.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/engines/ibis/connections/aws_credentials.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/engines/ibis/connections/databricks_patch.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/engines/ibis/connections/oracle_patch.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/engines/ibis/connections/redshift_patch.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/engines/ibis/dtype_category.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/engines/ibis/native_type.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/engines/ibis/snowflake_structured_types.py +0 -0
- {datacontract_cli-1.1.1/datacontract/output → datacontract_cli-1.1.3/datacontract/export}/__init__.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/export/avro_idl_exporter.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/export/bigquery_exporter.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/export/custom_exporter.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/export/data_caterer_exporter.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/export/dbt_exporter.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/export/dqx_exporter.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/export/excel_exporter.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/export/exporter_factory.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/export/go_exporter.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/export/iceberg_exporter.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/export/markdown_exporter.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/export/odcs_v3_exporter.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/export/pandas_type_converter.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/export/rdf_exporter.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/export/sodacl_exporter.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/export/spark_exporter.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/export/sql_exporter.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/export/sqlalchemy_exporter.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/imports/csv_importer.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/imports/dbt_importer.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/imports/glue_importer.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/imports/importer.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/imports/importer_factory.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/imports/json_importer.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/imports/odcs_helper.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/imports/spark_importer.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/imports/spark_type_json.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/init/init_template.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/integration/dbt_test_mapping.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/lint/resources.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/model/changelog.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/model/odcs.py +0 -0
- /datacontract_cli-1.1.1/datacontract/py.typed → /datacontract_cli-1.1.3/datacontract/output/__init__.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/output/ci_output.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/output/json_test_results.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/output/junit_test_results.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/output/output_format.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/output/text_changelog_results.py +0 -0
- /datacontract_cli-1.1.1/tests/test_description_linter.py → /datacontract_cli-1.1.3/datacontract/py.typed +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/schemas/datacontract-1.1.0.init.yaml +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/schemas/datacontract-1.1.0.schema.json +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/schemas/datacontract-1.2.0.init.yaml +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/schemas/datacontract-1.2.0.schema.json +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/schemas/datacontract-1.2.1.init.yaml +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/schemas/datacontract-1.2.1.schema.json +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/schemas/odcs-3.0.1.schema.json +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/schemas/odcs-3.0.2.schema.json +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/schemas/odcs-3.1.0.init.yaml +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/schemas/odcs-3.1.0.schema.json +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/templates/datacontract.html +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/templates/datacontract_odcs.html +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/templates/excel/odcs-template.xlsx +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/templates/index.html +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/templates/macros/authoritative_definitions.html +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/templates/macros/schema.html +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/templates/macros/server.html +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/templates/macros/support.html +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/templates/partials/datacontract_information.html +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/templates/partials/datacontract_servicelevels.html +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/templates/partials/datacontract_terms.html +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/templates/partials/definition.html +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/templates/partials/example.html +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/templates/partials/model_field.html +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/templates/partials/quality.html +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/templates/partials/server.html +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/templates/partials_odcs/custom_properties.html +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/templates/partials_odcs/dialog_yaml.html +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/templates/partials_odcs/footer.html +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/templates/partials_odcs/fundamentals.html +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/templates/partials_odcs/header.html +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/templates/partials_odcs/pricing.html +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/templates/partials_odcs/roles.html +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/templates/partials_odcs/schema.html +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/templates/partials_odcs/servers.html +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/templates/partials_odcs/sla.html +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/templates/partials_odcs/support.html +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/templates/partials_odcs/team.html +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/templates/partials_odcs/term_of_use.html +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/templates/partials_odcs/title.html +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract/templates/style/output.css +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract_cli.egg-info/dependency_links.txt +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract_cli.egg-info/entry_points.txt +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/datacontract_cli.egg-info/top_level.txt +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/setup.cfg +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_api_openapi.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_catalog.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_changelog.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_changelog_engine.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_changelog_normalize.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_changelog_output_text.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_ci_output.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_config_require.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_config_threading.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_connect_athena.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_connect_databricks.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_connect_impala.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_connect_mysql.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_connect_oracle.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_connect_postgres.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_connect_redshift.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_connect_s3_credentials.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_connect_snowflake.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_connect_sqlserver.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_connect_trino.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_create_checks_nested_type.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_create_checks_physical_type.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_create_checks_primary_key.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_create_checks_sql_placeholders.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_data_contract.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_data_contract_specification.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_dbt_test_mapping.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_docs_commands.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_docs_config_options.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_docs_ordering.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_download_datacontract_file.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_dtype_category.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_edit.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_export_avro_idl.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_export_bigquery.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_export_complex_data_contract.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_export_custom.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_export_custom_exporter.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_export_custom_model.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_export_data_caterer.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_export_dbml.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_export_dbt_models.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_export_dbt_sources.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_export_dbt_staging_sql.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_export_dqx.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_export_excel.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_export_go.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_export_iceberg.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_export_markdown.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_export_odcs_v3.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_export_protobuf.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_export_pydantic.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_export_rdf.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_export_spark.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_export_sql.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_export_sql_clickhouse.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_export_sql_query.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_export_sqlalchemy.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_ibis_mssql_pattern.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_ibis_mssql_schema.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_ibis_oracle_schema.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_import_athena.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_import_avro.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_import_bigquery.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_import_csv.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_import_dbml.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_import_dbt.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_import_excel.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_import_glue.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_import_iceberg.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_import_json.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_import_jsonschema.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_import_mysql.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_import_object_storage.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_import_odcs_v3.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_import_oracle.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_import_parquet.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_import_postgres.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_import_powerbi.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_import_protobuf.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_import_pydantic_model.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_import_redshift.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_import_snowflake.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_import_spark_type_json.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_import_sqlserver.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_import_trino.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_import_unity_file.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_install_extras.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_integration_entropydata.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_lint.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_native_type_schema_scope.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_oracle_patch.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_physical_type_match.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_redshift_credentials.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_redshift_patch.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_roundtrip_jsonschema.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_run_check_deprecated_fields.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_server_type_mssql.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_snowflake_structured_types.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_sql_type_converter_physicaltype.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_test_api.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_test_athena_iceberg.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_test_azure_remote.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_test_bigquery.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_test_checks_filter.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_test_databricks.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_test_delta.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_test_failed_samples.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_test_gcs_csv_remote.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_test_gcs_json_remote.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_test_kafka.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_test_kafka_remote.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_test_local_json.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_test_local_json_nd.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_test_metadata_only.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_test_mysql.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_test_oracle.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_test_oracle_xe.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_test_output_json.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_test_output_junit.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_test_parquet.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_test_quality_id_and_tag_filter.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_test_quality_local_fallback.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_test_quality_percent_severity.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_test_row_filter.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_test_s3_csv.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_test_s3_delta.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_test_s3_json.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_test_s3_json_complex.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_test_s3_json_multiple_models.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_test_s3_json_remote.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_test_schema_evolution.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_test_server_not_found.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_test_snowflake.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_test_sqlserver.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_test_trino.py +0 -0
- {datacontract_cli-1.1.1 → datacontract_cli-1.1.3}/tests/test_type_normalize.py +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Entropy Data GmbH
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: datacontract-cli
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.3
|
|
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@entropy-data.com>, Stefan Negele <stefan.negele@innoq.com>, Simon Harrer <simon.harrer@entropy-data.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -46,10 +46,10 @@ Requires-Dist: boto3<2.0.0,>=1.34.41
|
|
|
46
46
|
Requires-Dist: Jinja2<4.0.0,>=3.1.5
|
|
47
47
|
Requires-Dist: jinja_partials<1.0.0,>=0.2.1
|
|
48
48
|
Requires-Dist: datacontract-specification<2.0.0,>=1.2.3
|
|
49
|
-
Requires-Dist: open-data-contract-standard<
|
|
49
|
+
Requires-Dist: open-data-contract-standard<3.2.0,>=3.1.2
|
|
50
50
|
Requires-Dist: deepdiff<10.0.0,>=6.0.0
|
|
51
51
|
Provides-Extra: avro
|
|
52
|
-
Requires-Dist: avro==1.12.
|
|
52
|
+
Requires-Dist: avro==1.12.2; extra == "avro"
|
|
53
53
|
Provides-Extra: bigquery
|
|
54
54
|
Requires-Dist: ibis-framework[bigquery]<13.0.0,>=10.0.0; extra == "bigquery"
|
|
55
55
|
Provides-Extra: csv
|
|
@@ -67,7 +67,7 @@ Requires-Dist: rich>=12.4.4; extra == "dataframe"
|
|
|
67
67
|
Provides-Extra: databricks
|
|
68
68
|
Requires-Dist: ibis-framework[databricks]<13.0.0,>=10.0.0; extra == "databricks"
|
|
69
69
|
Requires-Dist: databricks-sql-connector<4.5.0,>=3.7.0; extra == "databricks"
|
|
70
|
-
Requires-Dist: databricks-sdk<0.
|
|
70
|
+
Requires-Dist: databricks-sdk<0.134.0; extra == "databricks"
|
|
71
71
|
Requires-Dist: packaging<27.0,>=21.3; extra == "databricks"
|
|
72
72
|
Provides-Extra: databricks-runtime
|
|
73
73
|
Requires-Dist: datacontract-cli[databricks]; extra == "databricks-runtime"
|
|
@@ -138,18 +138,19 @@ Requires-Dist: datacontract-cli[all]; extra == "dev"
|
|
|
138
138
|
Requires-Dist: httpx==0.28.1; extra == "dev"
|
|
139
139
|
Requires-Dist: kafka-python; extra == "dev"
|
|
140
140
|
Requires-Dist: minio==7.2.20; extra == "dev"
|
|
141
|
-
Requires-Dist: moto==5.2.
|
|
141
|
+
Requires-Dist: moto==5.2.3; extra == "dev"
|
|
142
142
|
Requires-Dist: openapi-spec-validator<0.10,>=0.7; extra == "dev"
|
|
143
143
|
Requires-Dist: pandas>=2.1.0; extra == "dev"
|
|
144
144
|
Requires-Dist: pre-commit<4.7.0,>=3.7.1; extra == "dev"
|
|
145
145
|
Requires-Dist: pytest; extra == "dev"
|
|
146
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
146
147
|
Requires-Dist: pytest-xdist; extra == "dev"
|
|
147
148
|
Requires-Dist: pymssql==2.3.13; extra == "dev"
|
|
148
149
|
Requires-Dist: psycopg2-binary<3.0,>=2.9; extra == "dev"
|
|
149
150
|
Requires-Dist: mysql-connector-python<26.8.0,>=8.0.30; extra == "dev"
|
|
150
|
-
Requires-Dist: ruff==0.16.
|
|
151
|
+
Requires-Dist: ruff==0.16.5; extra == "dev"
|
|
151
152
|
Requires-Dist: testcontainers[kafka,minio,mssql,mysql,postgres]==4.15.0; extra == "dev"
|
|
152
|
-
Requires-Dist: trino==0.
|
|
153
|
+
Requires-Dist: trino==0.339.0; extra == "dev"
|
|
153
154
|
Dynamic: license-file
|
|
154
155
|
|
|
155
156
|
# Data Contract CLI
|
|
@@ -295,6 +296,9 @@ $ datacontract lint odcs.yaml
|
|
|
295
296
|
# show a changelog between two data contracts
|
|
296
297
|
$ datacontract changelog v1.odcs.yaml v2.odcs.yaml
|
|
297
298
|
|
|
299
|
+
# fail when a contract change is backward-incompatible
|
|
300
|
+
$ datacontract breaking v1.odcs.yaml v2.odcs.yaml
|
|
301
|
+
|
|
298
302
|
# execute schema and quality checks (define credentials as environment variables)
|
|
299
303
|
$ datacontract test odcs.yaml
|
|
300
304
|
|
|
@@ -141,6 +141,9 @@ $ datacontract lint odcs.yaml
|
|
|
141
141
|
# show a changelog between two data contracts
|
|
142
142
|
$ datacontract changelog v1.odcs.yaml v2.odcs.yaml
|
|
143
143
|
|
|
144
|
+
# fail when a contract change is backward-incompatible
|
|
145
|
+
$ datacontract breaking v1.odcs.yaml v2.odcs.yaml
|
|
146
|
+
|
|
144
147
|
# execute schema and quality checks (define credentials as environment variables)
|
|
145
148
|
$ datacontract test odcs.yaml
|
|
146
149
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import logging
|
|
3
3
|
import os
|
|
4
|
+
import secrets
|
|
4
5
|
import tempfile
|
|
5
6
|
from importlib import metadata
|
|
6
7
|
from typing import Annotated, Optional
|
|
@@ -8,15 +9,15 @@ from typing import Annotated, Optional
|
|
|
8
9
|
import pydantic
|
|
9
10
|
import yaml
|
|
10
11
|
from fastapi import Body, Depends, FastAPI, HTTPException, Query, Request, status
|
|
11
|
-
from fastapi.middleware.cors import CORSMiddleware
|
|
12
12
|
from fastapi.responses import PlainTextResponse
|
|
13
13
|
from fastapi.security.api_key import APIKeyHeader
|
|
14
14
|
from pydantic import BaseModel, Field, ValidationError
|
|
15
15
|
|
|
16
16
|
from datacontract.config import Config, known_env_names
|
|
17
17
|
from datacontract.data_contract import DataContract, ExportFormat
|
|
18
|
+
from datacontract.model.breaking import BreakingChangeEntry
|
|
18
19
|
from datacontract.model.changelog import ChangelogEntry
|
|
19
|
-
from datacontract.model.exceptions import DataContractException
|
|
20
|
+
from datacontract.model.exceptions import DataContractException, DefinitionResolutionError
|
|
20
21
|
from datacontract.model.run import Check, ResultEnum, Run
|
|
21
22
|
|
|
22
23
|
DATA_CONTRACT_EXAMPLE_PAYLOAD = """apiVersion: v3.1.0
|
|
@@ -251,6 +252,16 @@ that value, and answers `401` when the header is missing and `403` when it is wr
|
|
|
251
252
|
Securing the API is strongly recommended: `POST /test` connects to your data sources, and test
|
|
252
253
|
results may contain sensitive information.
|
|
253
254
|
|
|
255
|
+
## Contracts are untrusted
|
|
256
|
+
|
|
257
|
+
A posted contract carries SQL and names the hosts to connect to, so the server treats it as
|
|
258
|
+
untrusted input: a `quality.type: sql` rule must be a read-only query, a credential held in the
|
|
259
|
+
server's environment is never sent to a host the contract names, a `publish_url` may only point at
|
|
260
|
+
the Entropy Data platform or the host configured via `ENTROPY_DATA_HOST`, and `servers[].type: local`
|
|
261
|
+
is refused so a caller cannot read the server's own files. Set
|
|
262
|
+
`DATACONTRACT_CLI_API_ALLOW_LOCAL_FILES=true` if the deployment serves its own files on purpose;
|
|
263
|
+
the contract is then still confined to the paths it declares.
|
|
264
|
+
|
|
254
265
|
## Connecting to data sources
|
|
255
266
|
|
|
256
267
|
`POST /test` needs credentials for the server described in the contract's `servers` section. Provide
|
|
@@ -313,6 +324,14 @@ app = FastAPI(
|
|
|
313
324
|
"url": "https://docs.datacontract.com/commands/changelog",
|
|
314
325
|
},
|
|
315
326
|
},
|
|
327
|
+
{
|
|
328
|
+
"name": "breaking",
|
|
329
|
+
"description": "Compare two versions of a data contract and grade each change by compatibility impact.",
|
|
330
|
+
"externalDocs": {
|
|
331
|
+
"description": "Documentation",
|
|
332
|
+
"url": "https://docs.datacontract.com/commands/breaking",
|
|
333
|
+
},
|
|
334
|
+
},
|
|
316
335
|
],
|
|
317
336
|
)
|
|
318
337
|
|
|
@@ -334,12 +353,17 @@ def _openapi_with_external_docs() -> dict:
|
|
|
334
353
|
|
|
335
354
|
app.openapi = _openapi_with_external_docs
|
|
336
355
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
356
|
+
# Contracts posted to this server arrive over HTTP from callers we do not trust,
|
|
357
|
+
# so a credential the server holds in its environment must never be sent to a
|
|
358
|
+
# host the contract names. The `edit` server reuses the `test` handler for a
|
|
359
|
+
# local file the user owns, so it does not set this flag and is not guarded.
|
|
360
|
+
app.state.untrusted_contracts = True
|
|
361
|
+
|
|
362
|
+
# No CORS middleware: the only browser client is the Swagger UI served at "/",
|
|
363
|
+
# which is same-origin and therefore not subject to CORS. Every other client
|
|
364
|
+
# (curl, the SDK, CI) is not a browser and ignores CORS. Allowing arbitrary
|
|
365
|
+
# origins would only let a page the operator happens to visit drive this server
|
|
366
|
+
# and read its responses, so the safe default is to send no CORS headers at all.
|
|
343
367
|
|
|
344
368
|
api_key_header = APIKeyHeader(
|
|
345
369
|
name="x-api-key",
|
|
@@ -446,6 +470,198 @@ def _parse_filters_query(filters: str | None) -> dict[str, str] | None:
|
|
|
446
470
|
return {schema_name: predicate.strip() for schema_name, predicate in parsed.items()}
|
|
447
471
|
|
|
448
472
|
|
|
473
|
+
# For each server type whose connection host can be taken from the posted
|
|
474
|
+
# contract: (the Server attribute that names the connection target, the Config
|
|
475
|
+
# field an operator sets to pin that target in the environment or None when the
|
|
476
|
+
# type has no such field, the Config fields that hold a connection secret).
|
|
477
|
+
# See _reject_environment_credentials_to_untrusted_host.
|
|
478
|
+
_ENVIRONMENT_CREDENTIAL_TARGETS: dict[str, tuple[str, str | None, tuple[str, ...]]] = {
|
|
479
|
+
"postgres": ("host", "postgres_host", ("postgres_password",)),
|
|
480
|
+
"mysql": ("host", "mysql_host", ("mysql_password",)),
|
|
481
|
+
"oracle": ("host", "oracle_host", ("oracle_password",)),
|
|
482
|
+
"impala": ("host", "impala_host", ("impala_password",)),
|
|
483
|
+
"trino": ("host", "trino_host", ("trino_password",)),
|
|
484
|
+
"redshift": ("host", "redshift_host", ("redshift_password",)),
|
|
485
|
+
"sqlserver": ("host", "sqlserver_host", ("sqlserver_password",)),
|
|
486
|
+
"snowflake": (
|
|
487
|
+
"account",
|
|
488
|
+
"snowflake_account",
|
|
489
|
+
("snowflake_password", "snowflake_token", "snowflake_private_key"),
|
|
490
|
+
),
|
|
491
|
+
"databricks": ("host", "databricks_server_hostname", ("databricks_token", "databricks_client_secret")),
|
|
492
|
+
# Kafka's broker host is always the contract's `server.host` (no environment
|
|
493
|
+
# option pins it), so an environment-held SASL password can never be paired
|
|
494
|
+
# with a posted contract safely -- host_field is None, so it is always rejected.
|
|
495
|
+
"kafka": ("broker host", None, ("kafka_sasl_password",)),
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
def _selected_server(body: str, server_name: str | None, config):
|
|
500
|
+
"""The server the request would test, or None if that cannot be determined.
|
|
501
|
+
|
|
502
|
+
A contract that does not resolve, or that names no such server, is reported
|
|
503
|
+
by test() itself -- the guards below simply have nothing to check.
|
|
504
|
+
"""
|
|
505
|
+
from datacontract.engines.data_contract_test import get_server
|
|
506
|
+
from datacontract.lint import resolve
|
|
507
|
+
|
|
508
|
+
try:
|
|
509
|
+
data_contract = resolve.resolve_data_contract(data_contract_str=body, config=config)
|
|
510
|
+
return get_server(data_contract, server_name)
|
|
511
|
+
except Exception:
|
|
512
|
+
return None
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
# Server types that read their data through the local filesystem of the machine
|
|
516
|
+
# running the test, rather than from a remote data source.
|
|
517
|
+
_LOCAL_SERVER_TYPES = frozenset({"local", "duckdb"})
|
|
518
|
+
|
|
519
|
+
ALLOW_LOCAL_FILES_ENV = "DATACONTRACT_CLI_API_ALLOW_LOCAL_FILES"
|
|
520
|
+
_TRUTHY = frozenset({"1", "true", "yes", "on"})
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
def _local_files_allowed() -> bool:
|
|
524
|
+
"""Whether the operator opted in to serving data from the server's own disk.
|
|
525
|
+
|
|
526
|
+
Off by default. A deployment that mounts the data next to the API -- the data
|
|
527
|
+
directory in the container, say -- turns it on, accepting that a caller then
|
|
528
|
+
picks the path.
|
|
529
|
+
"""
|
|
530
|
+
return (os.getenv(ALLOW_LOCAL_FILES_ENV) or "").strip().lower() in _TRUTHY
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
def _reject_local_server_type(body: str, server_name: str | None, config) -> None:
|
|
534
|
+
"""Keep a posted contract from reading the server's own disk.
|
|
535
|
+
|
|
536
|
+
`type: local` points the test at a path on the machine running it. For the
|
|
537
|
+
API server that machine is not the caller's, so the path is not theirs to
|
|
538
|
+
name -- and the result would tell them what is in it.
|
|
539
|
+
"""
|
|
540
|
+
from datacontract.config import Config
|
|
541
|
+
|
|
542
|
+
if _local_files_allowed():
|
|
543
|
+
return
|
|
544
|
+
server = _selected_server(body, server_name, Config.resolve(config))
|
|
545
|
+
if server is None or server.type not in _LOCAL_SERVER_TYPES:
|
|
546
|
+
return
|
|
547
|
+
raise HTTPException(
|
|
548
|
+
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
|
549
|
+
detail=(
|
|
550
|
+
f"Server type '{server.type}' reads from the file system of the server running this API, "
|
|
551
|
+
f"so it is refused. Use a server type that names a data source, such as s3, gcs, azure, "
|
|
552
|
+
f"postgres, or snowflake — or set {ALLOW_LOCAL_FILES_ENV}=true if this deployment serves "
|
|
553
|
+
f"its own files on purpose."
|
|
554
|
+
),
|
|
555
|
+
)
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
def _reject_environment_credentials_to_untrusted_host(body: str, server_name: str | None, config) -> None:
|
|
559
|
+
"""Keep an environment-held data-source credential from reaching a host the
|
|
560
|
+
posted contract chose.
|
|
561
|
+
|
|
562
|
+
A contract that arrives over HTTP is untrusted. If it could name the
|
|
563
|
+
connection host (its ``servers`` section) while the server supplies the
|
|
564
|
+
password from its own environment, an attacker could point the host at their
|
|
565
|
+
own machine and capture that password. So when a credential for the selected
|
|
566
|
+
server's type resolves from the environment, the connection target must
|
|
567
|
+
resolve from the environment too -- not from the contract, and not from a
|
|
568
|
+
per-request header (a caller who brings the whole credential set may still
|
|
569
|
+
aim it wherever they like; only the server's environment secret is pinned).
|
|
570
|
+
"""
|
|
571
|
+
from datacontract.config import Config
|
|
572
|
+
from datacontract.config.settings import env_name
|
|
573
|
+
|
|
574
|
+
resolved = Config.resolve(config)
|
|
575
|
+
server = _selected_server(body, server_name, resolved)
|
|
576
|
+
if server is None:
|
|
577
|
+
return
|
|
578
|
+
guard = _ENVIRONMENT_CREDENTIAL_TARGETS.get(server.type)
|
|
579
|
+
if guard is None:
|
|
580
|
+
return
|
|
581
|
+
target_attr, host_field, secret_fields = guard
|
|
582
|
+
if not any(resolved.option_source(field) == "env" for field in secret_fields):
|
|
583
|
+
return # no environment-held secret for this server type; nothing to protect
|
|
584
|
+
if host_field is not None and resolved.option_source(host_field) == "env":
|
|
585
|
+
return # the operator pinned the target host; the contract's host is ignored
|
|
586
|
+
if host_field is not None:
|
|
587
|
+
env_var = env_name(host_field, Config.model_fields[host_field])
|
|
588
|
+
detail = (
|
|
589
|
+
f"This server holds a {server.type} credential in its environment, so the connection "
|
|
590
|
+
f"{target_attr} must also come from the server's environment ({env_var}); it will not be "
|
|
591
|
+
f"taken from the posted data contract. Set {env_var}, or send the full set of credentials "
|
|
592
|
+
f"with the request instead of relying on the server's environment."
|
|
593
|
+
)
|
|
594
|
+
else:
|
|
595
|
+
detail = (
|
|
596
|
+
f"This server holds a {server.type} credential in its environment, but its {target_attr} "
|
|
597
|
+
f"can only come from the posted data contract, so the credential cannot be pinned to a "
|
|
598
|
+
f"trusted target. Send the credentials with the request instead of relying on the server's "
|
|
599
|
+
f"environment."
|
|
600
|
+
)
|
|
601
|
+
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=detail)
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
# The fields backing the platform API key and host, in the fallback order
|
|
605
|
+
# _get_api_key / _get_host read them (current name first, deprecated synonyms after).
|
|
606
|
+
_PLATFORM_KEY_FIELDS = ("entropy_data_api_key", "datamesh_manager_api_key", "datacontract_manager_api_key")
|
|
607
|
+
_PLATFORM_HOST_FIELDS = ("entropy_data_host", "datamesh_manager_host", "datacontract_manager_host")
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
def _reject_unpinned_publish_url(publish_url: str | None) -> None:
|
|
611
|
+
"""Keep the API server from POSTing test results to a caller-chosen host.
|
|
612
|
+
|
|
613
|
+
`publish_url` names where the finished run — check details, failed-row
|
|
614
|
+
samples — is POSTed, so an arbitrary URL would let a caller aim a blind
|
|
615
|
+
POST into the operator's network and exfiltrate the results. The target
|
|
616
|
+
must be a platform URL as the server's own environment sees it: a
|
|
617
|
+
per-request `entropy-data-host` header does not widen the set, or the
|
|
618
|
+
guard would be the caller's to configure.
|
|
619
|
+
"""
|
|
620
|
+
from datacontract.integration.entropy_data import is_platform_url
|
|
621
|
+
|
|
622
|
+
if publish_url is None or is_platform_url(publish_url, None):
|
|
623
|
+
return
|
|
624
|
+
raise HTTPException(
|
|
625
|
+
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
|
626
|
+
detail=(
|
|
627
|
+
"The publish_url must point at the Entropy Data platform. To publish to a self-hosted "
|
|
628
|
+
"deployment, set the environment variable ENTROPY_DATA_HOST on the server running this "
|
|
629
|
+
"API to that host."
|
|
630
|
+
),
|
|
631
|
+
)
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
def _reject_request_platform_host_with_environment_key(config) -> None:
|
|
635
|
+
"""Keep the environment-held platform API key from following a request-chosen host.
|
|
636
|
+
|
|
637
|
+
The platform host decides where the Entropy Data API key travels, and it can
|
|
638
|
+
be set per request (`entropy-data-host` header). A request that supplies the
|
|
639
|
+
host while the key resolves from the server's environment could therefore
|
|
640
|
+
aim that key at its own machine — same rule as the data sources: an
|
|
641
|
+
environment credential goes only to an environment-pinned destination. A
|
|
642
|
+
caller who brings the key itself may aim it wherever they like.
|
|
643
|
+
"""
|
|
644
|
+
from datacontract.config import Config
|
|
645
|
+
|
|
646
|
+
resolved = Config.resolve(config)
|
|
647
|
+
key_source = next(
|
|
648
|
+
(source for field in _PLATFORM_KEY_FIELDS if (source := resolved.option_source(field)) is not None), None
|
|
649
|
+
)
|
|
650
|
+
if key_source != "env":
|
|
651
|
+
return
|
|
652
|
+
if not any(resolved.option_source(field) == "request" for field in _PLATFORM_HOST_FIELDS):
|
|
653
|
+
return
|
|
654
|
+
raise HTTPException(
|
|
655
|
+
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
|
656
|
+
detail=(
|
|
657
|
+
"This server holds an Entropy Data API key in its environment, so the platform host must "
|
|
658
|
+
"also come from the server's environment (ENTROPY_DATA_HOST); it will not be taken from a "
|
|
659
|
+
"request header. Remove the entropy-data-host header, or send the API key with the request "
|
|
660
|
+
"instead of relying on the server's environment."
|
|
661
|
+
),
|
|
662
|
+
)
|
|
663
|
+
|
|
664
|
+
|
|
449
665
|
def check_api_key(api_key_header: str | None):
|
|
450
666
|
correct_api_key = os.getenv("DATACONTRACT_CLI_API_KEY")
|
|
451
667
|
if correct_api_key is None or correct_api_key == "":
|
|
@@ -457,7 +673,7 @@ def check_api_key(api_key_header: str | None):
|
|
|
457
673
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
458
674
|
detail="Missing API key. Use Header 'x-api-key' to provide the API key.",
|
|
459
675
|
)
|
|
460
|
-
if api_key_header
|
|
676
|
+
if not secrets.compare_digest(api_key_header, correct_api_key):
|
|
461
677
|
logging.info("The provided API key is not correct.")
|
|
462
678
|
raise HTTPException(
|
|
463
679
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
@@ -500,8 +716,9 @@ handled. A contract that cannot be parsed is reported the same way, as a failed
|
|
|
500
716
|
},
|
|
501
717
|
},
|
|
502
718
|
422: {
|
|
503
|
-
"description": "`filter` and `filters` were both given,
|
|
504
|
-
"mapping schema name to a SQL predicate
|
|
719
|
+
"description": "`filter` and `filters` were both given, `filters` is not a JSON object "
|
|
720
|
+
"mapping schema name to a SQL predicate, or `publish_url` does not point at the Entropy "
|
|
721
|
+
"Data platform or the host configured via `ENTROPY_DATA_HOST`.",
|
|
505
722
|
"model": UnprocessableEntityResponse,
|
|
506
723
|
},
|
|
507
724
|
},
|
|
@@ -529,7 +746,9 @@ async def test(
|
|
|
529
746
|
publish_url: Annotated[
|
|
530
747
|
str | None,
|
|
531
748
|
Query(
|
|
532
|
-
description="URL to publish test results. Optional, if you want to publish the test results to a Data Mesh Manager or Data Contract Manager. Example: https://api.datamesh-manager.com/api/test-results"
|
|
749
|
+
description="URL to publish test results. Optional, if you want to publish the test results to a Data Mesh Manager or Data Contract Manager. Example: https://api.datamesh-manager.com/api/test-results. "
|
|
750
|
+
"Must point at the Entropy Data platform, or at the host configured on this server "
|
|
751
|
+
"via the environment variable ENTROPY_DATA_HOST — other hosts are refused with 422.",
|
|
533
752
|
examples=["https://api.datamesh-manager.com/api/test-results"],
|
|
534
753
|
),
|
|
535
754
|
] = None,
|
|
@@ -560,14 +779,22 @@ async def test(
|
|
|
560
779
|
)
|
|
561
780
|
logging.info("Testing data contract...")
|
|
562
781
|
logging.info(body)
|
|
782
|
+
config = config_from_headers(request.headers)
|
|
783
|
+
untrusted_contract = getattr(request.app.state, "untrusted_contracts", False)
|
|
784
|
+
if untrusted_contract:
|
|
785
|
+
_reject_unpinned_publish_url(publish_url)
|
|
786
|
+
_reject_request_platform_host_with_environment_key(config)
|
|
787
|
+
_reject_environment_credentials_to_untrusted_host(body, server, config)
|
|
788
|
+
_reject_local_server_type(body, server, config)
|
|
563
789
|
return DataContract(
|
|
564
790
|
data_contract_str=body,
|
|
565
791
|
server=server,
|
|
566
792
|
publish_url=publish_url,
|
|
567
793
|
fastapi_url=str(request.url),
|
|
568
|
-
config=
|
|
794
|
+
config=config,
|
|
569
795
|
filter=filter,
|
|
570
796
|
filters=parsed_filters,
|
|
797
|
+
untrusted_contract=untrusted_contract,
|
|
571
798
|
).test()
|
|
572
799
|
|
|
573
800
|
|
|
@@ -663,6 +890,21 @@ class ChangelogResponse(BaseModel):
|
|
|
663
890
|
)
|
|
664
891
|
|
|
665
892
|
|
|
893
|
+
class BreakingChangesResponse(BaseModel):
|
|
894
|
+
"""Every change between two versions of a data contract, each graded `info`, `warning` or `error`."""
|
|
895
|
+
|
|
896
|
+
summary: list[BreakingChangeEntry] = Field(
|
|
897
|
+
description="One entry per changed element, graded by the most severe change. A property whose type and requiredness "
|
|
898
|
+
"both changed appears once.",
|
|
899
|
+
)
|
|
900
|
+
entries: list[BreakingChangeEntry] = Field(
|
|
901
|
+
description="Every single change with its grade and the old and new value.",
|
|
902
|
+
)
|
|
903
|
+
is_breaking: bool = Field(
|
|
904
|
+
description="Whether any detected change is classified as an error-level breaking change.",
|
|
905
|
+
)
|
|
906
|
+
|
|
907
|
+
|
|
666
908
|
@app.post(
|
|
667
909
|
"/changelog",
|
|
668
910
|
tags=["changelog"],
|
|
@@ -705,6 +947,59 @@ async def changelog_endpoint(
|
|
|
705
947
|
raise HTTPException(status_code=422, detail=f"Invalid YAML: {e}")
|
|
706
948
|
except pydantic.ValidationError as e:
|
|
707
949
|
raise HTTPException(status_code=422, detail=f"Invalid data contract: {e}")
|
|
950
|
+
except DefinitionResolutionError as e:
|
|
951
|
+
# The reason names the host that was contacted and what it answered.
|
|
952
|
+
# Omit for security reasons.
|
|
953
|
+
logging.warning("Definition resolution failed: %s", e)
|
|
954
|
+
raise HTTPException(status_code=422, detail=f"Could not resolve authoritative definition '{e.url}'.")
|
|
955
|
+
except DataContractException as e:
|
|
956
|
+
raise HTTPException(status_code=422, detail=f"Data Contract Validation Failure: {e}")
|
|
957
|
+
finally:
|
|
958
|
+
os.unlink(v1_path)
|
|
959
|
+
os.unlink(v2_path)
|
|
960
|
+
|
|
961
|
+
|
|
962
|
+
@app.post(
|
|
963
|
+
"/breaking",
|
|
964
|
+
tags=["breaking"],
|
|
965
|
+
operation_id="breakingChangesDataContracts",
|
|
966
|
+
summary="Show compatibility impact between two data contracts.",
|
|
967
|
+
description="""
|
|
968
|
+
Compare two versions of an ODCS data contract and detect backward-incompatible changes.
|
|
969
|
+
|
|
970
|
+
`POST` a JSON body with `v1` (before) and `v2` (after) as YAML strings. A contract that cannot be
|
|
971
|
+
parsed is answered with `422`.
|
|
972
|
+
""",
|
|
973
|
+
response_description="The breaking changes detected between the two data contracts.",
|
|
974
|
+
responses={
|
|
975
|
+
**AUTHENTICATION_RESPONSES,
|
|
976
|
+
422: {
|
|
977
|
+
"description": "One of the two data contracts is not valid YAML or not a valid data contract.",
|
|
978
|
+
"model": UnprocessableEntityResponse,
|
|
979
|
+
"content": {"application/json": {"example": {"detail": "Invalid YAML: while parsing a block mapping"}}},
|
|
980
|
+
},
|
|
981
|
+
},
|
|
982
|
+
)
|
|
983
|
+
def breaking_endpoint(
|
|
984
|
+
body: ChangelogRequest,
|
|
985
|
+
api_key: Annotated[str | None, Depends(api_key_header)] = None,
|
|
986
|
+
) -> BreakingChangesResponse:
|
|
987
|
+
check_api_key(api_key)
|
|
988
|
+
|
|
989
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f1:
|
|
990
|
+
f1.write(body.v1)
|
|
991
|
+
v1_path = f1.name
|
|
992
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f2:
|
|
993
|
+
f2.write(body.v2)
|
|
994
|
+
v2_path = f2.name
|
|
995
|
+
|
|
996
|
+
try:
|
|
997
|
+
result = DataContract(data_contract_file=v1_path).breaking(DataContract(data_contract_file=v2_path))
|
|
998
|
+
return BreakingChangesResponse(summary=result.summary, entries=result.entries, is_breaking=result.is_breaking)
|
|
999
|
+
except yaml.YAMLError as e:
|
|
1000
|
+
raise HTTPException(status_code=422, detail=f"Invalid YAML: {e}")
|
|
1001
|
+
except pydantic.ValidationError as e:
|
|
1002
|
+
raise HTTPException(status_code=422, detail=f"Invalid data contract: {e}")
|
|
708
1003
|
except DataContractException as e:
|
|
709
1004
|
raise HTTPException(status_code=422, detail=f"Data Contract Validation Failure: {e}")
|
|
710
1005
|
finally:
|
|
@@ -823,5 +1118,10 @@ def export(
|
|
|
823
1118
|
raise HTTPException(status_code=422, detail=f"Invalid YAML: {e}")
|
|
824
1119
|
except pydantic.ValidationError as e:
|
|
825
1120
|
raise HTTPException(status_code=422, detail=f"Invalid data contract: {e}")
|
|
1121
|
+
except DefinitionResolutionError as e:
|
|
1122
|
+
# The reason names the host that was contacted and what it answered.
|
|
1123
|
+
# Omit for security reasons.
|
|
1124
|
+
logging.warning("Definition resolution failed: %s", e)
|
|
1125
|
+
raise HTTPException(status_code=422, detail=f"Could not resolve authoritative definition '{e.url}'.")
|
|
826
1126
|
except DataContractException as e:
|
|
827
1127
|
raise HTTPException(status_code=422, detail=f"Data Contract Validation Failure: {e}")
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from collections import defaultdict
|
|
2
|
+
from collections.abc import Iterable
|
|
3
|
+
|
|
4
|
+
from datacontract.breaking.rules import DEFAULT_RULES, BreakingChangeRule
|
|
5
|
+
from datacontract.model.breaking import BreakingChangeEntry, BreakingChangeLevel, BreakingChangeResult
|
|
6
|
+
from datacontract.model.changelog import ChangelogEntry, ChangelogResult, ChangelogType
|
|
7
|
+
|
|
8
|
+
_LEVEL_ORDER = {
|
|
9
|
+
BreakingChangeLevel.INFO: 0,
|
|
10
|
+
BreakingChangeLevel.WARNING: 1,
|
|
11
|
+
BreakingChangeLevel.ERROR: 2,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BreakingChangeDetector:
|
|
16
|
+
"""Classify detailed changelog entries using an ordered rule set."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, rules: Iterable[BreakingChangeRule] = DEFAULT_RULES):
|
|
19
|
+
self._rules = tuple(rules)
|
|
20
|
+
if not self._rules:
|
|
21
|
+
raise ValueError("At least one breaking-change rule is required")
|
|
22
|
+
|
|
23
|
+
def detect(self, changelog: ChangelogResult) -> BreakingChangeResult:
|
|
24
|
+
entries = [self._classify(entry) for entry in changelog.entries]
|
|
25
|
+
# Nothing inside a newly added element can break a consumer: it did not exist before.
|
|
26
|
+
added = {entry.path for entry in entries if entry.change_type == ChangelogType.added}
|
|
27
|
+
for entry in entries:
|
|
28
|
+
segments = entry.path.split(".")
|
|
29
|
+
if any(".".join(segments[:i]) in added for i in range(1, len(segments))):
|
|
30
|
+
entry.level = BreakingChangeLevel.INFO
|
|
31
|
+
entries_by_prefix = defaultdict(list)
|
|
32
|
+
for entry in entries:
|
|
33
|
+
prefix = ""
|
|
34
|
+
for segment in entry.path.split("."):
|
|
35
|
+
prefix = f"{prefix}.{segment}" if prefix else segment
|
|
36
|
+
entries_by_prefix[prefix].append(entry)
|
|
37
|
+
summary = [self._summarize(entry, entries_by_prefix.get(entry.path, [])) for entry in changelog.summary]
|
|
38
|
+
return BreakingChangeResult(v1=changelog.v1, v2=changelog.v2, summary=summary, entries=entries)
|
|
39
|
+
|
|
40
|
+
def _classify(self, entry: ChangelogEntry) -> BreakingChangeEntry:
|
|
41
|
+
for rule in self._rules:
|
|
42
|
+
evaluation = rule.evaluate(entry)
|
|
43
|
+
if evaluation is not None:
|
|
44
|
+
return BreakingChangeEntry(
|
|
45
|
+
path=entry.path,
|
|
46
|
+
change_type=entry.type,
|
|
47
|
+
level=evaluation.level,
|
|
48
|
+
message=evaluation.message,
|
|
49
|
+
rule_id=evaluation.rule_id,
|
|
50
|
+
old_value=entry.old_value,
|
|
51
|
+
new_value=entry.new_value,
|
|
52
|
+
)
|
|
53
|
+
raise ValueError(f"No breaking-change rule classified {entry.path}")
|
|
54
|
+
|
|
55
|
+
@staticmethod
|
|
56
|
+
def _summarize(summary_entry: ChangelogEntry, entries: list[BreakingChangeEntry]) -> BreakingChangeEntry:
|
|
57
|
+
if not entries:
|
|
58
|
+
return BreakingChangeEntry(
|
|
59
|
+
path=summary_entry.path,
|
|
60
|
+
change_type=summary_entry.type,
|
|
61
|
+
level=BreakingChangeLevel.INFO,
|
|
62
|
+
message=f"Changed contract at {summary_entry.path}",
|
|
63
|
+
rule_id="summary-fallback",
|
|
64
|
+
)
|
|
65
|
+
highest = max(entries, key=lambda entry: _LEVEL_ORDER[entry.level])
|
|
66
|
+
return BreakingChangeEntry(
|
|
67
|
+
path=summary_entry.path,
|
|
68
|
+
change_type=summary_entry.type,
|
|
69
|
+
level=highest.level,
|
|
70
|
+
message=f"{highest.level.value.capitalize()} impact at {summary_entry.path}",
|
|
71
|
+
rule_id=f"summary:{highest.rule_id}",
|
|
72
|
+
old_value=highest.old_value,
|
|
73
|
+
new_value=highest.new_value,
|
|
74
|
+
)
|