keboola.component 1.8.2__tar.gz → 1.9.2__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.8.2/src/keboola.component.egg-info → keboola_component-1.9.2}/PKG-INFO +84 -46
- {keboola_component-1.8.2 → keboola_component-1.9.2}/README.md +81 -41
- {keboola_component-1.8.2 → keboola_component-1.9.2}/pyproject.toml +11 -7
- {keboola_component-1.8.2 → keboola_component-1.9.2}/src/keboola/component/base.py +151 -92
- {keboola_component-1.8.2 → keboola_component-1.9.2}/src/keboola/component/dao.py +577 -538
- {keboola_component-1.8.2 → keboola_component-1.9.2}/src/keboola/component/interface.py +460 -442
- {keboola_component-1.8.2 → keboola_component-1.9.2}/src/keboola/component/sync_actions.py +11 -11
- {keboola_component-1.8.2 → keboola_component-1.9.2}/src/keboola/component/table_schema.py +18 -15
- {keboola_component-1.8.2 → keboola_component-1.9.2/src/keboola.component.egg-info}/PKG-INFO +84 -46
- keboola_component-1.9.2/src/keboola.component.egg-info/requires.txt +3 -0
- {keboola_component-1.8.2 → keboola_component-1.9.2}/tests/test_base.py +16 -23
- keboola_component-1.9.2/tests/test_dao.py +929 -0
- keboola_component-1.9.2/tests/test_interface.py +1103 -0
- {keboola_component-1.8.2 → keboola_component-1.9.2}/tests/test_schema.py +65 -47
- {keboola_component-1.8.2 → keboola_component-1.9.2}/tests/test_sync_actions.py +3 -6
- keboola_component-1.8.2/src/keboola.component.egg-info/requires.txt +0 -3
- keboola_component-1.8.2/tests/test_dao.py +0 -853
- keboola_component-1.8.2/tests/test_interface.py +0 -1052
- {keboola_component-1.8.2 → keboola_component-1.9.2}/LICENSE +0 -0
- {keboola_component-1.8.2 → keboola_component-1.9.2}/setup.cfg +0 -0
- {keboola_component-1.8.2 → keboola_component-1.9.2}/src/keboola/component/__init__.py +0 -0
- {keboola_component-1.8.2 → keboola_component-1.9.2}/src/keboola/component/exceptions.py +0 -0
- {keboola_component-1.8.2 → keboola_component-1.9.2}/src/keboola.component.egg-info/SOURCES.txt +0 -0
- {keboola_component-1.8.2 → keboola_component-1.9.2}/src/keboola.component.egg-info/dependency_links.txt +0 -0
- {keboola_component-1.8.2 → keboola_component-1.9.2}/src/keboola.component.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: keboola.component
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.9.2
|
|
4
4
|
Summary: General library for Python applications running in Keboola Connection environment
|
|
5
5
|
Author-email: Keboola KDS Team <support@keboola.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -8,8 +8,6 @@ Project-URL: Documentation, https://keboola.github.io/python-component/interface
|
|
|
8
8
|
Project-URL: Repository, https://github.com/keboola/python-component
|
|
9
9
|
Project-URL: Component Template project, https://github.com/keboola/cookiecutter-python-component
|
|
10
10
|
Classifier: Programming Language :: Python :: 3
|
|
11
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
13
11
|
Classifier: Programming Language :: Python :: 3.10
|
|
14
12
|
Classifier: Programming Language :: Python :: 3.11
|
|
15
13
|
Classifier: Programming Language :: Python :: 3.12
|
|
@@ -22,19 +20,58 @@ Classifier: Intended Audience :: Education
|
|
|
22
20
|
Classifier: Topic :: Software Development :: Libraries
|
|
23
21
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
22
|
Classifier: Development Status :: 5 - Production/Stable
|
|
25
|
-
Requires-Python: >=3.
|
|
23
|
+
Requires-Python: >=3.10
|
|
26
24
|
Description-Content-Type: text/markdown
|
|
27
25
|
License-File: LICENSE
|
|
28
26
|
Requires-Dist: pygelf
|
|
29
|
-
Requires-Dist: pytz<2021.0
|
|
30
27
|
Requires-Dist: deprecated
|
|
28
|
+
Requires-Dist: keboola.vcr>=0.1.1
|
|
31
29
|
Dynamic: license-file
|
|
32
30
|
|
|
33
31
|
# Keboola Python Component library
|
|
34
32
|
|
|
33
|
+
## Table of Contents
|
|
34
|
+
|
|
35
|
+
- [Keboola Python Component library](#keboola-python-component-library)
|
|
36
|
+
- [Table of Contents](#table-of-contents)
|
|
37
|
+
- [Introduction](#introduction)
|
|
38
|
+
- [Links](#links)
|
|
39
|
+
- [Quick start](#quick-start)
|
|
40
|
+
- [Installation](#installation)
|
|
41
|
+
- [For Developers](#for-developers)
|
|
42
|
+
- [Core structure \& functionality](#core-structure--functionality)
|
|
43
|
+
- [CommonInterface](#commoninterface)
|
|
44
|
+
- [Initialization](#initialization)
|
|
45
|
+
- [Loading configuration parameters:](#loading-configuration-parameters)
|
|
46
|
+
- [Processing input tables - Manifest vs I/O mapping](#processing-input-tables---manifest-vs-io-mapping)
|
|
47
|
+
- [Manifest \& input folder content](#manifest--input-folder-content)
|
|
48
|
+
- [Using I/O mapping](#using-io-mapping)
|
|
49
|
+
- [I/O table manifests and processing results](#io-table-manifests-and-processing-results)
|
|
50
|
+
- [Get input table by name](#get-input-table-by-name)
|
|
51
|
+
- [Working with Input/Output Mapping](#working-with-inputoutput-mapping)
|
|
52
|
+
- [Accessing Input Tables from Mapping](#accessing-input-tables-from-mapping)
|
|
53
|
+
- [Creating Output Tables based on Output Mapping](#creating-output-tables-based-on-output-mapping)
|
|
54
|
+
- [Combining Input and Output Mapping](#combining-input-and-output-mapping)
|
|
55
|
+
- [Processing input files](#processing-input-files)
|
|
56
|
+
- [Grouping Files by Tags](#grouping-files-by-tags)
|
|
57
|
+
- [Creating Output Files](#creating-output-files)
|
|
58
|
+
- [Processing state files](#processing-state-files)
|
|
59
|
+
- [Logging](#logging)
|
|
60
|
+
- [ComponentBase](#componentbase)
|
|
61
|
+
- [Table Schemas in ComponentBase](#table-schemas-in-componentbase)
|
|
62
|
+
- [JSON Table Schema example file](#json-table-schema-example-file)
|
|
63
|
+
- [Out table definition from schema example](#out-table-definition-from-schema-example)
|
|
64
|
+
- [Sync Actions](#sync-actions)
|
|
65
|
+
- [Creating Sync Actions](#creating-sync-actions)
|
|
66
|
+
- [Returning Data from Sync Actions](#returning-data-from-sync-actions)
|
|
67
|
+
- [Validation Message Action](#validation-message-action)
|
|
68
|
+
- [No output](#no-output)
|
|
69
|
+
- [License](#license)
|
|
70
|
+
|
|
71
|
+
|
|
35
72
|
## Introduction
|
|
36
73
|
|
|
37
|
-

|
|
38
75
|
[](https://codeclimate.com/github/keboola/python-component)
|
|
39
76
|
[](https://badge.fury.io/py/keboola.component)
|
|
40
77
|
|
|
@@ -53,27 +90,28 @@ to simplify the I/O handling.
|
|
|
53
90
|
|
|
54
91
|
### Links
|
|
55
92
|
|
|
56
|
-
-
|
|
57
|
-
-
|
|
58
|
-
-
|
|
59
|
-
|
|
60
|
-
-
|
|
61
|
-
|
|
62
|
-
Documentation: [https://developers.keboola.com/extend/component/python-component-library](https://developers.keboola.com/extend/component/)
|
|
63
|
-
|
|
64
|
-
- Python Component Cookiecutter template
|
|
65
|
-
project: [https://bitbucket.org/kds_consulting_team/cookiecutter-python-component](https://bitbucket.org/kds_consulting_team/cookiecutter-python-component)
|
|
93
|
+
- [PyPI](https://pypi.org/project/keboola.component/) & [TestPyPI](https://test.pypi.org/project/keboola.component/)
|
|
94
|
+
- [API Documentation](https://keboola.github.io/python-component/interface.html)
|
|
95
|
+
- [Keboola Components for developers](https://developers.keboola.com/extend/component/)
|
|
96
|
+
- [Python Component Cookiecutter template project](https://github.com/keboola/cookiecutter-python-component)
|
|
66
97
|
|
|
67
98
|
# Quick start
|
|
68
99
|
|
|
69
100
|
## Installation
|
|
70
101
|
|
|
71
|
-
The package may be installed via
|
|
102
|
+
The package may be installed via uv 💜:
|
|
72
103
|
|
|
73
104
|
```
|
|
74
|
-
|
|
105
|
+
uv add keboola.component
|
|
75
106
|
```
|
|
76
107
|
|
|
108
|
+
## For Developers
|
|
109
|
+
|
|
110
|
+
> **Note for contributors:** Before creating a pull request, make sure to manually run the
|
|
111
|
+
> [Generate Documentation](https://github.com/keboola/python-component/actions/workflows/generate-docs.yml)
|
|
112
|
+
> workflow on your branch. This ensures the API documentation in the `docs/` folder is up-to-date
|
|
113
|
+
> with your code changes. The workflow can be triggered from the Actions tab on GitHub.
|
|
114
|
+
|
|
77
115
|
## Core structure & functionality
|
|
78
116
|
|
|
79
117
|
The package contains two core modules:
|
|
@@ -293,7 +331,7 @@ with open(out_table.full_path, 'w', newline='') as f:
|
|
|
293
331
|
"status": "completed",
|
|
294
332
|
"value": "123.45"
|
|
295
333
|
})
|
|
296
|
-
|
|
334
|
+
|
|
297
335
|
# Write manifest
|
|
298
336
|
ci.write_manifest(out_table)
|
|
299
337
|
```
|
|
@@ -317,7 +355,7 @@ out_table = ci.create_out_table_definition(
|
|
|
317
355
|
|
|
318
356
|
# Add columns using different data type methods
|
|
319
357
|
# Method 1: Using BaseType helper
|
|
320
|
-
out_table.add_column("id",
|
|
358
|
+
out_table.add_column("id",
|
|
321
359
|
ColumnDefinition(
|
|
322
360
|
primary_key=True,
|
|
323
361
|
data_types=BaseType.integer()
|
|
@@ -325,7 +363,7 @@ out_table.add_column("id",
|
|
|
325
363
|
)
|
|
326
364
|
|
|
327
365
|
# Method 2: Using SupportedDataTypes enum
|
|
328
|
-
out_table.add_column("created_at",
|
|
366
|
+
out_table.add_column("created_at",
|
|
329
367
|
ColumnDefinition(
|
|
330
368
|
data_types=BaseType(dtype=SupportedDataTypes.TIMESTAMP)
|
|
331
369
|
)
|
|
@@ -335,7 +373,7 @@ out_table.add_column("created_at",
|
|
|
335
373
|
out_table.add_column("status", ColumnDefinition())
|
|
336
374
|
|
|
337
375
|
# Method 4: Using BaseType with parameters
|
|
338
|
-
out_table.add_column("price",
|
|
376
|
+
out_table.add_column("price",
|
|
339
377
|
ColumnDefinition(
|
|
340
378
|
data_types=BaseType.numeric(length="10,2"),
|
|
341
379
|
description="Product price with 2 decimal places"
|
|
@@ -343,7 +381,7 @@ out_table.add_column("price",
|
|
|
343
381
|
)
|
|
344
382
|
|
|
345
383
|
# Method 5: Backend-specific data types
|
|
346
|
-
out_table.add_column("metadata",
|
|
384
|
+
out_table.add_column("metadata",
|
|
347
385
|
ColumnDefinition(
|
|
348
386
|
data_types={
|
|
349
387
|
"snowflake": DataType(dtype="VARIANT"),
|
|
@@ -355,7 +393,7 @@ out_table.add_column("metadata",
|
|
|
355
393
|
)
|
|
356
394
|
|
|
357
395
|
# Update existing column (example of column modification)
|
|
358
|
-
out_table.update_column("price",
|
|
396
|
+
out_table.update_column("price",
|
|
359
397
|
ColumnDefinition(
|
|
360
398
|
data_types={
|
|
361
399
|
"snowflake": DataType(dtype="NUMBER", length="15,4"),
|
|
@@ -377,7 +415,7 @@ with open(out_table.full_path, 'w', newline='') as f:
|
|
|
377
415
|
"price": "99.9999",
|
|
378
416
|
"metadata": '{"category": "electronics", "brand": "TechCorp"}'
|
|
379
417
|
})
|
|
380
|
-
|
|
418
|
+
|
|
381
419
|
# Write manifest
|
|
382
420
|
ci.write_manifest(out_table)
|
|
383
421
|
```
|
|
@@ -441,16 +479,16 @@ input_tables = ci.configuration.tables_input_mapping
|
|
|
441
479
|
for table in input_tables:
|
|
442
480
|
# Get the destination (filename in the /data/in/tables directory)
|
|
443
481
|
table_name = table.destination
|
|
444
|
-
|
|
482
|
+
|
|
445
483
|
# Load table definition from manifest
|
|
446
484
|
table_def = ci.get_input_table_definition_by_name(table_name)
|
|
447
|
-
|
|
485
|
+
|
|
448
486
|
# Print information about the table
|
|
449
487
|
print(f"Processing table: {table_name}")
|
|
450
488
|
print(f" - Source: {table.source}")
|
|
451
489
|
print(f" - Full path: {table_def.full_path}")
|
|
452
490
|
print(f" - Columns: {table_def.column_names}")
|
|
453
|
-
|
|
491
|
+
|
|
454
492
|
# Read data from the CSV file
|
|
455
493
|
with open(table_def.full_path, 'r') as input_file:
|
|
456
494
|
csv_reader = csv.DictReader(input_file)
|
|
@@ -476,20 +514,20 @@ for i, table_mapping in enumerate(output_tables):
|
|
|
476
514
|
# Get source (filename that should be created) and destination (where it will be stored in KBC)
|
|
477
515
|
source = table_mapping.source
|
|
478
516
|
destination = table_mapping.destination
|
|
479
|
-
|
|
517
|
+
|
|
480
518
|
# Create output table definition
|
|
481
519
|
out_table = ci.create_out_table_definition(
|
|
482
520
|
name=source,
|
|
483
521
|
destination=destination,
|
|
484
522
|
incremental=table_mapping.incremental
|
|
485
523
|
)
|
|
486
|
-
|
|
524
|
+
|
|
487
525
|
# Add some sample data (in a real component, this would be your processed data)
|
|
488
526
|
with open(out_table.full_path, 'w', newline='') as out_file:
|
|
489
527
|
writer = csv.DictWriter(out_file, fieldnames=['id', 'data'])
|
|
490
528
|
writer.writeheader()
|
|
491
529
|
writer.writerow({'id': f'{i+1}', 'data': f'Data for {destination}'})
|
|
492
|
-
|
|
530
|
+
|
|
493
531
|
# Write manifest file
|
|
494
532
|
ci.write_manifest(out_table)
|
|
495
533
|
```
|
|
@@ -513,27 +551,27 @@ output_tables = ci.configuration.tables_output_mapping
|
|
|
513
551
|
for i, out_mapping in enumerate(output_tables):
|
|
514
552
|
# Find corresponding input table if possible (matching by index for simplicity)
|
|
515
553
|
in_mapping = input_tables[i] if i < len(input_tables) else None
|
|
516
|
-
|
|
554
|
+
|
|
517
555
|
# Create output table
|
|
518
556
|
out_table = ci.create_out_table_definition(
|
|
519
557
|
name=out_mapping.source,
|
|
520
558
|
destination=out_mapping.destination,
|
|
521
559
|
incremental=out_mapping.incremental
|
|
522
560
|
)
|
|
523
|
-
|
|
561
|
+
|
|
524
562
|
# If we have an input table, transform its data
|
|
525
563
|
if in_mapping:
|
|
526
564
|
in_table = ci.get_input_table_definition_by_name(in_mapping.destination)
|
|
527
|
-
|
|
565
|
+
|
|
528
566
|
# Read input and write to output with transformation
|
|
529
567
|
with open(in_table.full_path, 'r') as in_file, open(out_table.full_path, 'w', newline='') as out_file:
|
|
530
568
|
reader = csv.DictReader(in_file)
|
|
531
|
-
|
|
569
|
+
|
|
532
570
|
# Create writer with same field names
|
|
533
571
|
fieldnames = reader.fieldnames
|
|
534
572
|
writer = csv.DictWriter(out_file, fieldnames=fieldnames)
|
|
535
573
|
writer.writeheader()
|
|
536
|
-
|
|
574
|
+
|
|
537
575
|
# Transform each row and write to output
|
|
538
576
|
for row in reader:
|
|
539
577
|
# Simple transformation example - uppercase all values
|
|
@@ -545,7 +583,7 @@ for i, out_mapping in enumerate(output_tables):
|
|
|
545
583
|
writer = csv.DictWriter(out_file, fieldnames=['id', 'data'])
|
|
546
584
|
writer.writeheader()
|
|
547
585
|
writer.writerow({'id': f'{i+1}', 'data': f'Sample data for {out_mapping.destination}'})
|
|
548
|
-
|
|
586
|
+
|
|
549
587
|
# Write manifest
|
|
550
588
|
ci.write_manifest(out_table)
|
|
551
589
|
```
|
|
@@ -575,14 +613,14 @@ for file in input_files:
|
|
|
575
613
|
print(f"Processing file: {file.name}")
|
|
576
614
|
print(f" - Full path: {file.full_path}")
|
|
577
615
|
print(f" - Tags: {file.tags}")
|
|
578
|
-
|
|
616
|
+
|
|
579
617
|
# Example: Process image files
|
|
580
618
|
if 'images' in file.tags:
|
|
581
619
|
# Process image using appropriate library
|
|
582
620
|
print(f" - Processing image: {file.name}")
|
|
583
621
|
# image = Image.open(file.full_path)
|
|
584
622
|
# ... process image ...
|
|
585
|
-
|
|
623
|
+
|
|
586
624
|
# Example: Process document files
|
|
587
625
|
if 'documents' in file.tags:
|
|
588
626
|
print(f" - Processing document: {file.name}")
|
|
@@ -944,7 +982,7 @@ class Component(ComponentBase):
|
|
|
944
982
|
def run(self):
|
|
945
983
|
# Main component logic
|
|
946
984
|
pass
|
|
947
|
-
|
|
985
|
+
|
|
948
986
|
@sync_action('testConnection')
|
|
949
987
|
def test_connection(self):
|
|
950
988
|
"""
|
|
@@ -952,11 +990,11 @@ class Component(ComponentBase):
|
|
|
952
990
|
"""
|
|
953
991
|
params = self.configuration.parameters
|
|
954
992
|
connection = params.get('connection', {})
|
|
955
|
-
|
|
993
|
+
|
|
956
994
|
# Validate connection parameters
|
|
957
995
|
if not connection.get('host') or not connection.get('username'):
|
|
958
996
|
raise UserException("Connection failed: Missing host or username")
|
|
959
|
-
|
|
997
|
+
|
|
960
998
|
# If no exception is raised, the connection test is considered successful
|
|
961
999
|
# The framework automatically returns {"status": "success"}
|
|
962
1000
|
```
|
|
@@ -981,7 +1019,7 @@ class Component(ComponentBase):
|
|
|
981
1019
|
{"id": "orders", "name": "Order History"},
|
|
982
1020
|
{"id": "products", "name": "Product Catalog"}
|
|
983
1021
|
]
|
|
984
|
-
|
|
1022
|
+
|
|
985
1023
|
# Return as list of SelectElement objects for UI dropdown
|
|
986
1024
|
return [
|
|
987
1025
|
SelectElement(value=table["id"], label=table["name"])
|
|
@@ -1004,7 +1042,7 @@ class Component(ComponentBase):
|
|
|
1004
1042
|
Validates the component configuration
|
|
1005
1043
|
"""
|
|
1006
1044
|
params = self.configuration.parameters
|
|
1007
|
-
|
|
1045
|
+
|
|
1008
1046
|
# Check configuration parameters
|
|
1009
1047
|
if params.get('extraction_type') == 'incremental' and not params.get('incremental_key'):
|
|
1010
1048
|
# Return warning message that will be displayed in UI
|
|
@@ -1012,7 +1050,7 @@ class Component(ComponentBase):
|
|
|
1012
1050
|
"Incremental extraction requires specifying an incremental key column.",
|
|
1013
1051
|
MessageType.WARNING
|
|
1014
1052
|
)
|
|
1015
|
-
|
|
1053
|
+
|
|
1016
1054
|
# Check for potential issues
|
|
1017
1055
|
if params.get('row_limit') and int(params.get('row_limit')) > 1000000:
|
|
1018
1056
|
# Return info message
|
|
@@ -1020,7 +1058,7 @@ class Component(ComponentBase):
|
|
|
1020
1058
|
"Large row limit may cause performance issues.",
|
|
1021
1059
|
MessageType.INFO
|
|
1022
1060
|
)
|
|
1023
|
-
|
|
1061
|
+
|
|
1024
1062
|
# Success with no message
|
|
1025
1063
|
return None
|
|
1026
1064
|
```
|
|
@@ -1,8 +1,47 @@
|
|
|
1
1
|
# Keboola Python Component library
|
|
2
2
|
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
- [Keboola Python Component library](#keboola-python-component-library)
|
|
6
|
+
- [Table of Contents](#table-of-contents)
|
|
7
|
+
- [Introduction](#introduction)
|
|
8
|
+
- [Links](#links)
|
|
9
|
+
- [Quick start](#quick-start)
|
|
10
|
+
- [Installation](#installation)
|
|
11
|
+
- [For Developers](#for-developers)
|
|
12
|
+
- [Core structure \& functionality](#core-structure--functionality)
|
|
13
|
+
- [CommonInterface](#commoninterface)
|
|
14
|
+
- [Initialization](#initialization)
|
|
15
|
+
- [Loading configuration parameters:](#loading-configuration-parameters)
|
|
16
|
+
- [Processing input tables - Manifest vs I/O mapping](#processing-input-tables---manifest-vs-io-mapping)
|
|
17
|
+
- [Manifest \& input folder content](#manifest--input-folder-content)
|
|
18
|
+
- [Using I/O mapping](#using-io-mapping)
|
|
19
|
+
- [I/O table manifests and processing results](#io-table-manifests-and-processing-results)
|
|
20
|
+
- [Get input table by name](#get-input-table-by-name)
|
|
21
|
+
- [Working with Input/Output Mapping](#working-with-inputoutput-mapping)
|
|
22
|
+
- [Accessing Input Tables from Mapping](#accessing-input-tables-from-mapping)
|
|
23
|
+
- [Creating Output Tables based on Output Mapping](#creating-output-tables-based-on-output-mapping)
|
|
24
|
+
- [Combining Input and Output Mapping](#combining-input-and-output-mapping)
|
|
25
|
+
- [Processing input files](#processing-input-files)
|
|
26
|
+
- [Grouping Files by Tags](#grouping-files-by-tags)
|
|
27
|
+
- [Creating Output Files](#creating-output-files)
|
|
28
|
+
- [Processing state files](#processing-state-files)
|
|
29
|
+
- [Logging](#logging)
|
|
30
|
+
- [ComponentBase](#componentbase)
|
|
31
|
+
- [Table Schemas in ComponentBase](#table-schemas-in-componentbase)
|
|
32
|
+
- [JSON Table Schema example file](#json-table-schema-example-file)
|
|
33
|
+
- [Out table definition from schema example](#out-table-definition-from-schema-example)
|
|
34
|
+
- [Sync Actions](#sync-actions)
|
|
35
|
+
- [Creating Sync Actions](#creating-sync-actions)
|
|
36
|
+
- [Returning Data from Sync Actions](#returning-data-from-sync-actions)
|
|
37
|
+
- [Validation Message Action](#validation-message-action)
|
|
38
|
+
- [No output](#no-output)
|
|
39
|
+
- [License](#license)
|
|
40
|
+
|
|
41
|
+
|
|
3
42
|
## Introduction
|
|
4
43
|
|
|
5
|
-

|
|
6
45
|
[](https://codeclimate.com/github/keboola/python-component)
|
|
7
46
|
[](https://badge.fury.io/py/keboola.component)
|
|
8
47
|
|
|
@@ -21,27 +60,28 @@ to simplify the I/O handling.
|
|
|
21
60
|
|
|
22
61
|
### Links
|
|
23
62
|
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
|
|
28
|
-
-
|
|
29
|
-
|
|
30
|
-
Documentation: [https://developers.keboola.com/extend/component/python-component-library](https://developers.keboola.com/extend/component/)
|
|
31
|
-
|
|
32
|
-
- Python Component Cookiecutter template
|
|
33
|
-
project: [https://bitbucket.org/kds_consulting_team/cookiecutter-python-component](https://bitbucket.org/kds_consulting_team/cookiecutter-python-component)
|
|
63
|
+
- [PyPI](https://pypi.org/project/keboola.component/) & [TestPyPI](https://test.pypi.org/project/keboola.component/)
|
|
64
|
+
- [API Documentation](https://keboola.github.io/python-component/interface.html)
|
|
65
|
+
- [Keboola Components for developers](https://developers.keboola.com/extend/component/)
|
|
66
|
+
- [Python Component Cookiecutter template project](https://github.com/keboola/cookiecutter-python-component)
|
|
34
67
|
|
|
35
68
|
# Quick start
|
|
36
69
|
|
|
37
70
|
## Installation
|
|
38
71
|
|
|
39
|
-
The package may be installed via
|
|
72
|
+
The package may be installed via uv 💜:
|
|
40
73
|
|
|
41
74
|
```
|
|
42
|
-
|
|
75
|
+
uv add keboola.component
|
|
43
76
|
```
|
|
44
77
|
|
|
78
|
+
## For Developers
|
|
79
|
+
|
|
80
|
+
> **Note for contributors:** Before creating a pull request, make sure to manually run the
|
|
81
|
+
> [Generate Documentation](https://github.com/keboola/python-component/actions/workflows/generate-docs.yml)
|
|
82
|
+
> workflow on your branch. This ensures the API documentation in the `docs/` folder is up-to-date
|
|
83
|
+
> with your code changes. The workflow can be triggered from the Actions tab on GitHub.
|
|
84
|
+
|
|
45
85
|
## Core structure & functionality
|
|
46
86
|
|
|
47
87
|
The package contains two core modules:
|
|
@@ -261,7 +301,7 @@ with open(out_table.full_path, 'w', newline='') as f:
|
|
|
261
301
|
"status": "completed",
|
|
262
302
|
"value": "123.45"
|
|
263
303
|
})
|
|
264
|
-
|
|
304
|
+
|
|
265
305
|
# Write manifest
|
|
266
306
|
ci.write_manifest(out_table)
|
|
267
307
|
```
|
|
@@ -285,7 +325,7 @@ out_table = ci.create_out_table_definition(
|
|
|
285
325
|
|
|
286
326
|
# Add columns using different data type methods
|
|
287
327
|
# Method 1: Using BaseType helper
|
|
288
|
-
out_table.add_column("id",
|
|
328
|
+
out_table.add_column("id",
|
|
289
329
|
ColumnDefinition(
|
|
290
330
|
primary_key=True,
|
|
291
331
|
data_types=BaseType.integer()
|
|
@@ -293,7 +333,7 @@ out_table.add_column("id",
|
|
|
293
333
|
)
|
|
294
334
|
|
|
295
335
|
# Method 2: Using SupportedDataTypes enum
|
|
296
|
-
out_table.add_column("created_at",
|
|
336
|
+
out_table.add_column("created_at",
|
|
297
337
|
ColumnDefinition(
|
|
298
338
|
data_types=BaseType(dtype=SupportedDataTypes.TIMESTAMP)
|
|
299
339
|
)
|
|
@@ -303,7 +343,7 @@ out_table.add_column("created_at",
|
|
|
303
343
|
out_table.add_column("status", ColumnDefinition())
|
|
304
344
|
|
|
305
345
|
# Method 4: Using BaseType with parameters
|
|
306
|
-
out_table.add_column("price",
|
|
346
|
+
out_table.add_column("price",
|
|
307
347
|
ColumnDefinition(
|
|
308
348
|
data_types=BaseType.numeric(length="10,2"),
|
|
309
349
|
description="Product price with 2 decimal places"
|
|
@@ -311,7 +351,7 @@ out_table.add_column("price",
|
|
|
311
351
|
)
|
|
312
352
|
|
|
313
353
|
# Method 5: Backend-specific data types
|
|
314
|
-
out_table.add_column("metadata",
|
|
354
|
+
out_table.add_column("metadata",
|
|
315
355
|
ColumnDefinition(
|
|
316
356
|
data_types={
|
|
317
357
|
"snowflake": DataType(dtype="VARIANT"),
|
|
@@ -323,7 +363,7 @@ out_table.add_column("metadata",
|
|
|
323
363
|
)
|
|
324
364
|
|
|
325
365
|
# Update existing column (example of column modification)
|
|
326
|
-
out_table.update_column("price",
|
|
366
|
+
out_table.update_column("price",
|
|
327
367
|
ColumnDefinition(
|
|
328
368
|
data_types={
|
|
329
369
|
"snowflake": DataType(dtype="NUMBER", length="15,4"),
|
|
@@ -345,7 +385,7 @@ with open(out_table.full_path, 'w', newline='') as f:
|
|
|
345
385
|
"price": "99.9999",
|
|
346
386
|
"metadata": '{"category": "electronics", "brand": "TechCorp"}'
|
|
347
387
|
})
|
|
348
|
-
|
|
388
|
+
|
|
349
389
|
# Write manifest
|
|
350
390
|
ci.write_manifest(out_table)
|
|
351
391
|
```
|
|
@@ -409,16 +449,16 @@ input_tables = ci.configuration.tables_input_mapping
|
|
|
409
449
|
for table in input_tables:
|
|
410
450
|
# Get the destination (filename in the /data/in/tables directory)
|
|
411
451
|
table_name = table.destination
|
|
412
|
-
|
|
452
|
+
|
|
413
453
|
# Load table definition from manifest
|
|
414
454
|
table_def = ci.get_input_table_definition_by_name(table_name)
|
|
415
|
-
|
|
455
|
+
|
|
416
456
|
# Print information about the table
|
|
417
457
|
print(f"Processing table: {table_name}")
|
|
418
458
|
print(f" - Source: {table.source}")
|
|
419
459
|
print(f" - Full path: {table_def.full_path}")
|
|
420
460
|
print(f" - Columns: {table_def.column_names}")
|
|
421
|
-
|
|
461
|
+
|
|
422
462
|
# Read data from the CSV file
|
|
423
463
|
with open(table_def.full_path, 'r') as input_file:
|
|
424
464
|
csv_reader = csv.DictReader(input_file)
|
|
@@ -444,20 +484,20 @@ for i, table_mapping in enumerate(output_tables):
|
|
|
444
484
|
# Get source (filename that should be created) and destination (where it will be stored in KBC)
|
|
445
485
|
source = table_mapping.source
|
|
446
486
|
destination = table_mapping.destination
|
|
447
|
-
|
|
487
|
+
|
|
448
488
|
# Create output table definition
|
|
449
489
|
out_table = ci.create_out_table_definition(
|
|
450
490
|
name=source,
|
|
451
491
|
destination=destination,
|
|
452
492
|
incremental=table_mapping.incremental
|
|
453
493
|
)
|
|
454
|
-
|
|
494
|
+
|
|
455
495
|
# Add some sample data (in a real component, this would be your processed data)
|
|
456
496
|
with open(out_table.full_path, 'w', newline='') as out_file:
|
|
457
497
|
writer = csv.DictWriter(out_file, fieldnames=['id', 'data'])
|
|
458
498
|
writer.writeheader()
|
|
459
499
|
writer.writerow({'id': f'{i+1}', 'data': f'Data for {destination}'})
|
|
460
|
-
|
|
500
|
+
|
|
461
501
|
# Write manifest file
|
|
462
502
|
ci.write_manifest(out_table)
|
|
463
503
|
```
|
|
@@ -481,27 +521,27 @@ output_tables = ci.configuration.tables_output_mapping
|
|
|
481
521
|
for i, out_mapping in enumerate(output_tables):
|
|
482
522
|
# Find corresponding input table if possible (matching by index for simplicity)
|
|
483
523
|
in_mapping = input_tables[i] if i < len(input_tables) else None
|
|
484
|
-
|
|
524
|
+
|
|
485
525
|
# Create output table
|
|
486
526
|
out_table = ci.create_out_table_definition(
|
|
487
527
|
name=out_mapping.source,
|
|
488
528
|
destination=out_mapping.destination,
|
|
489
529
|
incremental=out_mapping.incremental
|
|
490
530
|
)
|
|
491
|
-
|
|
531
|
+
|
|
492
532
|
# If we have an input table, transform its data
|
|
493
533
|
if in_mapping:
|
|
494
534
|
in_table = ci.get_input_table_definition_by_name(in_mapping.destination)
|
|
495
|
-
|
|
535
|
+
|
|
496
536
|
# Read input and write to output with transformation
|
|
497
537
|
with open(in_table.full_path, 'r') as in_file, open(out_table.full_path, 'w', newline='') as out_file:
|
|
498
538
|
reader = csv.DictReader(in_file)
|
|
499
|
-
|
|
539
|
+
|
|
500
540
|
# Create writer with same field names
|
|
501
541
|
fieldnames = reader.fieldnames
|
|
502
542
|
writer = csv.DictWriter(out_file, fieldnames=fieldnames)
|
|
503
543
|
writer.writeheader()
|
|
504
|
-
|
|
544
|
+
|
|
505
545
|
# Transform each row and write to output
|
|
506
546
|
for row in reader:
|
|
507
547
|
# Simple transformation example - uppercase all values
|
|
@@ -513,7 +553,7 @@ for i, out_mapping in enumerate(output_tables):
|
|
|
513
553
|
writer = csv.DictWriter(out_file, fieldnames=['id', 'data'])
|
|
514
554
|
writer.writeheader()
|
|
515
555
|
writer.writerow({'id': f'{i+1}', 'data': f'Sample data for {out_mapping.destination}'})
|
|
516
|
-
|
|
556
|
+
|
|
517
557
|
# Write manifest
|
|
518
558
|
ci.write_manifest(out_table)
|
|
519
559
|
```
|
|
@@ -543,14 +583,14 @@ for file in input_files:
|
|
|
543
583
|
print(f"Processing file: {file.name}")
|
|
544
584
|
print(f" - Full path: {file.full_path}")
|
|
545
585
|
print(f" - Tags: {file.tags}")
|
|
546
|
-
|
|
586
|
+
|
|
547
587
|
# Example: Process image files
|
|
548
588
|
if 'images' in file.tags:
|
|
549
589
|
# Process image using appropriate library
|
|
550
590
|
print(f" - Processing image: {file.name}")
|
|
551
591
|
# image = Image.open(file.full_path)
|
|
552
592
|
# ... process image ...
|
|
553
|
-
|
|
593
|
+
|
|
554
594
|
# Example: Process document files
|
|
555
595
|
if 'documents' in file.tags:
|
|
556
596
|
print(f" - Processing document: {file.name}")
|
|
@@ -912,7 +952,7 @@ class Component(ComponentBase):
|
|
|
912
952
|
def run(self):
|
|
913
953
|
# Main component logic
|
|
914
954
|
pass
|
|
915
|
-
|
|
955
|
+
|
|
916
956
|
@sync_action('testConnection')
|
|
917
957
|
def test_connection(self):
|
|
918
958
|
"""
|
|
@@ -920,11 +960,11 @@ class Component(ComponentBase):
|
|
|
920
960
|
"""
|
|
921
961
|
params = self.configuration.parameters
|
|
922
962
|
connection = params.get('connection', {})
|
|
923
|
-
|
|
963
|
+
|
|
924
964
|
# Validate connection parameters
|
|
925
965
|
if not connection.get('host') or not connection.get('username'):
|
|
926
966
|
raise UserException("Connection failed: Missing host or username")
|
|
927
|
-
|
|
967
|
+
|
|
928
968
|
# If no exception is raised, the connection test is considered successful
|
|
929
969
|
# The framework automatically returns {"status": "success"}
|
|
930
970
|
```
|
|
@@ -949,7 +989,7 @@ class Component(ComponentBase):
|
|
|
949
989
|
{"id": "orders", "name": "Order History"},
|
|
950
990
|
{"id": "products", "name": "Product Catalog"}
|
|
951
991
|
]
|
|
952
|
-
|
|
992
|
+
|
|
953
993
|
# Return as list of SelectElement objects for UI dropdown
|
|
954
994
|
return [
|
|
955
995
|
SelectElement(value=table["id"], label=table["name"])
|
|
@@ -972,7 +1012,7 @@ class Component(ComponentBase):
|
|
|
972
1012
|
Validates the component configuration
|
|
973
1013
|
"""
|
|
974
1014
|
params = self.configuration.parameters
|
|
975
|
-
|
|
1015
|
+
|
|
976
1016
|
# Check configuration parameters
|
|
977
1017
|
if params.get('extraction_type') == 'incremental' and not params.get('incremental_key'):
|
|
978
1018
|
# Return warning message that will be displayed in UI
|
|
@@ -980,7 +1020,7 @@ class Component(ComponentBase):
|
|
|
980
1020
|
"Incremental extraction requires specifying an incremental key column.",
|
|
981
1021
|
MessageType.WARNING
|
|
982
1022
|
)
|
|
983
|
-
|
|
1023
|
+
|
|
984
1024
|
# Check for potential issues
|
|
985
1025
|
if params.get('row_limit') and int(params.get('row_limit')) > 1000000:
|
|
986
1026
|
# Return info message
|
|
@@ -988,7 +1028,7 @@ class Component(ComponentBase):
|
|
|
988
1028
|
"Large row limit may cause performance issues.",
|
|
989
1029
|
MessageType.INFO
|
|
990
1030
|
)
|
|
991
|
-
|
|
1031
|
+
|
|
992
1032
|
# Success with no message
|
|
993
1033
|
return None
|
|
994
1034
|
```
|