keboola.component 1.4.2__tar.gz → 1.4.4__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.
Files changed (18) hide show
  1. {keboola.component-1.4.2/src/keboola.component.egg-info → keboola.component-1.4.4}/PKG-INFO +1 -1
  2. {keboola.component-1.4.2 → keboola.component-1.4.4}/setup.py +1 -1
  3. {keboola.component-1.4.2 → keboola.component-1.4.4}/src/keboola/component/dao.py +66 -15
  4. {keboola.component-1.4.2 → keboola.component-1.4.4}/src/keboola/component/interface.py +39 -23
  5. {keboola.component-1.4.2 → keboola.component-1.4.4}/src/keboola/component/sync_actions.py +2 -2
  6. {keboola.component-1.4.2 → keboola.component-1.4.4/src/keboola.component.egg-info}/PKG-INFO +1 -1
  7. {keboola.component-1.4.2 → keboola.component-1.4.4}/LICENSE +0 -0
  8. {keboola.component-1.4.2 → keboola.component-1.4.4}/README.md +0 -0
  9. {keboola.component-1.4.2 → keboola.component-1.4.4}/setup.cfg +0 -0
  10. {keboola.component-1.4.2 → keboola.component-1.4.4}/src/keboola/component/__init__.py +0 -0
  11. {keboola.component-1.4.2 → keboola.component-1.4.4}/src/keboola/component/base.py +0 -0
  12. {keboola.component-1.4.2 → keboola.component-1.4.4}/src/keboola/component/exceptions.py +0 -0
  13. {keboola.component-1.4.2 → keboola.component-1.4.4}/src/keboola/component/table_schema.py +0 -0
  14. {keboola.component-1.4.2 → keboola.component-1.4.4}/src/keboola.component.egg-info/SOURCES.txt +0 -0
  15. {keboola.component-1.4.2 → keboola.component-1.4.4}/src/keboola.component.egg-info/dependency_links.txt +0 -0
  16. {keboola.component-1.4.2 → keboola.component-1.4.4}/src/keboola.component.egg-info/not-zip-safe +0 -0
  17. {keboola.component-1.4.2 → keboola.component-1.4.4}/src/keboola.component.egg-info/requires.txt +0 -0
  18. {keboola.component-1.4.2 → keboola.component-1.4.4}/src/keboola.component.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: keboola.component
3
- Version: 1.4.2
3
+ Version: 1.4.4
4
4
  Summary: General library for Python applications running in Keboola Connection environment
5
5
  Home-page: https://github.com/keboola/python-component
6
6
  Author: Keboola KDS Team
@@ -13,7 +13,7 @@ project_urls = {
13
13
 
14
14
  setuptools.setup(
15
15
  name="keboola.component",
16
- version="1.4.2",
16
+ version="1.4.4",
17
17
  author="Keboola KDS Team",
18
18
  project_urls=project_urls,
19
19
  setup_requires=['pytest-runner', 'flake8'],
@@ -1,5 +1,6 @@
1
1
  import dataclasses
2
2
  import json
3
+ import logging
3
4
  from abc import ABC, abstractmethod
4
5
  from dataclasses import dataclass
5
6
  from datetime import datetime
@@ -7,6 +8,11 @@ from enum import Enum
7
8
  from pathlib import Path
8
9
  from typing import List, Union, Dict, Optional
9
10
 
11
+ try:
12
+ from typing import Literal
13
+ except ImportError:
14
+ from typing_extensions import Literal
15
+
10
16
  KBC_DEFAULT_TIME_FORMAT = '%Y-%m-%dT%H:%M:%S%z'
11
17
 
12
18
 
@@ -381,6 +387,30 @@ class TableMetadata:
381
387
  raise ValueError(', '.join(errors) + f'\n Supported base types are: [{SupportedDataTypes.list()}]')
382
388
 
383
389
 
390
+ @dataclass
391
+ class SupportedManifestAttributes(SubscriptableDataclass):
392
+ out_attributes: List[str]
393
+ in_attributes: List[str]
394
+ out_legacy_exclude: List[str] = dataclasses.field(default_factory=lambda: [])
395
+ in_legacy_exclude: List[str] = dataclasses.field(default_factory=lambda: [])
396
+
397
+ def get_attributes_by_stage(self, stage: Literal["in", "out"], legacy_queue: bool = False) -> List[str]:
398
+ if stage == 'out':
399
+ attributes = self.out_attributes
400
+ exclude = self.out_legacy_exclude
401
+ elif stage == 'in':
402
+ attributes = self.in_attributes
403
+ exclude = self.in_legacy_exclude
404
+ else:
405
+ raise ValueError(f"Unsupported stage {stage}")
406
+
407
+ if legacy_queue:
408
+ logging.warning(f"Running on legacy queue some manifest properties will be ignored: {exclude}")
409
+ attributes = list(set(attributes).difference(exclude))
410
+
411
+ return attributes
412
+
413
+
384
414
  class IODefinition(ABC):
385
415
 
386
416
  def __init__(self, full_path):
@@ -396,7 +426,7 @@ class IODefinition(ABC):
396
426
  ):
397
427
  raise NotImplementedError
398
428
 
399
- def _filter_attributes_by_manifest_type(self, manifest_type):
429
+ def _filter_attributes_by_manifest_type(self, manifest_type: Literal["in", "out"], legacy_queue: bool = False):
400
430
  """
401
431
  Filter manifest to contain only supported fields
402
432
  Args:
@@ -405,7 +435,8 @@ class IODefinition(ABC):
405
435
  Returns:
406
436
 
407
437
  """
408
- supported_fields = self._manifest_attributes.get(manifest_type, [])
438
+ supported_fields = self._manifest_attributes.get_attributes_by_stage(manifest_type, legacy_queue)
439
+
409
440
  new_dict = self._raw_manifest.copy()
410
441
  if supported_fields:
411
442
  for attr in self._raw_manifest:
@@ -413,10 +444,10 @@ class IODefinition(ABC):
413
444
  new_dict.pop(attr, None)
414
445
  return new_dict
415
446
 
416
- def get_manifest_dictionary(self, manifest_type: Optional[str] = None) -> dict:
447
+ def get_manifest_dictionary(self, manifest_type: Optional[str] = None, legacy_queue=False) -> dict:
417
448
  """
418
449
  Returns manifest dictionary in appropriate manifest_type: either 'in' or 'out'.
419
- By default returns output manifest.
450
+ By default, returns output manifest.
420
451
  The result keeps only values that are applicable for
421
452
  the selected type of the Manifest file. Because although input and output manifests share most of
422
453
  the attributes, some are not shared.
@@ -428,6 +459,7 @@ class IODefinition(ABC):
428
459
  manifest_type (str): either 'in' or 'out'.
429
460
  See [manifest files](https://developers.keboola.com/extend/common-interface/manifest-files)
430
461
  for more information.
462
+ legacy_queue (bool): optional flag marking project on legacy queue.(some options are not allowed on queue2)
431
463
 
432
464
  Returns:
433
465
  dict representation of the manifest file in a format expected / produced by the Keboola Connection
@@ -436,7 +468,7 @@ class IODefinition(ABC):
436
468
  if not manifest_type:
437
469
  manifest_type = self.stage
438
470
 
439
- return self._filter_attributes_by_manifest_type(manifest_type)
471
+ return self._filter_attributes_by_manifest_type(manifest_type, legacy_queue)
440
472
 
441
473
  @property
442
474
  def stage(self) -> str:
@@ -453,11 +485,11 @@ class IODefinition(ABC):
453
485
 
454
486
  @property
455
487
  @abstractmethod
456
- def _manifest_attributes(self) -> Dict[str, List[str]]:
488
+ def _manifest_attributes(self) -> SupportedManifestAttributes:
457
489
  """
458
490
  Manifest attributes
459
491
  """
460
- return {}
492
+ return SupportedManifestAttributes([], [])
461
493
 
462
494
  @property
463
495
  @abstractmethod
@@ -587,6 +619,7 @@ class TableDefinition(IODefinition):
587
619
  "columns",
588
620
  "incremental",
589
621
  "primary_key",
622
+ "write_always",
590
623
  "delimiter",
591
624
  "enclosure",
592
625
  "metadata",
@@ -596,6 +629,10 @@ class TableDefinition(IODefinition):
596
629
  "delete_where_operator"
597
630
  ]
598
631
 
632
+ OUTPUT_MANIFEST_LEGACY_EXCLUDES = [
633
+ "write_always"
634
+ ]
635
+
599
636
  MANIFEST_ATTRIBUTES = {'in': INPUT_MANIFEST_ATTRIBUTES,
600
637
  'out': OUTPUT_MANIFEST_ATTRIBUTES}
601
638
 
@@ -608,7 +645,9 @@ class TableDefinition(IODefinition):
608
645
  enclosure: str = '"',
609
646
  delimiter: str = ',',
610
647
  delete_where: dict = None,
611
- stage: str = 'in'):
648
+ stage: str = 'in',
649
+ write_always: bool = False
650
+ ):
612
651
  """
613
652
 
614
653
  Args:
@@ -630,6 +669,8 @@ class TableDefinition(IODefinition):
630
669
  delimiter: str: CSV delimiter, by default ,
631
670
  delete_where (dict): Dict with settings for deleting rows
632
671
  stage: str: Storage Stage 'in' or 'out'
672
+ write_always: Bool: If true, the table will be saved to Storage even when the job execution
673
+ fails.
633
674
  """
634
675
  super().__init__(full_path)
635
676
  self._name = name
@@ -650,6 +691,7 @@ class TableDefinition(IODefinition):
650
691
  self.table_metadata = table_metadata
651
692
  self.set_delete_where_from_dict(delete_where)
652
693
  self.stage = stage
694
+ self.write_always = write_always
653
695
 
654
696
  @classmethod
655
697
  def build_from_manifest(cls,
@@ -705,8 +747,9 @@ class TableDefinition(IODefinition):
705
747
  return table_def
706
748
 
707
749
  @property
708
- def _manifest_attributes(self) -> Dict[str, List[str]]:
709
- return self.MANIFEST_ATTRIBUTES
750
+ def _manifest_attributes(self) -> SupportedManifestAttributes:
751
+ return SupportedManifestAttributes(self.MANIFEST_ATTRIBUTES['out'], self.MANIFEST_ATTRIBUTES['in'],
752
+ self.OUTPUT_MANIFEST_LEGACY_EXCLUDES)
710
753
 
711
754
  # #### Manifest properties
712
755
  @property
@@ -797,6 +840,14 @@ class TableDefinition(IODefinition):
797
840
  if incremental:
798
841
  self._raw_manifest['incremental'] = True
799
842
 
843
+ @property
844
+ def write_always(self) -> bool:
845
+ return self._raw_manifest.get('write_always', False)
846
+
847
+ @write_always.setter
848
+ def write_always(self, write_always: bool):
849
+ self._raw_manifest['write_always'] = write_always
850
+
800
851
  @property
801
852
  def primary_key(self) -> List[str]:
802
853
  return self._raw_manifest.get('primary_key', [])
@@ -864,7 +915,7 @@ class TableDefinition(IODefinition):
864
915
  self._raw_manifest['metadata'] = table_metadata.get_table_metadata_for_manifest()
865
916
  self._raw_manifest['column_metadata'] = table_metadata.get_column_metadata_for_manifest()
866
917
 
867
- def get_manifest_dictionary(self, stage_type: Optional[str] = None) -> dict:
918
+ def get_manifest_dictionary(self, stage_type: Optional[str] = None, legacy_queue=False) -> dict:
868
919
  """
869
920
 
870
921
  Args:
@@ -877,8 +928,8 @@ class TableDefinition(IODefinition):
877
928
  """
878
929
  # in case the table_metadata is out of sync, e.g. the object was modified in-place
879
930
  self._set_table_metadata_to_manifest(self._table_metadata)
880
- self._raw_manifest = super(TableDefinition, self).get_manifest_dictionary(stage_type)
881
- return self._raw_manifest
931
+ raw_manifest = super(TableDefinition, self).get_manifest_dictionary(stage_type, legacy_queue)
932
+ return raw_manifest
882
933
 
883
934
 
884
935
  class FileDefinition(IODefinition):
@@ -1020,8 +1071,8 @@ class FileDefinition(IODefinition):
1020
1071
  return Path(self.full_path).name
1021
1072
 
1022
1073
  @property
1023
- def _manifest_attributes(self) -> Dict[str, List[str]]:
1024
- return {'out': self.OUTPUT_MANIFEST_KEYS}
1074
+ def _manifest_attributes(self) -> SupportedManifestAttributes:
1075
+ return SupportedManifestAttributes(self.OUTPUT_MANIFEST_KEYS, [])
1025
1076
 
1026
1077
  # ########### Output manifest properties - R/W
1027
1078
 
@@ -4,11 +4,11 @@ import glob
4
4
  import json
5
5
  import logging
6
6
  import os
7
+ import sys
7
8
  from datetime import datetime
8
9
  from pathlib import Path
9
10
  from typing import List, Dict, Optional, Union
10
11
 
11
- import sys
12
12
  from deprecated import deprecated
13
13
  from pygelf import GelfUdpHandler, GelfTcpHandler
14
14
  from pytz import utc
@@ -327,7 +327,8 @@ class CommonInterface:
327
327
  table_metadata: dao.TableMetadata = None,
328
328
  enclosure: str = '"',
329
329
  delimiter: str = ',',
330
- delete_where: dict = None) -> dao.TableDefinition:
330
+ delete_where: dict = None,
331
+ write_always: bool = False) -> dao.TableDefinition:
331
332
  """
332
333
  Helper method for dao.TableDefinition creation along with the "manifest".
333
334
  It initializes path according to the storage_stage type.
@@ -347,6 +348,8 @@ class CommonInterface:
347
348
  enclosure: str: CSV enclosure, by default "
348
349
  delimiter: str: CSV delimiter, by default ,
349
350
  delete_where: Dict with settings for deleting rows
351
+ write_always: Bool: If true, the table will be saved to Storage even when the job execution
352
+ fails.
350
353
  """
351
354
  if storage_stage == 'in':
352
355
  full_path = os.path.join(self.tables_in_path, name)
@@ -366,7 +369,8 @@ class CommonInterface:
366
369
  enclosure=enclosure,
367
370
  delimiter=delimiter,
368
371
  delete_where=delete_where,
369
- stage=storage_stage)
372
+ stage=storage_stage,
373
+ write_always=write_always)
370
374
 
371
375
  def create_in_table_definition(self, name: str,
372
376
  is_sliced: bool = False,
@@ -410,7 +414,8 @@ class CommonInterface:
410
414
  table_metadata: dao.TableMetadata = None,
411
415
  enclosure: str = '"',
412
416
  delimiter: str = ',',
413
- delete_where: dict = None) -> dao.TableDefinition:
417
+ delete_where: dict = None,
418
+ write_always: bool = False) -> dao.TableDefinition:
414
419
  """
415
420
  Helper method for output dao.TableDefinition creation along with the "manifest".
416
421
  It initializes path in data/tables/out/ folder.
@@ -426,6 +431,8 @@ class CommonInterface:
426
431
  enclosure: str: CSV enclosure, by default "
427
432
  delimiter: str: CSV delimiter, by default ,
428
433
  delete_where: Dict with settings for deleting rows
434
+ write_always: Bool: If true, the table will be saved to Storage even when the job execution
435
+ fails.
429
436
  """
430
437
 
431
438
  return self._create_table_definition(name=name,
@@ -438,7 +445,8 @@ class CommonInterface:
438
445
  table_metadata=table_metadata,
439
446
  enclosure=enclosure,
440
447
  delimiter=delimiter,
441
- delete_where=delete_where)
448
+ delete_where=delete_where,
449
+ write_always=write_always)
442
450
 
443
451
  # # File processing
444
452
 
@@ -862,8 +870,20 @@ class CommonInterface:
862
870
  def files_in_path(self):
863
871
  return os.path.join(self.data_folder_path, 'in', 'files')
864
872
 
865
- @staticmethod
866
- def write_manifest(io_definition: Union[dao.FileDefinition, dao.TableDefinition]):
873
+ @property
874
+ def is_legacy_queue(self) -> bool:
875
+ """
876
+ Check if the project is running on legacy queue (v1)
877
+ Returns:
878
+
879
+ """
880
+ features = os.environ.get('KBC_PROJECT_FEATURE_GATES')
881
+ is_legacy_queue = True
882
+ if not features or 'queuev2' in features:
883
+ is_legacy_queue = False
884
+ return is_legacy_queue
885
+
886
+ def write_manifest(self, io_definition: Union[dao.FileDefinition, dao.TableDefinition]):
867
887
  """
868
888
  Write a table manifest from dao.IODefinition. Creates the appropriate manifest file in the proper location.
869
889
 
@@ -895,14 +915,14 @@ class CommonInterface:
895
915
  Returns:
896
916
 
897
917
  """
898
- manifest = io_definition.get_manifest_dictionary()
918
+
919
+ manifest = io_definition.get_manifest_dictionary(legacy_queue=self.is_legacy_queue)
899
920
  # make dirs if not exist
900
921
  os.makedirs(os.path.dirname(io_definition.full_path), exist_ok=True)
901
922
  with open(io_definition.full_path + '.manifest', 'w') as manifest_file:
902
923
  json.dump(manifest, manifest_file)
903
924
 
904
- @staticmethod
905
- def write_manifests(io_definitions: List[Union[dao.FileDefinition, dao.TableDefinition]]):
925
+ def write_manifests(self, io_definitions: List[Union[dao.FileDefinition, dao.TableDefinition]]):
906
926
  """
907
927
  Process all table definition objects and create appropriate manifest files.
908
928
  Args:
@@ -912,13 +932,12 @@ class CommonInterface:
912
932
 
913
933
  """
914
934
  for io_def in io_definitions:
915
- CommonInterface.write_manifest(io_def)
935
+ self.write_manifest(io_def)
916
936
 
917
937
  # ############# DEPRECATED METHODS, TODO: remove
918
938
 
919
- @staticmethod
920
939
  @deprecated(version='1.3.0', reason="You should use write_manifest function")
921
- def write_filedef_manifest(file_definition: dao.FileDefinition):
940
+ def write_filedef_manifest(self, file_definition: dao.FileDefinition):
922
941
  """
923
942
  Write a table manifest from dao.FileDefinition. Creates the appropriate manifest file in the proper location.
924
943
 
@@ -942,11 +961,10 @@ class CommonInterface:
942
961
  Returns:
943
962
 
944
963
  """
945
- CommonInterface.write_manifest(file_definition)
964
+ self.write_manifest(file_definition)
946
965
 
947
- @staticmethod
948
966
  @deprecated(version='1.3.0', reason="You should use write_manifests function")
949
- def write_filedef_manifests(file_definitions: List[dao.FileDefinition]):
967
+ def write_filedef_manifests(self, file_definitions: List[dao.FileDefinition]):
950
968
  """
951
969
  Process all table definition objects and create appropriate manifest files.
952
970
  Args:
@@ -955,11 +973,10 @@ class CommonInterface:
955
973
  Returns:
956
974
 
957
975
  """
958
- CommonInterface.write_manifests(file_definitions)
976
+ self.write_manifests(file_definitions)
959
977
 
960
- @staticmethod
961
978
  @deprecated(version='1.3.0', reason="You should use write_manifest function")
962
- def write_tabledef_manifest(table_definition: dao.TableDefinition):
979
+ def write_tabledef_manifest(self, table_definition: dao.TableDefinition):
963
980
  """
964
981
  Write a table manifest from dao.TableDefinition. Creates the appropriate manifest file in the proper location.
965
982
 
@@ -988,11 +1005,10 @@ class CommonInterface:
988
1005
  Returns:
989
1006
 
990
1007
  """
991
- CommonInterface.write_manifest(table_definition)
1008
+ self.write_manifest(table_definition)
992
1009
 
993
- @staticmethod
994
1010
  @deprecated(version='1.3.0', reason="You should use write_manifests function")
995
- def write_tabledef_manifests(table_definitions: List[dao.TableDefinition]):
1011
+ def write_tabledef_manifests(self, table_definitions: List[dao.TableDefinition]):
996
1012
  """
997
1013
  Process all table definition objects and create appropriate manifest files.
998
1014
  Args:
@@ -1001,7 +1017,7 @@ class CommonInterface:
1001
1017
  Returns:
1002
1018
 
1003
1019
  """
1004
- CommonInterface.write_manifests(table_definitions)
1020
+ self.write_manifests(table_definitions)
1005
1021
 
1006
1022
 
1007
1023
  # ########## CONFIGURATION
@@ -63,7 +63,7 @@ class SelectElement(SyncActionResult):
63
63
  self.status = None
64
64
 
65
65
 
66
- def process_sync_action_result(result: Union[None, dict, SyncActionResult, List[SyncActionResult]]) -> str:
66
+ def process_sync_action_result(result: Union[None, List[dict], dict, SyncActionResult, List[SyncActionResult]]) -> str:
67
67
  """
68
68
  Converts Sync Action result into valid string (expected by Sync Action).
69
69
  Args:
@@ -75,7 +75,7 @@ def process_sync_action_result(result: Union[None, dict, SyncActionResult, List[
75
75
  if isinstance(result, SyncActionResult):
76
76
  result_str = str(result)
77
77
  elif isinstance(result, list):
78
- result_str = f'[{", ".join([str(r) for r in result])}]'
78
+ result_str = f'[{", ".join([json.dumps(r) if isinstance(r, dict) else str(r) for r in result])}]'
79
79
  elif result is None:
80
80
  result_str = json.dumps({'status': 'success'})
81
81
  elif isinstance(result, dict):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: keboola.component
3
- Version: 1.4.2
3
+ Version: 1.4.4
4
4
  Summary: General library for Python applications running in Keboola Connection environment
5
5
  Home-page: https://github.com/keboola/python-component
6
6
  Author: Keboola KDS Team