Skip to content

Commit 0eb0713

Browse files
author
maxi297
committed
Adding tests and fixing some stuff
1 parent 4720990 commit 0eb0713

File tree

5 files changed

+137
-7
lines changed

5 files changed

+137
-7
lines changed

airbyte_cdk/sources/declarative/schema/dynamic_schema_loader.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,28 @@ def _update_pointer(
114114

115115

116116
@deprecated("This class is experimental. Use at your own risk.", category=ExperimentalClassWarning)
117-
class AdditionalPropertyFieldsInferer(ABC):
117+
class AdditionalPropertyFieldsInferrer(ABC):
118+
"""
119+
Infers additional fields to be added to each property. For example, if this inferrer returns {"toto": "tata"}, a property that would have looked like this:
120+
```
121+
"properties": {
122+
"Id": {
123+
"type": ["null", "string"],
124+
},
125+
<...>
126+
}
127+
```
128+
... will look like this:
129+
```
130+
"properties": {
131+
"Id": {
132+
"type": ["null", "string"],
133+
"toto": "tata"
134+
},
135+
<...>
136+
}
137+
```
138+
"""
118139
@abstractmethod
119140
def infer(self, property_definition: MutableMapping[str, Any]) -> MutableMapping[str, Any]:
120141
"""
@@ -135,7 +156,8 @@ class DynamicSchemaLoader(SchemaLoader):
135156
parameters: InitVar[Mapping[str, Any]]
136157
schema_type_identifier: SchemaTypeIdentifier
137158
schema_transformations: List[RecordTransformation] = field(default_factory=lambda: [])
138-
additional_property_fields_inferrer: Optional[AdditionalPropertyFieldsInferer] = None
159+
additional_property_fields_inferrer: Optional[AdditionalPropertyFieldsInferrer] = None
160+
allow_additional_properties: bool = True
139161

140162
def get_json_schema(self) -> Mapping[str, Any]:
141163
"""
@@ -168,7 +190,7 @@ def get_json_schema(self) -> Mapping[str, Any]:
168190
return {
169191
"$schema": "https://json-schema.org/draft-07/schema#",
170192
"type": "object",
171-
"additionalProperties": True,
193+
"additionalProperties": self.allow_additional_properties,
172194
"properties": transformed_properties,
173195
}
174196

airbyte_cdk/test/catalog_builder.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
from typing import Any, Dict, List, Union, overload
44

5+
from airbyte_protocol_dataclasses.models import DestinationSyncMode
6+
57
from airbyte_cdk.models import (
68
ConfiguredAirbyteCatalog,
79
ConfiguredAirbyteStream,
@@ -32,6 +34,10 @@ def with_sync_mode(self, sync_mode: SyncMode) -> "ConfiguredAirbyteStreamBuilder
3234
self._stream["sync_mode"] = sync_mode.name
3335
return self
3436

37+
def with_destination_sync_mode(self, sync_mode: DestinationSyncMode) -> "ConfiguredAirbyteStreamBuilder":
38+
self._stream["destination_sync_mode"] = sync_mode.name
39+
return self
40+
3541
def with_primary_key(self, pk: List[List[str]]) -> "ConfiguredAirbyteStreamBuilder":
3642
self._stream["primary_key"] = pk
3743
self._stream["stream"]["source_defined_primary_key"] = pk # type: ignore # we assume that self._stream["stream"] is a Dict[str, Any]

airbyte_cdk/test/mock_http/request.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,11 @@ def _to_mapping(
7272
elif isinstance(body, bytes):
7373
return json.loads(body.decode()) # type: ignore # assumes return type of Mapping[str, Any]
7474
elif isinstance(body, str):
75-
return json.loads(body) # type: ignore # assumes return type of Mapping[str, Any]
75+
try:
76+
return json.loads(body)
77+
except json.JSONDecodeError:
78+
# one of the body is a mapping while the other isn't so comparison should fail anyway
79+
return None
7680
return None
7781

7882
@staticmethod

unit_tests/sources/declarative/schema/test_dynamic_schema_loader.py

Lines changed: 96 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44

55
import json
66
from copy import deepcopy
7-
from unittest.mock import MagicMock
7+
from typing import Any, Mapping, MutableMapping
8+
from unittest.mock import MagicMock, Mock
89

910
import pytest
1011

@@ -14,13 +15,21 @@
1415
from airbyte_cdk.sources.declarative.parsers.model_to_component_factory import (
1516
ModelToComponentFactory,
1617
)
17-
from airbyte_cdk.sources.declarative.schema import DynamicSchemaLoader, SchemaTypeIdentifier
18+
from airbyte_cdk.sources.declarative.retrievers import Retriever
19+
from airbyte_cdk.sources.declarative.schema import (
20+
DynamicSchemaLoader,
21+
SchemaTypeIdentifier,
22+
TypesMap,
23+
)
24+
from airbyte_cdk.sources.declarative.schema.dynamic_schema_loader import (
25+
AdditionalPropertyFieldsInferrer,
26+
)
1827
from airbyte_cdk.test.mock_http import HttpMocker, HttpRequest, HttpResponse
1928

2029
_CONFIG = {
2130
"start_date": "2024-07-01T00:00:00.000Z",
2231
}
23-
32+
_ANY_PARAMETERS = {}
2433
_MANIFEST = {
2534
"version": "6.7.0",
2635
"definitions": {
@@ -412,3 +421,87 @@ def test_dynamic_schema_loader_with_type_conditions():
412421

413422
assert len(actual_catalog.streams) == 1
414423
assert actual_catalog.streams[0].json_schema == expected_schema
424+
425+
426+
def _mock_schema_loader_retriever(http_response_body) -> Retriever:
427+
retriever = Mock(spec=Retriever)
428+
retriever.read_records.return_value = iter([http_response_body])
429+
return retriever
430+
431+
432+
class TestAdditionalPropertyFieldsInferrer(AdditionalPropertyFieldsInferrer):
433+
def __init__(self, properties_to_add: Mapping[str, Any]):
434+
self._properties_to_add = properties_to_add
435+
436+
def infer(self, property_definition: MutableMapping[str, Any]) -> Mapping[str, Any]:
437+
return self._properties_to_add
438+
439+
440+
def test_additional_property_fields_inferrer():
441+
properties_to_add = {"added_property": "a_value"}
442+
expected_schema = {
443+
"$schema": "https://json-schema.org/draft-07/schema#",
444+
"additionalProperties": False,
445+
"type": "object",
446+
"properties": {
447+
"id": {"type": ["null", "integer"]} | properties_to_add,
448+
},
449+
}
450+
schema_loader = DynamicSchemaLoader(
451+
retriever=_mock_schema_loader_retriever({"fields": [{"name": "id", "type": "integer"}]}),
452+
additional_property_fields_inferrer=TestAdditionalPropertyFieldsInferrer(properties_to_add),
453+
schema_type_identifier=SchemaTypeIdentifier(
454+
key_pointer=["name"],
455+
type_pointer=["type"],
456+
types_mapping=[
457+
TypesMap(
458+
current_type="integer",
459+
target_type="integer",
460+
condition=None,
461+
),
462+
],
463+
schema_pointer=["fields"],
464+
parameters=_ANY_PARAMETERS,
465+
),
466+
allow_additional_properties=False,
467+
config={},
468+
parameters=_ANY_PARAMETERS,
469+
)
470+
471+
schema = schema_loader.get_json_schema()
472+
473+
assert schema == expected_schema
474+
475+
476+
def test_additional_properties():
477+
expected_schema = {
478+
"$schema": "https://json-schema.org/draft-07/schema#",
479+
"additionalProperties": False,
480+
"type": "object",
481+
"properties": {
482+
"id": {"type": ["null", "integer"]},
483+
},
484+
}
485+
schema_loader = DynamicSchemaLoader(
486+
retriever=_mock_schema_loader_retriever({"fields": [{"name": "id", "type": "integer"}]}),
487+
schema_type_identifier=SchemaTypeIdentifier(
488+
key_pointer=["name"],
489+
type_pointer=["type"],
490+
types_mapping=[
491+
TypesMap(
492+
current_type="integer",
493+
target_type="integer",
494+
condition=None,
495+
),
496+
],
497+
schema_pointer=["fields"],
498+
parameters=_ANY_PARAMETERS,
499+
),
500+
allow_additional_properties=False,
501+
config={},
502+
parameters=_ANY_PARAMETERS,
503+
)
504+
505+
schema = schema_loader.get_json_schema()
506+
507+
assert schema == expected_schema

unit_tests/test/mock_http/test_request.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,3 +156,8 @@ def test_given_any_matcher_for_both_when_matches_then_return_true(self):
156156
request_to_match = HttpRequest("mock://test.com/path", ANY_QUERY_PARAMS)
157157
actual_request = HttpRequest("mock://test.com/path", ANY_QUERY_PARAMS)
158158
assert actual_request.matches(request_to_match)
159+
160+
def test_given_on_match_is_mapping_but_not_input_when_matches_then_return_false(self):
161+
request_to_match = HttpRequest("mock://test.com/path", body={"first_field": "another value"})
162+
actual_request = HttpRequest("mock://test.com/path", body="another_request_body")
163+
assert not actual_request.matches(request_to_match)

0 commit comments

Comments
 (0)