keboola.component 1.6.0__tar.gz → 1.6.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {keboola.component-1.6.0/src/keboola.component.egg-info → keboola.component-1.6.3}/PKG-INFO +1 -1
- {keboola.component-1.6.0 → keboola.component-1.6.3}/setup.py +1 -1
- {keboola.component-1.6.0 → keboola.component-1.6.3}/src/keboola/component/dao.py +212 -100
- {keboola.component-1.6.0 → keboola.component-1.6.3}/src/keboola/component/interface.py +33 -10
- {keboola.component-1.6.0 → keboola.component-1.6.3/src/keboola.component.egg-info}/PKG-INFO +1 -1
- {keboola.component-1.6.0 → keboola.component-1.6.3}/LICENSE +0 -0
- {keboola.component-1.6.0 → keboola.component-1.6.3}/README.md +0 -0
- {keboola.component-1.6.0 → keboola.component-1.6.3}/setup.cfg +0 -0
- {keboola.component-1.6.0 → keboola.component-1.6.3}/src/keboola/component/__init__.py +0 -0
- {keboola.component-1.6.0 → keboola.component-1.6.3}/src/keboola/component/base.py +0 -0
- {keboola.component-1.6.0 → keboola.component-1.6.3}/src/keboola/component/exceptions.py +0 -0
- {keboola.component-1.6.0 → keboola.component-1.6.3}/src/keboola/component/sync_actions.py +0 -0
- {keboola.component-1.6.0 → keboola.component-1.6.3}/src/keboola/component/table_schema.py +0 -0
- {keboola.component-1.6.0 → keboola.component-1.6.3}/src/keboola.component.egg-info/SOURCES.txt +0 -0
- {keboola.component-1.6.0 → keboola.component-1.6.3}/src/keboola.component.egg-info/dependency_links.txt +0 -0
- {keboola.component-1.6.0 → keboola.component-1.6.3}/src/keboola.component.egg-info/not-zip-safe +0 -0
- {keboola.component-1.6.0 → keboola.component-1.6.3}/src/keboola.component.egg-info/requires.txt +0 -0
- {keboola.component-1.6.0 → keboola.component-1.6.3}/src/keboola.component.egg-info/top_level.txt +0 -0
|
@@ -1,14 +1,20 @@
|
|
|
1
|
+
# Python 3.7 support
|
|
2
|
+
from __future__ import annotations
|
|
1
3
|
import dataclasses
|
|
2
4
|
import json
|
|
3
5
|
import logging
|
|
6
|
+
import warnings
|
|
4
7
|
from abc import ABC, abstractmethod
|
|
8
|
+
from collections import OrderedDict
|
|
5
9
|
from dataclasses import dataclass, field
|
|
6
10
|
from datetime import datetime
|
|
7
11
|
from enum import Enum
|
|
8
12
|
from pathlib import Path
|
|
9
13
|
from typing import List, Union, Dict, Optional, OrderedDict as TypeOrderedDict
|
|
10
|
-
|
|
14
|
+
|
|
15
|
+
|
|
11
16
|
from deprecated import deprecated
|
|
17
|
+
|
|
12
18
|
from .exceptions import UserException
|
|
13
19
|
|
|
14
20
|
try:
|
|
@@ -218,6 +224,22 @@ class TableMetadata:
|
|
|
218
224
|
|
|
219
225
|
Returns: dict
|
|
220
226
|
|
|
227
|
+
"""
|
|
228
|
+
|
|
229
|
+
return self._get_legacy_column_metadata_for_manifest()
|
|
230
|
+
|
|
231
|
+
def _get_legacy_column_metadata_for_manifest(self) -> dict:
|
|
232
|
+
"""
|
|
233
|
+
Returns column metadata dict as required by the
|
|
234
|
+
[manifest format](https://developers.keboola.com/extend/common-interface/manifest-files/#dataintables
|
|
235
|
+
-manifests)
|
|
236
|
+
|
|
237
|
+
e.g.
|
|
238
|
+
tm = TableMetadata()
|
|
239
|
+
manifest['column_metadata'] = tm.column_metadata
|
|
240
|
+
|
|
241
|
+
Returns: dict
|
|
242
|
+
|
|
221
243
|
"""
|
|
222
244
|
final_column_metadata = dict()
|
|
223
245
|
|
|
@@ -420,9 +442,9 @@ class TableMetadata:
|
|
|
420
442
|
|
|
421
443
|
|
|
422
444
|
@dataclass
|
|
423
|
-
class DataType
|
|
445
|
+
class DataType:
|
|
424
446
|
dtype: str
|
|
425
|
-
length: Optional[
|
|
447
|
+
length: Optional[str] = None
|
|
426
448
|
default: Optional[str] = None
|
|
427
449
|
|
|
428
450
|
def __post_init__(self):
|
|
@@ -430,10 +452,41 @@ class DataType(dict):
|
|
|
430
452
|
self.dtype = self.dtype.value
|
|
431
453
|
|
|
432
454
|
|
|
433
|
-
class BaseType(
|
|
434
|
-
def __init__(self, dtype: SupportedDataTypes = SupportedDataTypes.STRING, length: Optional[
|
|
455
|
+
class BaseType(dict):
|
|
456
|
+
def __init__(self, dtype: SupportedDataTypes = SupportedDataTypes.STRING, length: Optional[str] = None,
|
|
435
457
|
default: Optional[str] = None):
|
|
436
|
-
super().__init__(dtype=dtype, length=length, default=default)
|
|
458
|
+
super().__init__(base=DataType(dtype=dtype, length=length, default=default))
|
|
459
|
+
|
|
460
|
+
@classmethod
|
|
461
|
+
def string(cls, length: Optional[str] = None, default: Optional[str] = None) -> 'BaseType':
|
|
462
|
+
return BaseType(dtype=SupportedDataTypes.STRING, length=length, default=default)
|
|
463
|
+
|
|
464
|
+
@classmethod
|
|
465
|
+
def integer(cls, length: Optional[str] = None,
|
|
466
|
+
default: Optional[str] = None) -> 'BaseType':
|
|
467
|
+
return BaseType(dtype=SupportedDataTypes.INTEGER, length=length, default=default)
|
|
468
|
+
|
|
469
|
+
@classmethod
|
|
470
|
+
def numeric(cls, length: Optional[str] = None,
|
|
471
|
+
default: Optional[str] = None) -> 'BaseType':
|
|
472
|
+
return BaseType(dtype=SupportedDataTypes.NUMERIC, length=length, default=default)
|
|
473
|
+
|
|
474
|
+
@classmethod
|
|
475
|
+
def float(cls, length: Optional[str] = None,
|
|
476
|
+
default: Optional[str] = None) -> 'BaseType':
|
|
477
|
+
return BaseType(dtype=SupportedDataTypes.FLOAT, length=length, default=default)
|
|
478
|
+
|
|
479
|
+
@classmethod
|
|
480
|
+
def boolean(cls, default: Optional[str] = None) -> 'BaseType':
|
|
481
|
+
return BaseType(dtype=SupportedDataTypes.BOOLEAN, default=default)
|
|
482
|
+
|
|
483
|
+
@classmethod
|
|
484
|
+
def date(cls, default: Optional[str] = None) -> 'BaseType':
|
|
485
|
+
return BaseType(dtype=SupportedDataTypes.DATE, default=default)
|
|
486
|
+
|
|
487
|
+
@classmethod
|
|
488
|
+
def timestamp(cls, default: Optional[str] = None) -> 'BaseType':
|
|
489
|
+
return BaseType(dtype=SupportedDataTypes.TIMESTAMP, default=default)
|
|
437
490
|
|
|
438
491
|
|
|
439
492
|
@dataclass
|
|
@@ -457,15 +510,6 @@ class ColumnDefinition:
|
|
|
457
510
|
description: Optional[str] = None
|
|
458
511
|
metadata: Optional[Dict[str, str]] = None
|
|
459
512
|
|
|
460
|
-
def __post_init__(self):
|
|
461
|
-
if isinstance(self.data_types, DataType):
|
|
462
|
-
self.data_types = {"base": self.data_types}
|
|
463
|
-
|
|
464
|
-
def normalize_data_type(self, data_type: Union[Dict[str, DataType], BaseType]) -> Dict[str, DataType]:
|
|
465
|
-
if isinstance(data_type, DataType):
|
|
466
|
-
return {"base": data_type}
|
|
467
|
-
return data_type
|
|
468
|
-
|
|
469
513
|
def update_properties(self, **kwargs):
|
|
470
514
|
for key, value in kwargs.items():
|
|
471
515
|
if hasattr(self, key):
|
|
@@ -553,9 +597,6 @@ class IODefinition(ABC):
|
|
|
553
597
|
def __init__(self, full_path):
|
|
554
598
|
self.full_path = full_path
|
|
555
599
|
|
|
556
|
-
# infer stage by default
|
|
557
|
-
self.__stage = self.__get_stage_inferred()
|
|
558
|
-
|
|
559
600
|
@classmethod
|
|
560
601
|
def build_from_manifest(cls,
|
|
561
602
|
manifest_file_path: str
|
|
@@ -575,13 +616,13 @@ class IODefinition(ABC):
|
|
|
575
616
|
"""
|
|
576
617
|
Helper property marking the stage of the file. (str)
|
|
577
618
|
"""
|
|
578
|
-
return self.
|
|
619
|
+
return self._stage
|
|
579
620
|
|
|
580
621
|
@stage.setter
|
|
581
622
|
def stage(self, stage: str):
|
|
582
623
|
if stage not in ['in', 'out']:
|
|
583
624
|
raise ValueError(f'Invalid stage "{stage}", supported values are: "in", "out"')
|
|
584
|
-
self.
|
|
625
|
+
self._stage = stage
|
|
585
626
|
|
|
586
627
|
@property
|
|
587
628
|
@abstractmethod
|
|
@@ -599,17 +640,6 @@ class IODefinition(ABC):
|
|
|
599
640
|
"""
|
|
600
641
|
raise NotImplementedError
|
|
601
642
|
|
|
602
|
-
def __get_stage_inferred(self):
|
|
603
|
-
stage = 'in'
|
|
604
|
-
if not self.full_path or not Path(self.full_path).exists():
|
|
605
|
-
return stage
|
|
606
|
-
|
|
607
|
-
if Path(self.full_path).parent.parent.name == 'in':
|
|
608
|
-
stage = 'in'
|
|
609
|
-
elif Path(self.full_path).parent.parent.name == 'out':
|
|
610
|
-
stage = 'out'
|
|
611
|
-
return stage
|
|
612
|
-
|
|
613
643
|
# ############ Staging parameters
|
|
614
644
|
|
|
615
645
|
@dataclass
|
|
@@ -740,29 +770,19 @@ class TableDefinition(IODefinition):
|
|
|
740
770
|
def __init__(self, name: str,
|
|
741
771
|
full_path: Optional[Union[str, None]] = None,
|
|
742
772
|
is_sliced: Optional[bool] = False,
|
|
743
|
-
|
|
744
773
|
destination: Optional[str] = '',
|
|
745
774
|
primary_key: Optional[List[str]] = None,
|
|
746
|
-
|
|
775
|
+
schema: Optional[Union[TypeOrderedDict[str, ColumnDefinition], list[str]]] = None,
|
|
747
776
|
incremental: Optional[bool] = None,
|
|
748
777
|
table_metadata: Optional[TableMetadata] = None,
|
|
749
778
|
enclosure: Optional[str] = '"',
|
|
750
779
|
delimiter: Optional[str] = ',',
|
|
751
780
|
delete_where: Optional[dict] = None,
|
|
752
|
-
stage: Optional[str] = '
|
|
781
|
+
stage: Optional[str] = 'out',
|
|
753
782
|
write_always: Optional[bool] = False,
|
|
754
|
-
schema: Optional[TypeOrderedDict[str, ColumnDefinition]] = None,
|
|
755
|
-
rows_count: Optional[int] = None,
|
|
756
|
-
data_size_bytes: Optional[int] = None,
|
|
757
|
-
is_alias: Optional[bool] = False,
|
|
758
783
|
has_header: Optional[bool] = None,
|
|
759
|
-
|
|
760
784
|
# input
|
|
761
|
-
|
|
762
|
-
id: Optional[str] = '',
|
|
763
|
-
created: Optional[str] = None,
|
|
764
|
-
last_change_date: Optional[str] = None,
|
|
765
|
-
last_import_date: Optional[str] = None
|
|
785
|
+
**kwargs
|
|
766
786
|
):
|
|
767
787
|
"""
|
|
768
788
|
|
|
@@ -776,9 +796,9 @@ class TableDefinition(IODefinition):
|
|
|
776
796
|
The full_path is None when dealing with [workspaces](
|
|
777
797
|
https://developers.keboola.com/extend/common-interface/folders/#exchanging-data-via-workspace)
|
|
778
798
|
is_sliced: True if the full_path points to a folder with sliced tables
|
|
799
|
+
has_header: True if the file has a header, if emtpy inferred.
|
|
779
800
|
destination: String name of the table in Storage.
|
|
780
801
|
primary_key: List with names of columns used for primary key.
|
|
781
|
-
columns: List of columns for headless CSV files
|
|
782
802
|
incremental: Set to true to enable incremental loading
|
|
783
803
|
table_metadata: <.dao.TableMetadata> object containing column and table metadata (deprecated)
|
|
784
804
|
enclosure: str: CSV enclosure, by default "
|
|
@@ -787,19 +807,28 @@ class TableDefinition(IODefinition):
|
|
|
787
807
|
stage: str: Storage Stage 'in' or 'out'
|
|
788
808
|
write_always: Bool: If true, the table will be saved to Storage even when the job execution
|
|
789
809
|
fails.
|
|
790
|
-
schema: (dict) Mapping of column names andColumnDefinition objects
|
|
810
|
+
schema: (dict|lis[str]) Mapping of column names andColumnDefinition objects, or a list of names
|
|
791
811
|
|
|
792
812
|
"""
|
|
793
813
|
super().__init__(full_path)
|
|
794
814
|
self._name = name
|
|
795
815
|
self.is_sliced = is_sliced
|
|
796
816
|
|
|
797
|
-
self.schema = schema
|
|
798
|
-
|
|
799
817
|
# initialize manifest properties
|
|
800
818
|
self._destination = None
|
|
801
819
|
self.destination = destination
|
|
802
|
-
self.
|
|
820
|
+
self._schema = dict()
|
|
821
|
+
|
|
822
|
+
if schema:
|
|
823
|
+
self.schema = schema
|
|
824
|
+
# deprecated argument for backward compatibility
|
|
825
|
+
self._legacy_mode = False
|
|
826
|
+
if kwargs.get('force_legacy_mode'):
|
|
827
|
+
self._legacy_mode = True
|
|
828
|
+
if kwargs.get('columns'):
|
|
829
|
+
self.columns = kwargs['columns']
|
|
830
|
+
|
|
831
|
+
self._legacy_primary_key = list()
|
|
803
832
|
self.primary_key = primary_key
|
|
804
833
|
self._incremental = incremental
|
|
805
834
|
|
|
@@ -815,20 +844,25 @@ class TableDefinition(IODefinition):
|
|
|
815
844
|
self.delete_where_operator = None
|
|
816
845
|
|
|
817
846
|
self.set_delete_where_from_dict(delete_where)
|
|
818
|
-
self.stage = stage
|
|
819
847
|
self.write_always = write_always
|
|
820
|
-
self.legacy_columns = columns
|
|
821
|
-
self.has_header = has_header or self._has_header_in_file()
|
|
822
848
|
|
|
823
849
|
# input manifest properties
|
|
824
|
-
self._id = id
|
|
825
|
-
self._uri = uri
|
|
826
|
-
self._created = created
|
|
827
|
-
self._last_change_date = last_change_date
|
|
828
|
-
self._last_import_date = last_import_date
|
|
829
|
-
self._rows_count = rows_count
|
|
830
|
-
self._data_size_bytes = data_size_bytes
|
|
831
|
-
self._is_alias = is_alias
|
|
850
|
+
self._id = kwargs.get('id')
|
|
851
|
+
self._uri = kwargs.get('uri')
|
|
852
|
+
self._created = kwargs.get('created')
|
|
853
|
+
self._last_change_date = kwargs.get('last_change_date')
|
|
854
|
+
self._last_import_date = kwargs.get('last_import_date')
|
|
855
|
+
self._rows_count = kwargs.get('rows_count')
|
|
856
|
+
self._data_size_bytes = kwargs.get('data_size_bytes')
|
|
857
|
+
self._is_alias = kwargs.get('is_alias')
|
|
858
|
+
|
|
859
|
+
self.stage = stage
|
|
860
|
+
self.has_header = has_header or self._has_header_in_file()
|
|
861
|
+
|
|
862
|
+
def __get_stage_inferred(self):
|
|
863
|
+
if self._uri:
|
|
864
|
+
return 'in'
|
|
865
|
+
return 'out'
|
|
832
866
|
|
|
833
867
|
@classmethod
|
|
834
868
|
def build_output_definition(cls, name: str,
|
|
@@ -979,6 +1013,7 @@ class TableDefinition(IODefinition):
|
|
|
979
1013
|
schema[col.get("name")] = ColumnDefinition().from_dict(col)
|
|
980
1014
|
|
|
981
1015
|
else:
|
|
1016
|
+
# legacy support
|
|
982
1017
|
columns_metadata = json_data.get('column_metadata', {})
|
|
983
1018
|
primary_key = json_data.get('primary_key', [])
|
|
984
1019
|
columns = json_data.get('columns', [])
|
|
@@ -1045,11 +1080,25 @@ class TableDefinition(IODefinition):
|
|
|
1045
1080
|
else:
|
|
1046
1081
|
name = Path(manifest_file_path).stem
|
|
1047
1082
|
|
|
1083
|
+
# test if the manifest is output and incompatible
|
|
1084
|
+
force_legacy_mode = False
|
|
1085
|
+
if not manifest.get('columns') and manifest.get('primary_key'):
|
|
1086
|
+
warnings.warn('Primary key is set but columns are not. Forcing legacy mode for CSV file.',
|
|
1087
|
+
DeprecationWarning)
|
|
1088
|
+
force_legacy_mode = True
|
|
1089
|
+
|
|
1090
|
+
if manifest.get('id'):
|
|
1091
|
+
stage = 'in'
|
|
1092
|
+
else:
|
|
1093
|
+
stage = 'out'
|
|
1094
|
+
|
|
1048
1095
|
table_def = cls(name=name,
|
|
1096
|
+
stage=stage,
|
|
1049
1097
|
full_path=full_path,
|
|
1050
1098
|
is_sliced=is_sliced,
|
|
1051
1099
|
id=manifest.get('id'),
|
|
1052
1100
|
table_metadata=TableMetadata(manifest),
|
|
1101
|
+
primary_key=manifest.get('primary_key'),
|
|
1053
1102
|
schema=cls.return_schema_from_manifest(manifest),
|
|
1054
1103
|
uri=manifest.get('uri'),
|
|
1055
1104
|
created=manifest.get('created'),
|
|
@@ -1057,12 +1106,13 @@ class TableDefinition(IODefinition):
|
|
|
1057
1106
|
last_import_date=manifest.get('last_import_date'),
|
|
1058
1107
|
rows_count=manifest.get('rows_count'),
|
|
1059
1108
|
data_size_bytes=manifest.get('data_size_bytes'),
|
|
1060
|
-
is_alias=manifest.get('is_alias')
|
|
1109
|
+
is_alias=manifest.get('is_alias'),
|
|
1110
|
+
force_legacy_mode=force_legacy_mode
|
|
1061
1111
|
)
|
|
1062
1112
|
|
|
1063
1113
|
return table_def
|
|
1064
1114
|
|
|
1065
|
-
def get_manifest_dictionary(self, manifest_type: Optional[str] =
|
|
1115
|
+
def get_manifest_dictionary(self, manifest_type: Optional[str] = 'out', legacy_queue: bool = False,
|
|
1066
1116
|
legacy_manifest: Optional[bool] = None) -> dict:
|
|
1067
1117
|
"""
|
|
1068
1118
|
Returns manifest dictionary in appropriate manifest_type: either 'in' or 'out'.
|
|
@@ -1089,6 +1139,8 @@ class TableDefinition(IODefinition):
|
|
|
1089
1139
|
if not manifest_type:
|
|
1090
1140
|
manifest_type = self.stage
|
|
1091
1141
|
|
|
1142
|
+
if self._legacy_mode:
|
|
1143
|
+
legacy_manifest = True
|
|
1092
1144
|
dictionary = self._filter_attributes_by_manifest_type(manifest_type, legacy_queue, legacy_manifest)
|
|
1093
1145
|
|
|
1094
1146
|
filtered_dictionary = self._filter_dictionary(dictionary)
|
|
@@ -1134,14 +1186,13 @@ class TableDefinition(IODefinition):
|
|
|
1134
1186
|
'is_alias': self._is_alias,
|
|
1135
1187
|
|
|
1136
1188
|
'destination': self.destination,
|
|
1137
|
-
'columns': self.columns if not legacy_manifest else self.legacy_columns,
|
|
1138
1189
|
'incremental': self.incremental,
|
|
1139
1190
|
'primary_key': self.primary_key,
|
|
1140
1191
|
'write_always': self.write_always,
|
|
1141
1192
|
'delimiter': self.delimiter,
|
|
1142
1193
|
'enclosure': self.enclosure,
|
|
1143
1194
|
'metadata': self.table_metadata.get_table_metadata_for_manifest(legacy_manifest=True),
|
|
1144
|
-
'column_metadata': self.table_metadata.
|
|
1195
|
+
'column_metadata': self.table_metadata._get_legacy_column_metadata_for_manifest(),
|
|
1145
1196
|
'manifest_type': manifest_type,
|
|
1146
1197
|
'has_header': self.has_header,
|
|
1147
1198
|
'description': None,
|
|
@@ -1150,8 +1201,10 @@ class TableDefinition(IODefinition):
|
|
|
1150
1201
|
'delete_where_values': self.delete_where_values,
|
|
1151
1202
|
'delete_where_operator': self.delete_where_operator,
|
|
1152
1203
|
'schema': [col.to_dict(name)
|
|
1153
|
-
for name, col in self.schema.items()] if isinstance(self.schema, OrderedDict) else []
|
|
1204
|
+
for name, col in self.schema.items()] if isinstance(self.schema, (OrderedDict, dict)) else []
|
|
1154
1205
|
}
|
|
1206
|
+
if legacy_manifest:
|
|
1207
|
+
fields['columns'] = self.column_names
|
|
1155
1208
|
|
|
1156
1209
|
new_dict = fields.copy()
|
|
1157
1210
|
|
|
@@ -1164,7 +1217,7 @@ class TableDefinition(IODefinition):
|
|
|
1164
1217
|
def _has_header_in_file(self):
|
|
1165
1218
|
if self.is_sliced:
|
|
1166
1219
|
has_header = False
|
|
1167
|
-
elif self.
|
|
1220
|
+
elif self.column_names and not self.stage == 'in':
|
|
1168
1221
|
has_header = False
|
|
1169
1222
|
else:
|
|
1170
1223
|
has_header = True
|
|
@@ -1175,10 +1228,17 @@ class TableDefinition(IODefinition):
|
|
|
1175
1228
|
return self._schema
|
|
1176
1229
|
|
|
1177
1230
|
@schema.setter
|
|
1178
|
-
def schema(self, value):
|
|
1179
|
-
self._schema = OrderedDict()
|
|
1231
|
+
def schema(self, value: Union[TypeOrderedDict[str, ColumnDefinition], list[str]]):
|
|
1180
1232
|
if value:
|
|
1181
|
-
|
|
1233
|
+
if not isinstance(value, (list, dict, OrderedDict)):
|
|
1234
|
+
raise TypeError("Columns must be a list or a mapping of column names and ColumnDefinition objects")
|
|
1235
|
+
|
|
1236
|
+
if isinstance(value, list):
|
|
1237
|
+
self._schema = OrderedDict()
|
|
1238
|
+
for col in value:
|
|
1239
|
+
self._schema[col] = ColumnDefinition()
|
|
1240
|
+
else:
|
|
1241
|
+
self._schema = value
|
|
1182
1242
|
|
|
1183
1243
|
@property
|
|
1184
1244
|
def _manifest_attributes(self) -> SupportedManifestAttributes:
|
|
@@ -1232,21 +1292,27 @@ class TableDefinition(IODefinition):
|
|
|
1232
1292
|
@property
|
|
1233
1293
|
@deprecated(version='1.5.1', reason="Please use new column_names method instead of columns property")
|
|
1234
1294
|
def columns(self) -> List[str]:
|
|
1235
|
-
if isinstance(self.schema, OrderedDict):
|
|
1295
|
+
if isinstance(self.schema, (OrderedDict, dict)):
|
|
1236
1296
|
return list(self.schema.keys())
|
|
1237
1297
|
else:
|
|
1238
1298
|
return []
|
|
1239
1299
|
|
|
1240
1300
|
@columns.setter
|
|
1241
|
-
@deprecated(version='1.5.1', reason="
|
|
1301
|
+
@deprecated(version='1.5.1', reason="Please use new column_names method instead of schema property")
|
|
1242
1302
|
def columns(self, val: List[str]):
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1303
|
+
"""
|
|
1304
|
+
Set columns for the table.
|
|
1305
|
+
If list of names provided, the columns will be created with default settings Basetype.String.
|
|
1306
|
+
Args:
|
|
1307
|
+
val:
|
|
1246
1308
|
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1309
|
+
Returns:
|
|
1310
|
+
|
|
1311
|
+
"""
|
|
1312
|
+
if not isinstance(val, list):
|
|
1313
|
+
raise TypeError("Columns must be a list")
|
|
1314
|
+
|
|
1315
|
+
self.schema = val
|
|
1250
1316
|
|
|
1251
1317
|
@property
|
|
1252
1318
|
def column_names(self) -> List[str]:
|
|
@@ -1274,8 +1340,10 @@ class TableDefinition(IODefinition):
|
|
|
1274
1340
|
|
|
1275
1341
|
@property
|
|
1276
1342
|
def primary_key(self) -> List[str]:
|
|
1277
|
-
if
|
|
1343
|
+
if not self._legacy_mode:
|
|
1278
1344
|
return [column_name for column_name, column_def in self.schema.items() if column_def.primary_key]
|
|
1345
|
+
else:
|
|
1346
|
+
return self._legacy_primary_key
|
|
1279
1347
|
|
|
1280
1348
|
@primary_key.setter
|
|
1281
1349
|
def primary_key(self, primary_key: List[str]):
|
|
@@ -1284,12 +1352,15 @@ class TableDefinition(IODefinition):
|
|
|
1284
1352
|
|
|
1285
1353
|
if not isinstance(primary_key, list):
|
|
1286
1354
|
raise TypeError("Primary key must be a list")
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1355
|
+
if not self._legacy_mode:
|
|
1356
|
+
for col in primary_key:
|
|
1357
|
+
if col in self.schema:
|
|
1358
|
+
self.schema[col].primary_key = True
|
|
1359
|
+
else:
|
|
1360
|
+
raise UserException(f"Primary key column {col} not found in schema. "
|
|
1361
|
+
f"Please specify all columns / schema")
|
|
1362
|
+
else:
|
|
1363
|
+
self._legacy_primary_key = primary_key
|
|
1293
1364
|
|
|
1294
1365
|
@property
|
|
1295
1366
|
def delimiter(self) -> str:
|
|
@@ -1314,8 +1385,10 @@ class TableDefinition(IODefinition):
|
|
|
1314
1385
|
@table_metadata.setter
|
|
1315
1386
|
def table_metadata(self, table_metadata: TableMetadata):
|
|
1316
1387
|
self._table_metadata = table_metadata
|
|
1317
|
-
|
|
1318
|
-
for col, val in table_metadata.
|
|
1388
|
+
# backward compatibility legacy support
|
|
1389
|
+
for col, val in table_metadata._get_legacy_column_metadata_for_manifest().items():
|
|
1390
|
+
if not self.schema.get(col):
|
|
1391
|
+
self.schema[col] = ColumnDefinition()
|
|
1319
1392
|
self.schema[col].metadata = {item['key']: item['value'] for item in val}
|
|
1320
1393
|
|
|
1321
1394
|
@property
|
|
@@ -1325,6 +1398,22 @@ class TableDefinition(IODefinition):
|
|
|
1325
1398
|
else:
|
|
1326
1399
|
return None
|
|
1327
1400
|
|
|
1401
|
+
@property
|
|
1402
|
+
def uri(self) -> str:
|
|
1403
|
+
return self._uri
|
|
1404
|
+
|
|
1405
|
+
@property
|
|
1406
|
+
def last_change_date(self) -> str:
|
|
1407
|
+
return self._last_change_date
|
|
1408
|
+
|
|
1409
|
+
@property
|
|
1410
|
+
def last_import_date(self) -> str:
|
|
1411
|
+
return self._last_import_date
|
|
1412
|
+
|
|
1413
|
+
@property
|
|
1414
|
+
def is_alias(self) -> bool:
|
|
1415
|
+
return self._is_alias
|
|
1416
|
+
|
|
1328
1417
|
def add_column(self, name: str, definition: ColumnDefinition = ColumnDefinition()):
|
|
1329
1418
|
"""
|
|
1330
1419
|
Add column definition, accepts either ColumnDefinition or a string
|
|
@@ -1441,6 +1530,7 @@ class FileDefinition(IODefinition):
|
|
|
1441
1530
|
"notify"]
|
|
1442
1531
|
|
|
1443
1532
|
def __init__(self, full_path: str,
|
|
1533
|
+
stage: Optional[str] = 'out',
|
|
1444
1534
|
tags: Optional[List[str]] = None,
|
|
1445
1535
|
is_public: Optional[bool] = False,
|
|
1446
1536
|
is_permanent: Optional[bool] = False,
|
|
@@ -1457,6 +1547,7 @@ class FileDefinition(IODefinition):
|
|
|
1457
1547
|
|
|
1458
1548
|
Args:
|
|
1459
1549
|
full_path (str): Full path of the file.
|
|
1550
|
+
stage (str): Storage Stage 'in' or 'out' default out
|
|
1460
1551
|
tags (list):
|
|
1461
1552
|
List of tags that are assigned to this file
|
|
1462
1553
|
is_public: When true, the file URL will be permanent and publicly accessible.
|
|
@@ -1467,6 +1558,8 @@ class FileDefinition(IODefinition):
|
|
|
1467
1558
|
"""
|
|
1468
1559
|
super().__init__(full_path)
|
|
1469
1560
|
|
|
1561
|
+
self.stage = stage
|
|
1562
|
+
|
|
1470
1563
|
self.tags = tags
|
|
1471
1564
|
self.is_public = is_public
|
|
1472
1565
|
self.is_permanent = is_permanent
|
|
@@ -1507,7 +1600,7 @@ class FileDefinition(IODefinition):
|
|
|
1507
1600
|
Returns:
|
|
1508
1601
|
An instance of FileDefinition configured for output files.
|
|
1509
1602
|
"""
|
|
1510
|
-
return cls(full_path=full_path, tags=tags, is_public=is_public, is_permanent=is_permanent,
|
|
1603
|
+
return cls(full_path=full_path, stage="out", tags=tags, is_public=is_public, is_permanent=is_permanent,
|
|
1511
1604
|
is_encrypted=is_encrypted, notify=notify)
|
|
1512
1605
|
|
|
1513
1606
|
@classmethod
|
|
@@ -1537,7 +1630,7 @@ class FileDefinition(IODefinition):
|
|
|
1537
1630
|
Returns:
|
|
1538
1631
|
An instance of FileDefinition configured for input files.
|
|
1539
1632
|
"""
|
|
1540
|
-
return cls(full_path=full_path, id=id, s3=s3, abs=abs, created=created, size_bytes=size_bytes,
|
|
1633
|
+
return cls(full_path=full_path, stage="in", id=id, s3=s3, abs=abs, created=created, size_bytes=size_bytes,
|
|
1541
1634
|
max_age_days=max_age_days)
|
|
1542
1635
|
|
|
1543
1636
|
@classmethod
|
|
@@ -1572,7 +1665,13 @@ class FileDefinition(IODefinition):
|
|
|
1572
1665
|
|
|
1573
1666
|
full_path = str(file_path)
|
|
1574
1667
|
|
|
1668
|
+
if manifest.get('id'):
|
|
1669
|
+
stage = 'in'
|
|
1670
|
+
else:
|
|
1671
|
+
stage = 'out'
|
|
1672
|
+
|
|
1575
1673
|
file_def = cls(full_path=full_path,
|
|
1674
|
+
stage=stage,
|
|
1576
1675
|
tags=manifest.get('tags', []),
|
|
1577
1676
|
is_public=manifest.get('is_public', False),
|
|
1578
1677
|
is_permanent=manifest.get('is_permanent', False),
|
|
@@ -1637,18 +1736,31 @@ class FileDefinition(IODefinition):
|
|
|
1637
1736
|
|
|
1638
1737
|
"""
|
|
1639
1738
|
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1739
|
+
if manifest_type == 'in':
|
|
1740
|
+
|
|
1741
|
+
manifest_dictionary = {
|
|
1742
|
+
'id': self.id,
|
|
1743
|
+
'created': self.created.strftime('%Y-%m-%dT%H:%M:%S%z') if self.created else None,
|
|
1744
|
+
'is_public': self.is_public,
|
|
1745
|
+
'is_encrypted': self.is_encrypted,
|
|
1746
|
+
'name': self.name,
|
|
1747
|
+
'size_bytes': self.size_bytes,
|
|
1748
|
+
'tags': self.tags,
|
|
1749
|
+
'notify': self.notify,
|
|
1750
|
+
'max_age_days': self.max_age_days,
|
|
1751
|
+
'is_permanent': self.is_permanent,
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
else:
|
|
1755
|
+
manifest_dictionary = {
|
|
1756
|
+
'is_public': self.is_public,
|
|
1757
|
+
'is_permanent': self.is_permanent,
|
|
1758
|
+
'is_encrypted': self.is_encrypted,
|
|
1759
|
+
'tags': self.tags,
|
|
1760
|
+
'notify': self.notify,
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
return manifest_dictionary
|
|
1652
1764
|
|
|
1653
1765
|
@property
|
|
1654
1766
|
def name(self) -> str:
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
# Python 3.7 support
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
1
4
|
import argparse
|
|
2
5
|
import csv
|
|
3
6
|
import glob
|
|
@@ -5,17 +8,18 @@ import json
|
|
|
5
8
|
import logging
|
|
6
9
|
import os
|
|
7
10
|
import sys
|
|
11
|
+
import warnings
|
|
8
12
|
from datetime import datetime
|
|
9
13
|
from pathlib import Path
|
|
10
|
-
from typing import List, Dict, Optional, Union
|
|
14
|
+
from typing import List, Dict, Optional, Union, OrderedDict
|
|
11
15
|
|
|
12
16
|
from deprecated import deprecated
|
|
13
17
|
from pygelf import GelfUdpHandler, GelfTcpHandler
|
|
14
18
|
from pytz import utc
|
|
15
19
|
|
|
16
20
|
from . import dao
|
|
17
|
-
from .exceptions import UserException
|
|
18
21
|
from .dao import ColumnDefinition
|
|
22
|
+
from .exceptions import UserException
|
|
19
23
|
|
|
20
24
|
|
|
21
25
|
def register_csv_dialect():
|
|
@@ -331,7 +335,9 @@ class CommonInterface:
|
|
|
331
335
|
delimiter: str = ',',
|
|
332
336
|
delete_where: dict = None,
|
|
333
337
|
write_always: bool = False,
|
|
334
|
-
schema:
|
|
338
|
+
schema: Union[
|
|
339
|
+
OrderedDict[str, ColumnDefinition], list[str]] = None,
|
|
340
|
+
has_header: Optional[bool] = None) -> dao.TableDefinition:
|
|
335
341
|
"""
|
|
336
342
|
Helper method for dao.TableDefinition creation along with the "manifest".
|
|
337
343
|
It initializes path according to the storage_stage type.
|
|
@@ -361,6 +367,12 @@ class CommonInterface:
|
|
|
361
367
|
else:
|
|
362
368
|
raise ValueError(f'Invalid storage_stage value "{storage_stage}". Supported values are: "in" or "out"!')
|
|
363
369
|
|
|
370
|
+
# for transition period we need to force legacy mode for csv files w headers.
|
|
371
|
+
force_legacy_mode = False
|
|
372
|
+
if not schema and not columns and primary_key:
|
|
373
|
+
warnings.warn('Primary key is set but columns are not. Forcing legacy mode for CSV file.',
|
|
374
|
+
DeprecationWarning)
|
|
375
|
+
force_legacy_mode = True
|
|
364
376
|
return dao.TableDefinition(name=name,
|
|
365
377
|
full_path=full_path,
|
|
366
378
|
is_sliced=is_sliced,
|
|
@@ -374,7 +386,9 @@ class CommonInterface:
|
|
|
374
386
|
delete_where=delete_where,
|
|
375
387
|
stage=storage_stage,
|
|
376
388
|
write_always=write_always,
|
|
377
|
-
schema=schema
|
|
389
|
+
schema=schema,
|
|
390
|
+
has_header=has_header,
|
|
391
|
+
force_legacy_mode=force_legacy_mode)
|
|
378
392
|
|
|
379
393
|
def create_in_table_definition(self, name: str,
|
|
380
394
|
is_sliced: bool = False,
|
|
@@ -412,18 +426,22 @@ class CommonInterface:
|
|
|
412
426
|
delete_where=delete_where,
|
|
413
427
|
schema=schema)
|
|
414
428
|
|
|
429
|
+
SCHEMA_TYPE = Union[Dict[str, ColumnDefinition], OrderedDict[str, ColumnDefinition], List[str]]
|
|
430
|
+
|
|
415
431
|
def create_out_table_definition(self, name: str,
|
|
416
432
|
is_sliced: bool = False,
|
|
417
433
|
destination: str = '',
|
|
418
434
|
primary_key: List[str] = None,
|
|
419
|
-
|
|
435
|
+
schema: SCHEMA_TYPE = None,
|
|
420
436
|
incremental: bool = None,
|
|
421
437
|
table_metadata: dao.TableMetadata = None,
|
|
422
438
|
enclosure: str = '"',
|
|
423
439
|
delimiter: str = ',',
|
|
424
440
|
delete_where: dict = None,
|
|
425
441
|
write_always: bool = False,
|
|
426
|
-
|
|
442
|
+
has_header: Optional[bool] = None,
|
|
443
|
+
**kwargs
|
|
444
|
+
) -> dao.TableDefinition:
|
|
427
445
|
"""
|
|
428
446
|
Helper method for output dao.TableDefinition creation along with the "manifest".
|
|
429
447
|
It initializes path in data/tables/out/ folder.
|
|
@@ -433,7 +451,9 @@ class CommonInterface:
|
|
|
433
451
|
is_sliced: True if the full_path points to a folder with sliced tables
|
|
434
452
|
destination: String name of the table in Storage.
|
|
435
453
|
primary_key: List with names of columns used for primary key.
|
|
436
|
-
|
|
454
|
+
schema: List of columns or mapping of column names and ColumnDefinition objects.
|
|
455
|
+
if list of strings is provided, the columns will be created with default settings
|
|
456
|
+
(BaseType.string)
|
|
437
457
|
incremental: Set to true to enable incremental loading
|
|
438
458
|
table_metadata: <.dao.TableMetadata> object containing column and table metadata
|
|
439
459
|
enclosure: str: CSV enclosure, by default "
|
|
@@ -441,6 +461,8 @@ class CommonInterface:
|
|
|
441
461
|
delete_where: Dict with settings for deleting rows
|
|
442
462
|
write_always: Bool: If true, the table will be saved to Storage even when the job execution
|
|
443
463
|
fails.
|
|
464
|
+
has_header:Optional[bool] = flag whether the header is present in the file,
|
|
465
|
+
if None legacy method is used
|
|
444
466
|
"""
|
|
445
467
|
|
|
446
468
|
return self._create_table_definition(name=name,
|
|
@@ -448,14 +470,15 @@ class CommonInterface:
|
|
|
448
470
|
is_sliced=is_sliced,
|
|
449
471
|
destination=destination,
|
|
450
472
|
primary_key=primary_key,
|
|
451
|
-
columns=columns,
|
|
473
|
+
columns=kwargs.get('columns'),
|
|
452
474
|
incremental=incremental,
|
|
453
475
|
table_metadata=table_metadata,
|
|
454
476
|
enclosure=enclosure,
|
|
455
477
|
delimiter=delimiter,
|
|
456
478
|
delete_where=delete_where,
|
|
457
479
|
write_always=write_always,
|
|
458
|
-
schema=schema
|
|
480
|
+
schema=schema,
|
|
481
|
+
has_header=has_header)
|
|
459
482
|
|
|
460
483
|
# # File processing
|
|
461
484
|
|
|
@@ -927,7 +950,7 @@ class CommonInterface:
|
|
|
927
950
|
"""
|
|
928
951
|
|
|
929
952
|
if not legacy_manifest:
|
|
930
|
-
legacy_manifest = self.environment_variables.data_type_support not in ('authoritative', '
|
|
953
|
+
legacy_manifest = self.environment_variables.data_type_support not in ('authoritative', 'hints')
|
|
931
954
|
|
|
932
955
|
manifest = io_definition.get_manifest_dictionary(legacy_queue=self.is_legacy_queue,
|
|
933
956
|
legacy_manifest=legacy_manifest)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{keboola.component-1.6.0 → keboola.component-1.6.3}/src/keboola.component.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{keboola.component-1.6.0 → keboola.component-1.6.3}/src/keboola.component.egg-info/not-zip-safe
RENAMED
|
File without changes
|
{keboola.component-1.6.0 → keboola.component-1.6.3}/src/keboola.component.egg-info/requires.txt
RENAMED
|
File without changes
|
{keboola.component-1.6.0 → keboola.component-1.6.3}/src/keboola.component.egg-info/top_level.txt
RENAMED
|
File without changes
|