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 ADDED
@@ -0,0 +1,3 @@
1
+ """Python package for the tap-belvo CLI."""
2
+
3
+ from __future__ import annotations
tap_belvo/__main__.py ADDED
@@ -0,0 +1,7 @@
1
+ """Tap executable."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from tap_belvo.tap import TapBelvo
6
+
7
+ TapBelvo.cli()
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."""