datacontract-cli 0.10.7__py3-none-any.whl → 0.10.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of datacontract-cli might be problematic. Click here for more details.

Files changed (55) hide show
  1. datacontract/catalog/catalog.py +4 -2
  2. datacontract/cli.py +44 -15
  3. datacontract/data_contract.py +52 -206
  4. datacontract/engines/fastjsonschema/s3/s3_read_files.py +13 -1
  5. datacontract/engines/soda/check_soda_execute.py +9 -2
  6. datacontract/engines/soda/connections/bigquery.py +8 -1
  7. datacontract/engines/soda/connections/duckdb.py +28 -12
  8. datacontract/engines/soda/connections/trino.py +26 -0
  9. datacontract/export/__init__.py +0 -0
  10. datacontract/export/avro_converter.py +15 -3
  11. datacontract/export/avro_idl_converter.py +29 -22
  12. datacontract/export/bigquery_converter.py +15 -0
  13. datacontract/export/dbml_converter.py +9 -0
  14. datacontract/export/dbt_converter.py +26 -1
  15. datacontract/export/exporter.py +88 -0
  16. datacontract/export/exporter_factory.py +145 -0
  17. datacontract/export/go_converter.py +6 -0
  18. datacontract/export/great_expectations_converter.py +10 -0
  19. datacontract/export/html_export.py +6 -0
  20. datacontract/export/jsonschema_converter.py +31 -23
  21. datacontract/export/odcs_converter.py +24 -1
  22. datacontract/export/protobuf_converter.py +6 -0
  23. datacontract/export/pydantic_converter.py +6 -0
  24. datacontract/export/rdf_converter.py +9 -0
  25. datacontract/export/sodacl_converter.py +23 -12
  26. datacontract/export/spark_converter.py +211 -0
  27. datacontract/export/sql_converter.py +32 -2
  28. datacontract/export/sql_type_converter.py +32 -5
  29. datacontract/export/terraform_converter.py +6 -0
  30. datacontract/imports/avro_importer.py +8 -0
  31. datacontract/imports/bigquery_importer.py +47 -4
  32. datacontract/imports/glue_importer.py +122 -30
  33. datacontract/imports/importer.py +29 -0
  34. datacontract/imports/importer_factory.py +72 -0
  35. datacontract/imports/jsonschema_importer.py +8 -0
  36. datacontract/imports/odcs_importer.py +200 -0
  37. datacontract/imports/sql_importer.py +8 -0
  38. datacontract/imports/unity_importer.py +152 -0
  39. datacontract/lint/resolve.py +22 -1
  40. datacontract/model/data_contract_specification.py +36 -4
  41. datacontract/templates/datacontract.html +17 -2
  42. datacontract/templates/partials/datacontract_information.html +20 -0
  43. datacontract/templates/partials/datacontract_terms.html +7 -0
  44. datacontract/templates/partials/definition.html +9 -1
  45. datacontract/templates/partials/model_field.html +23 -6
  46. datacontract/templates/partials/server.html +113 -48
  47. datacontract/templates/style/output.css +51 -0
  48. datacontract/web.py +17 -0
  49. {datacontract_cli-0.10.7.dist-info → datacontract_cli-0.10.9.dist-info}/METADATA +298 -59
  50. datacontract_cli-0.10.9.dist-info/RECORD +93 -0
  51. {datacontract_cli-0.10.7.dist-info → datacontract_cli-0.10.9.dist-info}/WHEEL +1 -1
  52. datacontract_cli-0.10.7.dist-info/RECORD +0 -84
  53. {datacontract_cli-0.10.7.dist-info → datacontract_cli-0.10.9.dist-info}/LICENSE +0 -0
  54. {datacontract_cli-0.10.7.dist-info → datacontract_cli-0.10.9.dist-info}/entry_points.txt +0 -0
  55. {datacontract_cli-0.10.7.dist-info → datacontract_cli-0.10.9.dist-info}/top_level.txt +0 -0
@@ -2,6 +2,12 @@ import yaml
2
2
 
3
3
  from datacontract.export.sql_type_converter import convert_to_sql_type
4
4
  from datacontract.model.data_contract_specification import DataContractSpecification
5
+ from datacontract.export.exporter import Exporter
6
+
7
+
8
+ class SodaExporter(Exporter):
9
+ def export(self, data_contract, model, server, sql_server_type, export_args) -> dict:
10
+ return to_sodacl_yaml(data_contract)
5
11
 
6
12
 
7
13
  def to_sodacl_yaml(
@@ -35,27 +41,32 @@ def to_checks(model_key, model_value, server_type: str, check_types: bool):
35
41
  if field.unique:
36
42
  checks.append(check_field_unique(field_name, quote_field_name))
37
43
  if field.minLength is not None:
38
- checks.append(check_field_min_length(field_name, field.minLength))
44
+ checks.append(check_field_min_length(field_name, field.minLength, quote_field_name))
39
45
  if field.maxLength is not None:
40
- checks.append(check_field_max_length(field_name, field.maxLength))
46
+ checks.append(check_field_max_length(field_name, field.maxLength, quote_field_name))
41
47
  if field.minimum is not None:
42
- checks.append(check_field_minimum(field_name, field.minimum))
48
+ checks.append(check_field_minimum(field_name, field.minimum, quote_field_name))
43
49
  if field.maximum is not None:
44
- checks.append(check_field_maximum(field_name, field.maximum))
50
+ checks.append(check_field_maximum(field_name, field.maximum, quote_field_name))
45
51
  if field.exclusiveMinimum is not None:
46
- checks.append(check_field_minimum(field_name, field.exclusiveMinimum))
47
- checks.append(check_field_not_equal(field_name, field.exclusiveMinimum))
52
+ checks.append(check_field_minimum(field_name, field.exclusiveMinimum, quote_field_name))
53
+ checks.append(check_field_not_equal(field_name, field.exclusiveMinimum, quote_field_name))
48
54
  if field.exclusiveMaximum is not None:
49
- checks.append(check_field_maximum(field_name, field.exclusiveMaximum))
50
- checks.append(check_field_not_equal(field_name, field.exclusiveMaximum))
55
+ checks.append(check_field_maximum(field_name, field.exclusiveMaximum, quote_field_name))
56
+ checks.append(check_field_not_equal(field_name, field.exclusiveMaximum, quote_field_name))
51
57
  if field.pattern is not None:
52
- checks.append(check_field_regex(field_name, field.pattern))
58
+ checks.append(check_field_regex(field_name, field.pattern, quote_field_name))
53
59
  if field.enum is not None and len(field.enum) > 0:
54
- checks.append(check_field_enum(field_name, field.enum))
60
+ checks.append(check_field_enum(field_name, field.enum, quote_field_name))
55
61
  # TODO references: str = None
56
62
  # TODO format
57
63
 
58
- return f"checks for {model_key}", checks
64
+ checks_for_model_key = f"checks for {model_key}"
65
+
66
+ if quote_field_name:
67
+ checks_for_model_key = f'checks for "{model_key}"'
68
+
69
+ return checks_for_model_key, checks
59
70
 
60
71
 
61
72
  def check_field_is_present(field_name):
@@ -98,7 +109,7 @@ def check_field_min_length(field_name, min_length, quote_field_name: bool = Fals
98
109
  field_name = f'"{field_name}"'
99
110
  return {
100
111
  f"invalid_count({field_name}) = 0": {
101
- "name": f"Check that field {field_name} has a min length of {min}",
112
+ "name": f"Check that field {field_name} has a min length of {min_length}",
102
113
  "valid min length": min_length,
103
114
  }
104
115
  }
@@ -0,0 +1,211 @@
1
+ from pyspark.sql import types
2
+ from datacontract.model.data_contract_specification import (
3
+ DataContractSpecification,
4
+ Model,
5
+ Field,
6
+ )
7
+ from datacontract.export.exporter import Exporter
8
+
9
+
10
+ class SparkExporter(Exporter):
11
+ """
12
+ Exporter class for exporting data contracts to Spark schemas.
13
+ """
14
+
15
+ def export(
16
+ self,
17
+ data_contract: DataContractSpecification,
18
+ model,
19
+ server,
20
+ sql_server_type,
21
+ export_args,
22
+ ) -> dict[str, types.StructType]:
23
+ """
24
+ Export the given data contract to Spark schemas.
25
+
26
+ Args:
27
+ data_contract (DataContractSpecification): The data contract specification.
28
+ model: Not used in this implementation.
29
+ server: Not used in this implementation.
30
+ sql_server_type: Not used in this implementation.
31
+ export_args: Additional arguments for export.
32
+
33
+ Returns:
34
+ dict[str, types.StructType]: A dictionary mapping model names to their corresponding Spark schemas.
35
+ """
36
+ return to_spark(data_contract)
37
+
38
+
39
+ def to_spark(contract: DataContractSpecification) -> str:
40
+ """
41
+ Converts a DataContractSpecification into a Spark schema string.
42
+
43
+ Args:
44
+ contract (DataContractSpecification): The data contract specification containing models.
45
+
46
+ Returns:
47
+ str: A string representation of the Spark schema for each model in the contract.
48
+ """
49
+ return "\n\n".join(
50
+ f"{model_name} = {print_schema(to_spark_schema(model))}" for model_name, model in contract.models.items()
51
+ )
52
+
53
+
54
+ def to_spark_dict(contract: DataContractSpecification) -> dict[str, types.StructType]:
55
+ """
56
+ Convert a data contract specification to Spark schemas.
57
+
58
+ Args:
59
+ contract (DataContractSpecification): The data contract specification.
60
+
61
+ Returns:
62
+ dict[str, types.StructType]: A dictionary mapping model names to their corresponding Spark schemas.
63
+ """
64
+ return {model_name: to_spark_schema(model) for model_name, model in contract.models.items()}
65
+
66
+
67
+ def to_spark_schema(model: Model) -> types.StructType:
68
+ """
69
+ Convert a model to a Spark schema.
70
+
71
+ Args:
72
+ model (Model): The model to convert.
73
+
74
+ Returns:
75
+ types.StructType: The corresponding Spark schema.
76
+ """
77
+ return to_struct_type(model.fields)
78
+
79
+
80
+ def to_struct_type(fields: dict[str, Field]) -> types.StructType:
81
+ """
82
+ Convert a dictionary of fields to a Spark StructType.
83
+
84
+ Args:
85
+ fields (dict[str, Field]): The fields to convert.
86
+
87
+ Returns:
88
+ types.StructType: The corresponding Spark StructType.
89
+ """
90
+ struct_fields = [to_struct_field(field, field_name) for field_name, field in fields.items()]
91
+ return types.StructType(struct_fields)
92
+
93
+
94
+ def to_struct_field(field: Field, field_name: str) -> types.StructField:
95
+ """
96
+ Convert a field to a Spark StructField.
97
+
98
+ Args:
99
+ field (Field): The field to convert.
100
+ field_name (str): The name of the field.
101
+
102
+ Returns:
103
+ types.StructField: The corresponding Spark StructField.
104
+ """
105
+ data_type = to_data_type(field)
106
+ return types.StructField(name=field_name, dataType=data_type, nullable=not field.required)
107
+
108
+
109
+ def to_data_type(field: Field) -> types.DataType:
110
+ """
111
+ Convert a field to a Spark DataType.
112
+
113
+ Args:
114
+ field (Field): The field to convert.
115
+
116
+ Returns:
117
+ types.DataType: The corresponding Spark DataType.
118
+ """
119
+ field_type = field.type
120
+ if field_type is None or field_type in ["null"]:
121
+ return types.NullType()
122
+ if field_type == "array":
123
+ return types.ArrayType(to_data_type(field.items))
124
+ if field_type in ["object", "record", "struct"]:
125
+ return types.StructType(to_struct_type(field.fields))
126
+ if field_type in ["string", "varchar", "text"]:
127
+ return types.StringType()
128
+ if field_type in ["number", "decimal", "numeric"]:
129
+ return types.DecimalType()
130
+ if field_type in ["integer", "int"]:
131
+ return types.IntegerType()
132
+ if field_type == "long":
133
+ return types.LongType()
134
+ if field_type == "float":
135
+ return types.FloatType()
136
+ if field_type == "double":
137
+ return types.DoubleType()
138
+ if field_type == "boolean":
139
+ return types.BooleanType()
140
+ if field_type in ["timestamp", "timestamp_tz"]:
141
+ return types.TimestampType()
142
+ if field_type == "timestamp_ntz":
143
+ return types.TimestampNTZType()
144
+ if field_type == "date":
145
+ return types.DateType()
146
+ if field_type == "bytes":
147
+ return types.BinaryType()
148
+ return types.BinaryType()
149
+
150
+
151
+ def print_schema(dtype: types.DataType) -> str:
152
+ """
153
+ Converts a PySpark DataType schema to its equivalent code representation.
154
+
155
+ Args:
156
+ dtype (types.DataType): The PySpark DataType schema to be converted.
157
+
158
+ Returns:
159
+ str: The code representation of the PySpark DataType schema.
160
+ """
161
+
162
+ def indent(text: str, level: int) -> str:
163
+ """
164
+ Indents each line of the given text by a specified number of levels.
165
+
166
+ Args:
167
+ text (str): The text to be indented.
168
+ level (int): The number of indentation levels.
169
+
170
+ Returns:
171
+ str: The indented text.
172
+ """
173
+ return "\n".join([f'{" " * level}{line}' for line in text.split("\n")])
174
+
175
+ def repr_column(column: types.StructField) -> str:
176
+ """
177
+ Converts a PySpark StructField to its code representation.
178
+
179
+ Args:
180
+ column (types.StructField): The StructField to be converted.
181
+
182
+ Returns:
183
+ str: The code representation of the StructField.
184
+ """
185
+ name = f'"{column.name}"'
186
+ data_type = indent(print_schema(column.dataType), 1)
187
+ nullable = indent(f"{column.nullable}", 1)
188
+ return f"StructField({name},\n{data_type},\n{nullable}\n)"
189
+
190
+ def format_struct_type(struct_type: types.StructType) -> str:
191
+ """
192
+ Converts a PySpark StructType to its code representation.
193
+
194
+ Args:
195
+ struct_type (types.StructType): The StructType to be converted.
196
+
197
+ Returns:
198
+ str: The code representation of the StructType.
199
+ """
200
+ fields = ",\n".join([indent(repr_column(field), 1) for field in struct_type.fields])
201
+ return f"StructType([\n{fields}\n])"
202
+
203
+ if isinstance(dtype, types.StructType):
204
+ return format_struct_type(dtype)
205
+ elif isinstance(dtype, types.ArrayType):
206
+ return f"ArrayType({print_schema(dtype.elementType)})"
207
+ elif isinstance(dtype, types.DecimalType):
208
+ return f"DecimalType({dtype.precision}, {dtype.scale})"
209
+ else:
210
+ dtype_str = str(dtype)
211
+ return dtype_str if dtype_str.endswith("()") else f"{dtype_str}()"
@@ -1,6 +1,29 @@
1
1
  from datacontract.export.sql_type_converter import convert_to_sql_type
2
2
  from datacontract.model.data_contract_specification import DataContractSpecification, Model
3
3
 
4
+ from datacontract.export.exporter import Exporter, _check_models_for_export, _determine_sql_server_type
5
+
6
+
7
+ class SqlExporter(Exporter):
8
+ def export(self, data_contract, model, server, sql_server_type, export_args) -> dict:
9
+ server_type = _determine_sql_server_type(
10
+ data_contract,
11
+ sql_server_type,
12
+ )
13
+ return to_sql_ddl(data_contract, server_type, export_args.get("server"))
14
+
15
+
16
+ class SqlQueryExporter(Exporter):
17
+ def export(self, data_contract, model, server, sql_server_type, export_args) -> dict:
18
+ model_name, model_value = _check_models_for_export(data_contract, model, self.export_format)
19
+ server_type = _determine_sql_server_type(data_contract, sql_server_type, export_args.get("server"))
20
+ return to_sql_query(
21
+ data_contract,
22
+ model_name,
23
+ model_value,
24
+ server_type,
25
+ )
26
+
4
27
 
5
28
  def to_sql_query(
6
29
  data_contract_spec: DataContractSpecification, model_name: str, model_value: Model, server_type: str = "snowflake"
@@ -37,7 +60,9 @@ def _to_sql_query(model_name, model_value, server_type) -> str:
37
60
  return result
38
61
 
39
62
 
40
- def to_sql_ddl(data_contract_spec: DataContractSpecification, server_type: str = "snowflake") -> str:
63
+ def to_sql_ddl(
64
+ data_contract_spec: DataContractSpecification, server_type: str = "snowflake", server: str = None
65
+ ) -> str:
41
66
  if data_contract_spec is None:
42
67
  return ""
43
68
  if data_contract_spec.models is None or len(data_contract_spec.models) == 0:
@@ -45,7 +70,12 @@ def to_sql_ddl(data_contract_spec: DataContractSpecification, server_type: str =
45
70
 
46
71
  table_prefix = ""
47
72
 
48
- for server_name, server in iter(data_contract_spec.servers.items()):
73
+ if server is None:
74
+ servers = data_contract_spec.servers
75
+ else:
76
+ servers = {server: data_contract_spec.servers[server]}
77
+
78
+ for server_name, server in iter(servers.items()):
49
79
  if server.type == "snowflake":
50
80
  server_type = "snowflake"
51
81
  break
@@ -15,13 +15,15 @@ def convert_to_sql_type(field: Field, server_type: str) -> str:
15
15
  return convert_type_to_sqlserver(field)
16
16
  elif server_type == "bigquery":
17
17
  return convert_type_to_bigquery(field)
18
+ elif server_type == "trino":
19
+ return convert_type_to_trino(field)
18
20
  return field.type
19
21
 
20
22
 
21
23
  # snowflake data types:
22
24
  # https://docs.snowflake.com/en/sql-reference/data-types.html
23
25
  def convert_to_snowflake(field: Field) -> None | str:
24
- if field.config and field.config["snowflakeType"] is not None:
26
+ if field.config and "snowflakeType" in field.config:
25
27
  return field.config["snowflakeType"]
26
28
 
27
29
  type = field.type
@@ -64,7 +66,7 @@ def convert_to_snowflake(field: Field) -> None | str:
64
66
  # https://www.postgresql.org/docs/current/datatype.html
65
67
  # Using the name whenever possible
66
68
  def convert_type_to_postgres(field: Field) -> None | str:
67
- if field.config and field.config["postgresType"] is not None:
69
+ if field.config and "postgresType" in field.config:
68
70
  return field.config["postgresType"]
69
71
 
70
72
  type = field.type
@@ -109,7 +111,7 @@ def convert_type_to_postgres(field: Field) -> None | str:
109
111
  # databricks data types:
110
112
  # https://docs.databricks.com/en/sql/language-manual/sql-ref-datatypes.html
111
113
  def convert_to_databricks(field: Field) -> None | str:
112
- if field.config and field.config["databricksType"] is not None:
114
+ if field.config and "databricksType" in field.config:
113
115
  return field.config["databricksType"]
114
116
  type = field.type
115
117
  if type is None:
@@ -161,8 +163,7 @@ def convert_to_duckdb(field: Field) -> None | str:
161
163
  if type.lower() in ["time"]:
162
164
  return "TIME" # TIME WITHOUT TIME ZONE
163
165
  if type.lower() in ["number", "decimal", "numeric"]:
164
- # precision and scale not supported by data contract
165
- return "DECIMAL"
166
+ return f"DECIMAL({field.precision},{field.scale})"
166
167
  if type.lower() in ["float"]:
167
168
  return "FLOAT"
168
169
  if type.lower() in ["double"]:
@@ -250,3 +251,29 @@ def get_type_config(field: Field, config_attr: str) -> dict[str, str] | None:
250
251
  if not field.config:
251
252
  return None
252
253
  return field.config.get(config_attr, None)
254
+
255
+
256
+ def convert_type_to_trino(field: Field) -> None | str:
257
+ """Convert from supported datacontract types to equivalent trino types"""
258
+ field_type = field.type
259
+
260
+ if field_type.lower() in ["string", "text", "varchar"]:
261
+ return "varchar"
262
+ # tinyint, smallint not supported by data contract
263
+ if field_type.lower() in ["number", "decimal", "numeric"]:
264
+ # precision and scale not supported by data contract
265
+ return "decimal"
266
+ if field_type.lower() in ["int", "integer"]:
267
+ return "integer"
268
+ if field_type.lower() in ["long", "bigint"]:
269
+ return "bigint"
270
+ if field_type.lower() in ["float"]:
271
+ return "real"
272
+ if field_type.lower() in ["timestamp", "timestamp_tz"]:
273
+ return "timestamp(3) with time zone"
274
+ if field_type.lower() in ["timestamp_ntz"]:
275
+ return "timestamp(3)"
276
+ if field_type.lower() in ["bytes"]:
277
+ return "varbinary"
278
+ if field_type.lower() in ["object", "record", "struct"]:
279
+ return "json"
@@ -1,6 +1,12 @@
1
1
  import re
2
2
 
3
3
  from datacontract.model.data_contract_specification import DataContractSpecification, Server
4
+ from datacontract.export.exporter import Exporter
5
+
6
+
7
+ class TerraformExporter(Exporter):
8
+ def export(self, data_contract, model, server, sql_server_type, export_args) -> dict:
9
+ return to_terraform(data_contract)
4
10
 
5
11
 
6
12
  def to_terraform(data_contract_spec: DataContractSpecification, server_id: str = None) -> str:
@@ -1,9 +1,17 @@
1
1
  import avro.schema
2
2
 
3
+ from datacontract.imports.importer import Importer
3
4
  from datacontract.model.data_contract_specification import DataContractSpecification, Model, Field
4
5
  from datacontract.model.exceptions import DataContractException
5
6
 
6
7
 
8
+ class AvroImporter(Importer):
9
+ def import_source(
10
+ self, data_contract_specification: DataContractSpecification, source: str, import_args: dict
11
+ ) -> dict:
12
+ return import_avro(data_contract_specification, source)
13
+
14
+
7
15
  def import_avro(data_contract_specification: DataContractSpecification, source: str) -> DataContractSpecification:
8
16
  if data_contract_specification.models is None:
9
17
  data_contract_specification.models = {}
@@ -1,12 +1,28 @@
1
1
  import json
2
+ import logging
2
3
  from typing import List
3
4
 
4
- from google.cloud import bigquery
5
-
5
+ from datacontract.imports.importer import Importer
6
6
  from datacontract.model.data_contract_specification import DataContractSpecification, Model, Field
7
7
  from datacontract.model.exceptions import DataContractException
8
8
 
9
9
 
10
+ class BigQueryImporter(Importer):
11
+ def import_source(
12
+ self, data_contract_specification: DataContractSpecification, source: str, import_args: dict
13
+ ) -> dict:
14
+ if source is not None:
15
+ data_contract_specification = import_bigquery_from_json(data_contract_specification, source)
16
+ else:
17
+ data_contract_specification = import_bigquery_from_api(
18
+ data_contract_specification,
19
+ import_args.get("bigquery_tables"),
20
+ import_args.get("bigquery_project"),
21
+ import_args.get("bigquery_dataset"),
22
+ )
23
+ return data_contract_specification
24
+
25
+
10
26
  def import_bigquery_from_json(
11
27
  data_contract_specification: DataContractSpecification, source: str
12
28
  ) -> DataContractSpecification:
@@ -30,6 +46,18 @@ def import_bigquery_from_api(
30
46
  bigquery_project: str,
31
47
  bigquery_dataset: str,
32
48
  ) -> DataContractSpecification:
49
+ try:
50
+ from google.cloud import bigquery
51
+ except ImportError as e:
52
+ raise DataContractException(
53
+ type="schema",
54
+ result="failed",
55
+ name="bigquery extra missing",
56
+ reason="Install the extra datacontract-cli[bigquery] to use bigquery",
57
+ engine="datacontract",
58
+ original_exception=e,
59
+ )
60
+
33
61
  client = bigquery.Client(project=bigquery_project)
34
62
 
35
63
  if bigquery_tables is None:
@@ -63,7 +91,7 @@ def import_bigquery_from_api(
63
91
  return data_contract_specification
64
92
 
65
93
 
66
- def fetch_table_names(client: bigquery.Client, dataset: str) -> List[str]:
94
+ def fetch_table_names(client, dataset: str) -> List[str]:
67
95
  table_names = []
68
96
  api_tables = client.list_tables(dataset)
69
97
  for api_table in api_tables:
@@ -84,7 +112,9 @@ def convert_bigquery_schema(
84
112
  # what exactly leads to friendlyName being set
85
113
  table_id = bigquery_schema.get("tableReference").get("tableId")
86
114
 
87
- data_contract_specification.models[table_id] = Model(fields=fields, type="table")
115
+ data_contract_specification.models[table_id] = Model(
116
+ fields=fields, type=map_bigquery_type(bigquery_schema.get("type"))
117
+ )
88
118
 
89
119
  # Copy the description, if it exists
90
120
  if bigquery_schema.get("description") is not None:
@@ -176,3 +206,16 @@ def map_type_from_bigquery(bigquery_type_str: str):
176
206
  reason=f"Unsupported type {bigquery_type_str} in bigquery json definition.",
177
207
  engine="datacontract",
178
208
  )
209
+
210
+
211
+ def map_bigquery_type(bigquery_type: str) -> str:
212
+ if bigquery_type == "TABLE" or bigquery_type == "EXTERNAL" or bigquery_type == "SNAPSHOT":
213
+ return "table"
214
+ elif bigquery_type == "VIEW" or bigquery_type == "MATERIALIZED_VIEW":
215
+ return "view"
216
+ else:
217
+ logger = logging.getLogger(__name__)
218
+ logger.info(
219
+ f"Can't properly map bigquery table type '{bigquery_type}' to datacontracts model types. Mapping it to table."
220
+ )
221
+ return "table"