tap-belvo 0.3.5__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.
- tap_belvo/__init__.py +3 -0
- tap_belvo/__main__.py +7 -0
- tap_belvo/client.py +123 -0
- tap_belvo/openapi.json +40887 -0
- tap_belvo/streams/__init__.py +44 -0
- tap_belvo/streams/banking.py +103 -0
- tap_belvo/streams/core.py +54 -0
- tap_belvo/streams/enrichment.py +34 -0
- tap_belvo/streams/fiscal.py +66 -0
- tap_belvo/tap.py +74 -0
- tap_belvo-0.3.5.dist-info/METADATA +110 -0
- tap_belvo-0.3.5.dist-info/RECORD +15 -0
- tap_belvo-0.3.5.dist-info/WHEEL +4 -0
- tap_belvo-0.3.5.dist-info/entry_points.txt +2 -0
- tap_belvo-0.3.5.dist-info/licenses/LICENSE +21 -0
tap_belvo/__init__.py
ADDED
tap_belvo/__main__.py
ADDED
tap_belvo/client.py
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""REST client handling, including BelvoStream base class."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from abc import ABCMeta, abstractmethod
|
|
7
|
+
from copy import deepcopy
|
|
8
|
+
from importlib import resources
|
|
9
|
+
from typing import TYPE_CHECKING, Any
|
|
10
|
+
from urllib.parse import ParseResult, parse_qsl
|
|
11
|
+
|
|
12
|
+
from requests.auth import HTTPBasicAuth
|
|
13
|
+
from requests_cache import install_cache
|
|
14
|
+
from singer_sdk import OpenAPISchema, RESTStream
|
|
15
|
+
from singer_sdk.pagination import BaseHATEOASPaginator
|
|
16
|
+
|
|
17
|
+
if sys.version_info >= (3, 12):
|
|
18
|
+
from typing import override
|
|
19
|
+
else:
|
|
20
|
+
from typing_extensions import override
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from requests import Response
|
|
24
|
+
from singer_sdk.helpers.types import Context
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
PAGE_SIZE = 1000
|
|
28
|
+
OPENAPI = OpenAPISchema(resources.files("tap_belvo") / "openapi.json")
|
|
29
|
+
|
|
30
|
+
install_cache("tap_belvo_cache", backend="sqlite", expire_after=3600)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _handle_schema_nullable(schema: dict[str, Any]) -> dict[str, Any]:
|
|
34
|
+
"""Resolve x-nullable properties to standard JSON Schema nullable type.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
schema: A JSON Schema dictionary.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
A new JSON Schema dictionary with 'x-nullable' resolved to [<type>, "null"].
|
|
41
|
+
"""
|
|
42
|
+
result = deepcopy(schema)
|
|
43
|
+
|
|
44
|
+
if "object" in result["type"]:
|
|
45
|
+
for prop, prop_schema in result.get("properties", {}).items():
|
|
46
|
+
prop_type: str | list[str] = prop_schema.get("type", [])
|
|
47
|
+
types = [prop_type] if isinstance(prop_type, str) else prop_type
|
|
48
|
+
nullable: bool = prop_schema.get("nullable", False)
|
|
49
|
+
|
|
50
|
+
if nullable:
|
|
51
|
+
prop_schema["type"] = [*types, "null"]
|
|
52
|
+
|
|
53
|
+
result["properties"][prop] = _handle_schema_nullable(prop_schema)
|
|
54
|
+
|
|
55
|
+
elif "array" in result["type"]:
|
|
56
|
+
result["items"] = _handle_schema_nullable(result["items"])
|
|
57
|
+
|
|
58
|
+
if "enum" in result and None not in result["enum"]:
|
|
59
|
+
result["enum"].append(None)
|
|
60
|
+
|
|
61
|
+
return result
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class BelvoPaginator(BaseHATEOASPaginator):
|
|
65
|
+
"""Belvo API paginator class."""
|
|
66
|
+
|
|
67
|
+
@override
|
|
68
|
+
def get_next_url(self, response: Response) -> str | None:
|
|
69
|
+
"""Get the next URL from the response."""
|
|
70
|
+
return response.json().get("next") # type: ignore[no-any-return]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class BelvoStream(RESTStream[ParseResult], metaclass=ABCMeta):
|
|
74
|
+
"""Belvo stream class."""
|
|
75
|
+
|
|
76
|
+
records_jsonpath = "$.results[*]" # Or override `parse_response`.
|
|
77
|
+
|
|
78
|
+
@override
|
|
79
|
+
@property
|
|
80
|
+
def url_base(self) -> str:
|
|
81
|
+
return self.config["base_url"] # type: ignore[no-any-return]
|
|
82
|
+
|
|
83
|
+
@override
|
|
84
|
+
@property
|
|
85
|
+
def authenticator(self) -> HTTPBasicAuth:
|
|
86
|
+
return HTTPBasicAuth(self.config["secret_id"], self.config["password"])
|
|
87
|
+
|
|
88
|
+
@override
|
|
89
|
+
def get_new_paginator(self) -> BelvoPaginator:
|
|
90
|
+
return BelvoPaginator()
|
|
91
|
+
|
|
92
|
+
@override
|
|
93
|
+
def get_url_params(
|
|
94
|
+
self,
|
|
95
|
+
context: Context | None,
|
|
96
|
+
next_page_token: ParseResult | None,
|
|
97
|
+
) -> dict[str, Any]:
|
|
98
|
+
"""Get URL query parameters."""
|
|
99
|
+
params: dict[str, Any] = {
|
|
100
|
+
"page": 1,
|
|
101
|
+
"page_size": PAGE_SIZE,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if next_page_token:
|
|
105
|
+
params.update(parse_qsl(next_page_token.query))
|
|
106
|
+
|
|
107
|
+
if (
|
|
108
|
+
self.replication_key # Only if the stream is running incrementally
|
|
109
|
+
and (start_date := self.get_starting_timestamp(context))
|
|
110
|
+
):
|
|
111
|
+
params[f"{self.replication_key}__gte"] = start_date.date().isoformat()
|
|
112
|
+
|
|
113
|
+
return params
|
|
114
|
+
|
|
115
|
+
@override
|
|
116
|
+
@property
|
|
117
|
+
def schema(self) -> dict[str, Any]:
|
|
118
|
+
return _handle_schema_nullable(OPENAPI.fetch_schema(self.openapi_ref))
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
@abstractmethod
|
|
122
|
+
def openapi_ref(self) -> str:
|
|
123
|
+
"""OpenAPI component name for this stream."""
|