datacontract-cli 0.10.28__tar.gz → 0.10.29__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.
Potentially problematic release.
This version of datacontract-cli might be problematic. Click here for more details.
- {datacontract_cli-0.10.28/datacontract_cli.egg-info → datacontract_cli-0.10.29}/PKG-INFO +22 -32
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/README.md +10 -20
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/api.py +1 -1
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/cli.py +27 -2
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/engines/soda/connections/duckdb_connection.py +1 -1
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/export/rdf_converter.py +2 -2
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/export/sql_type_converter.py +2 -2
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/imports/excel_importer.py +2 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/imports/importer.py +1 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/imports/importer_factory.py +7 -0
- datacontract_cli-0.10.29/datacontract/imports/json_importer.py +325 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/init/init_template.py +1 -1
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/lint/resolve.py +1 -1
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/lint/schema.py +1 -1
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/schemas/datacontract-1.1.0.init.yaml +1 -1
- datacontract_cli-0.10.29/datacontract/schemas/datacontract-1.2.0.init.yaml +91 -0
- datacontract_cli-0.10.29/datacontract/schemas/datacontract-1.2.0.schema.json +2029 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29/datacontract_cli.egg-info}/PKG-INFO +22 -32
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract_cli.egg-info/SOURCES.txt +4 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract_cli.egg-info/requires.txt +11 -11
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/pyproject.toml +12 -12
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_api.py +1 -1
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_duckdb_json.py +1 -1
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_export_complex_data_contract.py +1 -1
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_export_rdf.py +6 -6
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_export_sodacl.py +1 -1
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_export_sql.py +2 -2
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_import_avro.py +5 -5
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_import_csv.py +1 -1
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_import_dbt.py +4 -4
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_import_iceberg.py +1 -1
- datacontract_cli-0.10.29/tests/test_import_json.py +151 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_import_parquet.py +1 -1
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_import_protobuf.py +1 -1
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_import_sql_postgres.py +2 -2
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_import_sql_sqlserver.py +1 -1
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_resolve.py +7 -7
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/LICENSE +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/MANIFEST.in +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/__init__.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/breaking/breaking.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/breaking/breaking_change.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/breaking/breaking_rules.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/catalog/catalog.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/data_contract.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/engines/__init__.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/engines/data_contract_checks.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/engines/data_contract_test.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/engines/datacontract/check_that_datacontract_contains_valid_servers_configuration.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/engines/datacontract/check_that_datacontract_file_exists.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/engines/fastjsonschema/check_jsonschema.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/engines/fastjsonschema/s3/s3_read_files.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/engines/soda/__init__.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/engines/soda/check_soda_execute.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/engines/soda/connections/bigquery.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/engines/soda/connections/databricks.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/engines/soda/connections/kafka.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/engines/soda/connections/postgres.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/engines/soda/connections/snowflake.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/engines/soda/connections/sqlserver.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/engines/soda/connections/trino.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/export/__init__.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/export/avro_converter.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/export/avro_idl_converter.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/export/bigquery_converter.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/export/custom_converter.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/export/data_caterer_converter.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/export/dbml_converter.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/export/dbt_converter.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/export/dcs_exporter.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/export/duckdb_type_converter.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/export/exporter.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/export/exporter_factory.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/export/go_converter.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/export/great_expectations_converter.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/export/html_exporter.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/export/iceberg_converter.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/export/jsonschema_converter.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/export/markdown_converter.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/export/mermaid_exporter.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/export/odcs_v3_exporter.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/export/pandas_type_converter.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/export/protobuf_converter.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/export/pydantic_converter.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/export/sodacl_converter.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/export/spark_converter.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/export/sql_converter.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/export/sqlalchemy_converter.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/export/terraform_converter.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/imports/avro_importer.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/imports/bigquery_importer.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/imports/csv_importer.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/imports/dbml_importer.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/imports/dbt_importer.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/imports/glue_importer.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/imports/iceberg_importer.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/imports/jsonschema_importer.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/imports/odcs_importer.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/imports/odcs_v3_importer.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/imports/parquet_importer.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/imports/protobuf_importer.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/imports/spark_importer.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/imports/sql_importer.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/imports/unity_importer.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/integration/datamesh_manager.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/lint/files.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/lint/lint.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/lint/linters/__init__.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/lint/linters/description_linter.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/lint/linters/field_pattern_linter.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/lint/linters/field_reference_linter.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/lint/linters/notice_period_linter.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/lint/linters/valid_constraints_linter.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/lint/resources.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/lint/urls.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/model/data_contract_specification/__init__.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/model/exceptions.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/model/odcs.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/model/run.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/output/__init__.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/output/junit_test_results.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/output/output_format.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/output/test_results_writer.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/py.typed +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/schemas/datacontract-1.1.0.schema.json +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/schemas/odcs-3.0.1.schema.json +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/templates/datacontract.html +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/templates/datacontract_odcs.html +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/templates/index.html +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/templates/partials/datacontract_information.html +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/templates/partials/datacontract_servicelevels.html +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/templates/partials/datacontract_terms.html +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/templates/partials/definition.html +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/templates/partials/example.html +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/templates/partials/model_field.html +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/templates/partials/quality.html +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/templates/partials/server.html +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/templates/style/output.css +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract_cli.egg-info/dependency_links.txt +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract_cli.egg-info/entry_points.txt +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract_cli.egg-info/top_level.txt +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/setup.cfg +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_breaking.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_catalog.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_changelog.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_cli.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_data_contract_checks.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_data_contract_specification.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_description_linter.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_documentation_linter.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_download_datacontract_file.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_export_avro.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_export_avro_idl.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_export_bigquery.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_export_custom.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_export_custom_exporter.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_export_data_caterer.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_export_dbml.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_export_dbt_models.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_export_dbt_sources.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_export_dbt_staging_sql.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_export_go.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_export_great_expectations.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_export_html.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_export_iceberg.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_export_jsonschema.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_export_markdown.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_export_mermaid.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_export_odcs_v3.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_export_protobuf.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_export_pydantic.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_export_spark.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_export_sql_query.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_export_sqlalchemy.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_export_terraform.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_field_constraint_linter.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_field_pattern_linter.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_field_reference_linter.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_import_bigquery.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_import_dbml.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_import_excel.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_import_glue.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_import_jsonschema.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_import_odcs_v3.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_import_spark.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_import_unity_file.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_integration_datameshmanager.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_lint.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_notice_period_linter.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_roundtrip_jsonschema.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_spec_fields_field.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_spec_ref.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_test_azure_remote.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_test_bigquery.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_test_databricks.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_test_dataframe.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_test_delta.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_test_gcs_json_remote.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_test_kafka.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_test_kafka_remote.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_test_local_json.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_test_output_junit.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_test_parquet.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_test_postgres.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_test_quality.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_test_s3_csv.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_test_s3_delta.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_test_s3_json.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_test_s3_json_complex.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_test_s3_json_multiple_models.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_test_s3_json_remote.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_test_snowflake.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/tests/test_test_sqlserver.py +0 -0
- {datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/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.10.
|
|
3
|
+
Version: 0.10.29
|
|
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
|
|
@@ -28,7 +28,7 @@ Requires-Dist: python-dotenv<2.0.0,>=1.0.0
|
|
|
28
28
|
Requires-Dist: boto3<2.0.0,>=1.34.41
|
|
29
29
|
Requires-Dist: Jinja2<4.0.0,>=3.1.5
|
|
30
30
|
Requires-Dist: jinja_partials<1.0.0,>=0.2.1
|
|
31
|
-
Requires-Dist: datacontract-specification<2.0.0,>=1.
|
|
31
|
+
Requires-Dist: datacontract-specification<2.0.0,>=1.2.0
|
|
32
32
|
Requires-Dist: open-data-contract-standard<4.0.0,>=3.0.4
|
|
33
33
|
Provides-Extra: avro
|
|
34
34
|
Requires-Dist: avro==1.12.0; extra == "avro"
|
|
@@ -42,21 +42,21 @@ Provides-Extra: databricks
|
|
|
42
42
|
Requires-Dist: soda-core-spark-df<3.6.0,>=3.3.20; extra == "databricks"
|
|
43
43
|
Requires-Dist: soda-core-spark[databricks]<3.6.0,>=3.3.20; extra == "databricks"
|
|
44
44
|
Requires-Dist: databricks-sql-connector<4.1.0,>=3.7.0; extra == "databricks"
|
|
45
|
-
Requires-Dist: databricks-sdk<0.
|
|
46
|
-
Requires-Dist: pyspark
|
|
45
|
+
Requires-Dist: databricks-sdk<0.58.0; extra == "databricks"
|
|
46
|
+
Requires-Dist: pyspark<4.0.0,>=3.5.5; extra == "databricks"
|
|
47
47
|
Provides-Extra: iceberg
|
|
48
|
-
Requires-Dist: pyiceberg==0.
|
|
48
|
+
Requires-Dist: pyiceberg==0.9.1; extra == "iceberg"
|
|
49
49
|
Provides-Extra: kafka
|
|
50
50
|
Requires-Dist: datacontract-cli[avro]; extra == "kafka"
|
|
51
51
|
Requires-Dist: soda-core-spark-df<3.6.0,>=3.3.20; extra == "kafka"
|
|
52
|
-
Requires-Dist: pyspark
|
|
52
|
+
Requires-Dist: pyspark<4.0.0,>=3.5.5; extra == "kafka"
|
|
53
53
|
Provides-Extra: postgres
|
|
54
54
|
Requires-Dist: soda-core-postgres<3.6.0,>=3.3.20; extra == "postgres"
|
|
55
55
|
Provides-Extra: s3
|
|
56
56
|
Requires-Dist: s3fs<2026.0.0,>=2025.2.0; extra == "s3"
|
|
57
57
|
Requires-Dist: aiobotocore<2.23.0,>=2.17.0; extra == "s3"
|
|
58
58
|
Provides-Extra: snowflake
|
|
59
|
-
Requires-Dist: snowflake-connector-python[pandas]<3.
|
|
59
|
+
Requires-Dist: snowflake-connector-python[pandas]<3.16,>=3.6; extra == "snowflake"
|
|
60
60
|
Requires-Dist: soda-core-snowflake<3.6.0,>=3.3.20; extra == "snowflake"
|
|
61
61
|
Provides-Extra: sqlserver
|
|
62
62
|
Requires-Dist: soda-core-sqlserver<3.6.0,>=3.3.20; extra == "sqlserver"
|
|
@@ -71,8 +71,8 @@ Requires-Dist: pyarrow>=18.1.0; extra == "parquet"
|
|
|
71
71
|
Provides-Extra: rdf
|
|
72
72
|
Requires-Dist: rdflib==7.0.0; extra == "rdf"
|
|
73
73
|
Provides-Extra: api
|
|
74
|
-
Requires-Dist: fastapi==0.115.
|
|
75
|
-
Requires-Dist: uvicorn==0.
|
|
74
|
+
Requires-Dist: fastapi==0.115.14; extra == "api"
|
|
75
|
+
Requires-Dist: uvicorn==0.35.0; extra == "api"
|
|
76
76
|
Provides-Extra: protobuf
|
|
77
77
|
Requires-Dist: grpcio-tools>=1.53; extra == "protobuf"
|
|
78
78
|
Provides-Extra: all
|
|
@@ -81,15 +81,15 @@ Provides-Extra: dev
|
|
|
81
81
|
Requires-Dist: datacontract-cli[all]; extra == "dev"
|
|
82
82
|
Requires-Dist: httpx==0.28.1; extra == "dev"
|
|
83
83
|
Requires-Dist: kafka-python; extra == "dev"
|
|
84
|
-
Requires-Dist: moto==5.1.
|
|
84
|
+
Requires-Dist: moto==5.1.6; extra == "dev"
|
|
85
85
|
Requires-Dist: pandas>=2.1.0; extra == "dev"
|
|
86
86
|
Requires-Dist: pre-commit<4.3.0,>=3.7.1; extra == "dev"
|
|
87
87
|
Requires-Dist: pytest; extra == "dev"
|
|
88
88
|
Requires-Dist: pytest-xdist; extra == "dev"
|
|
89
|
-
Requires-Dist: pymssql==2.3.
|
|
89
|
+
Requires-Dist: pymssql==2.3.6; extra == "dev"
|
|
90
90
|
Requires-Dist: ruff; extra == "dev"
|
|
91
91
|
Requires-Dist: testcontainers[kafka,minio,mssql,postgres]==4.10.0; extra == "dev"
|
|
92
|
-
Requires-Dist: trino==0.
|
|
92
|
+
Requires-Dist: trino==0.335.0; extra == "dev"
|
|
93
93
|
Dynamic: license-file
|
|
94
94
|
|
|
95
95
|
# Data Contract CLI
|
|
@@ -1286,9 +1286,9 @@ FROM
|
|
|
1286
1286
|
|
|
1287
1287
|
╭─ Options ────────────────────────────────────────────────────────────────────────────────────────╮
|
|
1288
1288
|
│ * --format [sql|avro|dbt|dbml|glue|jsonsc The format of the source file. │
|
|
1289
|
-
│ hema|bigquery|odcs|unity|
|
|
1290
|
-
│ |iceberg|parquet|csv|
|
|
1291
|
-
│ excel]
|
|
1289
|
+
│ hema|json|bigquery|odcs|unity| [default: None] │
|
|
1290
|
+
│ spark|iceberg|parquet|csv|prot [required] │
|
|
1291
|
+
│ obuf|excel] │
|
|
1292
1292
|
│ --output PATH Specify the file path where │
|
|
1293
1293
|
│ the Data Contract will be │
|
|
1294
1294
|
│ saved. If no path is provided, │
|
|
@@ -1712,6 +1712,8 @@ datacontract catalog --files "*.odcs.yaml"
|
|
|
1712
1712
|
information.
|
|
1713
1713
|
To connect to servers (such as a Snowflake data source), set the credentials as environment
|
|
1714
1714
|
variables as documented in https://cli.datacontract.com/#test
|
|
1715
|
+
It is possible to run the API with extra arguments for `uvicorn.run()` as keyword arguments, e.g.:
|
|
1716
|
+
`datacontract api --port 1234 --root_path /datacontract`.
|
|
1715
1717
|
|
|
1716
1718
|
╭─ Options ────────────────────────────────────────────────────────────────────────────────────────╮
|
|
1717
1719
|
│ --port INTEGER Bind socket to this port. [default: 4242] │
|
|
@@ -1978,7 +1980,7 @@ if __name__ == "__main__":
|
|
|
1978
1980
|
Output
|
|
1979
1981
|
|
|
1980
1982
|
```yaml
|
|
1981
|
-
dataContractSpecification: 1.
|
|
1983
|
+
dataContractSpecification: 1.2.0
|
|
1982
1984
|
id: uuid-custom
|
|
1983
1985
|
info:
|
|
1984
1986
|
title: my_custom_imported_data
|
|
@@ -1997,22 +1999,9 @@ models:
|
|
|
1997
1999
|
```
|
|
1998
2000
|
## Development Setup
|
|
1999
2001
|
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
# create venv
|
|
2004
|
-
python3.11 -m venv venv
|
|
2005
|
-
source venv/bin/activate
|
|
2006
|
-
|
|
2007
|
-
# Install Requirements
|
|
2008
|
-
pip install --upgrade pip setuptools wheel
|
|
2009
|
-
pip install -e '.[dev]'
|
|
2010
|
-
pre-commit install
|
|
2011
|
-
pre-commit run --all-files
|
|
2012
|
-
pytest
|
|
2013
|
-
```
|
|
2014
|
-
|
|
2015
|
-
### Use uv (recommended)
|
|
2002
|
+
- Install [uv](https://docs.astral.sh/uv/)
|
|
2003
|
+
- Python base interpreter should be 3.11.x .
|
|
2004
|
+
- Docker engine must be running to execute the tests.
|
|
2016
2005
|
|
|
2017
2006
|
```bash
|
|
2018
2007
|
# make sure uv is installed
|
|
@@ -2094,6 +2083,7 @@ We are happy to receive your contributions. Propose your change in an issue or d
|
|
|
2094
2083
|
- [INNOQ](https://innoq.com)
|
|
2095
2084
|
- [Data Catering](https://data.catering/)
|
|
2096
2085
|
- [Oliver Wyman](https://www.oliverwyman.com/)
|
|
2086
|
+
- [dmTECH](https://www.dmtech.tech/de)
|
|
2097
2087
|
- And many more. To add your company, please create a pull request.
|
|
2098
2088
|
|
|
2099
2089
|
## Related Tools
|
|
@@ -1192,9 +1192,9 @@ FROM
|
|
|
1192
1192
|
|
|
1193
1193
|
╭─ Options ────────────────────────────────────────────────────────────────────────────────────────╮
|
|
1194
1194
|
│ * --format [sql|avro|dbt|dbml|glue|jsonsc The format of the source file. │
|
|
1195
|
-
│ hema|bigquery|odcs|unity|
|
|
1196
|
-
│ |iceberg|parquet|csv|
|
|
1197
|
-
│ excel]
|
|
1195
|
+
│ hema|json|bigquery|odcs|unity| [default: None] │
|
|
1196
|
+
│ spark|iceberg|parquet|csv|prot [required] │
|
|
1197
|
+
│ obuf|excel] │
|
|
1198
1198
|
│ --output PATH Specify the file path where │
|
|
1199
1199
|
│ the Data Contract will be │
|
|
1200
1200
|
│ saved. If no path is provided, │
|
|
@@ -1618,6 +1618,8 @@ datacontract catalog --files "*.odcs.yaml"
|
|
|
1618
1618
|
information.
|
|
1619
1619
|
To connect to servers (such as a Snowflake data source), set the credentials as environment
|
|
1620
1620
|
variables as documented in https://cli.datacontract.com/#test
|
|
1621
|
+
It is possible to run the API with extra arguments for `uvicorn.run()` as keyword arguments, e.g.:
|
|
1622
|
+
`datacontract api --port 1234 --root_path /datacontract`.
|
|
1621
1623
|
|
|
1622
1624
|
╭─ Options ────────────────────────────────────────────────────────────────────────────────────────╮
|
|
1623
1625
|
│ --port INTEGER Bind socket to this port. [default: 4242] │
|
|
@@ -1884,7 +1886,7 @@ if __name__ == "__main__":
|
|
|
1884
1886
|
Output
|
|
1885
1887
|
|
|
1886
1888
|
```yaml
|
|
1887
|
-
dataContractSpecification: 1.
|
|
1889
|
+
dataContractSpecification: 1.2.0
|
|
1888
1890
|
id: uuid-custom
|
|
1889
1891
|
info:
|
|
1890
1892
|
title: my_custom_imported_data
|
|
@@ -1903,22 +1905,9 @@ models:
|
|
|
1903
1905
|
```
|
|
1904
1906
|
## Development Setup
|
|
1905
1907
|
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
# create venv
|
|
1910
|
-
python3.11 -m venv venv
|
|
1911
|
-
source venv/bin/activate
|
|
1912
|
-
|
|
1913
|
-
# Install Requirements
|
|
1914
|
-
pip install --upgrade pip setuptools wheel
|
|
1915
|
-
pip install -e '.[dev]'
|
|
1916
|
-
pre-commit install
|
|
1917
|
-
pre-commit run --all-files
|
|
1918
|
-
pytest
|
|
1919
|
-
```
|
|
1920
|
-
|
|
1921
|
-
### Use uv (recommended)
|
|
1908
|
+
- Install [uv](https://docs.astral.sh/uv/)
|
|
1909
|
+
- Python base interpreter should be 3.11.x .
|
|
1910
|
+
- Docker engine must be running to execute the tests.
|
|
1922
1911
|
|
|
1923
1912
|
```bash
|
|
1924
1913
|
# make sure uv is installed
|
|
@@ -2000,6 +1989,7 @@ We are happy to receive your contributions. Propose your change in an issue or d
|
|
|
2000
1989
|
- [INNOQ](https://innoq.com)
|
|
2001
1990
|
- [Data Catering](https://data.catering/)
|
|
2002
1991
|
- [Oliver Wyman](https://www.oliverwyman.com/)
|
|
1992
|
+
- [dmTECH](https://www.dmtech.tech/de)
|
|
2003
1993
|
- And many more. To add your company, please create a pull request.
|
|
2004
1994
|
|
|
2005
1995
|
## Related Tools
|
|
@@ -10,7 +10,7 @@ from fastapi.security.api_key import APIKeyHeader
|
|
|
10
10
|
from datacontract.data_contract import DataContract, ExportFormat
|
|
11
11
|
from datacontract.model.run import Run
|
|
12
12
|
|
|
13
|
-
DATA_CONTRACT_EXAMPLE_PAYLOAD = """dataContractSpecification: 1.
|
|
13
|
+
DATA_CONTRACT_EXAMPLE_PAYLOAD = """dataContractSpecification: 1.2.0
|
|
14
14
|
id: urn:datacontract:checkout:orders-latest
|
|
15
15
|
info:
|
|
16
16
|
title: Orders Latest
|
|
@@ -469,8 +469,26 @@ def diff(
|
|
|
469
469
|
console.print(result.changelog_str())
|
|
470
470
|
|
|
471
471
|
|
|
472
|
-
|
|
472
|
+
def _get_uvicorn_arguments(port: int, host: str, context: typer.Context) -> dict:
|
|
473
|
+
"""
|
|
474
|
+
Take the default datacontract uvicorn arguments and merge them with the
|
|
475
|
+
extra arguments passed to the command to start the API.
|
|
476
|
+
"""
|
|
477
|
+
default_args = {
|
|
478
|
+
"app": "datacontract.api:app",
|
|
479
|
+
"port": port,
|
|
480
|
+
"host": host,
|
|
481
|
+
"reload": True,
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
# Create a list of the extra arguments, remove the leading -- from the cli arguments
|
|
485
|
+
trimmed_keys = list(map(lambda x : str(x).replace("--", ""),context.args[::2]))
|
|
486
|
+
# Merge the two dicts and return them as one dict
|
|
487
|
+
return default_args | dict(zip(trimmed_keys, context.args[1::2]))
|
|
488
|
+
|
|
489
|
+
@app.command(context_settings={"allow_extra_args": True, "ignore_unknown_options": True})
|
|
473
490
|
def api(
|
|
491
|
+
ctx: Annotated[typer.Context, typer.Option(help="Extra arguments to pass to uvicorn.run().")],
|
|
474
492
|
port: Annotated[int, typer.Option(help="Bind socket to this port.")] = 4242,
|
|
475
493
|
host: Annotated[
|
|
476
494
|
str, typer.Option(help="Bind socket to this host. Hint: For running in docker, set it to 0.0.0.0")
|
|
@@ -488,6 +506,9 @@ def api(
|
|
|
488
506
|
|
|
489
507
|
To connect to servers (such as a Snowflake data source), set the credentials as environment variables as documented in
|
|
490
508
|
https://cli.datacontract.com/#test
|
|
509
|
+
|
|
510
|
+
It is possible to run the API with extra arguments for `uvicorn.run()` as keyword arguments, e.g.:
|
|
511
|
+
`datacontract api --port 1234 --root_path /datacontract`.
|
|
491
512
|
"""
|
|
492
513
|
import uvicorn
|
|
493
514
|
from uvicorn.config import LOGGING_CONFIG
|
|
@@ -495,7 +516,11 @@ def api(
|
|
|
495
516
|
log_config = LOGGING_CONFIG
|
|
496
517
|
log_config["root"] = {"level": "INFO"}
|
|
497
518
|
|
|
498
|
-
|
|
519
|
+
uvicorn_args = _get_uvicorn_arguments(port, host, ctx)
|
|
520
|
+
# Add the log config
|
|
521
|
+
uvicorn_args["log_config"] = log_config
|
|
522
|
+
# Run uvicorn
|
|
523
|
+
uvicorn.run(**uvicorn_args)
|
|
499
524
|
|
|
500
525
|
|
|
501
526
|
def _print_logs(run):
|
|
@@ -132,10 +132,10 @@ def setup_s3_connection(con, server):
|
|
|
132
132
|
use_ssl = "true"
|
|
133
133
|
url_style = "vhost"
|
|
134
134
|
if server.endpointUrl is not None:
|
|
135
|
+
url_style = "path"
|
|
135
136
|
s3_endpoint = server.endpointUrl.removeprefix("http://").removeprefix("https://")
|
|
136
137
|
if server.endpointUrl.startswith("http://"):
|
|
137
138
|
use_ssl = "false"
|
|
138
|
-
url_style = "path"
|
|
139
139
|
|
|
140
140
|
if s3_access_key_id is not None:
|
|
141
141
|
if s3_session_token is not None:
|
|
@@ -57,8 +57,8 @@ def to_rdf(data_contract_spec: DataContractSpecification, base) -> Graph:
|
|
|
57
57
|
else:
|
|
58
58
|
g = Graph(base=Namespace(""))
|
|
59
59
|
|
|
60
|
-
dc = Namespace("https://datacontract.com/DataContractSpecification/1.
|
|
61
|
-
dcx = Namespace("https://datacontract.com/DataContractSpecification/1.
|
|
60
|
+
dc = Namespace("https://datacontract.com/DataContractSpecification/1.2.0/")
|
|
61
|
+
dcx = Namespace("https://datacontract.com/DataContractSpecification/1.2.0/Extension/")
|
|
62
62
|
|
|
63
63
|
g.bind("dc", dc)
|
|
64
64
|
g.bind("dcx", dcx)
|
{datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/export/sql_type_converter.py
RENAMED
|
@@ -194,8 +194,8 @@ def convert_to_databricks(field: Field) -> None | str:
|
|
|
194
194
|
nested_fields = []
|
|
195
195
|
for nested_field_name, nested_field in field.fields.items():
|
|
196
196
|
nested_field_type = convert_to_databricks(nested_field)
|
|
197
|
-
nested_fields.append(f"{nested_field_name}
|
|
198
|
-
return f"STRUCT<{',
|
|
197
|
+
nested_fields.append(f"{nested_field_name}:{nested_field_type}")
|
|
198
|
+
return f"STRUCT<{','.join(nested_fields)}>"
|
|
199
199
|
if type.lower() in ["bytes"]:
|
|
200
200
|
return "BINARY"
|
|
201
201
|
if type.lower() in ["array"]:
|
{datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/imports/excel_importer.py
RENAMED
|
@@ -568,6 +568,8 @@ def import_roles(workbook: Workbook) -> Optional[List[Role]]:
|
|
|
568
568
|
|
|
569
569
|
roles_list = []
|
|
570
570
|
for row_idx in range(roles_range[0], roles_range[1]):
|
|
571
|
+
if len(list(roles_sheet.rows)) < row_idx + 1:
|
|
572
|
+
break
|
|
571
573
|
row = list(roles_sheet.rows)[row_idx]
|
|
572
574
|
|
|
573
575
|
role_name = get_cell_value(row, headers.get("role"))
|
{datacontract_cli-0.10.28 → datacontract_cli-0.10.29}/datacontract/imports/importer_factory.py
RENAMED
|
@@ -119,3 +119,10 @@ importer_factory.register_lazy_importer(
|
|
|
119
119
|
module_path="datacontract.imports.excel_importer",
|
|
120
120
|
class_name="ExcelImporter",
|
|
121
121
|
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
importer_factory.register_lazy_importer(
|
|
125
|
+
name=ImportFormat.json,
|
|
126
|
+
module_path="datacontract.imports.json_importer",
|
|
127
|
+
class_name="JsonImporter",
|
|
128
|
+
)
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import re
|
|
4
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
5
|
+
|
|
6
|
+
from datacontract.imports.importer import Importer
|
|
7
|
+
from datacontract.model.data_contract_specification import DataContractSpecification, Model, Server
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class JsonImporter(Importer):
|
|
11
|
+
def import_source(
|
|
12
|
+
self, data_contract_specification: DataContractSpecification, source: str, import_args: dict
|
|
13
|
+
) -> DataContractSpecification:
|
|
14
|
+
return import_json(data_contract_specification, source)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def is_ndjson(file_path: str) -> bool:
|
|
18
|
+
"""Check if a file contains newline-delimited JSON."""
|
|
19
|
+
with open(file_path, "r", encoding="utf-8") as file:
|
|
20
|
+
for _ in range(5):
|
|
21
|
+
line = file.readline().strip()
|
|
22
|
+
if not line:
|
|
23
|
+
continue
|
|
24
|
+
try:
|
|
25
|
+
json.loads(line)
|
|
26
|
+
return True
|
|
27
|
+
except json.JSONDecodeError:
|
|
28
|
+
break
|
|
29
|
+
return False
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def import_json(
|
|
33
|
+
data_contract_specification: DataContractSpecification, source: str, include_examples: bool = False
|
|
34
|
+
) -> DataContractSpecification:
|
|
35
|
+
# use the file name as base model name
|
|
36
|
+
base_model_name = os.path.splitext(os.path.basename(source))[0]
|
|
37
|
+
|
|
38
|
+
# check if file is newline-delimited JSON
|
|
39
|
+
if is_ndjson(source):
|
|
40
|
+
# load NDJSON data
|
|
41
|
+
json_data = []
|
|
42
|
+
with open(source, "r", encoding="utf-8") as file:
|
|
43
|
+
for line in file:
|
|
44
|
+
line = line.strip()
|
|
45
|
+
if line:
|
|
46
|
+
try:
|
|
47
|
+
json_data.append(json.loads(line))
|
|
48
|
+
except json.JSONDecodeError:
|
|
49
|
+
continue
|
|
50
|
+
else:
|
|
51
|
+
# load regular JSON data
|
|
52
|
+
with open(source, "r", encoding="utf-8") as file:
|
|
53
|
+
json_data = json.load(file)
|
|
54
|
+
|
|
55
|
+
if data_contract_specification.servers is None:
|
|
56
|
+
data_contract_specification.servers = {}
|
|
57
|
+
|
|
58
|
+
data_contract_specification.servers["production"] = Server(type="local", path=source, format="json")
|
|
59
|
+
|
|
60
|
+
# initialisation
|
|
61
|
+
models = {}
|
|
62
|
+
|
|
63
|
+
if isinstance(json_data, list) and json_data:
|
|
64
|
+
# Array of items
|
|
65
|
+
if all(isinstance(item, dict) for item in json_data[:5]):
|
|
66
|
+
# Array of objects, as table
|
|
67
|
+
fields = {}
|
|
68
|
+
for item in json_data[:20]:
|
|
69
|
+
for key, value in item.items():
|
|
70
|
+
field_def = generate_field_definition(value, key, base_model_name, models)
|
|
71
|
+
if key in fields:
|
|
72
|
+
fields[key] = merge_field_definitions(fields[key], field_def)
|
|
73
|
+
else:
|
|
74
|
+
fields[key] = field_def
|
|
75
|
+
|
|
76
|
+
models[base_model_name] = {
|
|
77
|
+
"type": "table",
|
|
78
|
+
"description": f"Generated from JSON array in {source}",
|
|
79
|
+
"fields": fields,
|
|
80
|
+
"examples": json_data[:3] if include_examples else None,
|
|
81
|
+
}
|
|
82
|
+
else:
|
|
83
|
+
# Simple array
|
|
84
|
+
item_type, item_format = infer_array_type(json_data[:20])
|
|
85
|
+
models[base_model_name] = {
|
|
86
|
+
"type": "array",
|
|
87
|
+
"description": f"Generated from JSON array in {source}",
|
|
88
|
+
"items": {"type": item_type, "format": item_format} if item_format else {"type": item_type},
|
|
89
|
+
"examples": [json_data[:5]] if include_examples else None,
|
|
90
|
+
}
|
|
91
|
+
elif isinstance(json_data, dict):
|
|
92
|
+
# Single object
|
|
93
|
+
fields = {}
|
|
94
|
+
for key, value in json_data.items():
|
|
95
|
+
fields[key] = generate_field_definition(value, key, base_model_name, models)
|
|
96
|
+
|
|
97
|
+
models[base_model_name] = {
|
|
98
|
+
"type": "object",
|
|
99
|
+
"description": f"Generated from JSON object in {source}",
|
|
100
|
+
"fields": fields,
|
|
101
|
+
"examples": [json_data] if include_examples else None,
|
|
102
|
+
}
|
|
103
|
+
else:
|
|
104
|
+
# Primitive value
|
|
105
|
+
field_type, field_format = determine_type_and_format(json_data)
|
|
106
|
+
models[base_model_name] = {
|
|
107
|
+
"type": field_type,
|
|
108
|
+
"description": f"Generated from JSON primitive in {source}",
|
|
109
|
+
"format": field_format,
|
|
110
|
+
"examples": [json_data] if include_examples and field_type != "boolean" else None,
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
for model_name, model_def in models.items():
|
|
114
|
+
model_type = model_def.pop("type")
|
|
115
|
+
data_contract_specification.models[model_name] = Model(type=model_type, **model_def)
|
|
116
|
+
|
|
117
|
+
return data_contract_specification
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def generate_field_definition(
|
|
121
|
+
value: Any, field_name: str, parent_model: str, models: Dict[str, Dict[str, Any]]
|
|
122
|
+
) -> Dict[str, Any]:
|
|
123
|
+
"""Generate a field definition for a JSON value, creating nested models."""
|
|
124
|
+
|
|
125
|
+
if isinstance(value, dict):
|
|
126
|
+
# Handle object fields
|
|
127
|
+
fields = {}
|
|
128
|
+
for key, nested_value in value.items():
|
|
129
|
+
fields[key] = generate_field_definition(nested_value, key, parent_model, models)
|
|
130
|
+
|
|
131
|
+
return {"type": "object", "fields": fields}
|
|
132
|
+
|
|
133
|
+
elif isinstance(value, list):
|
|
134
|
+
# Handle array fields
|
|
135
|
+
if not value:
|
|
136
|
+
return {"type": "array", "items": {"type": "string"}}
|
|
137
|
+
|
|
138
|
+
if all(isinstance(item, dict) for item in value):
|
|
139
|
+
# Array of objects
|
|
140
|
+
fields = {}
|
|
141
|
+
for item in value:
|
|
142
|
+
for key, nested_value in item.items():
|
|
143
|
+
field_def = generate_field_definition(nested_value, key, parent_model, models)
|
|
144
|
+
if key in fields:
|
|
145
|
+
fields[key] = merge_field_definitions(fields[key], field_def)
|
|
146
|
+
else:
|
|
147
|
+
fields[key] = field_def
|
|
148
|
+
|
|
149
|
+
return {"type": "array", "items": {"type": "object", "fields": fields}}
|
|
150
|
+
|
|
151
|
+
elif all(isinstance(item, list) for item in value):
|
|
152
|
+
# Array of arrays
|
|
153
|
+
inner_type, inner_format = infer_array_type(value[0])
|
|
154
|
+
return {
|
|
155
|
+
"type": "array",
|
|
156
|
+
"items": {
|
|
157
|
+
"type": "array",
|
|
158
|
+
"items": {"type": inner_type, "format": inner_format} if inner_format else {"type": inner_type},
|
|
159
|
+
},
|
|
160
|
+
"examples": value[:5], # Include examples for nested arrays
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
else:
|
|
164
|
+
# Array of simple or mixed types
|
|
165
|
+
item_type, item_format = infer_array_type(value)
|
|
166
|
+
items_def = {"type": item_type}
|
|
167
|
+
if item_format:
|
|
168
|
+
items_def["format"] = item_format
|
|
169
|
+
|
|
170
|
+
field_def = {"type": "array", "items": items_def}
|
|
171
|
+
|
|
172
|
+
# Add examples if appropriate
|
|
173
|
+
sample_values = [item for item in value[:5] if item is not None]
|
|
174
|
+
if sample_values:
|
|
175
|
+
field_def["examples"] = sample_values
|
|
176
|
+
|
|
177
|
+
return field_def
|
|
178
|
+
|
|
179
|
+
else:
|
|
180
|
+
# Handle primitive types
|
|
181
|
+
field_type, field_format = determine_type_and_format(value)
|
|
182
|
+
field_def = {"type": field_type}
|
|
183
|
+
if field_format:
|
|
184
|
+
field_def["format"] = field_format
|
|
185
|
+
|
|
186
|
+
# Add examples
|
|
187
|
+
if value is not None and field_type != "boolean":
|
|
188
|
+
field_def["examples"] = [value]
|
|
189
|
+
|
|
190
|
+
return field_def
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def infer_array_type(array: List) -> Tuple[str, Optional[str]]:
|
|
194
|
+
"""Infer the common type of items in an array."""
|
|
195
|
+
if not array:
|
|
196
|
+
return "string", None
|
|
197
|
+
|
|
198
|
+
# if all items are dictionaries with the same structure
|
|
199
|
+
if all(isinstance(item, dict) for item in array):
|
|
200
|
+
return "object", None
|
|
201
|
+
|
|
202
|
+
# if all items are of the same primitive type
|
|
203
|
+
non_null_items = [item for item in array if item is not None]
|
|
204
|
+
if not non_null_items:
|
|
205
|
+
return "null", None
|
|
206
|
+
|
|
207
|
+
types_and_formats = [determine_type_and_format(item) for item in non_null_items]
|
|
208
|
+
types = {t for t, _ in types_and_formats}
|
|
209
|
+
formats = {f for _, f in types_and_formats if f is not None}
|
|
210
|
+
|
|
211
|
+
# simplify type combinations
|
|
212
|
+
if types == {"integer", "number"}:
|
|
213
|
+
return "number", None
|
|
214
|
+
if len(types) == 1:
|
|
215
|
+
type_name = next(iter(types))
|
|
216
|
+
format_name = next(iter(formats)) if len(formats) == 1 else None
|
|
217
|
+
return type_name, format_name
|
|
218
|
+
if all(t in {"string", "integer", "number", "boolean", "null"} for t in types):
|
|
219
|
+
# If all string values have the same format, keep it
|
|
220
|
+
if len(formats) == 1 and "string" in types:
|
|
221
|
+
return "string", next(iter(formats))
|
|
222
|
+
return "string", None
|
|
223
|
+
|
|
224
|
+
# Mixed types
|
|
225
|
+
return "string", None
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def determine_type_and_format(value: Any) -> Tuple[str, Optional[str]]:
|
|
229
|
+
"""determine the datacontract type and format for a JSON value."""
|
|
230
|
+
if value is None:
|
|
231
|
+
return "null", None
|
|
232
|
+
elif isinstance(value, bool):
|
|
233
|
+
return "boolean", None
|
|
234
|
+
elif isinstance(value, int):
|
|
235
|
+
return "integer", None
|
|
236
|
+
elif isinstance(value, float):
|
|
237
|
+
return "number", None
|
|
238
|
+
elif isinstance(value, str):
|
|
239
|
+
try:
|
|
240
|
+
if re.match(r"^\d{4}-\d{2}-\d{2}$", value):
|
|
241
|
+
return "string", "date"
|
|
242
|
+
elif re.match(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?$", value):
|
|
243
|
+
return "string", "date-time"
|
|
244
|
+
elif re.match(r"^[\w\.-]+@([\w-]+\.)+[\w-]{2,4}$", value):
|
|
245
|
+
return "string", "email"
|
|
246
|
+
elif re.match(r"^[a-f0-9]{8}-?[a-f0-9]{4}-?[a-f0-9]{4}-?[a-f0-9]{4}-?[a-f0-9]{12}$", value.lower()):
|
|
247
|
+
return "string", "uuid"
|
|
248
|
+
else:
|
|
249
|
+
return "string", None
|
|
250
|
+
except re.error:
|
|
251
|
+
return "string", None
|
|
252
|
+
elif isinstance(value, dict):
|
|
253
|
+
return "object", None
|
|
254
|
+
elif isinstance(value, list):
|
|
255
|
+
return "array", None
|
|
256
|
+
else:
|
|
257
|
+
return "string", None
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def merge_field_definitions(field1: Dict[str, Any], field2: Dict[str, Any]) -> Dict[str, Any]:
|
|
261
|
+
"""Merge two field definitions."""
|
|
262
|
+
result = field1.copy()
|
|
263
|
+
if field1.get("type") == "object" and field2.get("type") != "object":
|
|
264
|
+
return field1
|
|
265
|
+
if field2.get("type") == "object" and field1.get("type") != "object":
|
|
266
|
+
return field2
|
|
267
|
+
# Handle type differences
|
|
268
|
+
if field1.get("type") != field2.get("type"):
|
|
269
|
+
type1, _ = field1.get("type", "string"), field1.get("format")
|
|
270
|
+
type2, _ = field2.get("type", "string"), field2.get("format")
|
|
271
|
+
|
|
272
|
+
if type1 == "integer" and type2 == "number" or type1 == "number" and type2 == "integer":
|
|
273
|
+
common_type = "number"
|
|
274
|
+
common_format = None
|
|
275
|
+
elif "string" in [type1, type2]:
|
|
276
|
+
common_type = "string"
|
|
277
|
+
common_format = None
|
|
278
|
+
elif all(t in ["string", "integer", "number", "boolean", "null"] for t in [type1, type2]):
|
|
279
|
+
common_type = "string"
|
|
280
|
+
common_format = None
|
|
281
|
+
elif type1 == "array" and type2 == "array":
|
|
282
|
+
# Handle mixed array types
|
|
283
|
+
items1 = field1.get("items", {})
|
|
284
|
+
items2 = field2.get("items", {})
|
|
285
|
+
if items1.get("type") == "object" or items2.get("type") == "object":
|
|
286
|
+
if items1.get("type") == "object" and items2.get("type") == "object":
|
|
287
|
+
merged_items = merge_field_definitions(items1, items2)
|
|
288
|
+
else:
|
|
289
|
+
merged_items = items1 if items1.get("type") == "object" else items2
|
|
290
|
+
return {"type": "array", "items": merged_items}
|
|
291
|
+
else:
|
|
292
|
+
merged_items = merge_field_definitions(items1, items2)
|
|
293
|
+
return {"type": "array", "items": merged_items}
|
|
294
|
+
else:
|
|
295
|
+
common_type = "array" if "array" in [type1, type2] else "object"
|
|
296
|
+
common_format = None
|
|
297
|
+
|
|
298
|
+
result["type"] = common_type
|
|
299
|
+
if common_format:
|
|
300
|
+
result["format"] = common_format
|
|
301
|
+
elif "format" in result:
|
|
302
|
+
del result["format"]
|
|
303
|
+
|
|
304
|
+
# Merge examples
|
|
305
|
+
if "examples" in field2:
|
|
306
|
+
if "examples" in result:
|
|
307
|
+
combined = result["examples"] + [ex for ex in field2["examples"] if ex not in result["examples"]]
|
|
308
|
+
result["examples"] = combined[:5] # Limit to 5 examples
|
|
309
|
+
else:
|
|
310
|
+
result["examples"] = field2["examples"]
|
|
311
|
+
|
|
312
|
+
# Handle nested structures
|
|
313
|
+
if result.get("type") == "array" and "items" in field1 and "items" in field2:
|
|
314
|
+
result["items"] = merge_field_definitions(field1["items"], field2["items"])
|
|
315
|
+
elif result.get("type") == "object" and "fields" in field1 and "fields" in field2:
|
|
316
|
+
# Merge fields from both objects
|
|
317
|
+
merged_fields = field1["fields"].copy()
|
|
318
|
+
for key, field_def in field2["fields"].items():
|
|
319
|
+
if key in merged_fields:
|
|
320
|
+
merged_fields[key] = merge_field_definitions(merged_fields[key], field_def)
|
|
321
|
+
else:
|
|
322
|
+
merged_fields[key] = field_def
|
|
323
|
+
result["fields"] = merged_fields
|
|
324
|
+
|
|
325
|
+
return result
|