airbyte-cdk 6.8.1rc9__py3-none-any.whl → 6.8.2.dev1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. airbyte_cdk/cli/source_declarative_manifest/_run.py +11 -5
  2. airbyte_cdk/config_observation.py +1 -1
  3. airbyte_cdk/connector_builder/main.py +1 -1
  4. airbyte_cdk/connector_builder/message_grouper.py +10 -10
  5. airbyte_cdk/destinations/destination.py +1 -1
  6. airbyte_cdk/destinations/vector_db_based/embedder.py +2 -2
  7. airbyte_cdk/destinations/vector_db_based/writer.py +12 -4
  8. airbyte_cdk/entrypoint.py +7 -6
  9. airbyte_cdk/logger.py +2 -2
  10. airbyte_cdk/sources/abstract_source.py +1 -1
  11. airbyte_cdk/sources/config.py +1 -1
  12. airbyte_cdk/sources/connector_state_manager.py +9 -4
  13. airbyte_cdk/sources/declarative/auth/oauth.py +1 -1
  14. airbyte_cdk/sources/declarative/auth/selective_authenticator.py +6 -1
  15. airbyte_cdk/sources/declarative/concurrent_declarative_source.py +76 -28
  16. airbyte_cdk/sources/declarative/datetime/min_max_datetime.py +10 -4
  17. airbyte_cdk/sources/declarative/declarative_component_schema.yaml +16 -17
  18. airbyte_cdk/sources/declarative/decoders/noop_decoder.py +4 -1
  19. airbyte_cdk/sources/declarative/extractors/record_filter.py +3 -5
  20. airbyte_cdk/sources/declarative/incremental/__init__.py +3 -0
  21. airbyte_cdk/sources/declarative/incremental/concurrent_partition_cursor.py +270 -0
  22. airbyte_cdk/sources/declarative/incremental/datetime_based_cursor.py +8 -6
  23. airbyte_cdk/sources/declarative/incremental/per_partition_cursor.py +9 -0
  24. airbyte_cdk/sources/declarative/interpolation/jinja.py +35 -36
  25. airbyte_cdk/sources/declarative/interpolation/macros.py +1 -1
  26. airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py +71 -17
  27. airbyte_cdk/sources/declarative/partition_routers/substream_partition_router.py +13 -7
  28. airbyte_cdk/sources/declarative/requesters/error_handlers/default_error_handler.py +1 -1
  29. airbyte_cdk/sources/declarative/requesters/error_handlers/http_response_filter.py +8 -6
  30. airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py +1 -1
  31. airbyte_cdk/sources/declarative/requesters/request_options/datetime_based_request_options_provider.py +2 -2
  32. airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_options_provider.py +1 -1
  33. airbyte_cdk/sources/declarative/retrievers/async_retriever.py +5 -2
  34. airbyte_cdk/sources/declarative/retrievers/simple_retriever.py +1 -1
  35. airbyte_cdk/sources/declarative/spec/spec.py +1 -1
  36. airbyte_cdk/sources/declarative/stream_slicers/declarative_partition_generator.py +0 -1
  37. airbyte_cdk/sources/embedded/base_integration.py +3 -2
  38. airbyte_cdk/sources/file_based/availability_strategy/abstract_file_based_availability_strategy.py +12 -4
  39. airbyte_cdk/sources/file_based/availability_strategy/default_file_based_availability_strategy.py +18 -7
  40. airbyte_cdk/sources/file_based/file_types/avro_parser.py +14 -11
  41. airbyte_cdk/sources/file_based/file_types/csv_parser.py +3 -3
  42. airbyte_cdk/sources/file_based/file_types/excel_parser.py +11 -5
  43. airbyte_cdk/sources/file_based/file_types/jsonl_parser.py +1 -1
  44. airbyte_cdk/sources/file_based/stream/abstract_file_based_stream.py +2 -2
  45. airbyte_cdk/sources/file_based/stream/concurrent/adapters.py +6 -3
  46. airbyte_cdk/sources/file_based/stream/cursor/default_file_based_cursor.py +1 -1
  47. airbyte_cdk/sources/http_logger.py +3 -3
  48. airbyte_cdk/sources/streams/concurrent/abstract_stream.py +5 -2
  49. airbyte_cdk/sources/streams/concurrent/adapters.py +6 -3
  50. airbyte_cdk/sources/streams/concurrent/availability_strategy.py +9 -3
  51. airbyte_cdk/sources/streams/concurrent/cursor.py +10 -1
  52. airbyte_cdk/sources/streams/concurrent/state_converters/datetime_stream_state_converter.py +2 -2
  53. airbyte_cdk/sources/streams/core.py +17 -14
  54. airbyte_cdk/sources/streams/http/http.py +19 -19
  55. airbyte_cdk/sources/streams/http/http_client.py +4 -48
  56. airbyte_cdk/sources/streams/http/requests_native_auth/abstract_token.py +2 -1
  57. airbyte_cdk/sources/streams/http/requests_native_auth/oauth.py +62 -33
  58. airbyte_cdk/sources/utils/record_helper.py +1 -1
  59. airbyte_cdk/sources/utils/schema_helpers.py +1 -1
  60. airbyte_cdk/sources/utils/transform.py +34 -15
  61. airbyte_cdk/test/entrypoint_wrapper.py +11 -6
  62. airbyte_cdk/test/mock_http/response_builder.py +1 -1
  63. airbyte_cdk/utils/airbyte_secrets_utils.py +1 -1
  64. airbyte_cdk/utils/event_timing.py +10 -10
  65. airbyte_cdk/utils/message_utils.py +4 -3
  66. airbyte_cdk/utils/spec_schema_transformations.py +3 -2
  67. airbyte_cdk/utils/traced_exception.py +14 -12
  68. airbyte_cdk-6.8.2.dev1.dist-info/METADATA +111 -0
  69. {airbyte_cdk-6.8.1rc9.dist-info → airbyte_cdk-6.8.2.dev1.dist-info}/RECORD +72 -71
  70. airbyte_cdk-6.8.1rc9.dist-info/METADATA +0 -307
  71. {airbyte_cdk-6.8.1rc9.dist-info → airbyte_cdk-6.8.2.dev1.dist-info}/LICENSE.txt +0 -0
  72. {airbyte_cdk-6.8.1rc9.dist-info → airbyte_cdk-6.8.2.dev1.dist-info}/WHEEL +0 -0
  73. {airbyte_cdk-6.8.1rc9.dist-info → airbyte_cdk-6.8.2.dev1.dist-info}/entry_points.txt +0 -0
@@ -23,7 +23,7 @@ from io import StringIO
23
23
  from pathlib import Path
24
24
  from typing import Any, List, Mapping, Optional, Union
25
25
 
26
- from orjson import orjson
26
+ import orjson
27
27
  from pydantic import ValidationError as V2ValidationError
28
28
  from serpyco_rs import SchemaValidationError
29
29
 
@@ -63,7 +63,7 @@ class EntrypointOutput:
63
63
  @staticmethod
64
64
  def _parse_message(message: str) -> AirbyteMessage:
65
65
  try:
66
- return AirbyteMessageSerializer.load(orjson.loads(message)) # type: ignore[no-any-return] # Serializer.load() always returns AirbyteMessage
66
+ return AirbyteMessageSerializer.load(orjson.loads(message))
67
67
  except (orjson.JSONDecodeError, SchemaValidationError):
68
68
  # The platform assumes that logs that are not of AirbyteMessage format are log messages
69
69
  return AirbyteMessage(
@@ -129,14 +129,19 @@ class EntrypointOutput:
129
129
  return [
130
130
  message
131
131
  for message in self._get_message_by_types([Type.TRACE])
132
- if message.trace.type == trace_type
133
- ] # type: ignore[union-attr] # trace has `type`
132
+ if message.trace.type == trace_type # type: ignore[union-attr] # trace has `type`
133
+ ]
134
134
 
135
135
  def is_in_logs(self, pattern: str) -> bool:
136
136
  """Check if any log message case-insensitive matches the pattern."""
137
137
  return any(
138
- re.search(pattern, entry.log.message, flags=re.IGNORECASE) for entry in self.logs
139
- ) # type: ignore[union-attr] # log has `message`
138
+ re.search(
139
+ pattern,
140
+ entry.log.message, # type: ignore[union-attr] # log has `message`
141
+ flags=re.IGNORECASE,
142
+ )
143
+ for entry in self.logs
144
+ )
140
145
 
141
146
  def is_not_in_logs(self, pattern: str) -> bool:
142
147
  """Check if no log message matches the case-insensitive pattern."""
@@ -183,7 +183,7 @@ class HttpResponseBuilder:
183
183
 
184
184
  def _get_unit_test_folder(execution_folder: str) -> FilePath:
185
185
  # FIXME: This function should be removed after the next CDK release to avoid breaking amazon-seller-partner test code.
186
- return get_unit_test_folder(execution_folder) # type: ignore # get_unit_test_folder is known to return a FilePath
186
+ return get_unit_test_folder(execution_folder)
187
187
 
188
188
 
189
189
  def find_template(resource: str, execution_folder: str) -> Dict[str, Any]:
@@ -47,7 +47,7 @@ def get_secrets(
47
47
  result = []
48
48
  for path in secret_paths:
49
49
  try:
50
- result.append(dpath.get(config, path))
50
+ result.append(dpath.get(config, path)) # type: ignore # dpath expect MutableMapping but doesn't need it
51
51
  except KeyError:
52
52
  # Since we try to get paths to all known secrets in the spec, in the case of oneOfs, some secret fields may not be present
53
53
  # In that case, a KeyError is thrown. This is expected behavior.
@@ -7,7 +7,7 @@ import logging
7
7
  import time
8
8
  from contextlib import contextmanager
9
9
  from dataclasses import dataclass, field
10
- from typing import Optional
10
+ from typing import Any, Generator, Literal, Optional
11
11
 
12
12
  logger = logging.getLogger("airbyte")
13
13
 
@@ -18,13 +18,13 @@ class EventTimer:
18
18
  Event nesting follows a LIFO pattern, so finish will apply to the last started event.
19
19
  """
20
20
 
21
- def __init__(self, name):
21
+ def __init__(self, name: str) -> None:
22
22
  self.name = name
23
- self.events = {}
23
+ self.events: dict[str, Any] = {}
24
24
  self.count = 0
25
- self.stack = []
25
+ self.stack: list[Any] = []
26
26
 
27
- def start_event(self, name):
27
+ def start_event(self, name: str) -> None:
28
28
  """
29
29
  Start a new event and push it to the stack.
30
30
  """
@@ -32,7 +32,7 @@ class EventTimer:
32
32
  self.count += 1
33
33
  self.stack.insert(0, self.events[name])
34
34
 
35
- def finish_event(self):
35
+ def finish_event(self) -> None:
36
36
  """
37
37
  Finish the current event and pop it from the stack.
38
38
  """
@@ -43,7 +43,7 @@ class EventTimer:
43
43
  else:
44
44
  logger.warning(f"{self.name} finish_event called without start_event")
45
45
 
46
- def report(self, order_by="name"):
46
+ def report(self, order_by: Literal["name", "duration"] = "name") -> str:
47
47
  """
48
48
  :param order_by: 'name' or 'duration'
49
49
  """
@@ -69,15 +69,15 @@ class Event:
69
69
  return (self.end - self.start) / 1e9
70
70
  return float("+inf")
71
71
 
72
- def __str__(self):
72
+ def __str__(self) -> str:
73
73
  return f"{self.name} {datetime.timedelta(seconds=self.duration)}"
74
74
 
75
- def finish(self):
75
+ def finish(self) -> None:
76
76
  self.end = time.perf_counter_ns()
77
77
 
78
78
 
79
79
  @contextmanager
80
- def create_timer(name):
80
+ def create_timer(name: str) -> Generator[EventTimer, Any, None]:
81
81
  """
82
82
  Creates a new EventTimer as a context manager to improve code readability.
83
83
  """
@@ -8,15 +8,16 @@ def get_stream_descriptor(message: AirbyteMessage) -> HashableStreamDescriptor:
8
8
  match message.type:
9
9
  case Type.RECORD:
10
10
  return HashableStreamDescriptor(
11
- name=message.record.stream, namespace=message.record.namespace
12
- ) # type: ignore[union-attr] # record has `stream` and `namespace`
11
+ name=message.record.stream, # type: ignore[union-attr] # record has `stream`
12
+ namespace=message.record.namespace, # type: ignore[union-attr] # record has `namespace`
13
+ )
13
14
  case Type.STATE:
14
15
  if not message.state.stream or not message.state.stream.stream_descriptor: # type: ignore[union-attr] # state has `stream`
15
16
  raise ValueError(
16
17
  "State message was not in per-stream state format, which is required for record counts."
17
18
  )
18
19
  return HashableStreamDescriptor(
19
- name=message.state.stream.stream_descriptor.name,
20
+ name=message.state.stream.stream_descriptor.name, # type: ignore[union-attr] # state has `stream`
20
21
  namespace=message.state.stream.stream_descriptor.namespace, # type: ignore[union-attr] # state has `stream`
21
22
  )
22
23
  case _:
@@ -4,11 +4,12 @@
4
4
 
5
5
  import json
6
6
  import re
7
+ from typing import Any
7
8
 
8
9
  from jsonschema import RefResolver
9
10
 
10
11
 
11
- def resolve_refs(schema: dict) -> dict:
12
+ def resolve_refs(schema: dict[str, Any]) -> dict[str, Any]:
12
13
  """
13
14
  For spec schemas generated using Pydantic models, the resulting JSON schema can contain refs between object
14
15
  relationships.
@@ -20,6 +21,6 @@ def resolve_refs(schema: dict) -> dict:
20
21
  str_schema = str_schema.replace(
21
22
  ref_block, json.dumps(json_schema_ref_resolver.resolve(ref)[1])
22
23
  )
23
- pyschema: dict = json.loads(str_schema)
24
+ pyschema: dict[str, Any] = json.loads(str_schema)
24
25
  del pyschema["definitions"]
25
26
  return pyschema
@@ -3,9 +3,9 @@
3
3
  #
4
4
  import time
5
5
  import traceback
6
- from typing import Optional
6
+ from typing import Any, Optional
7
7
 
8
- from orjson import orjson
8
+ import orjson
9
9
 
10
10
  from airbyte_cdk.models import (
11
11
  AirbyteConnectionStatus,
@@ -104,9 +104,9 @@ class AirbyteTracedException(Exception):
104
104
  cls,
105
105
  exc: BaseException,
106
106
  stream_descriptor: Optional[StreamDescriptor] = None,
107
- *args,
108
- **kwargs,
109
- ) -> "AirbyteTracedException": # type: ignore # ignoring because of args and kwargs
107
+ *args: Any,
108
+ **kwargs: Any,
109
+ ) -> "AirbyteTracedException":
110
110
  """
111
111
  Helper to create an AirbyteTracedException from an existing exception
112
112
  :param exc: the exception that caused the error
@@ -131,13 +131,15 @@ class AirbyteTracedException(Exception):
131
131
  """
132
132
  error_message = self.as_airbyte_message(stream_descriptor=stream_descriptor)
133
133
  if error_message.trace.error.message: # type: ignore[union-attr] # AirbyteMessage with MessageType.TRACE has AirbyteTraceMessage
134
- error_message.trace.error.message = filter_secrets(error_message.trace.error.message) # type: ignore[union-attr] # AirbyteMessage with MessageType.TRACE has AirbyteTraceMessage
134
+ error_message.trace.error.message = filter_secrets( # type: ignore[union-attr]
135
+ error_message.trace.error.message, # type: ignore[union-attr]
136
+ )
135
137
  if error_message.trace.error.internal_message: # type: ignore[union-attr] # AirbyteMessage with MessageType.TRACE has AirbyteTraceMessage
136
- error_message.trace.error.internal_message = filter_secrets(
137
- error_message.trace.error.internal_message
138
- ) # type: ignore[union-attr] # AirbyteMessage with MessageType.TRACE has AirbyteTraceMessage
138
+ error_message.trace.error.internal_message = filter_secrets( # type: ignore[union-attr] # AirbyteMessage with MessageType.TRACE has AirbyteTraceMessage
139
+ error_message.trace.error.internal_message # type: ignore[union-attr] # AirbyteMessage with MessageType.TRACE has AirbyteTraceMessage
140
+ )
139
141
  if error_message.trace.error.stack_trace: # type: ignore[union-attr] # AirbyteMessage with MessageType.TRACE has AirbyteTraceMessage
140
- error_message.trace.error.stack_trace = filter_secrets(
141
- error_message.trace.error.stack_trace
142
- ) # type: ignore[union-attr] # AirbyteMessage with MessageType.TRACE has AirbyteTraceMessage
142
+ error_message.trace.error.stack_trace = filter_secrets( # type: ignore[union-attr] # AirbyteMessage with MessageType.TRACE has AirbyteTraceMessage
143
+ error_message.trace.error.stack_trace # type: ignore[union-attr] # AirbyteMessage with MessageType.TRACE has AirbyteTraceMessage
144
+ )
143
145
  return error_message
@@ -0,0 +1,111 @@
1
+ Metadata-Version: 2.1
2
+ Name: airbyte-cdk
3
+ Version: 6.8.2.dev1
4
+ Summary: A framework for writing Airbyte Connectors.
5
+ Home-page: https://airbyte.com
6
+ License: MIT
7
+ Keywords: airbyte,connector-development-kit,cdk
8
+ Author: Airbyte
9
+ Author-email: contact@airbyte.io
10
+ Requires-Python: >=3.10,<3.13
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Scientific/Engineering
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Provides-Extra: file-based
21
+ Provides-Extra: sphinx-docs
22
+ Provides-Extra: sql
23
+ Provides-Extra: vector-db-based
24
+ Requires-Dist: Jinja2 (>=3.1.2,<3.2.0)
25
+ Requires-Dist: PyYAML (>=6.0.1,<7.0.0)
26
+ Requires-Dist: Sphinx (>=4.2,<4.3) ; extra == "sphinx-docs"
27
+ Requires-Dist: airbyte-protocol-models-dataclasses (>=0.14,<0.15)
28
+ Requires-Dist: avro (>=1.11.2,<1.12.0) ; extra == "file-based"
29
+ Requires-Dist: backoff
30
+ Requires-Dist: cachetools
31
+ Requires-Dist: cohere (==4.21) ; extra == "vector-db-based"
32
+ Requires-Dist: cryptography (>=42.0.5,<44.0.0)
33
+ Requires-Dist: dpath (>=2.1.6,<3.0.0)
34
+ Requires-Dist: dunamai (>=1.22.0,<2.0.0)
35
+ Requires-Dist: fastavro (>=1.8.0,<1.9.0) ; extra == "file-based"
36
+ Requires-Dist: genson (==1.3.0)
37
+ Requires-Dist: isodate (>=0.6.1,<0.7.0)
38
+ Requires-Dist: jsonref (>=0.2,<0.3)
39
+ Requires-Dist: jsonschema (>=4.17.3,<4.18.0)
40
+ Requires-Dist: langchain (==0.1.16) ; extra == "vector-db-based"
41
+ Requires-Dist: langchain_core (==0.1.42)
42
+ Requires-Dist: markdown ; extra == "file-based"
43
+ Requires-Dist: nltk (==3.9.1)
44
+ Requires-Dist: numpy (<2)
45
+ Requires-Dist: openai[embeddings] (==0.27.9) ; extra == "vector-db-based"
46
+ Requires-Dist: orjson (>=3.10.7,<4.0.0)
47
+ Requires-Dist: pandas (==2.2.2)
48
+ Requires-Dist: pdf2image (==1.16.3) ; extra == "file-based"
49
+ Requires-Dist: pdfminer.six (==20221105) ; extra == "file-based"
50
+ Requires-Dist: pendulum (<3.0.0)
51
+ Requires-Dist: psutil (==6.1.0)
52
+ Requires-Dist: pyarrow (>=15.0.0,<15.1.0) ; extra == "file-based"
53
+ Requires-Dist: pydantic (>=2.7,<3.0)
54
+ Requires-Dist: pyjwt (>=2.8.0,<3.0.0)
55
+ Requires-Dist: pyrate-limiter (>=3.1.0,<3.2.0)
56
+ Requires-Dist: pytesseract (==0.3.10) ; extra == "file-based"
57
+ Requires-Dist: python-calamine (==0.2.3) ; extra == "file-based"
58
+ Requires-Dist: python-dateutil
59
+ Requires-Dist: python-snappy (==0.7.3) ; extra == "file-based"
60
+ Requires-Dist: python-ulid (>=3.0.0,<4.0.0)
61
+ Requires-Dist: pytz (==2024.1)
62
+ Requires-Dist: rapidfuzz (>=3.10.1,<4.0.0)
63
+ Requires-Dist: requests
64
+ Requires-Dist: requests_cache
65
+ Requires-Dist: serpyco-rs (>=1.10.2,<2.0.0)
66
+ Requires-Dist: sphinx-rtd-theme (>=1.0,<1.1) ; extra == "sphinx-docs"
67
+ Requires-Dist: sqlalchemy (>=2.0,<3.0,!=2.0.36) ; extra == "sql"
68
+ Requires-Dist: tiktoken (==0.8.0) ; extra == "vector-db-based"
69
+ Requires-Dist: unstructured.pytesseract (>=0.3.12) ; extra == "file-based"
70
+ Requires-Dist: unstructured[docx,pptx] (==0.10.27) ; extra == "file-based"
71
+ Requires-Dist: wcmatch (==10.0)
72
+ Requires-Dist: xmltodict (>=0.13.0,<0.14.0)
73
+ Project-URL: Documentation, https://docs.airbyte.io/
74
+ Project-URL: Repository, https://github.com/airbytehq/airbyte-python-cdk
75
+ Description-Content-Type: text/markdown
76
+
77
+ # Airbyte Python CDK and Low-Code CDK
78
+
79
+ Airbyte Python CDK is a framework for building Airbyte API Source Connectors. It provides a set of
80
+ classes and helpers that make it easy to build a connector against an HTTP API (REST, GraphQL, etc),
81
+ or a generic Python source connector.
82
+
83
+ ## Building Connectors with the CDK
84
+
85
+ If you're looking to build a connector, we highly recommend that you first
86
+ [start with the Connector Builder](https://docs.airbyte.com/connector-development/connector-builder-ui/overview).
87
+ It should be enough for 90% connectors out there. For more flexible and complex connectors, use the
88
+ [low-code CDK and `SourceDeclarativeManifest`](https://docs.airbyte.com/connector-development/config-based/low-code-cdk-overview).
89
+
90
+ For more information on building connectors, please see the [Connector Development](https://docs.airbyte.com/connector-development/) guide on [docs.airbyte.com](https://docs.airbyte.com).
91
+
92
+ ## Python CDK Overview
93
+
94
+ Airbyte CDK code is within `airbyte_cdk` directory. Here's a high level overview of what's inside:
95
+
96
+ - `airbyte_cdk/connector_builder`. Internal wrapper that helps the Connector Builder platform run a declarative manifest (low-code connector). You should not use this code directly. If you need to run a `SourceDeclarativeManifest`, take a look at [`source-declarative-manifest`](https://github.com/airbytehq/airbyte/tree/master/airbyte-integrations/connectors/source-declarative-manifest) connector implementation instead.
97
+ - `airbyte_cdk/cli/source_declarative_manifest`. This module defines the `source-declarative-manifest` (aka "SDM") connector execution logic and associated CLI.
98
+ - `airbyte_cdk/destinations`. Basic Destination connector support! If you're building a Destination connector in Python, try that. Some of our vector DB destinations like `destination-pinecone` are using that code.
99
+ - `airbyte_cdk/models` expose `airbyte_protocol.models` as a part of `airbyte_cdk` package.
100
+ - `airbyte_cdk/sources/concurrent_source` is the Concurrent CDK implementation. It supports reading data from streams concurrently per slice / partition, useful for connectors with high throughput and high number of records.
101
+ - `airbyte_cdk/sources/declarative` is the low-code CDK. It works on top of Airbyte Python CDK, but provides a declarative manifest language to define streams, operations, etc. This makes it easier to build connectors without writing Python code.
102
+ - `airbyte_cdk/sources/file_based` is the CDK for file-based sources. Examples include S3, Azure, GCS, etc.
103
+
104
+ ## Contributing
105
+
106
+ For instructions on how to contribute, please see our [Contributing Guide](docs/CONTRIBUTING.md).
107
+
108
+ ## Release Management
109
+
110
+ Please see the [Release Management](docs/RELEASES.md) guide for information on how to perform releases and pre-releases.
111
+