1
0
mirror of synced 2025-12-31 06:05:12 -05:00
Files
airbyte/airbyte-cdk/python/airbyte_cdk/sources/message/repository.py
Alexandre Girard df01616951 [Issue #23497] Deduplicate query parameters for declarative connectors (#28550)
* remove duplicate param

* remove duplicate params

* fix some of the typing issues

* fix typing issues

* newline

* format

* Enable by default

* Add missing file

* refactor and remove flag

* none check

* move line of code

* fix typing in rate_limiting

* comment

* use typedef

* else branch

* format

* gate the feature

* rename test

* fix the test

* only dedupe if the values are the same

* Add some tests

* convert values to strings

* Document the change

* implement in requester too
2023-07-25 14:22:25 -07:00

130 lines
4.9 KiB
Python

#
# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
#
import json
import logging
from abc import ABC, abstractmethod
from collections import deque
from typing import Callable, Deque, Iterable, List, Optional
from airbyte_cdk.models import AirbyteLogMessage, AirbyteMessage, Level, Type
from airbyte_cdk.sources.utils.types import JsonType
from airbyte_cdk.utils.airbyte_secrets_utils import filter_secrets
_LOGGER = logging.getLogger("MessageRepository")
_SUPPORTED_MESSAGE_TYPES = {Type.CONTROL, Type.LOG}
LogMessage = dict[str, JsonType]
_SEVERITY_BY_LOG_LEVEL = {
Level.FATAL: 1,
Level.ERROR: 2,
Level.WARN: 3,
Level.INFO: 4,
Level.DEBUG: 5,
Level.TRACE: 5,
}
def _is_severe_enough(threshold: Level, level: Level) -> bool:
if threshold not in _SEVERITY_BY_LOG_LEVEL:
_LOGGER.warning(f"Log level {threshold} for threshold is not supported. This is probably a CDK bug. Please contact Airbyte.")
return True
if level not in _SEVERITY_BY_LOG_LEVEL:
_LOGGER.warning(
f"Log level {level} is not supported. This is probably a source bug. Please contact the owner of the source or Airbyte."
)
return True
return _SEVERITY_BY_LOG_LEVEL[threshold] >= _SEVERITY_BY_LOG_LEVEL[level]
class MessageRepository(ABC):
@abstractmethod
def emit_message(self, message: AirbyteMessage) -> None:
raise NotImplementedError()
@abstractmethod
def log_message(self, level: Level, message_provider: Callable[[], LogMessage]) -> None:
"""
Computing messages can be resource consuming. This method is specialized for logging because we want to allow for lazy evaluation if
the log level is less severe than what is configured
"""
raise NotImplementedError()
@abstractmethod
def consume_queue(self) -> Iterable[AirbyteMessage]:
raise NotImplementedError()
class NoopMessageRepository(MessageRepository):
def emit_message(self, message: AirbyteMessage) -> None:
pass
def log_message(self, level: Level, message_provider: Callable[[], LogMessage]) -> None:
pass
def consume_queue(self) -> Iterable[AirbyteMessage]:
return []
class InMemoryMessageRepository(MessageRepository):
def __init__(self, log_level: Level = Level.INFO) -> None:
self._message_queue: Deque[AirbyteMessage] = deque()
self._log_level = log_level
def emit_message(self, message: AirbyteMessage) -> None:
"""
:param message: As of today, only AirbyteControlMessages are supported given that supporting other types of message will need more
work and therefore this work has been postponed
"""
if message.type not in _SUPPORTED_MESSAGE_TYPES:
raise ValueError(f"As of today, only {_SUPPORTED_MESSAGE_TYPES} are supported as part of the InMemoryMessageRepository")
self._message_queue.append(message)
def log_message(self, level: Level, message_provider: Callable[[], LogMessage]) -> None:
if _is_severe_enough(self._log_level, level):
self.emit_message(
AirbyteMessage(type=Type.LOG, log=AirbyteLogMessage(level=level, message=filter_secrets(json.dumps(message_provider()))))
)
def consume_queue(self) -> Iterable[AirbyteMessage]:
while self._message_queue:
yield self._message_queue.popleft()
class LogAppenderMessageRepositoryDecorator(MessageRepository):
def __init__(self, dict_to_append: LogMessage, decorated: MessageRepository, log_level: Level = Level.INFO):
self._dict_to_append = dict_to_append
self._decorated = decorated
self._log_level = log_level
def emit_message(self, message: AirbyteMessage) -> None:
self._decorated.emit_message(message)
def log_message(self, level: Level, message_provider: Callable[[], LogMessage]) -> None:
if _is_severe_enough(self._log_level, level):
message = message_provider()
self._append_second_to_first(message, self._dict_to_append)
self._decorated.log_message(level, lambda: message)
def consume_queue(self) -> Iterable[AirbyteMessage]:
return self._decorated.consume_queue()
def _append_second_to_first(self, first: LogMessage, second: LogMessage, path: Optional[List[str]] = None) -> LogMessage:
if path is None:
path = []
for key in second:
if key in first:
if isinstance(first[key], dict) and isinstance(second[key], dict):
self._append_second_to_first(first[key], second[key], path + [str(key)]) # type: ignore # type is verified above
else:
if first[key] != second[key]:
_LOGGER.warning("Conflict at %s" % ".".join(path + [str(key)]))
first[key] = second[key]
else:
first[key] = second[key]
return first