mario-pipeline-tools 0.1__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.
@@ -0,0 +1,10 @@
1
+ Metadata-Version: 2.1
2
+ Name: mario-pipeline-tools
3
+ Version: 0.1
4
+ Summary: Base classes and helpers for common data pipeline tasks
5
+ Home-page: https://github.com/JiscDACT/mario
6
+ Author: scottbw
7
+ Author-email: scott.wilson@jisc.ac.uk
8
+ License: all rights reserved
9
+
10
+ A set of base classes and helpers for common data pipeline tasks, whether using in local control scripts or in Airflow tasks.
@@ -0,0 +1,10 @@
1
+ # General purpose data pipelines
2
+
3
+ A set of base classes and helpers for
4
+ common data pipeline tasks, whether using
5
+ in local control scripts or in Airflow tasks.
6
+
7
+ ## !WARNING!
8
+ This is a public repository for ease of deployment.
9
+ Be especially careful not to commit anything
10
+ that shouldn't be here.
File without changes
@@ -0,0 +1,235 @@
1
+ import logging
2
+ import shutil
3
+
4
+ import pandas as pd
5
+ import pantab
6
+ from pandas import DataFrame
7
+ from tableauhyperapi import TableName
8
+
9
+ from mario.dataset_specification import DatasetSpecification
10
+ from mario.metadata import Metadata
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class Configuration:
16
+ """
17
+ Configuration for a data extractor. This can include:
18
+ * A connection string: an ODBC connection string for SQL extraction
19
+ * A view name: A view as the base for the SQL code (H+, TDSA only)
20
+ * A hook: An object or function used to access data, e.g. S3
21
+ * A file path: A data file to load as the source of data, e.g. data.csv
22
+ """
23
+
24
+ def __init__(self,
25
+ connection_string: str = None,
26
+ hook=None,
27
+ view: str = None,
28
+ file_path: str = None
29
+ ):
30
+ self.connection_string = connection_string
31
+ self.hook = hook
32
+ self.view = view
33
+ self.file_path = file_path
34
+
35
+
36
+ class DataExtractor:
37
+
38
+ def __init__(self,
39
+ configuration: Configuration,
40
+ dataset_specification: DatasetSpecification,
41
+ metadata: Metadata):
42
+ self.configuration = configuration
43
+ self.dataset_specification = dataset_specification
44
+ self.metadata = metadata
45
+ self._data = pd.DataFrame()
46
+ if configuration.file_path is not None:
47
+ if configuration.file_path.endswith('.csv'):
48
+ self.__load_from_csv__()
49
+ elif configuration.file_path.endswith('.hyper'):
50
+ self.__load_from_hyper__()
51
+ else:
52
+ raise ValueError("Unsupported file type")
53
+ elif configuration.hook is not None:
54
+ # TODO something here for Airflow hooks
55
+ pass
56
+ else:
57
+ self.__load_from_sql__()
58
+
59
+ def __load_from_sql__(self):
60
+ query = self.__build_query__()
61
+ # TODO connect using ODBC connection string
62
+ # TODO run query using pyodbc
63
+ logger.info("Executing query: " + query)
64
+
65
+ def __build_query__(self):
66
+ query = ''
67
+ if self.configuration.view is not None:
68
+ query = 'SELECT * FROM ' + self.configuration.view + ' WITH(NOLOCK)'
69
+ else:
70
+ # TODO: Automation 2.0 or TDSA query builder
71
+ pass
72
+ return query
73
+
74
+ def __load_from_csv__(self):
75
+ self._data = pd.read_csv(self.configuration.file_path)
76
+
77
+ def __load_from_hyper__(self):
78
+ from tableau_builder import hyper_utils
79
+ table_parts = hyper_utils.get_default_table_and_schema(hyper_path=self.configuration.file_path)
80
+ table = TableName(table_parts['schema'], table_parts['table'])
81
+ self._data = pantab.frame_from_hyper(
82
+ source=self.configuration.file_path,
83
+ table=table
84
+ )
85
+
86
+ def __minimise_data__(self):
87
+ """ Minimise data so we only keep the columns in the spec """
88
+ columns_to_keep = []
89
+ for item in self.dataset_specification.items:
90
+ if not self.metadata.get_metadata(item).get_property('formula'):
91
+ columns_to_keep.append(item)
92
+ self._data = self._data[columns_to_keep]
93
+
94
+ def validate_data(self):
95
+ validation_errors = []
96
+ for item in self.dataset_specification.items:
97
+ metadata = self.metadata.get_metadata(item)
98
+ # Ignore calculated fields
99
+ if metadata.get_property('formula') is None:
100
+ # Check all columns present
101
+ if item not in self._data.columns:
102
+ validation_errors.append("Item missing: " + item)
103
+ else:
104
+ # Check domain
105
+ if metadata.get_property('domain') is not None:
106
+ data_domain = self._data[item].unique()
107
+ metadata_domain = metadata.get_property('domain')
108
+ for element in data_domain:
109
+ if element not in metadata_domain:
110
+ validation_errors.append("Domain validation failed for " + item)
111
+ logger.error("Validation error: '" + str(element) + "' is not in domain of " + item)
112
+ # Check range
113
+ if metadata.get_property('range') is not None:
114
+ min_value = metadata.get_property('range')[0]
115
+ max_value = metadata.get_property('range')[1]
116
+ data_min = self._data[item].min()
117
+ data_max = self._data[item].max()
118
+ if data_min < min_value:
119
+ validation_errors.append("Range validation failed for " + item)
120
+ logger.error("Validation error: '" + str(data_min) + "' is less than " + str(min_value))
121
+ if data_max > max_value:
122
+ validation_errors.append("Range validation failed for " + item)
123
+ logger.error("Validation error: '" + str(data_max) + "' is greater than " + str(max_value))
124
+
125
+ if len(validation_errors) != 0:
126
+ for validation_error in validation_errors:
127
+ logger.error(validation_error)
128
+ raise ValueError("Data validation failed - check the logs for details")
129
+ return True
130
+
131
+ def get_data_frame(self) -> DataFrame:
132
+ return self._data
133
+
134
+ def save_data_as_excel(self, file_path: str, minimise=True):
135
+ if minimise:
136
+ self.__minimise_data__()
137
+ # TODO use OpenPyxl here, or drop this method
138
+ self._data.to_csv(file_path)
139
+ pass
140
+
141
+ def save_data_as_csv(self, file_path: str, minimise=True):
142
+ if minimise:
143
+ self.__minimise_data__()
144
+ self._data.to_csv(file_path)
145
+
146
+ def save_data_as_hyper(self, file_path: str, table: str = 'Extract', schema: str = 'Extract', minimise=True):
147
+ table_name = TableName(schema, table)
148
+ if minimise:
149
+ self.__minimise_data__()
150
+ pantab.frame_to_hyper(df=self._data, database=file_path, table=table_name)
151
+
152
+
153
+ class HyperFile(DataExtractor):
154
+ """
155
+ Wrapper for a HyperFile as a data extractor - use when no data needs to be extracted,
156
+ and we just want to treat a hyper as a hyper with no conversion to/from dataframe
157
+ """
158
+
159
+ def __init__(self,
160
+ configuration: Configuration,
161
+ dataset_specification: DatasetSpecification,
162
+ metadata: Metadata):
163
+ super().__init__(configuration, dataset_specification, metadata)
164
+
165
+ def validate_data(self):
166
+ from tableau_builder.hyper_utils import get_default_table_and_schema, check_column_exists, check_type, \
167
+ check_domain, check_range
168
+
169
+ validation_errors = []
170
+
171
+ table_schema = get_default_table_and_schema(self.configuration.file_path)
172
+ table = table_schema['table']
173
+ schema = table_schema['schema']
174
+
175
+ for item in self.dataset_specification.items:
176
+ metadata = self.metadata.get_metadata(item)
177
+ if metadata.get_property('formula') is None:
178
+ if not check_column_exists(
179
+ hyper_path=self.configuration.file_path,
180
+ column_name=item,
181
+ table_name=table,
182
+ schema_name=schema
183
+ ):
184
+ validation_errors.append("Item missing: " + item)
185
+
186
+ if metadata.get_property('datatype') and not check_type(
187
+ hyper_path=self.configuration.file_path,
188
+ column_name=item,
189
+ expected_type=metadata.get_property('datatype'),
190
+ table_name=table,
191
+ schema_name=schema
192
+ ):
193
+ validation_errors.append("Item is wrong type: " + item)
194
+
195
+ if metadata.get_property('domain') is not None:
196
+ if not check_domain(
197
+ hyper_path=self.configuration.file_path,
198
+ field=item,
199
+ domain=metadata.get_property('domain'),
200
+ table_name=table,
201
+ schema_name=schema
202
+ ):
203
+ validation_errors.append("Domain validation failed for " + item)
204
+ if metadata.get_property('range') is not None:
205
+ if not check_range(
206
+ hyper_path=self.configuration.file_path,
207
+ field=item,
208
+ min_value=metadata.get_property('range')[0],
209
+ max_value=metadata.get_property('range')[1],
210
+ table_name=table,
211
+ schema_name=schema
212
+ ):
213
+ validation_errors.append("Range validation failed for " + item)
214
+
215
+ if len(validation_errors) != 0:
216
+ for validation_error in validation_errors:
217
+ logger.error(validation_error)
218
+ raise ValueError("Domain validation failed - check the logs for details")
219
+ return True
220
+
221
+ def __minimise_data__(self):
222
+ from tableau_builder import hyper_utils
223
+ columns_to_keep = self.dataset_specification.items
224
+ table = hyper_utils.get_default_table_and_schema(self.configuration.file_path)
225
+ hyper_utils.subset_columns(
226
+ columns_to_keep=columns_to_keep,
227
+ hyper_path=self.configuration.file_path,
228
+ schema_name=table['schema'],
229
+ table_name=table['table']
230
+ )
231
+
232
+ def save_data_as_hyper(self, file_path: str, table: str = 'Extract', schema: str = 'Extract', minimise=True):
233
+ if minimise:
234
+ self.__minimise_data__()
235
+ shutil.copyfile(self.configuration.file_path, file_path)
@@ -0,0 +1,99 @@
1
+ import logging
2
+ import os.path
3
+ import shutil
4
+ import tempfile
5
+ from enum import Enum
6
+
7
+ from tableau_builder.hyper_utils import get_default_table_and_schema
8
+ from tableau_builder.json_metadata import JsonRepository
9
+
10
+ from mario.data_extractor import DataExtractor
11
+ from mario.dataset_specification import DatasetSpecification
12
+ from mario.metadata import Metadata
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class Format(Enum):
18
+ TABLEAU_PACKAGED_DATASOURCE = 'tdsx'
19
+ POWER_BI_PACKAGE = 'pbix'
20
+ EXCEL_PIVOT = 'xlsx'
21
+ CSV = 'csv'
22
+
23
+
24
+ class DatasetBuilder:
25
+
26
+ def __init__(self,
27
+ dataset_specification: DatasetSpecification = None,
28
+ metadata: Metadata = None,
29
+ data: DataExtractor = None
30
+ ):
31
+ self.dataset_specification = dataset_specification
32
+ self.metadata = metadata
33
+ self.data = data
34
+
35
+ def validate_metadata(self):
36
+ """ Checks whether the metadata matches the specification"""
37
+ not_included = []
38
+ fields = self.dataset_specification.items
39
+
40
+ for field in fields:
41
+ try:
42
+ self.metadata.get_metadata(field)
43
+ except ValueError:
44
+ not_included.append(field)
45
+
46
+ if len(not_included) > 0:
47
+ logger.error(self.dataset_specification.name + ": Some metadata is missing")
48
+ logger.error(not_included)
49
+ return False
50
+ else:
51
+ logger.info(self.dataset_specification.name + ": All metadata present")
52
+ return True
53
+
54
+ def build(self, output_format: Format, file_path: str):
55
+ if output_format == Format.TABLEAU_PACKAGED_DATASOURCE:
56
+ self.__build_tdsx__(file_path)
57
+
58
+ def __build_excel_pivot__(self, file_path: str):
59
+ # TODO export Excel and Info sheet using OpenPyxl - see code in Automation2.0 and TDSA.
60
+ pass
61
+
62
+ def __build_csv__(self, file_path: str):
63
+ # TODO export Info sheet as well - see code in Automation2.0 and TDSA.
64
+ self.data.save_data_as_csv(file_path=file_path)
65
+
66
+ def __build_tdsx__(self, file_path: str):
67
+ with tempfile.TemporaryDirectory() as temp_folder:
68
+
69
+ # Metadata - convert to internal format used for Tableau
70
+ metadata_file_path = os.path.join(temp_folder, 'metadata.json')
71
+ self.metadata.save(metadata_file_path)
72
+ repository = JsonRepository(metadata_file_path)
73
+
74
+ # Move the hyper extract
75
+ data_file_path = os.path.join(temp_folder, 'data.hyper')
76
+ self.data.save_data_as_hyper(file_path=data_file_path)
77
+
78
+ # Get the table and schema name from the extract
79
+ table_schema = get_default_table_and_schema(data_file_path)
80
+
81
+ # Save the spec
82
+ dataset_file_path = os.path.join(temp_folder, 'dataset.json')
83
+ self.dataset_specification.save(dataset_file_path)
84
+
85
+ # Build a tdsx in a temp file
86
+ output_path = os.path.join(temp_folder, 'build')
87
+ from tableau_builder.dataset import create_tdsx, HYPER
88
+ create_tdsx(
89
+ dataset_file=dataset_file_path,
90
+ metadata_repository=repository,
91
+ data_file=data_file_path,
92
+ output_file=output_path,
93
+ data_source_type=HYPER,
94
+ table_name=table_schema['table'],
95
+ schema_name=table_schema['schema'],
96
+ use_metadata_groups=True
97
+ )
98
+ shutil.copyfile(src=output_path + '.tdsx', dst=file_path)
99
+
@@ -0,0 +1,87 @@
1
+ import json
2
+ from typing import List
3
+
4
+
5
+ class DatasetSpecification:
6
+ """
7
+ Base class/interface for dataset specifications. Use an implementation specific to the
8
+ format of specification used.
9
+ """
10
+
11
+ def __init__(self):
12
+ self._name = ''
13
+ self._collection: str = ''
14
+ self._measures: List[str] = []
15
+ self._dimensions: List[str] = []
16
+ self._properties = []
17
+
18
+ @property
19
+ def name(self):
20
+ return self._name
21
+
22
+ @name.setter
23
+ def name(self, value):
24
+ self._name = value
25
+
26
+ @property
27
+ def collection(self):
28
+ return self._collection
29
+
30
+ @collection.setter
31
+ def collection(self, value):
32
+ self._collection = value
33
+
34
+ @property
35
+ def enquiry(self):
36
+ return ''
37
+
38
+ @property
39
+ def measures(self):
40
+ return self._measures
41
+
42
+ @measures.setter
43
+ def measures(self, value):
44
+ self._measures = value
45
+
46
+ @property
47
+ def dimensions(self):
48
+ return self._dimensions
49
+
50
+ @dimensions.setter
51
+ def dimensions(self, value):
52
+ self._dimensions = value
53
+
54
+ @property
55
+ def items(self):
56
+ return self.dimensions + self.measures
57
+
58
+ def set_property(self, name, value):
59
+ self._properties[name] = value
60
+
61
+ def get_property(self, name):
62
+ if name in self._properties:
63
+ return self._properties[name]
64
+ return None
65
+
66
+ def save(self, file_path: str):
67
+ json_representation = {
68
+ "name": self.name,
69
+ "collection": self.collection,
70
+ "dimensions": self.dimensions,
71
+ "measures": self.measures
72
+ }
73
+ with open(file_path, 'w') as file:
74
+ json.dump(json_representation, file, default=vars)
75
+
76
+
77
+ def dataset_from_json(file_path: str = None) -> DatasetSpecification:
78
+ with open(file_path, mode='r') as source_file:
79
+ spec = json.load(source_file)
80
+
81
+ dataset_specification = DatasetSpecification()
82
+ dataset_specification.name = spec['name']
83
+ dataset_specification.collection = spec['collection']
84
+ dataset_specification.measures = spec['measures']
85
+ dataset_specification.dimensions = spec['dimensions']
86
+
87
+ return dataset_specification
@@ -0,0 +1,28 @@
1
+ import os
2
+
3
+ from mario.data_extractor import DataExtractor, Configuration
4
+ from mario.dataset_specification import dataset_from_json
5
+ from mario.dataset_builder import DatasetBuilder, Format
6
+ from mario.metadata import metadata_from_json
7
+
8
+ # Build a TD extract in Excel
9
+ dataset = dataset_from_json("data_spec.xlsx")
10
+ metadata = metadata_from_json('metadata.json')
11
+ configuration = Configuration(connection_string=os.environ.get('odbc_connection_string'))
12
+ extractor = DataExtractor(configuration=configuration, dataset_specification=dataset, metadata=metadata)
13
+ # Either put automation code in here, or override the base class
14
+ builder = DatasetBuilder(dataset_specification=dataset, metadata=metadata, data=extractor)
15
+ path = os.path.join(dataset.enquiry, dataset.name + '.xlsx')
16
+ builder.build(file_path=path, output_format=Format.EXCEL_PIVOT)
17
+
18
+ # Build a Heidi Plus Datasource in TDSX
19
+ dataset = dataset_from_json("dataset.json")
20
+ metadata = metadata_from_json('metadata.json')
21
+ configuration = Configuration(
22
+ connection_string=os.environ.get('odbc_connection_string'),
23
+ view='dbo.v_heidi_plus_student_fpe'
24
+ )
25
+ extractor = DataExtractor(configuration=configuration, dataset_specification=dataset, metadata=metadata)
26
+ builder = DatasetBuilder(dataset_specification=dataset, metadata=metadata, data=extractor)
27
+ path = os.path.join('datasources', dataset.collection, dataset.name + '.tdsx')
28
+ builder.build(file_path=path, output_format=Format.TABLEAU_PACKAGED_DATASOURCE)
@@ -0,0 +1,113 @@
1
+ import json
2
+ from typing import List
3
+
4
+
5
+ class Item:
6
+
7
+ def __init__(self):
8
+ self._name = ''
9
+ self._description = ''
10
+ self._properties = {}
11
+
12
+ @property
13
+ def name(self):
14
+ return self._name
15
+
16
+ @name.setter
17
+ def name(self, value):
18
+ self._name = value
19
+
20
+ @property
21
+ def description(self):
22
+ return self._description
23
+
24
+ @description.setter
25
+ def description(self, value):
26
+ self._description = value
27
+
28
+ def set_property(self, name, value):
29
+ self._properties[name] = value
30
+
31
+ def get_property(self, name):
32
+ if name in self._properties:
33
+ return self._properties[name]
34
+ return None
35
+
36
+ def to_json(self):
37
+ json_representation = {
38
+ "name": self.name,
39
+ "description": self.description
40
+ }
41
+ for key, value in self._properties.items():
42
+ json_representation[key] = value
43
+ return json_representation
44
+
45
+
46
+ class Metadata(Item):
47
+
48
+ def __init__(self, name: str = None):
49
+ super().__init__()
50
+ self._items: List[Item] = []
51
+ self.name = name
52
+ if self.name is None:
53
+ self.name = 'Metadata'
54
+
55
+ def get_metadata(self, name: str):
56
+ for item in self._items:
57
+ if item.name == name:
58
+ return item
59
+ return None
60
+
61
+ @property
62
+ def items(self):
63
+ return self._items
64
+
65
+ def add_item(self, item: Item) -> None:
66
+ self._items.append(item)
67
+
68
+ def merge_items(self, metadata) -> None:
69
+ for item in metadata.items:
70
+ self.add_item(item)
71
+
72
+ def save(self, file_path: str = None) -> None:
73
+ json_representation = {
74
+ "collection": {
75
+ "name": self.name,
76
+ "items": []
77
+ }
78
+ }
79
+ for item in self._items:
80
+ json_representation['collection']['items'].append(item.to_json())
81
+
82
+ with open(file_path, mode='w') as file:
83
+ json.dump(json_representation, file, default=vars)
84
+
85
+
86
+ def metadata_from_json(file_path: str = None):
87
+ """ Factory method for creating a Metadata instance from a JSON file"""
88
+ metadata = Metadata()
89
+
90
+ with open(file_path) as metadata_file:
91
+ metadata_json = json.load(metadata_file)
92
+
93
+ if 'collection' in metadata_json:
94
+ collection = 'collection'
95
+ items = 'items'
96
+ name = 'name'
97
+ else:
98
+ collection = 'datasource'
99
+ items = 'fields'
100
+ name = 'fieldName'
101
+
102
+ metadata.name = metadata_json[collection]['name']
103
+
104
+ for item in metadata_json[collection][items]:
105
+ metadata_item = Item()
106
+ metadata_item.name = item[name]
107
+ metadata_item.description = item['description']
108
+ for prop in item:
109
+ if prop not in [name, 'description']:
110
+ metadata_item.set_property(prop, item[prop])
111
+ metadata.add_item(metadata_item)
112
+
113
+ return metadata
@@ -0,0 +1,9 @@
1
+ import os
2
+ from datetime import datetime
3
+
4
+
5
+ def append_current_date_to_file_name(file_name: str) -> str:
6
+ date_time = datetime.now().strftime("%Y_%m_%d")
7
+ filename = os.path.splitext(os.path.basename(file_name))[0]
8
+ extension = os.path.splitext(os.path.basename(file_name))[1]
9
+ return filename + '_' + date_time + extension
@@ -0,0 +1,10 @@
1
+ Metadata-Version: 2.1
2
+ Name: mario-pipeline-tools
3
+ Version: 0.1
4
+ Summary: Base classes and helpers for common data pipeline tasks
5
+ Home-page: https://github.com/JiscDACT/mario
6
+ Author: scottbw
7
+ Author-email: scott.wilson@jisc.ac.uk
8
+ License: all rights reserved
9
+
10
+ A set of base classes and helpers for common data pipeline tasks, whether using in local control scripts or in Airflow tasks.
@@ -0,0 +1,18 @@
1
+ README.md
2
+ setup.py
3
+ mario/__init__.py
4
+ mario/data_extractor.py
5
+ mario/dataset_builder.py
6
+ mario/dataset_specification.py
7
+ mario/example.py
8
+ mario/metadata.py
9
+ mario/utils.py
10
+ mario_pipeline_tools.egg-info/PKG-INFO
11
+ mario_pipeline_tools.egg-info/SOURCES.txt
12
+ mario_pipeline_tools.egg-info/dependency_links.txt
13
+ mario_pipeline_tools.egg-info/requires.txt
14
+ mario_pipeline_tools.egg-info/top_level.txt
15
+ test/test_data_extractor.py
16
+ test/test_dataset_specification.py
17
+ test/test_integrated.py
18
+ test/test_metadata.py
@@ -0,0 +1,4 @@
1
+ pandas
2
+ tableauhyperapi
3
+ pantab
4
+ tableau-builder==0.18
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,20 @@
1
+ from setuptools import setup
2
+
3
+ setup(
4
+ name='mario-pipeline-tools',
5
+ version='0.1',
6
+ packages=['mario'],
7
+ url='https://github.com/JiscDACT/mario',
8
+ license='all rights reserved',
9
+ author='scottbw',
10
+ author_email='scott.wilson@jisc.ac.uk',
11
+ description='Base classes and helpers for common data pipeline tasks',
12
+ long_description='A set of base classes and helpers for common data pipeline tasks, '
13
+ 'whether using in local control scripts or in Airflow tasks.',
14
+ package_data={
15
+ # If any package contains *.ini files, include them
16
+ '': ['*.ini']
17
+ },
18
+ include_package_data=True,
19
+ install_requires=['pandas', 'tableauhyperapi', 'pantab', 'tableau-builder==0.18']
20
+ )
@@ -0,0 +1,53 @@
1
+ import os
2
+ import tempfile
3
+
4
+ from mario.data_extractor import DataExtractor, Configuration
5
+ from mario.dataset_specification import dataset_from_json
6
+ from mario.metadata import metadata_from_json
7
+
8
+
9
+ def test_csv_to_csv():
10
+ dataset = dataset_from_json(os.path.join('test', 'dataset.json'))
11
+ metadata = metadata_from_json(os.path.join('test', 'metadata.json'))
12
+ configuration = Configuration(
13
+ file_path=os.path.join('test', 'orders.csv')
14
+ )
15
+ extractor = DataExtractor(
16
+ dataset_specification=dataset,
17
+ metadata=metadata,
18
+ configuration=configuration
19
+ )
20
+ extractor.validate_data()
21
+ with tempfile.NamedTemporaryFile(suffix='.csv') as file:
22
+ extractor.save_data_as_csv(file_path=file.name)
23
+
24
+
25
+ def test_csv_to_hyper():
26
+ dataset = dataset_from_json(os.path.join('test', 'dataset.json'))
27
+ metadata = metadata_from_json(os.path.join('test', 'metadata.json'))
28
+ configuration = Configuration(
29
+ file_path=os.path.join('test', 'orders.csv')
30
+ )
31
+ extractor = DataExtractor(
32
+ dataset_specification=dataset,
33
+ metadata=metadata,
34
+ configuration=configuration
35
+ )
36
+ with tempfile.NamedTemporaryFile(suffix='.hyper') as file:
37
+ extractor.save_data_as_hyper(file_path=file.name)
38
+
39
+
40
+ def test_hyper_to_csv():
41
+ dataset = dataset_from_json(os.path.join('test', 'dataset.json'))
42
+ metadata = metadata_from_json(os.path.join('test', 'metadata.json'))
43
+ configuration = Configuration(
44
+ file_path=os.path.join('test', 'orders.hyper')
45
+ )
46
+ extractor = DataExtractor(
47
+ dataset_specification=dataset,
48
+ metadata=metadata,
49
+ configuration=configuration
50
+ )
51
+ extractor.validate_data()
52
+ with tempfile.NamedTemporaryFile(suffix='.csv') as file:
53
+ extractor.save_data_as_csv(file_path=file.name)
@@ -0,0 +1,11 @@
1
+ import os
2
+
3
+ from mario.dataset_specification import dataset_from_json
4
+
5
+
6
+ def test_import_dataset():
7
+ specification_file = os.path.join('test', 'dataset.json')
8
+ specification = dataset_from_json(file_path=specification_file)
9
+
10
+ assert specification.collection == 'superstore'
11
+ assert len(specification.measures) == 4
@@ -0,0 +1,27 @@
1
+ import os
2
+
3
+ from mario.data_extractor import Configuration, HyperFile
4
+ from mario.dataset_builder import DatasetBuilder, Format
5
+ from mario.dataset_specification import dataset_from_json
6
+ from mario.metadata import metadata_from_json
7
+
8
+
9
+ def test_integration_tdsx():
10
+ dataset = dataset_from_json(os.path.join('test', 'dataset.json'))
11
+ metadata = metadata_from_json(os.path.join('test', 'metadata.json'))
12
+ configuration = Configuration(file_path=os.path.join('test', 'orders.hyper'))
13
+ extractor = HyperFile(configuration=configuration, dataset_specification=dataset, metadata=metadata)
14
+ builder = DatasetBuilder(dataset_specification=dataset, metadata=metadata, data=extractor)
15
+ path = os.path.join('output', dataset.collection, dataset.name + '.tdsx')
16
+ os.makedirs(os.path.join('output', dataset.collection), exist_ok=True)
17
+ builder.build(file_path=path, output_format=Format.TABLEAU_PACKAGED_DATASOURCE)
18
+
19
+ def test_integration_csv():
20
+ dataset = dataset_from_json(os.path.join('test', 'dataset.json'))
21
+ metadata = metadata_from_json(os.path.join('test', 'metadata.json'))
22
+ configuration = Configuration(file_path=os.path.join('test', 'orders.hyper'))
23
+ extractor = HyperFile(configuration=configuration, dataset_specification=dataset, metadata=metadata)
24
+ builder = DatasetBuilder(dataset_specification=dataset, metadata=metadata, data=extractor)
25
+ path = os.path.join('output', dataset.collection, dataset.name + '.csv')
26
+ os.makedirs(os.path.join('output', dataset.collection), exist_ok=True)
27
+ builder.build(file_path=path, output_format=Format.CSV)
@@ -0,0 +1,24 @@
1
+ import os
2
+ import tempfile
3
+
4
+ from mario.metadata import metadata_from_json
5
+
6
+
7
+ def test_load_metadata():
8
+ metadata_file = os.path.join('test', 'metadata.json')
9
+ metadata = metadata_from_json(file_path=metadata_file)
10
+ assert metadata.get_metadata('Ship Mode') is not None
11
+ assert metadata.get_metadata('Ship Mode').description is not None
12
+ assert metadata.get_metadata('Ship Mode').get_property('groups') == ['Shipping']
13
+
14
+
15
+ def test_save_metadata():
16
+ metadata_file = os.path.join('test', 'metadata.json')
17
+ metadata = metadata_from_json(file_path=metadata_file)
18
+ with tempfile.NamedTemporaryFile() as file:
19
+ metadata.save(file_path=file.name)
20
+
21
+
22
+ def test_load_tdsa():
23
+ metadata_file = os.path.join('test', 'tdsa.json')
24
+ metadata = metadata_from_json(file_path=metadata_file)